powder that makes you say yes

This commit is contained in:
Bib Bob
2022-10-08 22:41:28 -05:00
544 changed files with 24792 additions and 19856 deletions

View File

@@ -0,0 +1,162 @@
//Flushable toilets on station levels. Flushing sends stuff directly to a trashpit landmark without stinking up the cargo office.
//Only on-station toilets are affected and only if the trashpit landmark also exists. Otherwise toilets will stay normal.
/obj/structure/toilet
var/teleplumbed = FALSE
var/exit_landmark
var/exit_container
var/refill_cooldown = 200
var/refilling = FALSE
/obj/structure/toilet/Initialize()
if(z in global.using_map.map_levels)
teleplumbed = TRUE
exit_landmark = locate(/obj/effect/landmark/teleplumb_exit)
exit_container = locate(/obj/structure/biowaste_tank)
if(exit_container)
exit_landmark = exit_container //Override landmark if container is available.
if(teleplumbed && exit_landmark)
desc = "The BS-500, a bluespace rift-rotation-based waste disposal unit for small matter. This one seems remarkably clean."
return ..()
/obj/structure/toilet/attack_hand(mob/living/user as mob)
if(open && teleplumbed && exit_landmark && !refilling)
var/list/bowl_contents = list()
for(var/obj/item/I in loc.contents)
if(istype(I) && !I.anchored)
bowl_contents += I
for(var/mob/living/L in loc.contents)
if(L.resting || L.lying && L.size_multiplier <= 0.75 && !L.buckled)
bowl_contents += L
if(bowl_contents.len)
refilling = TRUE
user.visible_message("<span class='notice'>[user] flushes the toilet.</span>", "<span class='notice'>You flush the toilet.</span>")
playsound(src, 'sound/vore/death7.ogg', 50, 1) //Got lazy about getting new sound files. Have a sick remix lmao.
playsound(src, 'sound/effects/bubbles.ogg', 50, 1)
playsound(src, 'sound/mecha/powerup.ogg', 30, 1)
var/bowl_conga = 0
for(var/atom/movable/F in bowl_contents)
if(bowl_conga < 150)
bowl_conga += 2
spawn(3 + bowl_conga)
F.SpinAnimation(5,3)
spawn(15)
if(F.loc == loc)
F.forceMove(src)
spawn(refill_cooldown)
for(var/atom/movable/F in bowl_contents)
if(F.loc == src)
F.forceMove(exit_landmark)
bowl_contents.Cut()
refilling = FALSE
return
if(refilling)
playsound(src, 'sound/machines/door_locked.ogg', 30, 1)
to_chat(user, "<span class='notice'>The toilet is still refilling its tank.</span>")
return ..()
/obj/structure/toilet/attackby(obj/item/I as obj, mob/living/user as mob)
if(refilling) //No cistern interactions until bowl contents have been dealt with.
return
return ..()
/obj/structure/toilet/attack_ai(mob/user as mob)
if(isrobot(user))
if(user.client && user.client.eye == user)
return attack_hand(user)
else
return attack_hand(user)
/obj/effect/landmark/teleplumb_exit
name = "teleplumbing exit"
/obj/effect/landmark/teleplumb_exit/Entered(atom/movable/thing, atom/OldLoc)
thing.forceMove(get_turf(src))
/obj/structure/biowaste_tank
name = "Bluespace Bio-Compostor Terminal"
icon = 'icons/obj/survival_pod_comp.dmi'
icon_state = "pod_computer"
desc = "It appears to be a massive sealed container attached to some heavy machinery and thick tubes containing a whole network of interdimensional pipeworks. It appears whatever vanished down the station's toilets ends up in this thing."
anchored = TRUE
density = TRUE
var/muffin_mode = FALSE
var/mob/living/simple_mob/vore/aggressive/corrupthound/muffinmonster
/obj/structure/biowaste_tank/Initialize()
muffinmonster = new /mob/living/simple_mob/vore/aggressive/corrupthound/muffinmonster(src)
muffinmonster.name = "Activate Muffin Monster"
muffinmonster.voremob_loaded = TRUE
muffinmonster.init_vore()
return ..()
/obj/structure/biowaste_tank/AllowDrop()
return TRUE
/obj/structure/biowaste_tank/Entered(atom/movable/thing, atom/OldLoc)
if(istype(thing, /obj/item/weapon/reagent_containers/food))
qdel(thing)
return
if(istype(thing, /obj/item/organ))
qdel(thing)
return
if(istype(thing, /obj/item/weapon/storage/vore_egg))
if(thing.contents.len)
for(var/atom/movable/C in thing.contents)
C.forceMove(src)
qdel(thing)
return
if(muffin_mode)
if(muffinmonster)
thing.forceMove(muffinmonster.vore_selected)
else
muffin_mode = FALSE
/obj/structure/biowaste_tank/attack_hand(var/mob/user as mob)
if(contents.len)
var/atom/movable/choice = tgui_input_list(usr, "It appears the machine has caught some items in the lost-and-found filter system. Would you like to eject something?", "Item Retrieval Console", contents)
if(choice)
if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr))
return
if(choice == muffinmonster && muffinmonster.loc == src)
muffin_mode = !muffin_mode
if(muffin_mode)
muffinmonster.name = "Deactivate Muffin Monster"
for(var/atom/movable/C in contents)
if(C == muffinmonster)
continue
C.forceMove(muffinmonster.vore_selected)
else
muffinmonster.name = "Activate Muffin Monster"
muffinmonster.release_vore_contents(include_absorbed = TRUE, silent = TRUE)
return
else
choice.forceMove(get_turf(src))
/obj/structure/biowaste_tank/emag_act(var/remaining_charges, var/mob/user, var/emag_source)
if(muffinmonster && muffin_mode)
muffinmonster.name = "Muffin Monster"
muffinmonster.forceMove(get_turf(src))
muffinmonster = null
muffin_mode = FALSE
/mob/living/simple_mob/vore/aggressive/corrupthound/muffinmonster
name = "Muffin Monster"
desc = "OH GOD IT'S LOOSE!"
icon_state = "muffinmonster"
icon_living = "muffinmonster"
icon_dead = "muffinmonster-dead"
icon_rest = "muffinmonster_rest"
icon = 'modular_chomp/icons/mob/vore64x32_ch.dmi'
has_eye_glow = FALSE
vore_default_item_mode = IM_DIGEST
/mob/living/simple_mob/vore/aggressive/corrupthound/muffinmonster/init_vore()
if(!voremob_loaded)
return
.=..()
var/obj/belly/B = vore_selected
B.name = "waste hopper"
B.desc = "With a resounding CRUNCH, your form has gotten snagged by the Muffin Monster's rotational interlocking cutters indiscriminately crunching away at anything unlucky enough to end up in its hopper, only for the insatiable machine to grind it all down into a slurry mulch fine enough to pass through the narrow sewage lines trouble-free..."
B.digest_brute = 20
B.special_entrance_sound = 'sound/machines/blender.ogg'

View File

@@ -1,7 +1,97 @@
//PLEASE give me a better way to do this
/obj/item/clothing/head/cone/New()
. = ..()
var/shtfound = sprite_sheets.Find(SPECIES_TESHARI)
if(shtfound)
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
else
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
/obj/item/clothing/head/wizard/fake/realistic/New()
. = ..()
var/shtfound = sprite_sheets.Find(SPECIES_TESHARI)
if(shtfound)
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
else
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
/obj/item/clothing/head/wizard/fake/realistic/colorable/New()
. = ..()
var/shtfound = sprite_sheets.Find(SPECIES_TESHARI)
if(shtfound)
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
else
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
/obj/item/clothing/head/cowboy/New()
. = ..()
var/shtfound = sprite_sheets.Find(SPECIES_TESHARI)
if(shtfound)
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
else
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
/obj/item/clothing/head/cowboy/rattan/New()
. = ..()
var/shtfound = sprite_sheets.Find(SPECIES_TESHARI)
if(shtfound)
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
else
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
/obj/item/clothing/head/cowboy/dark/New()
. = ..()
var/shtfound = sprite_sheets.Find(SPECIES_TESHARI)
if(shtfound)
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
else
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
/obj/item/clothing/head/cowboy/ranger/New()
. = ..()
var/shtfound = sprite_sheets.Find(SPECIES_TESHARI)
if(shtfound)
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
else
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
/obj/item/clothing/head/cowboy/black/New()
. = ..()
var/shtfound = sprite_sheets.Find(SPECIES_TESHARI)
if(shtfound)
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
else
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
/obj/item/clothing/head/cowboy/fancy/New()
. = ..()
var/shtfound = sprite_sheets.Find(SPECIES_TESHARI)
if(shtfound)
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
else
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
/obj/item/clothing/head/cowboy/rustler/New()
. = ..()
var/shtfound = sprite_sheets.Find(SPECIES_TESHARI)
if(shtfound)
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
else
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
/obj/item/clothing/head/cowboy/bandit/New()
. = ..()
var/shtfound = sprite_sheets.Find(SPECIES_TESHARI)
if(shtfound)
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
else
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
/obj/item/clothing/head/cowboy/wide/New()
. = ..()
var/shtfound = sprite_sheets.Find(SPECIES_TESHARI)
if(shtfound)
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'
else
sprite_sheets[SPECIES_TESHARI] = 'modular_chomp/icons/inventory/head/mob_teshari.dmi'

View File

@@ -0,0 +1,67 @@
/obj/item/weapon/rig/ch/clockwork
name = "brass box"
suit_type = "clockwork"
desc = "A subtly vibrating box made out of brass. It has some buttons and switches on one side, and what looks like vents on another."
icon = 'modular_chomp/icons/obj/rig_modules_ch.dmi'
icon_state = "clockwork_rig"
slot_flags = SLOT_BELT
armor = list(melee = 70, bullet = 55, laser = 65, energy = 65, bomb = 65, bio = 0, rad = 100)
slowdown = 2
offline_slowdown = 4
offline_vision_restriction = 1
emp_protection = 40
siemens_coefficient= 0.75
rigsuit_max_pressure = 8 * ONE_ATMOSPHERE
rigsuit_min_pressure = 0
chest_type = /obj/item/clothing/suit/space/rig/ch/clockwork
helm_type = /obj/item/clothing/head/helmet/space/rig/ch/clockwork
glove_type = /obj/item/clothing/gloves/gauntlets/rig/ch/clockwork
boot_type = /obj/item/clothing/shoes/magboots/rig/ch/clockwork
cell_type = /obj/item/weapon/cell/clockwork
allowed = list(
)
initial_modules = list(
/obj/item/rig_module/ai_container,
/obj/item/rig_module/maneuvering_jets,
)
req_access = list()
req_one_access = list()
/obj/item/clothing/suit/space/rig/ch/clockwork
name = "cuirass"
icon = 'icons/obj/clothing/spacesuits_ch.dmi'
desc = "A bulky cuirass made of brass."
/obj/item/clothing/head/helmet/space/rig/ch/clockwork
name = "helmet"
icon = 'icons/obj/clothing/hats_ch.dmi'
desc = "A heavy helmet made of brass."
/obj/item/clothing/gloves/gauntlets/rig/ch/clockwork
name = "gauntlets"
icon = 'icons/obj/clothing/gloves_ch.dmi'
desc = "Heavy, shock-resistant gauntlets with brass reinforcement."
siemens_coefficient = 0
/obj/item/clothing/shoes/magboots/rig/ch/clockwork
name = "treads"
icon = 'icons/obj/clothing/shoes_ch.dmi'
desc = "Industrial boots made of brass. They're very heavy."
/obj/item/weapon/cell/clockwork //using the stats of a precursor void cell until someone's motivated to port and make the suit use clockcult global power
name = "hierophant ansible"
desc = "A curiously cold brass doodad. It seems as though it really doesn't appreciate being held. Due to it's size and the apparent electrical arc, it might be useful as a battery?"
origin_tech = list(TECH_POWER = 8, TECH_ENGINEERING = 6)
icon = 'icons/obj/clockwork_objects.dmi'
icon_state = "hierophant_ansible"
maxcharge = 4800 //same stats as a void cell, but slower at recharging itself
charge_amount = 120
self_recharge = TRUE
charge_delay = 50
matter = null
standard_overlays = FALSE

View File

@@ -81,6 +81,8 @@
/area/survivalpod/superpose/WoodenCamp
/area/survivalpod/superpose/AnimalHospital
/obj/item/device/survivalcapsule/superpose
name = "superposed surfluid shelter capsule"
desc = "A proprietary hyperstructure of many three-dimensional spaces superposed around a supermatter nano crystal; use a pen to reach the reset button. There's a license for use printed on the bottom."

View File

@@ -247,3 +247,9 @@
mappath = "modular_chomp/maps/submaps/shelters/WoodenCamp-10x10.dmm"
name = "Wooden camp."
description = "A very small camping lodge, a quick emergency hut to stave off the planets weather."
/datum/map_template/shelter/superpose/AnimalHospital
shelter_id = "AnimalHospital"
mappath = "modular_chomp/maps/submaps/shelters/AnimalHospital-20x28.dmm"
name = "Low-Tech Hospital."
description = "An animal hospital, doesnt not contain high end medical supplies, better then nothing."

View File

@@ -57,7 +57,7 @@
if(!(wear_suit && wear_suit.flags_inv & HIDETAIL))
var/vs_fullness = vore_fullness_ex["stomach"]
var/icon/vorebelly_s = new/icon(icon = 'icons/mob/vore/Bellies.dmi', icon_state = "[species.vore_belly_default_variant]Belly[vs_fullness][struggle_anim_stomach ? "" : " idle"]")
vorebelly_s.Blend(rgb(r_skin, g_skin, b_skin), species.color_mult ? ICON_MULTIPLY : ICON_ADD)
vorebelly_s.Blend(vore_sprite_color["stomach"], vore_sprite_multiply["stomach"] ? ICON_MULTIPLY : ICON_ADD)
var/image/working = image(vorebelly_s)
working.overlays += em_block_image_generic(working)
return working
@@ -88,7 +88,7 @@
if(tail_style && istaurtail(tail_style) && tail_style:vore_tail_sprite_variant)
var/vs_fullness = vore_fullness_ex["taur belly"]
var/icon/vorebelly_s = new/icon(icon = 'icons/mob/vore/Taur_Bellies.dmi', icon_state = "Taur[tail_style:vore_tail_sprite_variant]-Belly-[vs_fullness][struggle_anim_taur ? "" : " idle"]")
vorebelly_s.Blend(rgb(src.r_tail, src.g_tail, src.b_tail), tail_style.color_blend_mode)
vorebelly_s.Blend(vore_sprite_color["taur belly"], vore_sprite_multiply["taur belly"] ? ICON_MULTIPLY : ICON_ADD)
var/image/working = image(vorebelly_s)
working.pixel_x = -16
if(tail_style.em_block)

View File

@@ -1,4 +1,9 @@
/mob
var/voice_freq = 42500 // Preference for character voice frequency
var/list/voice_sounds_list = list() // The sound list containing our voice sounds!
var/died_in_vr = FALSE //For virtual reality sleepers
var/died_in_vr = FALSE //For virtual reality sleepers
/mob/is_incorporeal()
if(incorporeal_move)
return 1
..()

View File

@@ -1,13 +1,19 @@
/datum/sprite_accessory/tail/taur
var/vore_tail_sprite_variant = ""
/datum/sprite_accessory/tail/taur/wolf
vore_tail_sprite_variant = "N"
/datum/sprite_accessory/tail/taur/ch/longvirus
name = "Long Virus (Taur)"
icon_state = "longvirus_s"
extra_overlay = "longvirus_markings"
icon_sprite_tag = "virus"
//suit_sprites = 'icons/mob/taursuits_noodle.dmi' Aye, I've gotta sprite that shit.
/datum/sprite_accessory/tail/taur
var/vore_tail_sprite_variant = ""
/datum/sprite_accessory/tail/taur/wolf
vore_tail_sprite_variant = "N"
/datum/sprite_accessory/tail/taur/ch/longvirus
name = "Long Virus (Taur)"
icon_state = "longvirus_s"
extra_overlay = "longvirus_markings"
icon_sprite_tag = "virus"
//suit_sprites = 'icons/mob/taursuits_noodle.dmi' Aye, I've gotta sprite that shit.´
/datum/sprite_accessory/tail/taur/ch/fox
name = "Fox (Taur, 3-color)"
icon_state = "fox"
extra_overlay = "fox_markings"
extra_overlay2 = "fox_markings2"

View File

@@ -0,0 +1,637 @@
#define SYNTHESIZER_MAX_CARTRIDGES 40
#define SYNTHESIZER_MAX_RECIPES 20
#define SYNTHESIZER_MAX_QUEUE 40
#define RECIPE_MAX_STRING 160
#define RECIPE_MAX_STEPS 16
// Recipes are stored as a list which alternates between chemical id's and volumes to add, e.g. 1 = 'Carbon', 2 = 20, 3 = 'Silicon', 4 = 20
/obj/machinery/chemical_synthesizer
name = "chemical synthesizer"
desc = "A programmable machine capable of automatically synthesizing medicine."
icon = 'modular_chomp/icons/obj/chemical_ch.dmi'
icon_state = "synth_idle_bottle"
use_power = USE_POWER_IDLE
power_channel = EQUIP
idle_power_usage = 100
active_power_usage = 150
anchored = TRUE
unacidable = TRUE
density = TRUE
panel_open = TRUE
var/busy = FALSE
var/production_mode = FALSE // Toggle between click-step input and comma-delineated text input for creating recipes.
var/use_catalyst = TRUE // Determines whether or not the catalyst will be added to reagents while processing a recipe.
var/delay_modifier = 4 // This is multiplied by the volume of a step to determine how long each step takes. Bigger volume = slower.
var/obj/item/weapon/reagent_containers/glass/catalyst = null // This is where the user adds catalyst. Usually phoron.
var/list/recipes = list() // This holds chemical recipes up to a maximum determined by SYNTHESIZER_MAX_RECIPES. Two-dimensional.
var/list/queue = list() // This holds the recipe id's for queued up recipes.
var/list/catalyst_ids = list() // This keeps track of the chemicals in the catalyst to remove before bottling.
var/list/cartridges = list() // Associative, label -> cartridge
var/list/spawn_cartridges = list(
/obj/item/weapon/reagent_containers/chem_disp_cartridge/hydrogen,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/lithium,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/carbon,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/nitrogen,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/oxygen,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/fluorine,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/sodium,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/aluminum,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/silicon,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/phosphorus,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/sulfur,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/chlorine,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/potassium,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/iron,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/copper,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/mercury,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/radium,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/water,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/ethanol,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/sugar,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/sacid,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/tungsten,
/obj/item/weapon/reagent_containers/chem_disp_cartridge/calcium
)
var/_recharge_reagents = TRUE
var/process_tick = 0
var/list/dispense_reagents = list(
"hydrogen", "lithium", "carbon", "nitrogen", "oxygen", "fluorine", "sodium",
"aluminum", "silicon", "phosphorus", "sulfur", "chlorine", "potassium", "iron",
"copper", "mercury", "radium", "water", "ethanol", "sugar", "sacid", "tungsten", "calcium"
)
/obj/machinery/chemical_synthesizer/Initialize()
. = ..()
// Create the reagents datum which will act as the machine's reaction vessel.
create_reagents(600)
catalyst = new /obj/item/weapon/reagent_containers/glass/beaker(src)
if(spawn_cartridges)
for(var/type in spawn_cartridges)
add_cartridge(new type(src))
panel_open = FALSE
var/obj/item/weapon/paper/P = new /obj/item/weapon/paper(get_turf(src))
P.name = "Synthesizer Instructions"
P.desc = "A photocopy of a handwritten note."
P.info = {"Hello there! This device is a new NanoTrasen product currently being shipped to select facilities \
for internal testing! We haven't finished the instruction manual yet so each unit shipped with this pamphlet \
(I really hope you can read my handwriting). This machine is a programmable chemical synthesizer which, if used \
correctly, will allow you to queue up some recipes and go work on something else while the medicine manufactures. \
It's slower than doing things by hand but it also keeps your hands free! And yes, it bottles automatically. \
<BR><BR>To get started, you need to program some recipes. The machine has two modes for this: tutorial and production. \
Tutorial is intended to teach you how recipes work, or to create recipes for later use with production mode. This one \
should be self-explanatory, just follow the prompts. Production mode allows you to rapidly import recipes in CSV format. \
If I've lost you, just keep reading; once you give it a name and tell the machine how many steps are in the recipe, just \
input the recipe as a comma-separated string of chemical names and volumes to be added, like "Chem1,10,Chem2,20,... \
<BR><BR>If that still doesn't make sense, I've included an example for Dylovene at the bottom. Also, don't include \
catalyst reagents in the recipe, read below for that part. Also also, remember that chemical names are case sensitive and \
cartridge names are usually Capitalized Like These Words (AND FOR THE LOVE OF GOD KEVIN, STOP CHANGING THE NAMES ON THE \
LABELS WHEN THE CHEMISTS AREN'T LOOKING IT ISN'T FUNNY ANYMORE). \
<BR><BR>Next important concept is the catalyst, intended for catalyst reagents (usually phoron). When the catalyst option is \
enabled, whatever is in the catalyst bottle gets added to the reaction chamber before synthesis begins. When the \
recipe is done, it extracts the catalyst, bottles whatever you made, then adds the catalyst back before starting \
the next recipe in the queue. It's up to you to make sure no unwanted side reactions happen, and yes, this means \
you cannot queue up catalyst recipes with recipes ruined by the catalyst. Maybe add "NO CAT" or something to the \
name for recipes like that? \
<BR><BR>And that's the really important stuff. Remember you can export recipes for later shifts, just copy the \
output into your PDA or something. Oh, and stalling. Say you're missing a cartridge or the vessel is full (the \
capacity is [src.reagents.maximum_volume]) or you press the emergency stop button. The machine will stall, \
clearing the temporary memory. To get it started again, you just need to empty the vessel. Anyway, here's \
example recipe. \
<BR><BR> Name: Dylovene (60u) \
<BR> Number of steps: 3 \
<BR> Recipe string: Silicon,20,Nitrogen,20,Potassium,20"}
/obj/machinery/chemical_synthesizer/examine(mob/user)
. = ..()
if(panel_open)
. += "It has [cartridges.len] cartridges installed, and has space for [SYNTHESIZER_MAX_CARTRIDGES - cartridges.len] more."
/obj/machinery/chemical_synthesizer/power_change()
. = ..()
update_icon()
/obj/machinery/chemical_synthesizer/update_icon()
underlays.Cut()
if(stat & BROKEN)
icon_state = "synth_broken"
return
if(stat & NOPOWER)
icon_state = "synth_off"
return
if(!busy)
if(catalyst)
icon_state = "synth_idle_bottle"
else
icon_state = "synth_idle"
else
icon_state = "synth_working"
if(catalyst) // All underlay icon_states requires the catalyst bottle to be present, so this works as a check.
if(catalyst.reagents.reagent_list.len)
var/image/cat_filling = image(icon, src, "synth_catalyst", -1)
cat_filling.color = catalyst.reagents.get_color()
underlays += cat_filling
if(src.reagents.reagent_list.len)
var/image/ves_filling = image(icon, src, "synth_vessel", -2)
ves_filling.color = src.reagents.get_color()
underlays += ves_filling
/obj/machinery/chemical_synthesizer/proc/add_cartridge(obj/item/weapon/reagent_containers/chem_disp_cartridge/C, mob/user)
if(!panel_open)
if(user)
to_chat(user, "<span class='warning'>\The panel is locked!</span>")
return
if(!istype(C))
if(user)
to_chat(user, "<span class='warning'>\The [C] will not fit in \the [src]!</span>")
return
if(cartridges.len >= SYNTHESIZER_MAX_CARTRIDGES)
if(user)
to_chat(user, "<span class='warning'>\The [src] does not have any slots open for \the [C] to fit into!</span>")
return
if(!C.label)
if(user)
to_chat(user, "<span class='warning'>\The [C] does not have a label!</span>")
return
if(cartridges[C.label])
if(user)
to_chat(user, "<span class='warning'>\The [src] already contains a cartridge with that label!</span>")
return
if(user)
user.drop_from_inventory(C)
to_chat(user, "<span class='notice'>You add \the [C] to \the [src].</span>")
C.loc = src
cartridges[C.label] = C
cartridges = sortAssoc(cartridges)
SStgui.update_uis(src)
/obj/machinery/chemical_synthesizer/proc/remove_cartridge(label)
. = cartridges[label]
cartridges -= label
SStgui.update_uis(src)
/obj/machinery/chemical_synthesizer/attackby(obj/item/weapon/W, mob/user)
// Why do so many people code in wrenching when there's already a proc for it?
if(!busy && default_unfasten_wrench(user, W, 40))
return
if(istype(W, /obj/item/weapon/reagent_containers/chem_disp_cartridge))
add_cartridge(W, user)
return
// But we won't use the screwdriver proc because chem dispenser behavior.
if(panel_open && W.is_screwdriver())
var/label = tgui_input_list(user, "Which cartridge would you like to remove?", "Chemical Synthesizer", cartridges)
if(!label)
return
var/obj/item/weapon/reagent_containers/chem_disp_cartridge/C = remove_cartridge(label)
if(C)
to_chat(user, "<span class='notice'>You remove \the [C] from \the [src].</span>")
C.loc = loc
playsound(src, W.usesound, 50, 1)
return
// We don't need a busy check here as the catalyst slot must be occupied for the machine to function.
if(istype(W, /obj/item/weapon/reagent_containers/glass))
if(catalyst)
to_chat(user, "<span class='warning'>There is already \a [catalyst] in \the [src] catalyst slot!</span>")
return
if(stat & (BROKEN|NOPOWER))
to_chat(user, "<span class='warning'>The clamp will not secure the catalyst while the machine is down!</span>")
return
var/obj/item/weapon/reagent_containers/RC = W
if(!RC.is_open_container())
to_chat(user, "<span class='warning'>You don't see how \the [src] could extract reagents from \the [RC].</span>")
return
catalyst = RC
user.drop_from_inventory(RC)
RC.loc = src
to_chat(user, "<span class='notice'>You set \the [RC] on \the [src].</span>")
update_icon()
return
return ..()
// More stolen chemical_dispenser code.
/obj/machinery/chemical_synthesizer/process()
if(!_recharge_reagents)
return
if(stat & (BROKEN|NOPOWER))
return
if(--process_tick <= 0)
process_tick = 15
. = 0
for(var/id in dispense_reagents)
var/datum/reagent/R = SSchemistry.chemical_reagents[id]
if(!R)
stack_trace("[src] at [x],[y],[z] failed to find reagent '[id]'!")
dispense_reagents -= id
continue
var/obj/item/weapon/reagent_containers/chem_disp_cartridge/C = cartridges[R.name]
if(C && C.reagents.total_volume < C.reagents.maximum_volume)
var/to_restore = min(C.reagents.maximum_volume - C.reagents.total_volume, 5)
use_power(to_restore * 500)
C.reagents.add_reagent(id, to_restore)
. = 1
if(.)
SStgui.update_uis(src)
/obj/machinery/chemical_synthesizer/tgui_interact(mob/user, datum/tgui/ui = null)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ChemSynthesizer", name)
ui.open()
/obj/machinery/chemical_synthesizer/tgui_data(mob/user)
var/list/data = list()
data["busy"] = busy
data["production_mode"] = production_mode
data["panel_open"] = panel_open
data["use_catalyst"] = use_catalyst
var/list/tmp_queue = list()
for(var/i = 1, i <= queue.len, i++)
tmp_queue.Add(list(list("name" = queue[i], "index" = i))) // Thanks byond
data["queue"] = tmp_queue
// Convert the recipes list into an array of strings. The UI does not need the associative list attached to each string.
var/list/tmp_recipes = list()
for(var/i = 1, i <= recipes.len, i++)
tmp_recipes.Add(list(list("name" = recipes[i])))
data["recipes"] = tmp_recipes
// Read data from the reaction vessel.
var/list/vessel_reagents_list = list()
data["rxn_vessel"] = vessel_reagents_list
for(var/datum/reagent/R in src.reagents.reagent_list)
vessel_reagents_list[++vessel_reagents_list.len] = list("name" = R.name, "volume" = R.volume, "description" = R.description, "id" = R.id)
// Read data from the catalyst, if present.
data["catalyst"] = catalyst ? 1 : 0
if(catalyst)
var/list/catalyst_reagents_list = list()
data["catalyst_reagents"] = catalyst_reagents_list
for(var/datum/reagent/R in catalyst.reagents.reagent_list)
catalyst_reagents_list[++catalyst_reagents_list.len] = list("name" = R.name, "volume" = R.volume, "description" = R.description, "id" = R.id)
if(catalyst)
data["catalystCurrentVolume"] = catalyst.reagents.total_volume
data["catalystMaxVolume"] = catalyst.reagents.maximum_volume
else
data["catalystCurrentVolume"] = null
data["catalystMaxVolume"] = null
var/chemicals[0]
for(var/label in cartridges)
var/obj/item/weapon/reagent_containers/chem_disp_cartridge/C = cartridges[label]
chemicals.Add(list(list("title" = label, "id" = label, "amount" = C.reagents.total_volume))) // list in a list because Byond merges the first list
data["chemicals"] = chemicals
return data
/obj/machinery/chemical_synthesizer/tgui_act(action, params)
if(..())
return TRUE
. = TRUE
switch(action)
if("start_queue")
// Start up the queue.
if(!busy)
start_queue(usr)
if("rem_queue")
// Remove a single entry from the queue. Sanity checks also prevent removing the first entry if the machine is busy though UI should already prevent that.
var/index = text2num(params["q_index"])
if(!isnum(index) || !ISINTEGER(index) || !istype(queue) || (index<1 || index>length(queue) || (busy && index == 1)))
return
queue -= queue[index]
if("clear_queue")
// Remove all entries from the queue except the currently processing recipe.
var/confirm = alert(usr, "Are you sure you want to clear the running queue?", "Confirm", "No", "Yes")
if(confirm == "Yes")
if(busy)
// Oh no, I've broken code convention to remove all entries but the first.
for(var/i = queue.len, i >= 2, i--)
queue -= queue[i]
else
queue = list()
if("eject_catalyst")
// Removes the catalyst bottle from the machine.
if(!busy && catalyst)
catalyst.forceMove(get_turf(src))
catalyst = null
update_icon()
if("toggle_catalyst")
// Decides if the machine uses the catalyst.
if(!busy)
use_catalyst = !use_catalyst
if("emergency_stop")
// Stops everything if that's desirable for some reason.
if(busy)
var/confirm = alert(usr, "Are you sure you want to stall the machine?", "Confirm", "Yes", "No")
if(confirm == "Yes")
stall()
if("bottle_product")
// Bottles the reaction mixture if stalled.
if(!busy)
bottle_product()
if("panel_toggle")
// Opens/closes the panel.
if(!busy)
panel_open = !panel_open
if("mode_toggle")
// Toggles production mode.
production_mode = !production_mode
if("add_recipe")
// Allows the user to add a recipe. Kinda vital for this machine to do anything useful.
if(recipes.len >= SYNTHESIZER_MAX_RECIPES)
to_chat(usr, "<span class='warning'>Maximum recipes exceeded!</span>")
return
if(!production_mode)
babystep_recipe(usr)
else
import_recipe(usr)
if("rem_recipe")
// Allows the user to remove recipes while the machine is idle.
if(!busy)
var/confirm = alert(usr, "Are you sure you want to remove this recipe?", "Confirm", "No", "Yes")
if(confirm == "Yes")
var/index = params["rm_index"]
if(index in recipes)
recipes.Remove(list(index)) // Fuck off Byond.
else
to_chat(usr, "<span class='warning'>You cannot remove recipes while the machine is running!</span>")
if("exp_recipe")
// Allows the user to export recipes to chat formatted for easy importing.
var/index = params["exp_index"]
export_recipe(usr, index)
if("add_queue")
// Adds recipes to the queue.
if(queue.len >= SYNTHESIZER_MAX_QUEUE)
to_chat(usr, "<span class='warning'>Synthesizer queue full!</span>")
return
var/index = params["qa_index"]
// If you forgot, this is a string returned by the user pressing the "add to queue" button on a recipe.
if(index in recipes)
queue[++queue.len] = index
add_fingerprint(usr)
/obj/machinery/chemical_synthesizer/attack_ghost(mob/user)
if(stat & (BROKEN|NOPOWER))
return
tgui_interact(user)
/obj/machinery/chemical_synthesizer/attack_ai(mob/user)
attack_hand(user)
/obj/machinery/chemical_synthesizer/attack_hand(mob/user)
if(stat & (BROKEN|NOPOWER))
return
tgui_interact(user)
// This proc is lets users create recipes step-by-step and exports a comma delineated list to chat. It's intended to teach how to use the machine.
/obj/machinery/chemical_synthesizer/proc/babystep_recipe(mob/user)
var/rec_name = sanitizeSafe(input(user, "Name your recipe. Consider including the output volume.", "Recipe naming", null) as text, MAX_NAME_LEN)
if(!rec_name || (rec_name in recipes)) // Code requires each recipe to have a unique name.
to_chat(user, "Please provide a unique recipe name!")
return
var/steps = 2 * CLAMP(round(input(user, "How many steps does your recipe contain (16 max)?", "Steps", null) as num), 0, RECIPE_MAX_STEPS) // Round to get a whole integer, clamp to ensure proper range.
if(!steps)
to_chat(user, "Please input a valid number of steps!")
return
var/list/new_rec = list() // This holds the actual recipe.
for(var/i = 1, i < steps, i += 2) // For the user, 1 step is both text and volume. For list arithmetic, that's 2 steps.
var/label = tgui_input_list(user, "Which chemical would you like to use?", "Chemical Synthesizer", cartridges)
if(!label)
to_chat(user, "Please select a chemical!")
return
new_rec[++new_rec.len] = label // Add the reagent ID.
var/amount = CLAMP(round(input(user, "How much of the chemical would you like to add?", "Volume", null) as num), 0, src.reagents.maximum_volume)
if(!amount)
to_chat(user, "Please select a volume!")
return
new_rec[++new_rec.len] = amount // Add the amount of reagent.
recipes[rec_name] = new_rec
SStgui.update_uis(src)
export_recipe(user, rec_name) // Now export the recipe to the user's chatbox formatted for import_recipe().
return
// This proc allows users to copy-paste a comma delineated list to create a recipe. The recipe will cause a stall() if formatted incorrectly.
/obj/machinery/chemical_synthesizer/proc/import_recipe(mob/user)
var/rec_name = sanitizeSafe(input(user, "Name your recipe. Consider including the output volume.", "Recipe naming", null) as text, MAX_NAME_LEN)
if(!rec_name || (rec_name in recipes)) // Code requires each recipe to have a unique name.
to_chat(user, "Please provide a unique recipe name!")
return
var/rec_input = input(user, "Input your recipe as 'Chem1,vol1,Chem2,vol2,...'", "Import recipe", null)
if(!rec_input || (length(rec_input) > RECIPE_MAX_STRING) || !findtext(rec_input, ",")) // The smallest possible recipe will contain 1 comma.
to_chat(user, "Invalid input or recipe max length exceeded!")
return
rec_input = trim(rec_input) // Sanitize.
var/list/new_rec = list() // This holds the actual recipe.
var/vol = FALSE // This tracks if the next step is a chemical name or a volume.
var/index = findtext(rec_input, ",") // This tracks the delineation index in the user-provided string. Should never be null at this point.
var/i = 1 // This tracks the index for new_rec, the actual list which gets added to recipes[rec_name].
while(index) // Alternates between text strings and numbers. When false, the rest of rec_input is the final step.
new_rec[++new_rec.len] = copytext(rec_input, 1, index)
if(vol)
new_rec[i] = text2num(new_rec[i]) // If it's a volume step, convert to a number.
vol = FALSE
else
vol = TRUE
i++
rec_input = copytext(rec_input, index + 1) // Trim previous substrings from rec_input.
index = findtext(rec_input, ",")
if(rec_input) // The remainder of rec_input should be the final volume step of the recipe. The if() is a sanity check.
new_rec[++new_rec.len] = text2num(rec_input)
recipes[rec_name] = new_rec // Finally, add the recipe to the recipes list.
SStgui.update_uis(src)
return
// This proc exports stored recipes to the user's chatbox formatted as a comma delineated list for use with import_recpe()
/obj/machinery/chemical_synthesizer/proc/export_recipe(mob/user, rec_name)
var/list/export = recipes[rec_name]
if(!export)
return
var/display_txt = export.Join(",") // This converts the entire list into a CSV string.
to_chat(user, "[display_txt]")
// This proc handles adding the catalyst starting the synthesizer's queue.
/obj/machinery/chemical_synthesizer/proc/start_queue(mob/user)
if(stat & (BROKEN|NOPOWER))
return
if(!queue)
to_chat(user, "You can't start an empty queue!")
return
if(!catalyst)
to_chat(user, "Place a bottle in the catalyst slot before starting the queue!")
return
if(panel_open)
to_chat(user, "Close the panel before starting the queue!")
return
if(reagents.total_volume)
to_chat(user, "Empty the reaction vessel before starting the queue!")
return
busy = TRUE
use_power = USE_POWER_ACTIVE
if(use_catalyst)
// Populate the list of catalyst chems. This is important when it's time to bottle_product().
for(var/datum/reagent/chem in catalyst.reagents.reagent_list)
catalyst_ids += chem.id
// Transfer the catalyst to the synthesizer's reagent holder.
catalyst.reagents.trans_to_holder(src.reagents, catalyst.reagents.total_volume)
// Start the first recipe in the queue, starting with step 1.
update_icon()
follow_recipe(queue[1], 1)
// This proc controls the timing for each step in a reaction. Step is the index for the current chem of our recipe, step + 1 is the volume of said chem.
/obj/machinery/chemical_synthesizer/proc/follow_recipe(var/r_id, var/step as num)
if(stat & (BROKEN|NOPOWER))
stall()
return
if(!step)
step = 1
// The time between each step is the volume required by a step multiplied by the delay_modifier (in ticks/deciseconds).
addtimer(CALLBACK(src, .proc/perform_reaction, r_id, step), recipes[r_id][step + 1] * delay_modifier)
// This proc carries out the actual steps in each reaction.
/obj/machinery/chemical_synthesizer/proc/perform_reaction(var/r_id, var/step as num)
if(stat & (BROKEN|NOPOWER))
stall()
return
//Let's store these as temporary variables to make the code more readable.
var/label = recipes[r_id][step]
var/quantity = recipes[r_id][step+1]
// If we're missing a cartridge somehow or lack space for the next step, stall. It's now up to the chemist to fix this.
if(!cartridges[label])
visible_message("<span class='warning'>The [src] beeps loudly, flashing a 'cartridge missing' error!</span>", "You hear loud beeping!")
playsound(src, 'sound/weapons/smg_empty_alarm.ogg', 40)
stall()
return
if(quantity > reagents.get_free_space())
visible_message("<span class='warning'>The [src] beeps loudly, flashing a 'maximum volume exceeded' error!</span>", "You hear loud beeping!")
playsound(src, 'sound/weapons/smg_empty_alarm.ogg', 40)
stall()
return
// If there isn't enough reagent left for this step, try again in a minute.
var/obj/item/weapon/reagent_containers/chem_disp_cartridge/C = cartridges[label]
if(quantity > C.reagents.total_volume)
visible_message("<span class='notice'>The [src] flashes an 'insufficient reagents' warning.</span>")
addtimer(CALLBACK(src, .proc/perform_reaction, r_id, step), 1 MINUTE)
return
// After all this mess of code, we reach the line where the magic happens.
C.reagents.trans_to_holder(src.reagents, quantity)
update_icon() // Update underlays.
playsound(src, 'modular_chomp/sound/machines/HPLC_binary_pump.ogg', 15, 1)
// Advance to the next step in the recipe. If this is outside of the recipe's index, we're finished. Otherwise, proceed to next step.
step += 2
var/list/tmp = recipes[r_id]
if(step > tmp.len)
// First extract the catalyst(s), if any remain.
if(use_catalyst)
for(var/chem in catalyst_ids)
var/amount = reagents.get_reagent_amount(chem)
reagents.trans_id_to(catalyst, chem, amount)
// Add a delay of 1 tick per unit of reagent. Clear the catalyst_ids.
catalyst_ids = list()
var/delay = reagents.total_volume
update_icon() // Update the icon first to remove underlays, then switch to the new icon_state.
icon_state = "synth_finished"
addtimer(CALLBACK(src, .proc/bottle_product, r_id), delay)
else
follow_recipe(r_id, step)
// Now that we're done, bottle up the product.
/obj/machinery/chemical_synthesizer/proc/bottle_product(var/r_id)
if(stat & (BROKEN|NOPOWER))
stall()
return
if(!r_id)
r_id = "[reagents.get_master_reagent_name()]"
while(reagents.total_volume)
var/obj/item/weapon/reagent_containers/glass/bottle/B = new(src.loc)
B.name = "[r_id] bottle"
B.pixel_x = rand(-7, 7) // random position
B.pixel_y = rand(-7, 7)
B.icon_state = "bottle-4"
reagents.trans_to_obj(B, min(reagents.total_volume, MAX_UNITS_PER_BOTTLE))
B.update_icon()
// Sanity check when manual bottling is triggered.
if(queue.len)
queue -= queue[1]
// If the queue is now empty, we're done. Otherwise, re-add catalyst and proceed to the next recipe.
if(queue.len)
if(use_catalyst)
for(var/datum/reagent/chem in catalyst.reagents.reagent_list)
catalyst_ids += chem.id
catalyst.reagents.trans_to_holder(src.reagents, catalyst.reagents.total_volume)
update_icon()
follow_recipe(queue[1], 1)
else
busy = FALSE
use_power = USE_POWER_IDLE
queue = list()
update_icon()
// What happens to the synthesizer if it breaks or loses power in the middle of running. Chemists must fix things manually.
/obj/machinery/chemical_synthesizer/proc/stall()
busy = FALSE
use_power = USE_POWER_IDLE
queue = list()
catalyst_ids = list()
update_icon()
#undef SYNTHESIZER_MAX_CARTRIDGES
#undef SYNTHESIZER_MAX_RECIPES
#undef SYNTHESIZER_MAX_QUEUE
#undef RECIPE_MAX_STRING
#undef RECIPE_MAX_STEPS