Merge remote-tracking branch 'upstream/master'

This commit is contained in:
DeltaFire
2020-08-21 18:49:06 +02:00
82 changed files with 3002 additions and 203 deletions
BIN
View File
Binary file not shown.
+1
View File
@@ -312,6 +312,7 @@
#define COMSIG_OBJ_DECONSTRUCT "obj_deconstruct" //from base of obj/deconstruct(): (disassembled)
#define COMSIG_OBJ_BREAK "obj_break" //from base of /obj/obj_break(): (damage_flag)
#define COMSIG_OBJ_SETANCHORED "obj_setanchored" //called in /obj/structure/setAnchored(): (value)
#define COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH "obj_default_unfasten_wrench" //called exclusively in plumbing, for now
#define COMSIG_OBJ_ATTACK_GENERIC "obj_attack_generic" //from base of atom/animal_attack(): (/mob/user)
#define COMPONENT_STOP_GENERIC_ATTACK 1
+9
View File
@@ -0,0 +1,9 @@
#define FIRST_DUCT_LAYER 1
#define SECOND_DUCT_LAYER 2
#define THIRD_DUCT_LAYER 4
#define FOURTH_DUCT_LAYER 8
#define FIFTH_DUCT_LAYER 16
#define DUCT_LAYER_DEFAULT THIRD_DUCT_LAYER
#define MACHINE_REAGENT_TRANSFER 10
+11 -4
View File
@@ -6,10 +6,17 @@
#define QDEL_HINT_IWILLGC 2 //functionally the same as the above. qdel should assume the object will gc on its own, and not check it.
#define QDEL_HINT_HARDDEL 3 //qdel should assume this object won't gc, and queue a hard delete using a hard reference.
#define QDEL_HINT_HARDDEL_NOW 4 //qdel should assume this object won't gc, and hard del it post haste.
#define QDEL_HINT_FINDREFERENCE 5 //functionally identical to QDEL_HINT_QUEUE if TESTING is not enabled in _compiler_options.dm.
//if TESTING is enabled, qdel will call this object's find_references() verb.
#define QDEL_HINT_IFFAIL_FINDREFERENCE 6 //Above but only if gc fails.
//defines for the gc_destroyed var
#ifdef LEGACY_REFERENCE_TRACKING
/** If LEGACY_REFERENCE_TRACKING is enabled, qdel will call this object's find_references() verb.
*
* Functionally identical to QDEL_HINT_QUEUE if GC_FAILURE_HARD_LOOKUP is not enabled in _compiler_options.dm.
*/
#define QDEL_HINT_FINDREFERENCE 5
/// Behavior as QDEL_HINT_FINDREFERENCE, but only if the GC fails and a hard delete is forced.
#define QDEL_HINT_IFFAIL_FINDREFERENCE 6
#endif
#define GC_QUEUE_CHECK 1
#define GC_QUEUE_HARDDELETE 2
+4
View File
@@ -57,6 +57,10 @@
#define ADD_REAGENT 2 // reagent added
#define REM_REAGENT 3 // reagent removed (may still exist)
#define PILL_STYLE_COUNT 22 //Update this if you add more pill icons or you die (literally, we'll toss a nuke at whever your ip turns up)
#define RANDOM_PILL_STYLE 22 //Dont change this one though
#define THRESHOLD_UNHUSK 50 // health threshold for synthflesh/rezadone to unhusk someone
//reagent bitflags, used for altering how they works
+1
View File
@@ -75,6 +75,7 @@
#define VV_HK_MARK "mark"
#define VV_HK_ADDCOMPONENT "addcomponent"
#define VV_HK_MODIFY_TRAITS "modtraits"
#define VV_HK_VIEW_REFERENCES "viewreferences"
// /datum/gas_mixture
#define VV_HK_SET_MOLES "set_moles"
+17 -4
View File
@@ -11,15 +11,28 @@
#ifdef TESTING
#define DATUMVAR_DEBUGGING_MODE
//#define GC_FAILURE_HARD_LOOKUP //makes paths that fail to GC call find_references before del'ing.
//implies FIND_REF_NO_CHECK_TICK
/*
* Enables extools-powered reference tracking system, letting you see what is referencing objects that refuse to hard delete.
*
* * Requires TESTING to be defined to work.
*/
//#define REFERENCE_TRACKING
//#define FIND_REF_NO_CHECK_TICK //Sets world.loop_checks to false and prevents find references from sleeping
///Method of tracking references without using extools. Slower, kept to avoid over-reliance on extools.
//#define LEGACY_REFERENCE_TRACKING
#ifdef LEGACY_REFERENCE_TRACKING
///Use the legacy reference on things hard deleting by default.
//#define GC_FAILURE_HARD_LOOKUP
#ifdef GC_FAILURE_HARD_LOOKUP
#define FIND_REF_NO_CHECK_TICK
#endif //ifdef GC_FAILURE_HARD_LOOKUP
#endif //ifdef LEGACY_REFERENCE_TRACKING
//#define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green
#endif
#endif //ifdef TESTING
//#define UNIT_TESTS //Enables unit tests via TEST_RUN_PARAMETER
#ifndef PRELOAD_RSC //set to:
@@ -534,4 +534,4 @@
config_entry_value = 6
/datum/config_entry/number/max_shuttle_size
config_entry_value = 250
config_entry_value = 500
+5
View File
@@ -0,0 +1,5 @@
PROCESSING_SUBSYSTEM_DEF(fluids)
name = "Fluids"
wait = 20
stat_tag = "FD" //its actually Fluid Ducts
flags = SS_NO_INIT | SS_TICKER
+22 -130
View File
@@ -25,7 +25,7 @@ SUBSYSTEM_DEF(garbage)
//Queue
var/list/queues
#ifdef TESTING
#ifdef LEGACY_REFERENCE_TRACKING
var/list/reference_find_on_fail = list()
var/list/reference_find_on_fail_types = list()
#endif
@@ -134,7 +134,7 @@ SUBSYSTEM_DEF(garbage)
++gcedlasttick
++totalgcs
pass_counts[level]++
#ifdef TESTING
#ifdef LEGACY_REFERENCE_TRACKING
reference_find_on_fail -= refID //It's deleted we don't care anymore.
#endif
if (MC_TICK_CHECK)
@@ -145,7 +145,9 @@ SUBSYSTEM_DEF(garbage)
fail_counts[level]++
switch (level)
if (GC_QUEUE_CHECK)
#ifdef TESTING
#ifdef REFERENCE_TRACKING
D.find_references()
#elif defined(LEGACY_REFERENCE_TRACKING)
if(reference_find_on_fail[refID])
D.find_references()
#ifdef GC_FAILURE_HARD_LOOKUP
@@ -156,7 +158,19 @@ SUBSYSTEM_DEF(garbage)
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
#ifdef TESTING
log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --")
for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
var/client/admin = c
if(!check_rights_for(admin, R_ADMIN))
continue
to_chat(admin, "## TESTING: GC: -- [ADMIN_VV(D)] | [type] was unable to be GC'd --")
testing("GC: -- \ref[src] | [type] was unable to be GC'd --")
#endif
#ifdef REFERENCE_TRACKING
GLOB.deletion_failures += D //It should no longer be bothered by the GC, manual deletion only.
continue
#endif
I.failures++
if (GC_QUEUE_HARDDELETE)
HardDelete(D)
@@ -181,7 +195,7 @@ SUBSYSTEM_DEF(garbage)
var/gctime = world.time
var/refid = "\ref[D]"
#ifdef TESTING
#ifdef LEGACY_REFERENCE_TRACKING
if(reference_find_on_fail_types[D.type])
reference_find_on_fail["\ref[D]"] = TRUE
#endif
@@ -193,7 +207,7 @@ SUBSYSTEM_DEF(garbage)
queue[refid] = gctime
#ifdef TESTING
#ifdef LEGACY_REFERENCE_TRACKING
/datum/controller/subsystem/garbage/proc/add_type_to_findref(type)
if(!ispath(type))
return "NOT A VAILD PATH"
@@ -260,12 +274,6 @@ SUBSYSTEM_DEF(garbage)
/datum/qdel_item/New(mytype)
name = "[mytype]"
#ifdef TESTING
/proc/qdel_and_find_ref_if_fail(datum/D, force = FALSE)
SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE
qdel(D, force)
#endif
// Should be treated as a replacement for the 'del' keyword.
// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
/proc/qdel(datum/D, force=FALSE, ...)
@@ -319,16 +327,13 @@ SUBSYSTEM_DEF(garbage)
SSgarbage.Queue(D, GC_QUEUE_HARDDELETE)
if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
SSgarbage.HardDelete(D)
if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion.
#ifdef LEGACY_REFERENCE_TRACKING
if (QDEL_HINT_FINDREFERENCE) //qdel will, if LEGACY_REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
SSgarbage.Queue(D)
#ifdef TESTING
D.find_references()
#endif
if (QDEL_HINT_IFFAIL_FINDREFERENCE)
SSgarbage.Queue(D)
#ifdef TESTING
SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE
#endif
#endif
else
#ifdef TESTING
if(!I.no_hint)
@@ -339,119 +344,6 @@ SUBSYSTEM_DEF(garbage)
else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
#ifdef TESTING
/datum/verb/find_refs()
set category = "Debug"
set name = "Find References"
set src in world
find_references(FALSE)
/datum/proc/find_references(skip_alert)
running_find_references = type
if(usr && usr.client)
if(usr.client.running_find_references)
testing("CANCELLED search for references to a [usr.client.running_find_references].")
usr.client.running_find_references = null
running_find_references = null
//restart the garbage collector
SSgarbage.can_fire = 1
SSgarbage.next_fire = world.time + world.tick_lag
return
if(!skip_alert)
if(alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") == "No")
running_find_references = null
return
//this keeps the garbage collector from failing to collect objects being searched for in here
SSgarbage.can_fire = 0
if(usr && usr.client)
usr.client.running_find_references = type
testing("Beginning search for references to a [type].")
last_find_references = world.time
DoSearchVar(GLOB) //globals
for(var/datum/thing in world) //atoms (don't beleive it's lies)
DoSearchVar(thing, "World -> [thing]")
for (var/datum/thing) //datums
DoSearchVar(thing, "World -> [thing]")
for (var/client/thing) //clients
DoSearchVar(thing, "World -> [thing]")
testing("Completed search for references to a [type].")
if(usr && usr.client)
usr.client.running_find_references = null
running_find_references = null
//restart the garbage collector
SSgarbage.can_fire = 1
SSgarbage.next_fire = world.time + world.tick_lag
/datum/verb/qdel_then_find_references()
set category = "Debug"
set name = "qdel() then Find References"
set src in world
qdel(src, TRUE) //Force.
if(!running_find_references)
find_references(TRUE)
/datum/verb/qdel_then_if_fail_find_references()
set category = "Debug"
set name = "qdel() then Find References if GC failure"
set src in world
qdel_and_find_ref_if_fail(src, TRUE)
/datum/proc/DoSearchVar(X, Xname, recursive_limit = 64)
if(usr && usr.client && !usr.client.running_find_references)
return
if (!recursive_limit)
return
if(istype(X, /datum))
var/datum/D = X
if(D.last_find_references == last_find_references)
return
D.last_find_references = last_find_references
var/list/L = D.vars
for(var/varname in L)
if (varname == "vars")
continue
var/variable = L[varname]
if(variable == src)
testing("Found [src.type] \ref[src] in [D.type]'s [varname] var. [Xname]")
else if(islist(variable))
DoSearchVar(variable, "[Xname] -> list", recursive_limit-1)
else if(islist(X))
var/normal = IS_NORMAL_LIST(X)
for(var/I in X)
if (I == src)
testing("Found [src.type] \ref[src] in list [Xname].")
else if (I && !isnum(I) && normal && X[I] == src)
testing("Found [src.type] \ref[src] in list [Xname]\[[I]\]")
else if (islist(I))
DoSearchVar(I, "[Xname] -> list", recursive_limit-1)
#ifndef FIND_REF_NO_CHECK_TICK
CHECK_TICK
#endif
#endif
#ifdef TESTING
/proc/writeDatumCount()
var/list/datums = list()
@@ -2,6 +2,15 @@
//Large Objects//
/////////////////
/datum/crafting_recipe/plunger
name = "Plunger"
result = /obj/item/plunger
time = 1
reqs = list(/obj/item/stack/sheet/plastic = 1,
/obj/item/stack/sheet/mineral/wood = 1)
category = CAT_MISC
subcategory = CAT_TOOL
/datum/crafting_recipe/showercurtain
name = "Shower Curtains"
reqs = list(/obj/item/stack/sheet/cloth = 2,
@@ -337,7 +346,7 @@
result = /obj/item/toy/sword/cx
subcategory = CAT_MISCELLANEOUS
category = CAT_MISC
/datum/crafting_recipe/catgirlplushie
name = "Catgirl Plushie"
reqs = list(/obj/item/toy/plush/hairball = 3)
@@ -0,0 +1,215 @@
/datum/component/plumbing
///Index with "1" = /datum/ductnet/theductpointingnorth etc. "1" being the num2text from NORTH define
var/list/datum/ductnet/ducts = list()
///shortcut to our parents' reagent holder
var/datum/reagents/reagents
///TRUE if we wanna add proper pipe outless under our parent object. this is pretty good if i may so so myself
var/use_overlays = TRUE
///We can't just cut all of the parents' overlays, so we'll track them here
var/list/image/ducterlays
///directions in wich we act as a supplier
var/supply_connects
///direction in wich we act as a demander
var/demand_connects
///FALSE to pretty much just not exist in the plumbing world so we can be moved, TRUE to go plumbo mode
var/active = FALSE
///if TRUE connects will spin with the parent object visually and codually, so you can have it work in any direction. FALSE if you want it to be static
var/turn_connects = TRUE
/datum/component/plumbing/Initialize(start=TRUE, _turn_connects=TRUE) //turn_connects for wheter or not we spin with the object to change our pipes
if(parent && !istype(parent, /atom/movable))
return COMPONENT_INCOMPATIBLE
var/atom/movable/AM = parent
if(!AM.reagents)
return COMPONENT_INCOMPATIBLE
reagents = AM.reagents
turn_connects = _turn_connects
RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED,COMSIG_PARENT_PREQDELETED), .proc/disable)
RegisterSignal(parent, list(COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH), .proc/toggle_active)
if(start)
enable()
if(use_overlays)
create_overlays()
/datum/component/plumbing/process()
if(!demand_connects || !reagents)
STOP_PROCESSING(SSfluids, src)
return
if(reagents.total_volume < reagents.maximum_volume)
for(var/D in GLOB.cardinals)
if(D & demand_connects)
send_request(D)
///Can we be added to the ductnet?
/datum/component/plumbing/proc/can_add(datum/ductnet/D, dir)
if(!active)
return
if(!dir || !D)
return FALSE
if(num2text(dir) in ducts)
return FALSE
return TRUE
///called from in process(). only calls process_request(), but can be overwritten for children with special behaviour
/datum/component/plumbing/proc/send_request(dir)
process_request(amount = MACHINE_REAGENT_TRANSFER, reagent = null, dir = dir)
///check who can give us what we want, and how many each of them will give us
/datum/component/plumbing/proc/process_request(amount, reagent, dir)
var/list/valid_suppliers = list()
var/datum/ductnet/net
if(!ducts.Find(num2text(dir)))
return
net = ducts[num2text(dir)]
for(var/A in net.suppliers)
var/datum/component/plumbing/supplier = A
if(supplier.can_give(amount, reagent, net))
valid_suppliers += supplier
for(var/A in valid_suppliers)
var/datum/component/plumbing/give = A
give.transfer_to(src, amount / valid_suppliers.len, reagent, net)
///returns TRUE when they can give the specified amount and reagent. called by process request
/datum/component/plumbing/proc/can_give(amount, reagent, datum/ductnet/net)
if(amount <= 0)
return
if(reagent) //only asked for one type of reagent
for(var/A in reagents.reagent_list)
var/datum/reagent/R = A
if(R.type == reagent)
return TRUE
else if(reagents.total_volume > 0) //take whatever
return TRUE
///this is where the reagent is actually transferred and is thus the finish point of our process()
/datum/component/plumbing/proc/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net)
if(!reagents || !target || !target.reagents)
return FALSE
if(reagent)
reagents.trans_id_to(target.parent, reagent, amount)
else
reagents.trans_to(target.parent, amount)
///We create our luxurious piping overlays/underlays, to indicate where we do what. only called once if use_overlays = TRUE in Initialize()
/datum/component/plumbing/proc/create_overlays()
var/atom/movable/AM = parent
for(var/image/I in ducterlays)
AM.overlays.Remove(I)
qdel(I)
ducterlays = list()
for(var/D in GLOB.cardinals)
var/color
var/direction
if(D & demand_connects)
color = "red" //red because red is mean and it takes
else if(D & supply_connects)
color = "blue" //blue is nice and gives
else
continue
var/image/I
if(turn_connects)
switch(D)
if(NORTH)
direction = "north"
if(SOUTH)
direction = "south"
if(EAST)
direction = "east"
if(WEST)
direction = "west"
I = image('icons/obj/plumbing/plumbers.dmi', "[direction]-[color]", layer = AM.layer - 1)
else
I = image('icons/obj/plumbing/plumbers.dmi', color, layer = AM.layer - 1) //color is not color as in the var, it's just the name
I.dir = D
AM.add_overlay(I)
ducterlays += I
///we stop acting like a plumbing thing and disconnect if we are, so we can safely be moved and stuff
/datum/component/plumbing/proc/disable()
if(!active)
return
STOP_PROCESSING(SSfluids, src)
for(var/A in ducts)
var/datum/ductnet/D = ducts[A]
D.remove_plumber(src)
active = FALSE
for(var/D in GLOB.cardinals)
if(D & (demand_connects | supply_connects))
for(var/obj/machinery/duct/duct in get_step(parent, D))
duct.attempt_connect()
///settle wherever we are, and start behaving like a piece of plumbing
/datum/component/plumbing/proc/enable()
if(active)
return
update_dir()
active = TRUE
var/atom/movable/AM = parent
for(var/obj/machinery/duct/D in AM.loc) //Destroy any ducts under us. Ducts also self destruct if placed under a plumbing machine. machines disable when they get moved
if(D.anchored) //that should cover everything
D.disconnect_duct()
if(demand_connects)
START_PROCESSING(SSfluids, src)
for(var/D in GLOB.cardinals)
if(D & (demand_connects | supply_connects))
for(var/atom/movable/A in get_step(parent, D))
if(istype(A, /obj/machinery/duct))
var/obj/machinery/duct/duct = A
duct.attempt_connect()
else
var/datum/component/plumbing/P = A.GetComponent(/datum/component/plumbing)
if(P)
direct_connect(P, D)
/// Toggle our machinery on or off. This is called by a hook from default_unfasten_wrench with anchored as only param, so we dont have to copypaste this on every object that can move
/datum/component/plumbing/proc/toggle_active(obj/O, new_state)
if(new_state)
enable()
else
disable()
/** We update our connects only when we settle down by taking our current and original direction to find our new connects
* If someone wants it to fucking spin while connected to something go actually knock yourself out
*/
/datum/component/plumbing/proc/update_dir()
if(!turn_connects)
return
var/atom/movable/AM = parent
var/new_demand_connects
var/new_supply_connects
var/new_dir = AM.dir
var/angle = 180 - dir2angle(new_dir)
if(new_dir == SOUTH)
demand_connects = initial(demand_connects)
supply_connects = initial(supply_connects)
else
for(var/D in GLOB.cardinals)
if(D & initial(demand_connects))
new_demand_connects += turn(D, angle)
if(D & initial(supply_connects))
new_supply_connects += turn(D, angle)
demand_connects = new_demand_connects
supply_connects = new_supply_connects
///Give the direction of a pipe, and it'll return wich direction it originally was when it's object pointed SOUTH
/datum/component/plumbing/proc/get_original_direction(dir)
var/atom/movable/AM = parent
return turn(dir, dir2angle(AM.dir) - 180)
//special case in-case we want to connect directly with another machine without a duct
/datum/component/plumbing/proc/direct_connect(datum/component/plumbing/P, dir)
if(!P.active)
return
var/opposite_dir = turn(dir, 180)
if(P.demand_connects & opposite_dir && supply_connects & dir || P.supply_connects & opposite_dir && demand_connects & dir) //make sure we arent connecting two supplies or demands
var/datum/ductnet/net = new()
net.add_plumber(src, dir)
net.add_plumber(P, opposite_dir)
///has one pipe input that only takes, example is manual output pipe
/datum/component/plumbing/simple_demand
demand_connects = NORTH
///has one pipe output that only supplies. example is liquid pump and manual input pipe
/datum/component/plumbing/simple_supply
supply_connects = NORTH
///input and output, like a holding tank
/datum/component/plumbing/tank
demand_connects = WEST
supply_connects = EAST
@@ -0,0 +1,21 @@
/datum/component/plumbing/acclimator
demand_connects = WEST
supply_connects = EAST
var/obj/machinery/plumbing/acclimator/AC
/datum/component/plumbing/acclimator/Initialize(start=TRUE, _turn_connects=TRUE)
. = ..()
if(!istype(parent, /obj/machinery/plumbing/acclimator))
return COMPONENT_INCOMPATIBLE
AC = parent
/datum/component/plumbing/acclimator/can_give(amount, reagent)
. = ..()
if(. && AC.emptying)
return TRUE
return FALSE
///We're overriding process and not send_request, because all process does is do the requests, so we might aswell cut out the middle man and save some code from running
/datum/component/plumbing/acclimator/process()
if(AC.emptying)
return
. = ..()
+59
View File
@@ -0,0 +1,59 @@
///The magical plumbing component used by the chemical filters. The different supply connects behave differently depending on the filters set on the chemical filter
/datum/component/plumbing/filter
demand_connects = NORTH
supply_connects = SOUTH | EAST | WEST //SOUTH is straight, EAST is left and WEST is right. We look from the perspective of the insert
/datum/component/plumbing/filter/Initialize()
. = ..()
if(!istype(parent, /obj/machinery/plumbing/filter))
return COMPONENT_INCOMPATIBLE
/datum/component/plumbing/filter/can_give(amount, reagent, datum/ductnet/net)
. = ..()
if(.)
var/direction
for(var/A in ducts)
if(ducts[A] == net)
direction = get_original_direction(text2num(A)) //we need it relative to the direction, so filters don't change when we turn the filter
break
if(!direction)
return FALSE
if(reagent)
if(!can_give_in_direction(direction, reagent))
return FALSE
/datum/component/plumbing/filter/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net)
if(!reagents || !target || !target.reagents)
return FALSE
var/direction
for(var/A in ducts)
if(ducts[A] == net)
direction = get_original_direction(text2num(A))
break
if(reagent)
reagents.trans_id_to(target.parent, reagent, amount)
else
for(var/A in reagents.reagent_list)
var/datum/reagent/R = A
if(!can_give_in_direction(direction, R.type))
continue
var/new_amount
if(R.volume < amount)
new_amount = amount - R.volume
reagents.trans_id_to(target.parent, R.type, amount)
amount = new_amount
if(amount <= 0)
break
///We check if the direction and reagent are valid to give. Needed for filters since different outputs have different behaviours
/datum/component/plumbing/filter/proc/can_give_in_direction(dir, reagent)
var/obj/machinery/plumbing/filter/F = parent
switch(dir)
if(SOUTH) //straight
if(!F.left.Find(reagent) && !F.right.Find(reagent))
return TRUE
if(WEST) //right
if(F.right.Find(reagent))
return TRUE
if(EAST) //left
if(F.left.Find(reagent))
return TRUE
@@ -0,0 +1,38 @@
/datum/component/plumbing/reaction_chamber
demand_connects = WEST
supply_connects = EAST
/datum/component/plumbing/reaction_chamber/Initialize(start=TRUE, _turn_connects=TRUE)
. = ..()
if(!istype(parent, /obj/machinery/plumbing/reaction_chamber))
return COMPONENT_INCOMPATIBLE
/datum/component/plumbing/reaction_chamber/can_give(amount, reagent, datum/ductnet/net)
. = ..()
var/obj/machinery/plumbing/reaction_chamber/RC = parent
if(!. || !RC.emptying)
return FALSE
/datum/component/plumbing/reaction_chamber/send_request(dir)
var/obj/machinery/plumbing/reaction_chamber/RC = parent
if(RC.emptying || !LAZYLEN(RC.required_reagents))
return
for(var/RT in RC.required_reagents)
var/has_reagent = FALSE
for(var/A in reagents.reagent_list)
var/datum/reagent/RD = A
if(RT == RD.type)
has_reagent = TRUE
if(RD.volume < RC.required_reagents[RT])
process_request(min(RC.required_reagents[RT] - RD.volume, MACHINE_REAGENT_TRANSFER) , RT, dir)
return
if(!has_reagent)
process_request(min(RC.required_reagents[RT], MACHINE_REAGENT_TRANSFER), RT, dir)
return
RC.reagent_flags &= ~NO_REACT
reagents.handle_reactions()
RC.emptying = TRUE //If we move this up, it'll instantly get turned off since any reaction always sets the reagent_total to zero. Other option is make the reaction update
//everything for every chemical removed, wich isn't a good option either.
RC.on_reagent_change() //We need to check it now, because some reactions leave nothing left.
@@ -0,0 +1,45 @@
/datum/component/plumbing/splitter
demand_connects = NORTH
supply_connects = SOUTH | EAST
/datum/component/plumbing/splitter/Initialize()
. = ..()
if(. && !istype(parent, /obj/machinery/plumbing/splitter))
return FALSE
/datum/component/plumbing/splitter/can_give(amount, reagent, datum/ductnet/net)
. = ..()
if(!.)
return
. = FALSE
var/direction
for(var/A in ducts)
if(ducts[A] == net)
direction = get_original_direction(text2num(A))
break
var/obj/machinery/plumbing/splitter/S = parent
switch(direction)
if(SOUTH)
if(S.turn_straight && S.transfer_straight <= amount)
S.turn_straight = FALSE
return TRUE
if(EAST)
if(!S.turn_straight && S.transfer_side <= amount)
S.turn_straight = TRUE
return TRUE
/datum/component/plumbing/splitter/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net)
var/direction
for(var/A in ducts)
if(ducts[A] == net)
direction = get_original_direction(text2num(A))
break
var/obj/machinery/plumbing/splitter/S = parent
switch(direction)
if(SOUTH)
if(amount >= S.transfer_straight)
amount = S.transfer_straight
if(EAST)
if(amount >= S.transfer_side)
amount = S.transfer_side
. = ..()
+3
View File
@@ -31,6 +31,9 @@
VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player")
VV_DROPDOWN_OPTION(VV_HK_ADDCOMPONENT, "Add Component/Element")
VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRAITS, "Modify Traits")
#ifdef REFERENCE_TRACKING
VV_DROPDOWN_OPTION(VV_HK_VIEW_REFERENCES, "View References")
#endif
//This proc is only called if everything topic-wise is verified. The only verifications that should happen here is things like permission checks!
//href_list is a reference, modifying it in these procs WILL change the rest of the proc in topic.dm of admin/view_variables!
+65
View File
@@ -0,0 +1,65 @@
///We handle the unity part of plumbing. We track who is connected to who.
/datum/ductnet
var/list/suppliers = list()
var/list/demanders = list()
var/list/obj/machinery/duct/ducts = list()
var/capacity
///Add a duct to our network
/datum/ductnet/proc/add_duct(obj/machinery/duct/D)
if(!D || (D in ducts))
return
ducts += D
D.duct = src
///Remove a duct from our network and commit suicide, because this is probably easier than to check who that duct was connected to and what part of us was lost
/datum/ductnet/proc/remove_duct(obj/machinery/duct/ducting)
destroy_network(FALSE)
for(var/obj/machinery/duct/D in ducting.neighbours)
addtimer(CALLBACK(D, /obj/machinery/duct/proc/reconnect), 0) //all needs to happen after the original duct that was destroyed finishes destroying itself
addtimer(CALLBACK(D, /obj/machinery/duct/proc/generate_connects), 0)
qdel(src)
///add a plumbing object to either demanders or suppliers
/datum/ductnet/proc/add_plumber(datum/component/plumbing/P, dir)
if(!P.can_add(src, dir))
return FALSE
P.ducts[num2text(dir)] = src
if(dir & P.supply_connects)
suppliers += P
else if(dir & P.demand_connects)
demanders += P
return TRUE
///remove a plumber. we dont delete ourselves because ductnets dont persist through plumbing objects
/datum/ductnet/proc/remove_plumber(datum/component/plumbing/P)
suppliers.Remove(P) //we're probably only in one of these, but Remove() is inherently sane so this is fine
demanders.Remove(P)
for(var/dir in P.ducts)
if(P.ducts[dir] == src)
P.ducts -= dir
if(!ducts.len) //there were no ducts, so it was a direct connection. we destroy ourselves since a ductnet with only one plumber and no ducts is worthless
destroy_network()
///we combine ductnets. this occurs when someone connects to seperate sets of fluid ducts
/datum/ductnet/proc/assimilate(datum/ductnet/D)
ducts.Add(D.ducts)
suppliers.Add(D.suppliers)
demanders.Add(D.demanders)
for(var/A in D.suppliers + D.demanders)
var/datum/component/plumbing/P = A
for(var/s in P.ducts)
if(P.ducts[s] != D)
continue
P.ducts[s] = src //all your ducts are belong to us
for(var/A in D.ducts)
var/obj/machinery/duct/M = A
M.duct = src //forget your old master
destroy_network()
///destroy the network and tell all our ducts and plumbers we are gone
/datum/ductnet/proc/destroy_network(delete=TRUE)
for(var/A in suppliers + demanders)
remove_plumber(A)
for(var/A in ducts)
var/obj/machinery/duct/D = A
D.duct = null
if(delete) //I don't want code to run with qdeleted objects because that can never be good, so keep this in-case the ductnet has some business left to attend to before commiting suicide
qdel(src)
+7
View File
@@ -646,3 +646,10 @@
animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING)
sleep(1)
animate(I, alpha = 0, transform = matrix(), time = 1)
/atom/movable/proc/set_anchored(anchorvalue) //literally only for plumbing ran
SHOULD_CALL_PARENT(TRUE)
if(anchored == anchorvalue)
return
. = anchored
anchored = anchorvalue
+2
View File
@@ -117,6 +117,7 @@ Class Procs:
var/new_occupant_dir = SOUTH //The direction the occupant will be set to look at when entering the machine.
var/speed_process = FALSE // Process as fast as possible?
var/obj/item/circuitboard/circuit // Circuit to be created and inserted when the machinery is created
var/wire_compatible = FALSE
// For storing and overriding ui id and dimensions
var/tgui_id // ID of TGUI interface
var/ui_style // ID of custom TGUI style (optional)
@@ -433,6 +434,7 @@ Class Procs:
to_chat(user, "<span class='notice'>You [anchored ? "un" : ""]secure [src].</span>")
setAnchored(!anchored)
playsound(src, 'sound/items/deconstruct.ogg', 50, 1)
SEND_SIGNAL(src, COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH, anchored)
return SUCCESSFUL_UNFASTEN
return FAILED_UNFASTEN
return CANT_UNFASTEN
+84 -7
View File
@@ -135,6 +135,14 @@ RLD
flick("[icon_state]_empty", src) //somewhat hacky thing to make RCDs with ammo counters actually have a blinking yellow light
return .
/obj/item/construction/proc/check_menu(mob/living/user)
if(!istype(user))
return FALSE
if(user.incapacitated() || !user.Adjacent(src))
return FALSE
return TRUE
/obj/item/construction/proc/range_check(atom/A, mob/user)
if(!(A in range(custom_range, get_turf(user))))
to_chat(user, "<span class='warning'>The \'Out of Range\' light on [src] blinks red.</span>")
@@ -276,13 +284,6 @@ RLD
//Not scaling these down to button size because they look horrible then, instead just bumping up radius.
return MA
/obj/item/construction/rcd/proc/check_menu(mob/living/user)
if(!istype(user))
return FALSE
if(user.incapacitated() || !user.Adjacent(src))
return FALSE
return TRUE
/obj/item/construction/rcd/proc/change_computer_dir(mob/user)
if(!user)
return
@@ -856,6 +857,82 @@ RLD
desc = "It contains the design for firelock, air alarm, fire alarm, apc circuits and crap power cells."
upgrade = RCD_UPGRADE_SIMPLE_CIRCUITS
/obj/item/construction/plumbing
name = "Plumbing Constructor"
desc = "An expertly modified RCD outfitted to construct plumbing machinery. Reload with compressed matter cartridges."
icon = 'icons/obj/tools.dmi'
icon_state = "arcd"
item_state = "oldrcd"
has_ammobar = FALSE
matter = 200
max_matter = 200
///type of the plumbing machine
var/blueprint = null
///index, used in the attack self to get the type. stored here since it doesnt change
var/list/choices = list()
///index, used in the attack self to get the type. stored here since it doesnt change
var/list/name_to_type = list()
///
var/list/machinery_data = list("cost" = list(), "delay" = list())
/obj/item/construction/plumbing/attack_self(mob/user)
..()
if(!choices.len)
for(var/A in subtypesof(/obj/machinery/plumbing))
var/obj/machinery/plumbing/M = A
if(initial(M.rcd_constructable))
choices += list(initial(M.name) = image(icon = initial(M.icon), icon_state = initial(M.icon_state)))
name_to_type[initial(M.name)] = M
machinery_data["cost"][A] = initial(M.rcd_cost)
machinery_data["delay"][A] = initial(M.rcd_delay)
var/choice = show_radial_menu(user, src, choices, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE)
if(!check_menu(user))
return
blueprint = name_to_type[choice]
playsound(src, 'sound/effects/pop.ogg', 50, FALSE)
to_chat(user, "<span class='notice'>You change [name]s blueprint to '[choice]'.</span>")
///pretty much rcd_create, but named differently to make myself feel less bad for copypasting from a sibling-type
/obj/item/construction/plumbing/proc/create_machine(atom/A, mob/user)
if(!machinery_data || !isopenturf(A))
return FALSE
if(checkResource(machinery_data["cost"][blueprint], user) && blueprint)
if(do_after(user, machinery_data["delay"][blueprint], target = A))
if(checkResource(machinery_data["cost"][blueprint], user) && canPlace(A))
useResource(machinery_data["cost"][blueprint], user)
activate()
playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
new blueprint (A, FALSE, FALSE)
return TRUE
/obj/item/construction/plumbing/proc/canPlace(turf/T)
if(!isopenturf(T))
return FALSE
. = TRUE
for(var/obj/O in T.contents)
if(O.density) //let's not built ontop of dense stuff, like big machines and other obstacles, it kills my immershion
return FALSE
/obj/item/construction/plumbing/afterattack(atom/A, mob/user)
. = ..()
if(!range_check(A, user))
return
if(istype(A, /obj/machinery/plumbing))
var/obj/machinery/plumbing/P = A
if(P.anchored)
to_chat(user, "<span class='warning'>The [P.name] needs to be unanchored!</span>")
return
if(do_after(user, 20, target = P))
P.deconstruct() //Let's not substract matter
playsound(get_turf(src), 'sound/machines/click.ogg', 50, TRUE) //this is just such a great sound effect
else
create_machine(A, user)
#undef GLOW_MODE
#undef LIGHT_MODE
#undef REMOVE_MODE
+71 -3
View File
@@ -6,6 +6,7 @@ RPD
#define ATMOS_CATEGORY 0
#define DISPOSALS_CATEGORY 1
#define TRANSIT_CATEGORY 2
#define PLUMBING_CATEGORY 3
#define BUILD_MODE (1<<0)
#define WRENCH_MODE (1<<1)
@@ -75,6 +76,13 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
)
))
GLOBAL_LIST_INIT(fluid_duct_recipes, list(
"Fluid Ducts" = list(
new /datum/pipe_info/plumbing("Duct", /obj/machinery/duct, PIPE_ONEDIR),
new /datum/pipe_info/plumbing/multilayer("Duct Layer-Manifold",/obj/machinery/duct/multilayered, PIPE_STRAIGHT)
)
))
/datum/pipe_info
var/name
var/icon_state
@@ -175,6 +183,15 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
if(dt == PIPE_UNARY_FLIPPABLE)
icon_state = "[icon_state]_preview"
/datum/pipe_info/plumbing/New(label, obj/path, dt=PIPE_UNARY)
name = label
id = path
icon_state = initial(path.icon_state)
dirtype = dt
/datum/pipe_info/plumbing/multilayer //exists as identifier so we can see the difference between multi_layer and just ducts properly later on
/obj/item/pipe_dispenser
name = "Rapid Piping Device (RPD)"
desc = "A device used to rapidly pipe things."
@@ -200,15 +217,19 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
var/atmos_build_speed = 5 //deciseconds (500ms)
var/disposal_build_speed = 5
var/transit_build_speed = 5
var/plumbing_build_speed = 5
var/destroy_speed = 5
var/paint_speed = 5
var/category = ATMOS_CATEGORY
var/piping_layer = PIPING_LAYER_DEFAULT
var/ducting_layer = DUCT_LAYER_DEFAULT
var/datum/pipe_info/recipe
var/static/datum/pipe_info/first_atmos
var/static/datum/pipe_info/first_disposal
var/static/datum/pipe_info/first_transit
var/mode = BUILD_MODE | DESTROY_MODE | WRENCH_MODE
var/static/datum/pipe_info/first_plumbing
var/locked = FALSE //wheter we can change categories. Useful for the plumber
/obj/item/pipe_dispenser/New()
. = ..()
@@ -253,12 +274,15 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
var/list/data = list(
"category" = category,
"piping_layer" = piping_layer,
// "ducting_layer" = ducting_layer, //uhh is this for chem thing?
"ducting_layer" = ducting_layer,
"preview_rows" = recipe.get_preview(p_dir),
"categories" = list(),
"selected_color" = paint_color,
"paint_colors" = GLOB.pipe_paint_colors,
"mode" = mode
"mode" = mode,
"locked" = locked
)
var/list/recipes
@@ -269,6 +293,8 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
recipes = GLOB.disposal_pipe_recipes
if(TRANSIT_CATEGORY)
recipes = GLOB.transit_tube_recipes
if(PLUMBING_CATEGORY)
recipes = GLOB.fluid_duct_recipes
for(var/c in recipes)
var/list/cat = recipes[c]
var/list/r = list()
@@ -297,6 +323,8 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
recipe = first_atmos
if(TRANSIT_CATEGORY)
recipe = first_transit
if(PLUMBING_CATEGORY)
recipe = first_plumbing
p_dir = NORTH
playeffect = FALSE
if("piping_layer")
@@ -468,16 +496,56 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
if(mode & WRENCH_MODE)
tube.wrench_act(user, src)
return
if(PLUMBING_CATEGORY) //Plumbing.
if(!can_make_pipe)
return ..()
A = get_turf(A)
if(isclosedturf(A))
to_chat(user, "<span class='warning'>[src]'s error light flickers; there's something in the way!</span>")
return
to_chat(user, "<span class='notice'>You start building a fluid duct...</span>")
playsound(get_turf(src), 'sound/machines/click.ogg', 50, 1)
if(do_after(user, plumbing_build_speed, target = A))
var/obj/machinery/duct/D
if(recipe.type == /datum/pipe_info/plumbing/multilayer)
var/temp_connects = NORTH + SOUTH
if(queued_p_dir == EAST)
temp_connects = EAST + WEST
D = new queued_p_type (A, TRUE, GLOB.pipe_paint_colors[paint_color], ducting_layer, temp_connects)
else
D = new queued_p_type (A, TRUE, GLOB.pipe_paint_colors[paint_color], ducting_layer)
D.add_fingerprint(usr)
if(mode & WRENCH_MODE)
D.wrench_act(user, src)
else
return ..()
/obj/item/pipe_dispenser/proc/activate()
playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 1)
/* unneeded, you can craft ducts from plastic
/obj/item/pipe_dispenser/plumbing
name = "Plumberinator"
desc = "A crude device to rapidly plumb things."
icon_state = "plumberer"
category = PLUMBING_CATEGORY
locked = TRUE
/obj/item/pipe_dispenser/plumbing/Initialize()
. = ..()
spark_system = new
spark_system.set_up(5, 0, src)
spark_system.attach(src)
if(!first_plumbing)
first_plumbing = GLOB.fluid_duct_recipes[GLOB.fluid_duct_recipes[1]][1]
recipe = first_plumbing
*/
#undef ATMOS_CATEGORY
#undef DISPOSALS_CATEGORY
#undef TRANSIT_CATEGORY
#undef PLUMBING_CATEGORY
#undef BUILD_MODE
#undef DESTROY_MODE
@@ -48,6 +48,13 @@
/obj/item/stack/cable_coil = 5,
/obj/item/stack/sheet/glass = 1)
/obj/item/circuitboard/machine/medipen_refiller
name = "Medipen Refiller (Machine Board)"
icon_state = "medical"
build_path = /obj/machinery/medipen_refiller
req_components = list(
/obj/item/stock_parts/matter_bin = 1)
/obj/item/circuitboard/machine/clonepod
name = "Clone Pod (Machine Board)"
build_path = /obj/machinery/clonepod
@@ -512,6 +519,10 @@
/obj/item/stack/sheet/glass = 1)
needs_anchored = FALSE
/obj/item/circuitboard/machine/hydroponics/automagic
name = "Automatic Hydroponics Tray (Machine Board)"
build_path = /obj/machinery/hydroponics/constructable/automagic
/obj/item/circuitboard/machine/seed_extractor
name = "Seed Extractor (Machine Board)"
build_path = /obj/machinery/seed_extractor
@@ -128,6 +128,7 @@
absorption_capacity = 5
splint_factor = 0.35
custom_price = PRICE_REALLY_CHEAP
grind_results = list(/datum/reagent/cellulose = 2)
// gauze is only relevant for wounds, which are handled in the wounds themselves
/obj/item/stack/medical/gauze/try_heal(mob/living/M, mob/user, silent)
@@ -412,6 +412,7 @@ GLOBAL_LIST_INIT(cloth_recipes, list ( \
force = 0
throwforce = 0
merge_type = /obj/item/stack/sheet/cloth
grind_results = list(/datum/reagent/cellulose = 2)
/obj/item/stack/sheet/cloth/get_main_recipes()
. = ..()
@@ -773,6 +774,7 @@ GLOBAL_LIST_INIT(plastic_recipes, list(
new /datum/stack_recipe("water bottle", /obj/item/reagent_containers/glass/beaker/waterbottle/empty), \
new /datum/stack_recipe("large water bottle", /obj/item/reagent_containers/glass/beaker/waterbottle/large/empty,3), \
new /datum/stack_recipe("shower curtain", /obj/structure/curtain, 10, time = 10, one_per_turf = 1, on_floor = 1), \
new /datum/stack_recipe("duct", /obj/item/stack/ducts,1), \
new /datum/stack_recipe("laser pointer case", /obj/item/glasswork/glass_base/laserpointer_shell, 30), \
new /datum/stack_recipe("wet floor sign", /obj/item/caution, 2)))
@@ -841,6 +843,7 @@ new /datum/stack_recipe("paper frame door", /obj/structure/mineral_door/paperfra
merge_type = /obj/item/stack/sheet/cotton
var/pull_effort = 30
var/loom_result = /obj/item/stack/sheet/cloth
grind_results = list(/datum/reagent/cellulose = 5)
/obj/item/stack/sheet/cotton/ten
amount = 10
@@ -856,6 +859,7 @@ new /datum/stack_recipe("paper frame door", /obj/structure/mineral_door/paperfra
merge_type = /obj/item/stack/sheet/cotton/durathread
pull_effort = 70
loom_result = /obj/item/stack/sheet/durathread
grind_results = list(/datum/reagent/cellulose = 10)
/obj/item/stack/sheet/meat
name = "meat sheets"
+2 -2
View File
@@ -478,7 +478,7 @@
/obj/item/assembly/signaler,
/obj/item/lightreplacer,
/obj/item/rcd_ammo,
/obj/item/construction/rcd,
/obj/item/construction,
/obj/item/pipe_dispenser,
/obj/item/stack/rods,
/obj/item/stack/tile/plasteel,
@@ -492,7 +492,7 @@
icon_state = "grenadebeltnew"
item_state = "security"
rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/belt/grenade/ComponentInitialize()
. = ..()
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
+3
View File
@@ -329,3 +329,6 @@
. = ..()
if(. && ricochet_damage_mod)
take_damage(P.damage * ricochet_damage_mod, P.damage_type, P.flag, 0, turn(P.dir, 180), P.armour_penetration) // pass along ricochet_damage_mod damage to the structure for the ricochet
/obj/proc/plunger_act(obj/item/plunger/P, mob/living/user, reinforced)
return
@@ -9,7 +9,7 @@
var/erupting_state = null //set to null to get it greyscaled from "[icon_state]_soup". Not very usable with the whole random thing, but more types can be added if you change the spawn prob
var/activated = FALSE //whether we are active and generating chems
var/reagent_id = /datum/reagent/fuel/oil
var/reagent_id = /datum/reagent/oil
var/potency = 2 //how much reagents we add every process (2 seconds)
var/max_volume = 500
var/start_volume = 50
@@ -77,10 +77,10 @@
/obj/item/plunger/reinforced
name = "reinforced plunger"
desc = "It's an M. 7 Reinforced Plunger© for heavy duty plunging."
desc = "It's an M. 7 Reinforced Plunger for heavy duty plunging."
icon_state = "reinforced_plunger"
reinforced = TRUE
plunge_mod = 0.8
custom_premium_price = 1200
custom_premium_price = 600
@@ -149,6 +149,8 @@
var/list/megafauna_spawn_list
/// Flora that can spawn in the tunnel, weighted list
var/list/flora_spawn_list
//terrain to spawn weighted list
var/list/terrain_spawn_list
/// Turf type to choose when spawning in tunnel at 1% chance, weighted list
var/list/choose_turf_type
/// if the tunnel should keep being created
@@ -230,7 +232,8 @@
megafauna_spawn_list = list(/mob/living/simple_animal/hostile/megafauna/dragon = 4, /mob/living/simple_animal/hostile/megafauna/colossus = 2, /mob/living/simple_animal/hostile/megafauna/bubblegum = SPAWN_BUBBLEGUM)
if (!flora_spawn_list)
flora_spawn_list = list(/obj/structure/flora/ash/leaf_shroom = 2 , /obj/structure/flora/ash/cap_shroom = 2 , /obj/structure/flora/ash/stem_shroom = 2 , /obj/structure/flora/ash/cacti = 1, /obj/structure/flora/ash/tall_shroom = 2)
if(!terrain_spawn_list)
terrain_spawn_list = list(/obj/structure/geyser/random = 1)
. = ..()
if(!has_data)
produce_tunnel_from_data()
@@ -334,8 +337,19 @@
spawned_flora = SpawnFlora(T)
if(!spawned_flora) // no rocks beneath mob spawners / mobs.
SpawnMonster(T)
SpawnTerrain(T)
T.ChangeTurf(turf_type, null, CHANGETURF_IGNORE_AIR)
/turf/open/floor/plating/asteroid/airless/cave/proc/SpawnTerrain(turf/T)
if(prob(1))
if(istype(loc, /area/mine/explored) || istype(loc, /area/lavaland/surface/outdoors/explored))
return
var/randumb = pickweight(terrain_spawn_list)
for(var/obj/structure/geyser/F in range(7, T))
if(istype(F, randumb))
return
new randumb(T)
/// Spawns a random mob or megafauna in the tunnel
/turf/open/floor/plating/asteroid/airless/cave/proc/SpawnMonster(turf/T)
if(!isarea(loc))
+3
View File
@@ -12,6 +12,9 @@ GLOBAL_LIST(topic_status_cache)
if (fexists(EXTOOLS))
call(EXTOOLS, "maptick_initialize")()
enable_debugger()
#ifdef REFERENCE_TRACKING
enable_reference_tracking()
#endif
world.Profile(PROFILE_START)
+5 -1
View File
@@ -171,7 +171,11 @@ GLOBAL_LIST_INIT(admin_verbs_debug, world.AVerbsDebug())
/client/proc/cmd_display_overlay_log,
/client/proc/reload_configuration,
/datum/admins/proc/create_or_modify_area,
/client/proc/generate_wikichem_list //DO NOT PRESS UNLESS YOU WANT SUPERLAG
#ifdef REFERENCE_TRACKING
/datum/admins/proc/view_refs,
/datum/admins/proc/view_del_failures,
#endif
/client/proc/generate_wikichem_list, //DO NOT PRESS UNLESS YOU WANT SUPERLAG
)
GLOBAL_PROTECT(admin_verbs_debug)
GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release))
@@ -0,0 +1,225 @@
#ifdef REFERENCE_TRACKING
GLOBAL_LIST_EMPTY(deletion_failures)
/world/proc/enable_reference_tracking()
if (fexists(EXTOOLS))
call(EXTOOLS, "ref_tracking_initialize")()
/proc/get_back_references(datum/D)
CRASH("/proc/get_back_references not hooked by extools, reference tracking will not function!")
/proc/get_forward_references(datum/D)
CRASH("/proc/get_forward_references not hooked by extools, reference tracking will not function!")
/proc/clear_references(datum/D)
return
/datum/admins/proc/view_refs(atom/D in world) //it actually supports datums as well but byond no likey
set category = "Debug"
set name = "View References"
if(!check_rights(R_DEBUG) || !D)
return
var/list/backrefs = get_back_references(D)
if(isnull(backrefs))
var/datum/browser/popup = new(usr, "ref_view", "<div align='center'>Error</div>")
popup.set_content("Reference tracking not enabled")
popup.open(FALSE)
return
var/list/frontrefs = get_forward_references(D)
var/list/dat = list()
dat += "<h1>References of \ref[D] - [D]</h1><br><a href='?_src_=vars;[HrefToken()];[VV_HK_VIEW_REFERENCES]=TRUE;[VV_HK_TARGET]=[REF(D)]'>\[Refresh\]</a><hr>"
dat += "<h3>Back references - these things hold references to this object.</h3>"
dat += "<table>"
dat += "<tr><th>Ref</th><th>Type</th><th>Variable Name</th><th>Follow</th>"
for(var/ref in backrefs)
var/datum/backreference = ref
if(isnull(backreference))
dat += "<tr><td>GC'd Reference</td></tr>"
if(istype(backreference))
dat += "<tr><td><a href='?_src_=vars;[HrefToken()];Vars=[REF(backreference)]'>[REF(backreference)]</td><td>[backreference.type]</td><td>[backrefs[backreference]]</td><td><a href='?_src_=vars;[HrefToken()];[VV_HK_VIEW_REFERENCES]=TRUE;[VV_HK_TARGET]=[REF(backreference)]'>\[Follow\]</a></td></tr>"
else if(islist(backreference))
dat += "<tr><td><a href='?_src_=vars;[HrefToken()];Vars=[REF(backreference)]'>[REF(backreference)]</td><td>list</td><td>[backrefs[backreference]]</td><td><a href='?_src_=vars;[HrefToken()];[VV_HK_VIEW_REFERENCES]=TRUE;[VV_HK_TARGET]=[REF(backreference)]'>\[Follow\]</a></td></tr>"
else
dat += "<tr><td>Weird reference type. Add more debugging checks.</td></tr>"
dat += "</table><hr>"
dat += "<h3>Forward references - this object is referencing those things.</h3>"
dat += "<table>"
dat += "<tr><th>Variable name</th><th>Ref</th><th>Type</th><th>Follow</th>"
for(var/ref in frontrefs)
var/datum/backreference = frontrefs[ref]
dat += "<tr><td>[ref]</td><td><a href='?_src_=vars;[HrefToken()];Vars=[REF(backreference)]'>[REF(backreference)]</a></td><td>[backreference.type]</td><td><a href='?_src_=vars;[HrefToken()];[VV_HK_VIEW_REFERENCES]=TRUE;[VV_HK_TARGET]=[REF(backreference)]'>\[Follow\]</a></td></tr>"
dat += "</table><hr>"
dat = dat.Join()
var/datum/browser/popup = new(usr, "ref_view", "<div align='center'>References of \ref[D]</div>")
popup.set_content(dat)
popup.open(FALSE)
/datum/admins/proc/view_del_failures()
set category = "Debug"
set name = "View Deletion Failures"
if(!check_rights(R_DEBUG))
return
var/list/dat = list("<table>")
for(var/t in GLOB.deletion_failures)
if(isnull(t))
dat += "<tr><td>GC'd Reference | <a href='byond://?src=[REF(src)];[HrefToken(TRUE)];delfail_clearnulls=TRUE'>Clear Nulls</a></td></tr>"
continue
var/datum/thing = t
dat += "<tr><td>\ref[thing] | [thing.type][thing.gc_destroyed ? " (destroyed)" : ""] [ADMIN_VV(thing)]</td></tr>"
dat += "</table><hr>"
dat = dat.Join()
var/datum/browser/popup = new(usr, "del_failures", "<div align='center'>Deletion Failures</div>")
popup.set_content(dat)
popup.open(FALSE)
/datum/proc/find_references()
testing("Beginning search for references to a [type].")
var/list/backrefs = get_back_references(src)
for(var/ref in backrefs)
if(isnull(ref))
log_world("## TESTING: Datum reference found, but gone now.")
continue
if(islist(ref))
log_world("## TESTING: Found [type] \ref[src] in list.")
continue
var/datum/datum_ref = ref
if(!istype(datum_ref))
log_world("## TESTING: Found [type] \ref[src] in unknown type reference: [datum_ref].")
return
log_world("## TESTING: Found [type] \ref[src] in [datum_ref.type][datum_ref.gc_destroyed ? " (destroyed)" : ""]")
message_admins("Found [type] \ref[src] [ADMIN_VV(src)] in [datum_ref.type][datum_ref.gc_destroyed ? " (destroyed)" : ""] [ADMIN_VV(datum_ref)]")
testing("Completed search for references to a [type].")
#endif
#ifdef LEGACY_REFERENCE_TRACKING
/datum/verb/legacy_find_refs()
set category = "Debug"
set name = "Find References"
set src in world
find_references(FALSE)
/datum/proc/find_references_legacy(skip_alert)
running_find_references = type
if(usr?.client)
if(usr.client.running_find_references)
testing("CANCELLED search for references to a [usr.client.running_find_references].")
usr.client.running_find_references = null
running_find_references = null
//restart the garbage collector
SSgarbage.can_fire = TRUE
SSgarbage.next_fire = world.time + world.tick_lag
return
if(!skip_alert && alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") != "Yes")
running_find_references = null
return
//this keeps the garbage collector from failing to collect objects being searched for in here
SSgarbage.can_fire = FALSE
if(usr?.client)
usr.client.running_find_references = type
testing("Beginning search for references to a [type].")
last_find_references = world.time
DoSearchVar(GLOB) //globals
for(var/datum/thing in world) //atoms (don't beleive its lies)
DoSearchVar(thing, "World -> [thing]")
for(var/datum/thing) //datums
DoSearchVar(thing, "World -> [thing]")
for(var/client/thing) //clients
DoSearchVar(thing, "World -> [thing]")
testing("Completed search for references to a [type].")
if(usr?.client)
usr.client.running_find_references = null
running_find_references = null
//restart the garbage collector
SSgarbage.can_fire = TRUE
SSgarbage.next_fire = world.time + world.tick_lag
/datum/verb/qdel_then_find_references()
set category = "Debug"
set name = "qdel() then Find References"
set src in world
qdel(src, TRUE) //force a qdel
if(!running_find_references)
find_references(TRUE)
/datum/verb/qdel_then_if_fail_find_references()
set category = "Debug"
set name = "qdel() then Find References if GC failure"
set src in world
qdel_and_find_ref_if_fail(src, TRUE)
/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64)
if(usr?.client && !usr.client.running_find_references)
return
if(!recursive_limit)
return
if(istype(potential_container, /datum))
var/datum/datum_container = potential_container
if(datum_container.last_find_references == last_find_references)
return
datum_container.last_find_references = last_find_references
var/list/vars_list = datum_container.vars
for(var/varname in vars_list)
if (varname == "vars")
continue
var/variable = vars_list[varname]
if(variable == src)
testing("Found [type] \ref[src] in [datum_container.type]'s [varname] var. [container_name]")
else if(islist(variable))
DoSearchVar(variable, "[container_name] -> list", recursive_limit - 1)
else if(islist(potential_container))
var/normal = IS_NORMAL_LIST(potential_container)
for(var/element_in_list in potential_container)
if(element_in_list == src)
testing("Found [type] \ref[src] in list [container_name].")
else if(element_in_list && !isnum(element_in_list) && normal && potential_container[element_in_list] == src)
testing("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]")
else if(islist(element_in_list))
DoSearchVar(element_in_list, "[container_name] -> list", recursive_limit - 1)
#ifndef FIND_REF_NO_CHECK_TICK
CHECK_TICK
#endif
/proc/qdel_and_find_ref_if_fail(datum/thing_to_del, force = FALSE)
SSgarbage.reference_find_on_fail[REF(thing_to_del)] = TRUE
qdel(thing_to_del, force)
#endif
@@ -45,6 +45,16 @@
usr.client.admin_delete(target)
if (isturf(src)) // show the turf that took its place
usr.client.debug_variables(src)
return
#ifdef REFERENCE_TRACKING
if(href_list[VV_HK_VIEW_REFERENCES])
var/datum/D = locate(href_list[VV_HK_TARGET])
if(!D)
to_chat(usr, "<span class='warning'>Unable to locate item.</span>")
return
usr.client.holder.view_refs(target)
return
#endif
if(href_list[VV_HK_MARK])
usr.client.mark_datum(target)
if(href_list[VV_HK_ADDCOMPONENT])
@@ -61,6 +61,7 @@
"Set len" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_SET_LENGTH),
"Shuffle" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_SHUFFLE),
"Show VV To Player" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_EXPOSE),
"View References" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_VIEW_REFERENCES),
"---"
)
for(var/i in 1 to length(dropdownoptions))
+3 -1
View File
@@ -264,7 +264,7 @@
name = "pipes"
/datum/asset/spritesheet/pipes/register()
for (var/each in list('icons/obj/atmospherics/pipes/pipe_item.dmi', 'icons/obj/atmospherics/pipes/disposal.dmi'))
for (var/each in list('icons/obj/atmospherics/pipes/pipe_item.dmi', 'icons/obj/atmospherics/pipes/disposal.dmi', 'icons/obj/atmospherics/pipes/transit_tube.dmi', 'icons/obj/plumbing/fluid_ducts.dmi'))
InsertAll("", each, GLOB.alldirs)
..()
@@ -388,9 +388,11 @@
Insert("polycrystal", 'icons/obj/telescience.dmi', "polycrystal")
..()
/datum/asset/spritesheet/mafia
name = "mafia"
/datum/asset/spritesheet/mafia/register()
InsertAll("", 'icons/obj/mafia.dmi')
..()
+5 -1
View File
@@ -5,7 +5,7 @@
// You do not need to raise this if you are adding new values that have sane defaults.
// Only raise this value when changing the meaning/format/name/layout of an existing value
// where you would want the updater procs below to run
#define SAVEFILE_VERSION_MAX 34
#define SAVEFILE_VERSION_MAX 35
/*
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
@@ -200,6 +200,10 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
features["silicon_flavor_text"] = html_encode(features["silicon_flavor_text"])
features["ooc_notes"] = html_encode(features["ooc_notes"])
if(current_version < 35)
if(S["species"] == "lizard")
features["mam_snouts"] = features["snout"]
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
if(!ckey)
return
+14 -10
View File
@@ -30,7 +30,7 @@
var/self_sufficiency_req = 20 //Required total dose to make a self-sufficient hydro tray. 1:1 with earthsblood.
var/self_sufficiency_progress = 0
var/self_sustaining = FALSE //If the tray generates nutrients and water on its own
var/canirrigate = TRUE //tin
/obj/machinery/hydroponics/constructable
name = "hydroponics tray"
@@ -847,12 +847,13 @@
if (!anchored)
to_chat(user, "<span class='warning'>Anchor the tray first!</span>")
return
using_irrigation = !using_irrigation
O.play_tool_sound(src)
user.visible_message("<span class='notice'>[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.</span>", \
"<span class='notice'>You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.</span>")
for(var/obj/machinery/hydroponics/h in range(1,src))
h.update_icon()
if(canirrigate)
using_irrigation = !using_irrigation
O.play_tool_sound(src)
user.visible_message("<span class='notice'>[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.</span>", \
"<span class='notice'>You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.</span>")
for(var/obj/machinery/hydroponics/h in range(1,src))
h.update_icon()
else if(istype(O, /obj/item/shovel/spade))
if(!myseed && !weedlevel)
@@ -910,11 +911,14 @@
harvest = 0
lastproduce = age
if(istype(myseed, /obj/item/seeds/replicapod))
to_chat(user, "<span class='notice'>You harvest from the [myseed.plantname].</span>")
if(user)//runtimes
to_chat(user, "<span class='notice'>You harvest from the [myseed.plantname].</span>")
else if(myseed.getYield() <= 0)
to_chat(user, "<span class='warning'>You fail to harvest anything useful!</span>")
if(user)
to_chat(user, "<span class='warning'>You fail to harvest anything useful!</span>")
else
to_chat(user, "<span class='notice'>You harvest [myseed.getYield()] items from the [myseed.plantname].</span>")
if(user)
to_chat(user, "<span class='notice'>You harvest [myseed.getYield()] items from the [myseed.plantname].</span>")
if(!myseed.get_gene(/datum/plant_gene/trait/repeated_harvest))
qdel(myseed)
myseed = null
+25
View File
@@ -190,6 +190,31 @@ obj/item/seeds/proc/is_gene_forbidden(typepath)
parent.update_tray(user)
return result
/obj/item/seeds/proc/harvest_userless()
var/obj/machinery/hydroponics/parent = loc //for ease of access
var/t_amount = 0
var/list/result = list()
var/output_loc = parent.loc
var/product_name
while(t_amount < getYield())
var/obj/item/reagent_containers/food/snacks/grown/t_prod = new product(output_loc, src)
if(parent.myseed.plantname != initial(parent.myseed.plantname))
t_prod.name = lowertext(parent.myseed.plantname)
if(productdesc)
t_prod.desc = productdesc
t_prod.seed.name = parent.myseed.name
t_prod.seed.desc = parent.myseed.desc
t_prod.seed.plantname = parent.myseed.plantname
result.Add(t_prod) // User gets a consumable
if(!t_prod)
return
t_amount++
product_name = parent.myseed.plantname
if(getYield() >= 1)
SSblackbox.record_feedback("tally", "food_harvested", getYield(), product_name)
parent.investigate_log("autmoatic harvest of [getYield()] of [src], with seed traits [english_list(genes)] and reagents_add [english_list(reagents_add)] and potency [potency].", INVESTIGATE_BOTANY)
parent.update_tray()
return result
/obj/item/seeds/proc/prepare_result(var/obj/item/reagent_containers/food/snacks/grown/T)
if(!T.reagents)
@@ -11,7 +11,7 @@
mutanttail = /obj/item/organ/tail/lizard
coldmod = 1.5
heatmod = 0.67
mutant_bodyparts = list("mcolor" = "0F0", "mcolor2" = "0F0", "mcolor3" = "0F0", "tail_lizard" = "Smooth", "snout" = "Round",
mutant_bodyparts = list("mcolor" = "0F0", "mcolor2" = "0F0", "mcolor3" = "0F0", "tail_lizard" = "Smooth", "mam_snouts" = "Round",
"horns" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None",
"legs" = "Digitigrade", "taur" = "None", "deco_wings" = "None")
attack_verb = "slash"
+433
View File
@@ -0,0 +1,433 @@
/*
All the important duct code:
/code/datums/components/plumbing/plumbing.dm
/code/datums/ductnet.dm
*/
/obj/machinery/duct
name = "fluid duct"
icon = 'icons/obj/plumbing/fluid_ducts.dmi'
icon_state = "nduct"
///bitfield with the directions we're connected in
var/connects
///set to TRUE to disable smart duct behaviour
var/dumb = FALSE
///wheter we allow our connects to be changed after initialization or not
var/lock_connects = FALSE
///our ductnet, wich tracks what we're connected to
var/datum/ductnet/duct
///amount we can transfer per process. note that the ductnet can carry as much as the lowest capacity duct
var/capacity = 10
///the color of our duct
var/duct_color = null
///TRUE to ignore colors, so yeah we also connect with other colors without issue
var/ignore_colors = FALSE
///1,2,4,8,16
var/duct_layer = DUCT_LAYER_DEFAULT
///whether we allow our layers to be altered
var/lock_layers = FALSE
///TRUE to let colors connect when forced with a wrench, false to just not do that at all
var/color_to_color_support = TRUE
///wheter to even bother with plumbing code or not
var/active = TRUE
///track ducts we're connected to. Mainly for ducts we connect to that we normally wouldn't, like different layers and colors, for when we regenerate the ducts
var/list/neighbours = list()
///wheter we just unanchored or drop whatever is in the variable. either is safe
var/drop_on_wrench = /obj/item/stack/ducts
/obj/machinery/duct/Initialize(mapload, no_anchor, color_of_duct = "#ffffff", layer_of_duct = DUCT_LAYER_DEFAULT, force_connects)
. = ..()
if(no_anchor)
active = FALSE
set_anchored(FALSE)
else if(!can_anchor())
qdel(src)
CRASH("Overlapping ducts detected")
if(force_connects)
connects = force_connects //skip change_connects() because we're still initializing and we need to set our connects at one point
if(!lock_layers)
duct_layer = layer_of_duct
if(!ignore_colors)
duct_color = color_of_duct
if(duct_color)
add_atom_colour(duct_color, FIXED_COLOUR_PRIORITY)
handle_layer()
for(var/obj/machinery/duct/D in loc)
if(D == src)
continue
if(D.duct_layer & duct_layer)
disconnect_duct()
if(active)
attempt_connect()
///start looking around us for stuff to connect to
/obj/machinery/duct/proc/attempt_connect()
for(var/atom/movable/AM in loc)
var/datum/component/plumbing/P = AM.GetComponent(/datum/component/plumbing)
if(P?.active)
disconnect_duct() //let's not built under plumbing machinery
return
for(var/D in GLOB.cardinals)
if(dumb && !(D & connects))
continue
for(var/atom/movable/AM in get_step(src, D))
if(connect_network(AM, D))
add_connects(D)
update_icon()
///see if whatever we found can be connected to
/obj/machinery/duct/proc/connect_network(atom/movable/AM, direction, ignore_color)
if(istype(AM, /obj/machinery/duct))
return connect_duct(AM, direction, ignore_color)
var/plumber = AM.GetComponent(/datum/component/plumbing)
if(!plumber)
return
return connect_plumber(plumber, direction)
///connect to a duct
/obj/machinery/duct/proc/connect_duct(obj/machinery/duct/D, direction, ignore_color)
var/opposite_dir = turn(direction, 180)
if(!active || !D.active)
return
if(!dumb && D.dumb && !(opposite_dir & D.connects))
return
if(dumb && D.dumb && !(connects & D.connects)) //we eliminated a few more scenarios in attempt connect
return
if((duct == D.duct) && duct)//check if we're not just comparing two null values
add_neighbour(D, direction)
D.add_connects(opposite_dir)
D.update_icon()
return TRUE //tell the current pipe to also update it's sprite
if(!(D in neighbours)) //we cool
if((duct_color != D.duct_color) && !(ignore_colors || D.ignore_colors))
return
if(!(duct_layer & D.duct_layer))
return
if(D.duct)
if(duct)
duct.assimilate(D.duct)
else
D.duct.add_duct(src)
else
if(duct)
duct.add_duct(D)
else
create_duct()
duct.add_duct(D)
add_neighbour(D, direction)
//tell our buddy its time to pass on the torch of connecting to pipes. This shouldn't ever infinitely loop since it only works on pipes that havent been inductrinated
D.attempt_connect()
return TRUE
///connect to a plumbing object
/obj/machinery/duct/proc/connect_plumber(datum/component/plumbing/P, direction)
var/opposite_dir = turn(direction, 180)
if(duct_layer != DUCT_LAYER_DEFAULT) //plumbing devices don't support multilayering. 3 is the default layer so we only use that. We can change this later
return FALSE
if(!P.active)
return
var/comp_directions = P.supply_connects + P.demand_connects //they should never, ever have supply and demand connects overlap or catastrophic failure
if(opposite_dir & comp_directions)
if(!duct)
create_duct()
if(duct.add_plumber(P, opposite_dir))
neighbours[P.parent] = direction
return TRUE
///we disconnect ourself from our neighbours. we also destroy our ductnet and tell our neighbours to make a new one
/obj/machinery/duct/proc/disconnect_duct(skipanchor)
if(!skipanchor) //since set_anchored calls us too.
set_anchored(FALSE)
active = FALSE
if(duct)
duct.remove_duct(src)
lose_neighbours()
reset_connects(0)
update_icon()
if(ispath(drop_on_wrench) && !QDELING(src))
new drop_on_wrench(drop_location())
qdel(src)
///''''''''''''''''optimized''''''''''''''''' proc for quickly reconnecting after a duct net was destroyed
/obj/machinery/duct/proc/reconnect()
if(neighbours.len && !duct)
create_duct()
for(var/atom/movable/AM in neighbours)
if(istype(AM, /obj/machinery/duct))
var/obj/machinery/duct/D = AM
if(D.duct)
if(D.duct == duct) //we're already connected
continue
else
duct.assimilate(D.duct)
continue
else
duct.add_duct(D)
D.reconnect()
else
var/datum/component/plumbing/P = AM.GetComponent(/datum/component/plumbing)
if(AM in get_step(src, neighbours[AM])) //did we move?
if(P)
connect_plumber(P, neighbours[AM])
else
neighbours -= AM //we moved
///Special proc to draw a new connect frame based on neighbours. not the norm so we can support multiple duct kinds
/obj/machinery/duct/proc/generate_connects()
if(lock_connects)
return
connects = 0
for(var/A in neighbours)
connects |= neighbours[A]
update_icon()
///create a new duct datum
/obj/machinery/duct/proc/create_duct()
duct = new()
duct.add_duct(src)
///add a duct as neighbour. this means we're connected and will connect again if we ever regenerate
/obj/machinery/duct/proc/add_neighbour(obj/machinery/duct/D, direction)
if(!(D in neighbours))
neighbours[D] = direction
if(!(src in D.neighbours))
D.neighbours[src] = turn(direction, 180)
///remove all our neighbours, and remove us from our neighbours aswell
/obj/machinery/duct/proc/lose_neighbours()
for(var/obj/machinery/duct/D in neighbours)
D.neighbours.Remove(src)
neighbours = list()
///add a connect direction
/obj/machinery/duct/proc/add_connects(new_connects) //make this a define to cut proc calls?
if(!lock_connects)
connects |= new_connects
///remove a connect direction
/obj/machinery/duct/proc/remove_connects(dead_connects)
if(!lock_connects)
connects &= ~dead_connects
///remove our connects
/obj/machinery/duct/proc/reset_connects()
if(!lock_connects)
connects = 0
///get a list of the ducts we can connect to if we are dumb
/obj/machinery/duct/proc/get_adjacent_ducts()
var/list/adjacents = list()
for(var/A in GLOB.cardinals)
if(A & connects)
for(var/obj/machinery/duct/D in get_step(src, A))
if((turn(A, 180) & D.connects) && D.active)
adjacents += D
return adjacents
/obj/machinery/duct/update_icon_state()
var/temp_icon = initial(icon_state)
for(var/D in GLOB.cardinals)
if(D & connects)
if(D == NORTH)
temp_icon += "_n"
if(D == SOUTH)
temp_icon += "_s"
if(D == EAST)
temp_icon += "_e"
if(D == WEST)
temp_icon += "_w"
icon_state = temp_icon
///update the layer we are on
/obj/machinery/duct/proc/handle_layer()
var/offset
switch(duct_layer)//it's a bitfield, but it's fine because it only works when there's one layer, and multiple layers should be handled differently
if(FIRST_DUCT_LAYER)
offset = -10
if(SECOND_DUCT_LAYER)
offset = -5
if(THIRD_DUCT_LAYER)
offset = 0
if(FOURTH_DUCT_LAYER)
offset = 5
if(FIFTH_DUCT_LAYER)
offset = 10
pixel_x = offset
pixel_y = offset
/obj/machinery/duct/set_anchored(anchorvalue)
. = ..()
if(isnull(.))
return
if(anchorvalue)
active = TRUE
attempt_connect()
else
disconnect_duct(TRUE)
/obj/machinery/duct/wrench_act(mob/living/user, obj/item/I) //I can also be the RPD
..()
add_fingerprint(user)
I.play_tool_sound(src)
if(anchored || can_anchor())
set_anchored(!anchored)
user.visible_message( \
"[user] [anchored ? null : "un"]fastens \the [src].", \
"<span class='notice'>You [anchored ? null : "un"]fasten \the [src].</span>", \
"<span class='hear'>You hear ratcheting.</span>")
return TRUE
///collection of all the sanity checks to prevent us from stacking ducts that shouldn't be stacked
/obj/machinery/duct/proc/can_anchor(turf/T)
if(!T)
T = get_turf(src)
for(var/obj/machinery/duct/D in T)
if(!anchored || D == src)
continue
for(var/A in GLOB.cardinals)
if(A & connects && A & D.connects)
return FALSE
return TRUE
/obj/machinery/duct/doMove(destination)
. = ..()
disconnect_duct()
anchored = FALSE
/obj/machinery/duct/Destroy()
disconnect_duct()
return ..()
/obj/machinery/duct/MouseDrop_T(atom/A, mob/living/user)
if(!istype(A, /obj/machinery/duct))
return
var/obj/machinery/duct/D = A
var/obj/item/I = user.get_active_held_item()
if(I?.tool_behaviour != TOOL_WRENCH)
to_chat(user, "<span class='warning'>You need to be holding a wrench in your active hand to do that!</span>")
return
if(get_dist(src, D) != 1)
return
var/direction = get_dir(src, D)
if(!(direction in GLOB.cardinals))
return
if(duct_layer != D.duct_layer)
return
add_connects(direction) //the connect of the other duct is handled in connect_network, but do this here for the parent duct because it's not necessary in normal cases
add_neighbour(D, direction)
connect_network(D, direction, TRUE)
update_icon()
///has a total of 5 layers and doesnt give a shit about color. its also dumb so doesnt autoconnect.
/obj/machinery/duct/multilayered
name = "duct layer-manifold"
icon = 'icons/obj/2x2.dmi'
icon_state = "multiduct"
pixel_x = -15
pixel_y = -15
color_to_color_support = FALSE
duct_layer = FIRST_DUCT_LAYER | SECOND_DUCT_LAYER | THIRD_DUCT_LAYER | FOURTH_DUCT_LAYER | FIFTH_DUCT_LAYER
drop_on_wrench = null
lock_connects = TRUE
lock_layers = TRUE
ignore_colors = TRUE
dumb = TRUE
active = FALSE
anchored = FALSE
/obj/machinery/duct/multilayered/Initialize(mapload, no_anchor, color_of_duct, layer_of_duct = DUCT_LAYER_DEFAULT, force_connects)
. = ..()
update_connects()
/obj/machinery/duct/multilayered/ComponentInitialize()
. = ..()
AddElement(/datum/element/update_icon_blocker)
/obj/machinery/duct/multilayered/wrench_act(mob/living/user, obj/item/I)
. = ..()
update_connects()
/obj/machinery/duct/multilayered/proc/update_connects()
if(dir & NORTH || dir & SOUTH)
connects = NORTH | SOUTH
else
connects = EAST | WEST
///don't connect to other multilayered stuff because honestly it shouldn't be done and I dont wanna deal with it
/obj/machinery/duct/multilayered/connect_duct(obj/machinery/duct/D, direction, ignore_color)
if(istype(D, /obj/machinery/duct/multilayered))
return
return ..()
/obj/machinery/duct/multilayered/handle_layer()
return
/obj/item/stack/ducts
name = "stack of duct"
desc = "A stack of fluid ducts."
singular_name = "duct"
icon = 'icons/obj/plumbing/fluid_ducts.dmi'
icon_state = "ducts"
custom_materials = list(/datum/material/iron=500)
w_class = WEIGHT_CLASS_TINY
novariants = FALSE
max_amount = 50
item_flags = NOBLUDGEON
merge_type = /obj/item/stack/ducts
///Color of our duct
var/duct_color = "grey"
///Default layer of our duct
var/duct_layer = "Default Layer"
///Assoc index with all the available layers. yes five might be a bit much. Colors uses a global by the way
var/list/layers = list("First Layer" = FIRST_DUCT_LAYER, "Second Layer" = SECOND_DUCT_LAYER, "Default Layer" = DUCT_LAYER_DEFAULT,
"Fourth Layer" = FOURTH_DUCT_LAYER, "Fifth Layer" = FIFTH_DUCT_LAYER)
/obj/item/stack/ducts/examine(mob/user)
. = ..()
. += "<span class='notice'>It's current color and layer are [duct_color] and [duct_layer]. Use in-hand to change.</span>"
/obj/item/stack/ducts/attack_self(mob/user)
var/new_layer = input("Select a layer", "Layer") as null|anything in layers
if(new_layer)
duct_layer = new_layer
var/new_color = input("Select a color", "Color") as null|anything in GLOB.pipe_paint_colors
if(new_color)
duct_color = new_color
add_atom_colour(GLOB.pipe_paint_colors[new_color], FIXED_COLOUR_PRIORITY)
/obj/item/stack/ducts/afterattack(atom/A, user, proximity)
. = ..()
if(!proximity)
return
if(istype(A, /obj/machinery/duct))
var/obj/machinery/duct/D = A
if(!D.anchored)
add(1)
qdel(D)
if(istype(A, /turf/open) && use(1))
var/turf/open/OT = A
new /obj/machinery/duct(OT, FALSE, GLOB.pipe_paint_colors[duct_color], layers[duct_layer])
playsound(get_turf(src), 'sound/machines/click.ogg', 50, TRUE)
/obj/item/stack/ducts/fifty
amount = 50
@@ -0,0 +1,98 @@
/**Basic plumbing object.
* It doesn't really hold anything special, YET.
* Objects that are plumbing but not a subtype are as of writing liquid pumps and the reagent_dispenser tank
* Also please note that the plumbing component is toggled on and off by the component using a signal from default_unfasten_wrench, so dont worry about it
*/
/obj/machinery/plumbing
name = "pipe thing"
icon = 'icons/obj/plumbing/plumbers.dmi'
icon_state = "pump"
density = TRUE
active_power_usage = 30
use_power = ACTIVE_POWER_USE
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
///Plumbing machinery is always gonna need reagents, so we might aswell put it here
var/buffer = 50
///Flags for reagents, like INJECTABLE, TRANSPARENT bla bla everything thats in DEFINES/reagents.dm
var/reagent_flags = TRANSPARENT
///wheter we partake in rcd construction or not
var/rcd_constructable = TRUE
///cost of the plumbing rcd construction
var/rcd_cost = 15
///delay of constructing it throught the plumbing rcd
var/rcd_delay = 10
/obj/machinery/plumbing/Initialize(mapload, bolt = TRUE)
. = ..()
anchored = bolt
create_reagents(buffer, reagent_flags)
AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
/obj/machinery/plumbing/proc/can_be_rotated(mob/user,rotation_type)
return TRUE
/obj/machinery/plumbing/examine(mob/user)
. = ..()
. += "<span class='notice'>The maximum volume display reads: <b>[reagents.maximum_volume] units</b>.</span>"
/obj/machinery/plumbing/wrench_act(mob/living/user, obj/item/I)
..()
default_unfasten_wrench(user, I)
return TRUE
/obj/machinery/plumbing/plunger_act(obj/item/plunger/P, mob/living/user, reinforced)
to_chat(user, "<span class='notice'>You start furiously plunging [name].")
if(do_after(user, 30, target = src))
to_chat(user, "<span class='notice'>You finish plunging the [name].")
reagents.reaction(get_turf(src), TOUCH) //splash on the floor
reagents.clear_reagents()
/obj/machinery/plumbing/welder_act(mob/living/user, obj/item/I)
. = ..()
if(anchored)
to_chat(user, "<span class='warning'>The [name] needs to be unbolted to do that!</span")
if(I.tool_start_check(user, amount=0))
to_chat(user, "<span class='notice'>You start slicing the [name] apart.</span")
if(I.use_tool(src, user, rcd_delay * 2, volume=50))
deconstruct(TRUE)
to_chat(user, "<span class='notice'>You slice the [name] apart.</span")
return TRUE
///We can empty beakers in here and everything
/obj/machinery/plumbing/input
name = "input gate"
desc = "Can be manually filled with reagents from containers."
icon_state = "pipe_input"
reagent_flags = TRANSPARENT | REFILLABLE
rcd_cost = 5
rcd_delay = 5
/obj/machinery/plumbing/input/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/simple_supply, bolt)
///We can fill beakers in here and everything. we dont inheret from input because it has nothing that we need
/obj/machinery/plumbing/output
name = "output gate"
desc = "A manual output for plumbing systems, for taking reagents directly into containers."
icon_state = "pipe_output"
reagent_flags = TRANSPARENT | DRAINABLE
rcd_cost = 5
rcd_delay = 5
/obj/machinery/plumbing/output/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/simple_demand, bolt)
/obj/machinery/plumbing/tank
name = "chemical tank"
desc = "A massive chemical holding tank."
icon_state = "tank"
buffer = 400
rcd_cost = 25
rcd_delay = 20
/obj/machinery/plumbing/tank/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/tank, bolt)
@@ -0,0 +1,105 @@
//we cant use defines in tgui, so use a string instead of magic numbers
#define COOLING "Cooling"
#define HEATING "Heating"
#define NEUTRAL "Neutral"
///this the plumbing version of a heater/freezer.
/obj/machinery/plumbing/acclimator
name = "chemical acclimator"
desc = "An efficient cooler and heater for the perfect showering temperature or illicit chemical factory."
icon_state = "acclimator"
buffer = 200
///towards wich temperature do we build?
var/target_temperature = 300
///I cant find a good name for this. Basically if target is 300, and this is 10, it will still target 300 but will start emptying itself at 290 and 310.
var/allowed_temperature_difference = 1
///cool/heat power
var/heater_coefficient = 0.1
///Are we turned on or off? this is from the on and off button
var/enabled = TRUE
///COOLING, HEATING or NEUTRAL. We track this for change, so we dont needlessly update our icon
var/acclimate_state
/**We can't take anything in, at least till we're emptied. Down side of the round robin chem transfer, otherwise while emptying 5u of an unreacted chem gets added,
and you get nasty leftovers
*/
var/emptying = FALSE
ui_x = 320
ui_y = 310
/obj/machinery/plumbing/acclimator/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/acclimator, bolt)
/obj/machinery/plumbing/acclimator/process()
if(stat & NOPOWER || !enabled || !reagents.total_volume || reagents.chem_temp == target_temperature)
if(acclimate_state != NEUTRAL)
acclimate_state = NEUTRAL
update_icon()
if(!reagents.total_volume)
emptying = FALSE
return
if(reagents.chem_temp < target_temperature && acclimate_state != HEATING) //note that we check if the temperature is the same at the start
acclimate_state = HEATING
update_icon()
else if(reagents.chem_temp > target_temperature && acclimate_state != COOLING)
acclimate_state = COOLING
update_icon()
if(!emptying)
if(reagents.chem_temp >= target_temperature && target_temperature + allowed_temperature_difference >= reagents.chem_temp) //cooling here
emptying = TRUE
if(reagents.chem_temp <= target_temperature && target_temperature - allowed_temperature_difference <= reagents.chem_temp) //heating here
emptying = TRUE
reagents.adjust_thermal_energy((target_temperature - reagents.chem_temp) * heater_coefficient * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater
reagents.handle_reactions()
/obj/machinery/plumbing/acclimator/update_icon()
icon_state = initial(icon_state)
switch(acclimate_state)
if(COOLING)
icon_state += "_cold"
if(HEATING)
icon_state += "_hot"
/obj/machinery/plumbing/acclimator/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ChemAcclimator", name)
ui.open()
/obj/machinery/plumbing/acclimator/ui_data(mob/user)
var/list/data = list()
data["enabled"] = enabled
data["chem_temp"] = reagents.chem_temp
data["target_temperature"] = target_temperature
data["allowed_temperature_difference"] = allowed_temperature_difference
data["acclimate_state"] = acclimate_state
data["max_volume"] = reagents.maximum_volume
data["reagent_volume"] = reagents.total_volume
data["emptying"] = emptying
return data
/obj/machinery/plumbing/acclimator/ui_act(action, params)
if(..())
return
. = TRUE
switch(action)
if("set_target_temperature")
var/target = text2num(params["temperature"])
target_temperature = clamp(target, 0, 1000)
if("set_allowed_temperature_difference")
var/target = text2num(params["temperature"])
allowed_temperature_difference = clamp(target, 0, 1000)
if("toggle_power")
enabled = !enabled
if("change_volume")
var/target = text2num(params["volume"])
reagents.maximum_volume = clamp(round(target), 1, buffer)
#undef COOLING
#undef HEATING
#undef NEUTRAL
@@ -0,0 +1,65 @@
/obj/machinery/hydroponics/constructable/automagic
name = "automated hydroponics system"
desc = "The bane of botanists everywhere. Accepts chemical reagents via plumbing, automatically harvests and removes dead plants."
obj_flags = CAN_BE_HIT | UNIQUE_RENAME
circuit = /obj/item/circuitboard/machine/hydroponics/automagic
self_sufficiency_req = 400 //automating hydroponics makes gaia sad so she needs more drugs to turn they tray godly.
canirrigate = FALSE
/obj/machinery/hydroponics/constructable/automagic/attackby(obj/item/O, mob/user, params)
if(istype(O, /obj/item/reagent_containers))
return FALSE //avoid fucky wuckies
..()
/obj/machinery/hydroponics/constructable/automagic/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
. = ..()
if(. == SUCCESSFUL_UNFASTEN)
user.visible_message("<span class='notice'>[user.name] [anchored ? "fasten" : "unfasten"] [src]</span>", \
"<span class='notice'>You [anchored ? "fasten" : "unfasten"] [src]</span>")
var/datum/component/plumbing/CP = GetComponent(/datum/component/plumbing)
if(anchored)
CP.enable()
else
CP.disable()
/obj/machinery/hydroponics/constructable/automagic/Destroy()
. = ..()
STOP_PROCESSING(SSobj, src)
/obj/machinery/hydroponics/constructable/automagic/Initialize(mapload)
. = ..()
START_PROCESSING(SSobj, src)
create_reagents(100 , AMOUNT_VISIBLE)
/obj/machinery/hydroponics/constructable/automagic/ComponentInitialize()
. = ..()
AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
AddComponent(/datum/component/plumbing/simple_demand)
/obj/machinery/hydroponics/constructable/proc/can_be_rotated(mob/user, rotation_type)
return !anchored
/obj/machinery/hydroponics/constructable/automagic/process()
if(reagents)
applyChemicals(reagents)
reagents.clear_reagents()
if(dead)
dead = 0
qdel(myseed)
myseed = null
update_icon()
name = initial(name)
desc = initial(desc)
if(harvest)
myseed.harvest_userless()
harvest = 0
lastproduce = age
if(!myseed.get_gene(/datum/plant_gene/trait/repeated_harvest))
qdel(myseed)
myseed = null
dead = 0
name = initial(name)
desc = initial(desc)
update_icon()
..()
+79
View File
@@ -0,0 +1,79 @@
/obj/machinery/plumbing/bottler
name = "chemical bottler"
desc = "Puts reagents into containers, like bottles and beakers."
icon_state = "bottler"
layer = ABOVE_ALL_MOB_LAYER
reagent_flags = TRANSPARENT | DRAINABLE
rcd_cost = 50
rcd_delay = 50
buffer = 100
///how much do we fill
var/wanted_amount = 10
///where things are sent
var/turf/goodspot = null
///where things are taken
var/turf/inputspot = null
///where beakers that are already full will be sent
var/turf/badspot = null
/obj/machinery/plumbing/bottler/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/simple_demand, bolt)
setDir(dir)
/obj/machinery/plumbing/bottler/can_be_rotated(mob/user, rotation_type)
if(anchored)
to_chat(user, "<span class='warning'>It is fastened to the floor!</span>")
return FALSE
return TRUE
///changes the tile array
/obj/machinery/plumbing/bottler/setDir(newdir)
. = ..()
switch(dir)
if(NORTH)
goodspot = get_step(get_turf(src), NORTH)
inputspot = get_step(get_turf(src), SOUTH)
badspot = get_step(get_turf(src), EAST)
if(SOUTH)
goodspot = get_step(get_turf(src), SOUTH)
inputspot = get_step(get_turf(src), NORTH)
badspot = get_step(get_turf(src), WEST)
if(WEST)
goodspot = get_step(get_turf(src), WEST)
inputspot = get_step(get_turf(src), EAST)
badspot = get_step(get_turf(src), NORTH)
if(EAST)
goodspot = get_step(get_turf(src), EAST)
inputspot = get_step(get_turf(src), WEST)
badspot = get_step(get_turf(src), SOUTH)
///changing input ammount with a window
/obj/machinery/plumbing/bottler/interact(mob/user)
. = ..()
wanted_amount = clamp(round(input(user,"maximum is 100u","set ammount to fill with") as num|null, 1), 1, 100)
reagents.clear_reagents()
to_chat(user, "<span class='notice'> The [src] will now fill for [wanted_amount]u.</span>")
/obj/machinery/plumbing/bottler/process()
if(stat & NOPOWER)
return
///see if machine has enough to fill
if(reagents.total_volume >= wanted_amount && anchored)
var/obj/AM = pick(inputspot.contents)///pick a reagent_container that could be used
if(istype(AM, /obj/item/reagent_containers) && (!istype(AM, /obj/item/reagent_containers/hypospray/medipen)))
var/obj/item/reagent_containers/B = AM
///see if it would overflow else inject
if((B.reagents.total_volume + wanted_amount) <= B.reagents.maximum_volume)
reagents.trans_to(B, wanted_amount)
B.forceMove(goodspot)
return
///glass was full so we move it away
AM.forceMove(badspot)
if(istype(AM, /obj/item/slime_extract)) ///slime extracts need inject
AM.forceMove(goodspot)
reagents.trans_to(AM, wanted_amount)
return
if(istype(AM, /obj/item/slimecross/industrial)) ///no need to move slimecross industrial things
reagents.trans_to(AM, wanted_amount)
return
@@ -0,0 +1,21 @@
/obj/machinery/plumbing/disposer
name = "chemical disposer"
desc = "Breaks down chemicals and annihilates them."
icon_state = "disposal"
///we remove 10 reagents per second
var/disposal_rate = 10
/obj/machinery/plumbing/disposer/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/simple_demand, bolt)
/obj/machinery/plumbing/disposer/process()
if(stat & NOPOWER)
return
if(reagents.total_volume)
if(icon_state != initial(icon_state) + "_working") //threw it here instead of update icon since it only has two states
icon_state = initial(icon_state) + "_working"
reagents.remove_any(disposal_rate)
else
if(icon_state != initial(icon_state))
icon_state = initial(icon_state)
@@ -0,0 +1,59 @@
/obj/machinery/plumbing/fermenter //FULLY AUTOMATIC BEER BREWING. TRULY, THE FUTURE.
name = "chemical fermenter"
desc = "Turns plants into various types of booze."
icon_state = "fermenter"
layer = ABOVE_ALL_MOB_LAYER
reagent_flags = TRANSPARENT | DRAINABLE
rcd_cost = 30
rcd_delay = 30
buffer = 400
///input dir
var/eat_dir = SOUTH
/obj/machinery/plumbing/fermenter/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/simple_supply, bolt)
/obj/machinery/plumbing/fermenter/can_be_rotated(mob/user,rotation_type)
if(anchored)
to_chat(user, "<span class='warning'>It is fastened to the floor!</span>")
return FALSE
switch(eat_dir)
if(WEST)
eat_dir = NORTH
return TRUE
if(EAST)
eat_dir = SOUTH
return TRUE
if(NORTH)
eat_dir = EAST
return TRUE
if(SOUTH)
eat_dir = WEST
return TRUE
/obj/machinery/plumbing/fermenter/CanPass(atom/movable/AM)
. = ..()
if(!anchored)
return
var/move_dir = get_dir(loc, AM.loc)
if(move_dir == eat_dir)
return TRUE
/obj/machinery/plumbing/fermenter/Crossed(atom/movable/AM)
. = ..()
ferment(AM)
/obj/machinery/plumbing/fermenter/proc/ferment(atom/AM)
if(stat & NOPOWER)
return
if(reagents.holder_full())
return
if(!isitem(AM))
return
if(istype(AM, /obj/item/reagent_containers/food/snacks/grown))
var/obj/item/reagent_containers/food/snacks/grown/G = AM
if(G.distill_reagent)
var/amount = G.seed.potency * 0.25
reagents.add_reagent(G.distill_reagent, amount)
qdel(G)
+65
View File
@@ -0,0 +1,65 @@
///chemical plumbing filter. If it's not filtered by left and right, it goes straight.
/obj/machinery/plumbing/filter
name = "chemical filter"
desc = "A chemical filter for filtering chemicals. The left and right outputs appear to be from the perspective of the input port."
icon_state = "filter"
density = FALSE
///whitelist of chems id's that go to the left side. Empty to disable port
var/list/left = list()
///whitelist of chem id's that go to the right side. Empty to disable port
var/list/right = list()
///whitelist of chems but their name instead of path
var/list/english_left = list()
///whitelist of chems but their name instead of path
var/list/english_right = list()
/obj/machinery/plumbing/filter/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/filter, bolt)
/obj/machinery/plumbing/filter/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ChemFilter", name)
ui.open()
/obj/machinery/plumbing/filter/ui_data(mob/user)
var/list/data = list()
data["left"] = english_left
data["right"] = english_right
return data
/obj/machinery/plumbing/filter/ui_act(action, params)
if(..())
return
. = TRUE
switch(action)
if("add")
var/new_chem_name = params["name"]
var/chem_id = get_chem_id(new_chem_name)
if(chem_id)
switch(params["which"])
if("left")
if(!left.Find(chem_id))
english_left += new_chem_name
left += chem_id
if("right")
if(!right.Find(chem_id))
english_right += new_chem_name
right += chem_id
else
to_chat(usr, "<span class='warning'>No such known reagent exists!</span>")
if("remove")
var/chem_name = params["reagent"]
var/chem_id = get_chem_id(chem_name)
switch(params["which"])
if("left")
if(english_left.Find(chem_name))
english_left -= chem_name
left -= chem_id
if("right")
if(english_right.Find(chem_name))
english_right -= chem_name
right -= chem_id
@@ -0,0 +1,64 @@
/obj/machinery/plumbing/grinder_chemical
name = "chemical grinder"
desc = "chemical grinder."
icon_state = "grinder_chemical"
layer = ABOVE_ALL_MOB_LAYER
reagent_flags = TRANSPARENT | DRAINABLE
rcd_cost = 30
rcd_delay = 30
buffer = 400
var/eat_dir = NORTH
/obj/machinery/plumbing/grinder_chemical/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/simple_supply, bolt)
/obj/machinery/plumbing/grinder_chemical/can_be_rotated(mob/user,rotation_type)
if(anchored)
to_chat(user, "<span class='warning'>It is fastened to the floor!</span>")
return FALSE
switch(eat_dir)
if(WEST)
eat_dir = NORTH
return TRUE
if(EAST)
eat_dir = SOUTH
return TRUE
if(NORTH)
eat_dir = EAST
return TRUE
if(SOUTH)
eat_dir = WEST
return TRUE
/obj/machinery/plumbing/grinder_chemical/CanPass(atom/movable/AM)
. = ..()
if(!anchored)
return
var/move_dir = get_dir(loc, AM.loc)
if(move_dir == eat_dir)
return TRUE
/obj/machinery/plumbing/grinder_chemical/Crossed(atom/movable/AM)
. = ..()
grind(AM)
/obj/machinery/plumbing/grinder_chemical/proc/grind(atom/AM)
if(stat & NOPOWER)
return
if(reagents.holder_full())
return
if(!isitem(AM))
return
var/obj/item/I = AM
if(I.juice_results || I.grind_results)
if(I.juice_results)
I.on_juice()
reagents.add_reagent_list(I.juice_results)
if(I.reagents)
I.reagents.trans_to(src, I.reagents.total_volume)
qdel(I)
return
I.on_grind()
reagents.add_reagent_list(I.grind_results)
qdel(I)
@@ -0,0 +1,94 @@
/obj/machinery/medipen_refiller
name = "Medipen Refiller"
desc = "A machine that refills used medipens with chemicals."
icon = 'icons/obj/machines/medipen_refiller.dmi'
icon_state = "medipen_refiller"
density = TRUE
circuit = /obj/item/circuitboard/machine/medipen_refiller
idle_power_usage = 100
/// list of medipen subtypes it can refill
var/list/allowed = list(/obj/item/reagent_containers/hypospray/medipen = /datum/reagent/medicine/epinephrine,
/obj/item/reagent_containers/hypospray/medipen/ekit = /datum/reagent/medicine/epinephrine,
/obj/item/reagent_containers/hypospray/medipen/firelocker = /datum/reagent/medicine/oxandrolone,
/obj/item/reagent_containers/hypospray/medipen/stimpack = /datum/reagent/medicine/ephedrine,
/obj/item/reagent_containers/hypospray/medipen/blood_loss = /datum/reagent/medicine/coagulant/weak)
/// var to prevent glitches in the animation
var/busy = FALSE
/obj/machinery/medipen_refiller/Initialize()
. = ..()
create_reagents(100, TRANSPARENT)
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
reagents.maximum_volume += 100 * B.rating
AddComponent(/datum/component/plumbing/simple_demand)
/obj/machinery/medipen_refiller/RefreshParts()
var/new_volume = 100
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
new_volume += 100 * B.rating
if(!reagents)
create_reagents(new_volume, TRANSPARENT)
reagents.maximum_volume = new_volume
return TRUE
/// handles the messages and animation, calls refill to end the animation
/obj/machinery/medipen_refiller/attackby(obj/item/I, mob/user, params)
if(busy)
to_chat(user, "<span class='danger'>The machine is busy.</span>")
return
if(istype(I, /obj/item/reagent_containers) && I.is_open_container())
var/obj/item/reagent_containers/RC = I
var/units = RC.reagents.trans_to(src, RC.amount_per_transfer_from_this)
if(units)
to_chat(user, "<span class='notice'>You transfer [units] units of the solution to the [name].</span>")
return
else
to_chat(user, "<span class='danger'>The [name] is full.</span>")
return
if(istype(I, /obj/item/reagent_containers/hypospray/medipen))
var/obj/item/reagent_containers/hypospray/medipen/P = I
if(!(LAZYFIND(allowed, P.type)))
to_chat(user, "<span class='danger'>Error! Unknown schematics.</span>")
return
if(P.reagents?.reagent_list.len)
to_chat(user, "<span class='notice'>The medipen is already filled.</span>")
return
if(reagents.has_reagent(allowed[P.type], 10))
busy = TRUE
add_overlay("active")
addtimer(CALLBACK(src, .proc/refill, P, user), 20)
qdel(P)
return
to_chat(user, "<span class='danger'>There aren't enough reagents to finish this operation.</span>")
return
..()
/obj/machinery/medipen_refiller/plunger_act(obj/item/plunger/P, mob/living/user, reinforced)
to_chat(user, "<span class='notice'>You start furiously plunging [name].</span>")
if(do_after(user, 30, target = src))
to_chat(user, "<span class='notice'>You finish plunging the [name].</span>")
reagents.clear_reagents()
/obj/machinery/medipen_refiller/wrench_act(mob/living/user, obj/item/I)
..()
default_unfasten_wrench(user, I)
return TRUE
/obj/machinery/medipen_refiller/crowbar_act(mob/user, obj/item/I)
..()
default_deconstruction_crowbar(I)
return TRUE
/obj/machinery/medipen_refiller/screwdriver_act(mob/living/user, obj/item/I)
. = ..()
if(!.)
return default_deconstruction_screwdriver(user, "medipen_refiller_open", "medipen_refiller", I)
/// refills the medipen
/obj/machinery/medipen_refiller/proc/refill(obj/item/reagent_containers/hypospray/medipen/P, mob/user)
new P.type(loc)
reagents.remove_reagent(allowed[P.type], 10)
cut_overlays()
busy = FALSE
to_chat(user, "<span class='notice'>Medipen refilled.</span>")
@@ -0,0 +1,127 @@
///We take a constant input of reagents, and produce a pill once a set volume is reached
/obj/machinery/plumbing/pill_press
name = "chemical press"
desc = "A press that makes pills, patches and bottles."
icon_state = "pill_press"
///maximum size of a pill
var/max_pill_volume = 50
///maximum size of a patch
var/max_patch_volume = 40
///maximum size of a bottle
var/max_bottle_volume = 30
///current operating product (pills or patches)
var/product = "pill"
///the minimum size a pill or patch can be
var/min_volume = 5
///the maximum size a pill or patch can be
var/max_volume = 50
///selected size of the product
var/current_volume = 10
///prefix for the product name
var/product_name = "factory"
///the icon_state number for the pill.
var/pill_number = RANDOM_PILL_STYLE
///list of id's and icons for the pill selection of the ui
var/list/pill_styles
///list of products stored in the machine, so we dont have 610 pills on one tile
var/list/stored_products = list()
///max amount of pills allowed on our tile before we start storing them instead
var/max_floor_products = 50 //haha massive pill piles
/obj/machinery/plumbing/pill_press/examine(mob/user)
. = ..()
. += "<span class='notice'>The [name] currently has [stored_products.len] stored. There needs to be less than [max_floor_products ] on the floor to continue dispensing.</span>"
/obj/machinery/plumbing/pill_press/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/simple_demand, bolt)
//expertly copypasted from chemmasters
var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/pills)
pill_styles = list()
for (var/x in 1 to PILL_STYLE_COUNT)
var/list/SL = list()
SL["id"] = x
SL["htmltag"] = assets.icon_tag("pill[x]")
pill_styles += list(SL)
/obj/machinery/plumbing/pill_press/process()
if(stat & NOPOWER)
return
if(reagents.total_volume >= current_volume)
if (product == "pill")
var/obj/item/reagent_containers/pill/P = new(src)
reagents.trans_to(P, current_volume)
P.name = trim("[product_name] pill")
stored_products += P
if(pill_number == RANDOM_PILL_STYLE)
P.icon_state = "pill[rand(1,21)]"
else
P.icon_state = "pill[pill_number]"
if(P.icon_state == "pill4") //mirrored from chem masters
P.desc = "A tablet or capsule, but not just any, a red one, one taken by the ones not scared of knowledge, freedom, uncertainty and the brutal truths of reality."
else if (product == "patch")
var/obj/item/reagent_containers/pill/patch/P = new(src)
reagents.trans_to(P, current_volume)
P.name = trim("[product_name] patch")
stored_products += P
else if (product == "bottle")
var/obj/item/reagent_containers/glass/bottle/P = new(src)
reagents.trans_to(P, current_volume)
P.name = trim("[product_name] bottle")
stored_products += P
if(stored_products.len)
var/pill_amount = 0
for(var/obj/item/reagent_containers/pill/P in loc)
pill_amount++
if(pill_amount >= max_floor_products) //too much so just stop
break
if(pill_amount < max_floor_products)
var/atom/movable/AM = stored_products[1] //AM because forceMove is all we need
stored_products -= AM
AM.forceMove(drop_location())
/obj/machinery/plumbing/pill_press/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/simple/pills),
)
/obj/machinery/plumbing/pill_press/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ChemPress", name)
ui.open()
/obj/machinery/plumbing/pill_press/ui_data(mob/user)
var/list/data = list()
data["pill_style"] = pill_number
data["current_volume"] = current_volume
data["product_name"] = product_name
data["pill_styles"] = pill_styles
data["product"] = product
data["min_volume"] = min_volume
data["max_volume"] = max_volume
return data
/obj/machinery/plumbing/pill_press/ui_act(action, params)
if(..())
return
. = TRUE
switch(action)
if("change_pill_style")
pill_number = clamp(text2num(params["id"]), 1 , PILL_STYLE_COUNT)
if("change_current_volume")
current_volume = clamp(text2num(params["volume"]), min_volume, max_volume)
if("change_product_name")
product_name = html_encode(params["name"])
if("change_product")
product = params["product"]
if (product == "pill")
max_volume = max_pill_volume
else if (product == "patch")
max_volume = max_patch_volume
else if (product == "bottle")
max_volume = max_bottle_volume
current_volume = clamp(current_volume, min_volume, max_volume)
+64
View File
@@ -0,0 +1,64 @@
///We pump liquids from activated(plungerated) geysers to a plumbing outlet. We don't need to be wired.
/obj/machinery/plumbing/liquid_pump
name = "liquid pump"
desc = "Pump up those sweet liquids from under the surface. Uses thermal energy from geysers to power itself." //better than placing 200 cables, because it wasnt fun
icon = 'icons/obj/plumbing/plumbers.dmi'
icon_state = "pump"
anchored = FALSE
density = TRUE
idle_power_usage = 10
active_power_usage = 1000
rcd_cost = 30
rcd_delay = 40
///units we pump per process (2 seconds)
var/pump_power = 2
///set to true if the loop couldnt find a geyser in process, so it remembers and stops checking every loop until moved. more accurate name would be absolutely_no_geyser_under_me_so_dont_try
var/geyserless = FALSE
///The geyser object
var/obj/structure/geyser/geyser
///volume of our internal buffer
var/volume = 200
/obj/machinery/plumbing/liquid_pump/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/simple_supply, bolt)
///please note that the component has a hook in the parent call, wich handles activating and deactivating
/obj/machinery/plumbing/liquid_pump/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
. = ..()
if(. == SUCCESSFUL_UNFASTEN)
geyser = null
update_icon()
geyserless = FALSE //we switched state, so lets just set this back aswell
/obj/machinery/plumbing/liquid_pump/process()
if(!anchored || panel_open || geyserless)
return
if(!geyser)
for(var/obj/structure/geyser/G in loc.contents)
geyser = G
update_icon()
if(!geyser) //we didnt find one, abort
geyserless = TRUE
visible_message("<span class='warning'>The [name] makes a sad beep!</span>")
playsound(src, 'sound/machines/buzz-sigh.ogg', 50)
return
pump()
///pump up that sweet geyser nectar
/obj/machinery/plumbing/liquid_pump/proc/pump()
if(!geyser || !geyser.reagents)
return
geyser.reagents.trans_to(src, pump_power)
/obj/machinery/plumbing/liquid_pump/update_icon_state()
if(geyser)
icon_state = initial(icon_state) + "-on"
else if(panel_open)
icon_state = initial(icon_state) + "-open"
else
icon_state = initial(icon_state)
@@ -0,0 +1,63 @@
///a reaction chamber for plumbing. pretty much everything can react, but this one keeps the reagents seperated and only reacts under your given terms
/obj/machinery/plumbing/reaction_chamber
name = "reaction chamber"
desc = "Keeps chemicals seperated until given conditions are met."
icon_state = "reaction_chamber"
buffer = 200
reagent_flags = TRANSPARENT | NO_REACT
/**list of set reagents that the reaction_chamber allows in, and must all be present before mixing is enabled.
* example: list(/datum/reagent/water = 20, /datum/reagent/fuel/oil = 50)
*/
var/list/required_reagents = list()
///our reagent goal has been reached, so now we lock our inputs and start emptying
var/emptying = FALSE
/obj/machinery/plumbing/reaction_chamber/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/reaction_chamber, bolt)
/obj/machinery/plumbing/reaction_chamber/on_reagent_change()
if(reagents.total_volume == 0 && emptying) //we were emptying, but now we aren't
emptying = FALSE
reagent_flags |= NO_REACT
/obj/machinery/plumbing/reaction_chamber/power_change()
. = ..()
if(use_power != NO_POWER_USE)
icon_state = initial(icon_state) + "_on"
else
icon_state = initial(icon_state)
/obj/machinery/plumbing/reaction_chamber/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ChemReactionChamber", name)
ui.open()
/obj/machinery/plumbing/reaction_chamber/ui_data(mob/user)
var/list/data = list()
var/list/text_reagents = list()
for(var/A in required_reagents) //make a list where the key is text, because that looks alot better in the ui than a typepath
var/datum/reagent/R = A
text_reagents[initial(R.name)] = required_reagents[R]
data["reagents"] = text_reagents
data["emptying"] = emptying
return data
/obj/machinery/plumbing/reaction_chamber/ui_act(action, params)
if(..())
return
. = TRUE
switch(action)
if("remove")
var/reagent = get_chem_id(params["chem"])
if(reagent)
required_reagents.Remove(reagent)
if("add")
var/input_reagent = get_chem_id(params["chem"])
if(input_reagent && !required_reagents.Find(input_reagent))
var/input_amount = text2num(params["amount"])
if(input_amount)
required_reagents[input_reagent] = input_amount
@@ -0,0 +1,50 @@
///it splits the reagents however you want. So you can "every 60 units, 45 goes left and 15 goes straight". The side direction is EAST, you can change this in the component
/obj/machinery/plumbing/splitter
name = "Chemical Splitter"
desc = "A chemical splitter for smart chemical factorization. Waits till a set of conditions is met and then stops all input and splits the buffer evenly or other in two ducts."
icon_state = "splitter"
buffer = 100
density = FALSE
///constantly switches between TRUE and FALSE. TRUE means the batch tick goes straight, FALSE means the next batch goes in the side duct.
var/turn_straight = TRUE
///how much we must transfer straight. note input can be as high as 10 reagents per process, usually
var/transfer_straight = 5
///how much we must transfer to the side
var/transfer_side = 5
//the maximum you can set the transfer to
var/max_transfer = 9
/obj/machinery/plumbing/splitter/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/splitter, bolt)
/obj/machinery/plumbing/splitter/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ChemSplitter", name)
ui.open()
/obj/machinery/plumbing/splitter/ui_data(mob/user)
var/list/data = list()
data["straight"] = transfer_straight
data["side"] = transfer_side
data["max_transfer"] = max_transfer
return data
/obj/machinery/plumbing/splitter/ui_act(action, params)
if(..())
return
. = TRUE
switch(action)
if("set_amount")
var/direction = params["target"]
var/value = clamp(text2num(params["amount"]), 1, max_transfer)
switch(direction)
if("straight")
transfer_straight = value
if("side")
transfer_side = value
else
return FALSE
@@ -0,0 +1,111 @@
///A single machine that produces a single chem. Can be placed in unison with others through plumbing to create chemical factories
/obj/machinery/plumbing/synthesizer
name = "chemical synthesizer"
desc = "Produces a single chemical at a given volume. Must be plumbed. Most effective when working in unison with other chemical synthesizers, heaters and filters."
icon_state = "synthesizer"
icon = 'icons/obj/plumbing/plumbers.dmi'
rcd_cost = 25
rcd_delay = 15
///Amount we produce for every process. Ideally keep under 5 since thats currently the standard duct capacity
var/amount = 1
///The maximum we can produce for every process
buffer = 5
///I track them here because I have no idea how I'd make tgui loop like that
var/static/list/possible_amounts = list(0,1,2,3,4,5)
///The reagent we are producing. We are a typepath, but are also typecast because there's several occations where we need to use initial.
var/datum/reagent/reagent_id = null
///straight up copied from chem dispenser. Being a subtype would be extremely tedious and making it global would restrict potential subtypes using different dispensable_reagents
var/list/dispensable_reagents = list(
/datum/reagent/aluminium,
/datum/reagent/bromine,
/datum/reagent/carbon,
/datum/reagent/chlorine,
/datum/reagent/copper,
/datum/reagent/consumable/ethanol,
/datum/reagent/fluorine,
/datum/reagent/hydrogen,
/datum/reagent/iodine,
/datum/reagent/iron,
/datum/reagent/lithium,
/datum/reagent/mercury,
/datum/reagent/nitrogen,
/datum/reagent/oxygen,
/datum/reagent/phosphorus,
/datum/reagent/potassium,
/datum/reagent/radium,
/datum/reagent/silicon,
/datum/reagent/silver,
/datum/reagent/sodium,
/datum/reagent/stable_plasma,
/datum/reagent/consumable/sugar,
/datum/reagent/sulfur,
/datum/reagent/toxin/acid,
/datum/reagent/water,
/datum/reagent/fuel,
)
/obj/machinery/plumbing/synthesizer/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/simple_supply, bolt)
/obj/machinery/plumbing/synthesizer/process()
if(stat & NOPOWER || !reagent_id || !amount)
return
if(reagents.total_volume >= amount) //otherwise we get leftovers, and we need this to be precise
return
reagents.add_reagent(reagent_id, amount)
/obj/machinery/plumbing/synthesizer/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ChemSynthesizer", name)
ui.open()
/obj/machinery/plumbing/synthesizer/ui_data(mob/user)
var/list/data = list()
var/is_hallucinating = user.hallucinating()
var/list/chemicals = list()
for(var/A in dispensable_reagents)
var/datum/reagent/R = GLOB.chemical_reagents_list[A]
if(R)
var/chemname = R.name
if(is_hallucinating && prob(5))
chemname = "[pick_list_replacements("hallucination.json", "chemicals")]"
chemicals.Add(list(list("title" = chemname, "id" = ckey(R.name))))
data["chemicals"] = chemicals
data["amount"] = amount
data["possible_amounts"] = possible_amounts
data["current_reagent"] = ckey(initial(reagent_id.name))
return data
/obj/machinery/plumbing/synthesizer/ui_act(action, params)
if(..())
return
. = TRUE
switch(action)
if("amount")
var/new_amount = text2num(params["target"])
if(new_amount in possible_amounts)
amount = new_amount
. = TRUE
if("select")
var/new_reagent = GLOB.name2reagent[params["reagent"]]
if(new_reagent in dispensable_reagents)
reagent_id = new_reagent
. = TRUE
update_icon()
reagents.clear_reagents()
/obj/machinery/plumbing/synthesizer/update_overlays()
. = ..()
var/mutable_appearance/r_overlay = mutable_appearance(icon, "[icon_state]_overlay")
if(reagent_id)
r_overlay.color = initial(reagent_id.color)
else
r_overlay.color = "#FFFFFF"
. += r_overlay
@@ -76,7 +76,8 @@
return 1
/obj/item/ammo_box/attackby(obj/item/A, mob/user, params, silent = FALSE, replace_spent = 0)
if(INTERACTING_WITH(user, A))
if(INTERACTING_WITH(user, src) || INTERACTING_WITH(user, A))
to_chat(user, "<span class='notice'>You're already doing that!</span>")
return FALSE
var/num_loaded = 0
if(!can_load(user))
@@ -84,13 +85,16 @@
if(istype(A, /obj/item/ammo_box))
var/obj/item/ammo_box/AM = A
for(var/obj/item/ammo_casing/AC in AM.stored_ammo)
if(load_delay && do_after(user, load_delay, target = src))
var/did_load = give_round(AC, replace_spent)
if(did_load)
AM.stored_ammo -= AC
num_loaded++
if(!did_load || !multiload)
break
if(load_delay || AM.load_delay)
var/loadtime = max(AM.load_delay, load_delay)
if(!do_after(user, loadtime, target = src))
return FALSE
var/did_load = give_round(AC, replace_spent)
if(did_load)
AM.stored_ammo -= AC
num_loaded++
if(!did_load || !multiload)
break
if(istype(A, /obj/item/ammo_casing))
var/obj/item/ammo_casing/AC = A
if(give_round(AC, replace_spent))
@@ -53,6 +53,8 @@
..()
if (istype(A, /obj/item/ammo_box/magazine))
var/obj/item/ammo_box/magazine/AM = A
if(AM.load_delay && !do_after(user, AM.load_delay, target = src))
return FALSE
if (!magazine && istype(AM, mag_type))
if(user.transferItemToLoc(AM, src))
magazine = AM
@@ -1165,3 +1165,9 @@
random_reagents += R
var/picked_reagent = pick(random_reagents)
return picked_reagent
/proc/get_chem_id(chem_name)
for(var/X in GLOB.chemical_reagents_list)
var/datum/reagent/R = GLOB.chemical_reagents_list[X]
if(ckey(chem_name) == ckey(lowertext(R.name)))
return X
@@ -1,5 +1,3 @@
#define PILL_STYLE_COUNT 22 //Update this if you add more pill icons or you die
#define RANDOM_PILL_STYLE 22 //Dont change this one though
/obj/machinery/chem_master
name = "ChemMaster 3000"
@@ -36,6 +36,14 @@
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
reagents.maximum_volume += REAGENTS_BASE_VOLUME * B.rating
/obj/machinery/smoke_machine/ComponentInitialize()
. = ..()
AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
AddComponent(/datum/component/plumbing/simple_demand) //this SURELY CANT' LEAD TO BAD THINGS HAPPENING.
/obj/machinery/smoke_machine/proc/can_be_rotated(mob/user, rotation_type)
return !anchored
/obj/machinery/smoke_machine/update_icon_state()
if((!is_operational()) || (!on) || (reagents.total_volume == 0))
if (panel_open)
+9 -1
View File
@@ -8,6 +8,7 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent())
if (length(initial(R.name)))
.[ckey(initial(R.name))] = t
//Various reagents
//Toxin & acid reagents
//Hydroponics stuff
@@ -52,6 +53,14 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent())
var/metabolizing = FALSE
var/chemical_flags // See fermi/readme.dm REAGENT_DEAD_PROCESS, REAGENT_DONOTSPLIT, REAGENT_ONLYINVERSE, REAGENT_ONMOBMERGE, REAGENT_INVISIBLE, REAGENT_FORCEONNEW, REAGENT_SNEAKYNAME
var/value = REAGENT_VALUE_NONE //How much does it sell for in cargo?
var/datum/material/material //are we made of material?
/datum/reagent/New()
. = ..()
if(material)
material = SSmaterials.GetMaterialRef(material)
/datum/reagent/Destroy() // This should only be called by the holder, so it's already handled clearing its references
. = ..()
@@ -220,4 +229,3 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent())
bloodsuckerdatum.handle_eat_human_food(disgust, blood_puke, force)
if(blood_change)
bloodsuckerdatum.AddBloodVolume(blood_change)
@@ -515,12 +515,13 @@
overdose_threshold = 30
pH = 2
value = REAGENT_VALUE_UNCOMMON
var/healing = 0.5
/datum/reagent/medicine/omnizine/on_mob_life(mob/living/carbon/M)
M.adjustToxLoss(-0.5*REM, 0)
M.adjustOxyLoss(-0.5*REM, 0)
M.adjustBruteLoss(-0.5*REM, 0)
M.adjustFireLoss(-0.5*REM, 0)
M.adjustToxLoss(-healing*REM, 0)
M.adjustOxyLoss(-healing*REM, 0)
M.adjustBruteLoss(-healing*REM, 0)
M.adjustFireLoss(-healing*REM, 0)
..()
. = 1
@@ -532,6 +533,12 @@
..()
. = 1
/datum/reagent/medicine/omnizine/protozine
name = "Protozine"
description = "A less environmentally friendly and somewhat weaker variant of omnizine."
color = "#d8c7b7"
healing = 0.2
/datum/reagent/medicine/calomel
name = "Calomel"
description = "Quickly purges the body of all chemicals. Toxin damage is dealt if the patient is in good condition."
@@ -313,6 +313,13 @@
metabolization_rate = 45 * REAGENTS_METABOLISM
. = 1
/datum/reagent/water/hollowwater
name = "Hollow Water"
description = "An ubiquitous chemical substance that is composed of hydrogen and oxygen, but it looks kinda hollow."
color = "#88878777"
taste_description = "emptyiness"
/datum/reagent/water/holywater
name = "Holy Water"
description = "Water blessed by some deity."
@@ -950,6 +957,7 @@
color = "#1C1300" // rgb: 30, 20, 0
taste_description = "sour chalk"
pH = 5
material = /datum/material/diamond
/datum/reagent/carbon/reaction_turf(turf/T, reac_volume)
if(!isspaceturf(T))
@@ -1072,6 +1080,7 @@
pH = 6
overdose_threshold = 30
color = "#c2391d"
material = /datum/material/iron
/datum/reagent/iron/on_mob_life(mob/living/carbon/C)
if((HAS_TRAIT(C, TRAIT_NOMARROW)))
@@ -1103,6 +1112,7 @@
reagent_state = SOLID
color = "#F7C430" // rgb: 247, 196, 48
taste_description = "expensive metal"
material = /datum/material/gold
/datum/reagent/silver
name = "Silver"
@@ -1110,6 +1120,7 @@
reagent_state = SOLID
color = "#D0D0D0" // rgb: 208, 208, 208
taste_description = "expensive yet reasonable metal"
material = /datum/material/silver
/datum/reagent/silver/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
if(M.has_bane(BANE_SILVER))
@@ -1123,6 +1134,7 @@
color = "#B8B8C0" // rgb: 184, 184, 192
taste_description = "the inside of a reactor"
pH = 4
material = /datum/material/uranium
/datum/reagent/uranium/on_mob_life(mob/living/carbon/M)
M.apply_effect(1/M.metabolism_efficiency,EFFECT_IRRADIATE,0)
@@ -1144,6 +1156,7 @@
taste_description = "fizzling blue"
pH = 12
value = REAGENT_VALUE_RARE
material = /datum/material/bluespace
/datum/reagent/bluespace/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
if(method == TOUCH || method == VAPOR)
@@ -1182,6 +1195,7 @@
color = "#A8A8A8" // rgb: 168, 168, 168
taste_mult = 0
pH = 10
material = /datum/material/glass
/datum/reagent/fuel
name = "Welding fuel"
@@ -2206,6 +2220,66 @@
color = "#f7685e"
metabolization_rate = REAGENTS_METABOLISM * 0.25
/datum/reagent/wittel
name = "Wittel"
description = "An extremely rare metallic-white substance only found on demon-class planets."
color = "#FFFFFF" // rgb: 255, 255, 255
taste_mult = 0 // oderless and tasteless
/datum/reagent/metalgen
name = "Metalgen"
data = list("material"=null)
description = "A purple metal morphic liquid, said to impose it's metallic properties on whatever it touches."
color = "#b000aa"
taste_mult = 0 // oderless and tasteless
var/applied_material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR
var/minumum_material_amount = 100
/datum/reagent/metalgen/reaction_obj(obj/O, volume)
metal_morph(O)
return
/datum/reagent/metalgen/reaction_turf(turf/T, volume)
metal_morph(T)
return
///turn an object into a special material
/datum/reagent/metalgen/proc/metal_morph(atom/A)
var/metal_ref = data["material"]
if(!metal_ref)
return
var/metal_amount = 0
for(var/B in A.custom_materials) //list with what they're made of
metal_amount += A.custom_materials[B]
if(!metal_amount)
metal_amount = minumum_material_amount //some stuff doesn't have materials at all. To still give them properties, we give them a material. Basically doesnt exist
var/list/metal_dat = list()
metal_dat[metal_ref] = metal_amount //if we pass the list directly, byond turns metal_ref into "metal_ref" kjewrg8fwcyvf
A.material_flags = applied_material_flags
A.set_custom_materials(metal_dat)
/datum/reagent/gravitum
name = "Gravitum"
description = "A rare kind of null fluid, capable of temporalily removing all weight of whatever it touches." //i dont even
color = "#050096" // rgb: 5, 0, 150
taste_mult = 0 // oderless and tasteless
metabolization_rate = 0.1 * REAGENTS_METABOLISM //20 times as long, so it's actually viable to use
var/time_multiplier = 1 MINUTES //1 minute per unit of gravitum on objects. Seems overpowered, but the whole thing is very niche
/datum/reagent/gravitum/reaction_obj(obj/O, volume)
O.AddElement(/datum/element/forced_gravity, 0)
addtimer(CALLBACK(O, .proc/_RemoveElement, /datum/element/forced_gravity, 0), volume * time_multiplier)
/datum/reagent/gravitum/on_mob_add(mob/living/L)
L.AddElement(/datum/element/forced_gravity, 0) //0 is the gravity, and in this case weightless
/datum/reagent/gravitum/on_mob_end_metabolize(mob/living/L)
L.RemoveElement(/datum/element/forced_gravity, 0)
//body bluids
/datum/reagent/consumable/semen
@@ -2334,6 +2408,7 @@ datum/reagent/eldritch
color = "#E6E6DA"
taste_mult = 0
/datum/reagent/hairball
name = "Hairball"
description = "A bundle of keratinous bits and fibers, not easily digestible."
@@ -64,6 +64,7 @@
toxpwr = 3
pH = 4
value = REAGENT_VALUE_RARE //sheets are worth more
material = /datum/material/plasma
/datum/reagent/toxin/plasma/on_mob_life(mob/living/carbon/C)
if(holder.has_reagent(/datum/reagent/medicine/epinephrine))
@@ -219,6 +219,12 @@
results = list(/datum/reagent/medicine/strange_reagent = 3)
required_reagents = list(/datum/reagent/medicine/omnizine = 1, /datum/reagent/water/holywater = 1, /datum/reagent/toxin/mutagen = 1)
/datum/chemical_reaction/strange_reagent/alt
name = "Strange Reagent"
id = /datum/reagent/medicine/strange_reagent
results = list(/datum/reagent/medicine/strange_reagent = 2)
required_reagents = list(/datum/reagent/medicine/omnizine/protozine = 1, /datum/reagent/water/holywater = 1, /datum/reagent/toxin/mutagen = 1)
/datum/chemical_reaction/mannitol
name = "Mannitol"
id = /datum/reagent/medicine/mannitol
@@ -345,4 +351,20 @@
/datum/chemical_reaction/medmesh/on_reaction(datum/reagents/holder, created_volume)
var/location = get_turf(holder.my_atom)
for(var/i = 1, i <= created_volume, i++)
new /obj/item/stack/medical/mesh/advanced(location)
new /obj/item/stack/medical/mesh/advanced(location)
/datum/chemical_reaction/suture
required_reagents = list(/datum/reagent/cellulose = 2, /datum/reagent/medicine/styptic_powder = 2)
/datum/chemical_reaction/suture/on_reaction(datum/reagents/holder, created_volume)
var/location = get_turf(holder.my_atom)
for(var/i = 1, i <= created_volume, i++)
new /obj/item/stack/medical/suture/(location)
/datum/chemical_reaction/mesh
required_reagents = list(/datum/reagent/cellulose = 2, /datum/reagent/medicine/silver_sulfadiazine = 2)
/datum/chemical_reaction/mesh/on_reaction(datum/reagents/holder, created_volume)
var/location = get_turf(holder.my_atom)
for(var/i = 1, i <= created_volume, i++)
new /obj/item/stack/medical/mesh/(location)
@@ -1,3 +1,34 @@
/datum/chemical_reaction/metalgen
name = "metalgen"
id = /datum/reagent/metalgen
required_reagents = list(/datum/reagent/wittel = 1, /datum/reagent/bluespace = 1, /datum/reagent/toxin/mutagen = 1)
results = list(/datum/reagent/metalgen = 1)
/datum/chemical_reaction/metalgen_imprint
name = "metalgen imprint"
id = /datum/reagent/metalgen
required_reagents = list(/datum/reagent/metalgen = 1, /datum/reagent/liquid_dark_matter = 1)
results = list(/datum/reagent/metalgen = 1)
/datum/chemical_reaction/holywater
name = "Holy Water"
id = /datum/reagent/water/holywater
results = list(/datum/reagent/water/holywater = 1)
required_reagents = list(/datum/reagent/water/hollowwater = 1)
required_catalysts = list(/datum/reagent/water/holywater = 1)
/datum/chemical_reaction/metalgen_imprint/on_reaction(datum/reagents/holder, created_volume)
var/datum/reagent/metalgen/MM = holder.get_reagent(/datum/reagent/metalgen)
for(var/datum/reagent/R in holder.reagent_list)
if(R.material && R.volume >= 40)
MM.data["material"] = R.material
holder.remove_reagent(R.type, 40)
/datum/chemical_reaction/gravitum
name = "gravitum"
id = /datum/reagent/gravitum
required_reagents = list(/datum/reagent/wittel = 1, /datum/reagent/sorium = 10)
results = list(/datum/reagent/gravitum = 10)
/datum/chemical_reaction/sterilizine
name = "Sterilizine"
@@ -711,7 +742,7 @@
/datum/chemical_reaction/slime_extractification/on_reaction(datum/reagents/holder, created_volume)
var/location = get_turf(holder.my_atom)
new /obj/item/slime_extract/grey(location)
// Liquid Carpets
/datum/chemical_reaction/carpet
+34 -3
View File
@@ -24,7 +24,8 @@
/obj/structure/reagent_dispensers/Initialize()
create_reagents(tank_volume, DRAINABLE | AMOUNT_VISIBLE)
reagents.add_reagent(reagent_id, tank_volume)
if(reagent_id)
reagents.add_reagent(reagent_id, tank_volume)
. = ..()
/obj/structure/reagent_dispensers/proc/boom()
@@ -91,6 +92,38 @@
user.put_in_hands(S)
paper_cups--
/obj/structure/reagent_dispensers/plumbed
name = "stationairy water tank"
anchored = TRUE
icon_state = "water_stationairy"
desc = "A stationairy, plumbed, water tank."
/obj/structure/reagent_dispensers/plumbed/wrench_act(mob/living/user, obj/item/I)
default_unfasten_wrench(user, I)
return TRUE
/obj/structure/reagent_dispensers/plumbed/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
. = ..()
if(. == SUCCESSFUL_UNFASTEN)
user.visible_message("<span class='notice'>[user.name] [anchored ? "fasten" : "unfasten"] [src]</span>", \
"<span class='notice'>You [anchored ? "fasten" : "unfasten"] [src]</span>")
var/datum/component/plumbing/CP = GetComponent(/datum/component/plumbing)
if(anchored)
CP.enable()
else
CP.disable()
/obj/structure/reagent_dispensers/plumbed/ComponentInitialize()
AddComponent(/datum/component/plumbing/simple_supply)
/obj/structure/reagent_dispensers/plumbed/storage
name = "stationairy storage tank"
icon_state = "tank_stationairy"
reagent_id = null //start empty
/obj/structure/reagent_dispensers/plumbed/storage/ComponentInitialize()
AddComponent(/datum/component/plumbing/tank)
//////////////
//Fuel Tanks//
//////////////
@@ -271,5 +304,3 @@
icon_state = "bluekeg"
reagent_id = /datum/reagent/consumable/ethanol/neurotoxin
tank_volume = 100 //2.5x less than the other kegs because it's harder to get
@@ -105,3 +105,11 @@
build_path = /obj/item/circuitboard/machine/bloodbankgen
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
category = list ("Medical Machinery")
/datum/design/board/medipen_refiller
name = "Machine Design (Medipen Refiller)"
desc = "The circuit board for a Medipen Refiller."
id = "medipen_refiller"
build_path = /obj/item/circuitboard/machine/medipen_refiller
category = list ("Medical Machinery")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
@@ -81,6 +81,14 @@
category = list ("Hydroponics Machinery")
departmental_flags = DEPARTMENTAL_FLAG_SERVICE
/datum/design/board/hydroponics/auto
name = "Machine Design (Automatic Hydroponics Tray Board)"
desc = "The circuit board for an automatic hydroponics tray. GIVE ME THE PLANT, CAPTAIN."
id = "autohydrotray"
build_path = /obj/machinery/hydroponics/constructable/automagic
category = list ("Hydroponics Machinery")
departmental_flags = DEPARTMENTAL_FLAG_SERVICE | DEPARTMENTAL_FLAG_MEDICAL
/datum/design/board/monkey_recycler
name = "Machine Design (Monkey Recycler Board)"
desc = "The circuit board for a monkey recycler."
@@ -961,3 +961,158 @@
build_path = /obj/item/bodypart/r_arm/robot/surplus_upgraded
category = list("Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/acclimator
name = "Plumbing Acclimator"
desc = "A heating and cooling device for pipes!"
id = "acclimator"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
construction_time = 15
build_path = /obj/machinery/plumbing/acclimator
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/disposer
name = "Plumbing Disposer"
desc = "Using the power of Science, dissolves reagents into nothing (almost)."
id = "disposer"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 500, /datum/material/glass = 100)
construction_time = 15
build_path = /obj/machinery/plumbing/disposer
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/plumb_filter
name = "Plumbing Filter"
desc = "Filters out chemicals by their NTDB ID."
id = "plumb_filter"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
construction_time = 15
build_path = /obj/machinery/plumbing/filter
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/plumb_synth
name = "Plumbing Synthesizer"
desc = "Using standard mass-energy dynamic autoconverters, generates reagents from power and puts them in a pipe."
id = "plumb_synth"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 5000, /datum/material/glass = 1000, /datum/material/plastic = 1000)
construction_time = 15
build_path = /obj/machinery/plumbing/synthesizer
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/plumb_grinder
name = "Plumbing-Linked Autogrinder"
desc = "Automatically extracts reagents from an item by grinding it. Think of the possibilities! Note: does not grind people."
id = "plumb_grinder"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 2000, /datum/material/glass = 1500)
construction_time = 15
build_path = /obj/machinery/plumbing/grinder_chemical
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/reaction_chamber
name = "Plumbing Reaction Chamber"
desc = "You can set a list of allowed reagents and amounts. Once the chamber has these reagents, will let the products through."
id = "reaction_chamber"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
construction_time = 15
build_path = /obj/machinery/plumbing/reaction_chamber
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/duct_print
name = "Plumbing Ducts"
desc = "Ducts for plumbing! Now lathed for efficiency."
id = "duct_print"
build_type = PROTOLATHE
materials = list(/datum/material/plastic = 400)
construction_time = 1
build_path = /obj/item/stack/ducts
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/plumb_splitter
name = "Plumbing Chemical Splitter"
desc = "A splitter. Has 2 outputs. Can be configured to allow a certain amount through each side."
id = "plumb_splitter"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 750, /datum/material/glass = 250)
construction_time = 15
build_path = /obj/machinery/plumbing/splitter
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/pill_press
name = "Plumbing Automatic Pill Former"
desc = "Automatically forms pills to the required parameters with piped reagents! A good replacement for those lazy, useless chemists."
id = "pill_press"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
construction_time = 15
build_path = /obj/machinery/plumbing/pill_press
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/plumb_pump
name = "Liquid Extraction Pump"
desc = "Use it for extracting liquids from lavaland's geysers!"
id = "plumb_pump"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
construction_time = 15
build_path = /obj/machinery/plumbing/liquid_pump
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/plumb_in
name = "Plumbing Input Device"
desc = "A big piped funnel for putting stuff in the pipe network."
id = "plumb_in"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 400, /datum/material/glass = 400)
construction_time = 15
build_path = /obj/machinery/plumbing/input
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/plumb_out
name = "Plumbing Output Device"
desc = "A big piped funnel for taking stuff out of the pipe network."
id = "plumb_out"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 400, /datum/material/glass = 400)
construction_time = 15
build_path = /obj/machinery/plumbing/output
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/plumb_tank
name = "Plumbed Storage Tank"
desc = "A tank for storing plumbed reagents."
id = "plumb_tank"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 10000, /datum/material/glass = 10000, /datum/material/plastic = 4000)
construction_time = 15
build_path = /obj/machinery/plumbing/tank
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/plumb_rcd
name = "Plumbed Autoconstruction Device"
desc = "A RCD for plumbing machines! Cannot make ducts."
id = "plumb_rcd"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 20000, /datum/material/glass = 10000, /datum/material/plastic = 20000, /datum/material/titanium = 2000, /datum/material/diamond = 800, /datum/material/gold = 2000, /datum/material/silver = 2000)
construction_time = 150
build_path = /obj/item/construction/plumbing
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
@@ -24,6 +24,23 @@
design_ids = list("defib_decay", "defib_shock", "defib_heal", "defib_speed")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
/datum/techweb_node/plumbing
id = "plumbing"
display_name = "Reagent Plumbing Technology"
description = "Plastic tubes, and machinery used for manipulating things in them."
prereq_ids = list("base")
design_ids = list("acclimator", "disposer", "plumb_filter", "plumb_synth", "plumb_grinder", "reaction_chamber", "duct_print", "plumb_splitter", "pill_press", "plumb_pump", "plumb_in", "plumb_out", "plumb_tank", "medipen_refiller")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1000)
/datum/techweb_node/advplumbing
id = "advplumbing"
display_name = "Advanced Plumbing Technology"
description = "Plumbing RCD."
prereq_ids = list("plumbing", "adv_engi")
design_ids = list("plumb_rcd", "autohydrotray")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
//////////////////////Cybernetics/////////////////////
/datum/techweb_node/surplus_limbs
@@ -43,6 +43,7 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
. = ..()
internal_shuttle_creator = new()
internal_shuttle_creator.owner_rsd = src
desc += " Attention, the max size of the shuttle is [SHUTTLE_CREATOR_MAX_SIZE]."
overlay_holder = new()
/obj/item/shuttle_creator/Destroy()
@@ -237,13 +238,13 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
port.register()
icon_state = "rsd_used"
icon_state = "rsd_empty"
//Clear highlights
overlay_holder.clear_highlights()
GLOB.custom_shuttle_count ++
message_admins("[ADMIN_LOOKUPFLW(user)] created a new shuttle with a [src] at [ADMIN_VERBOSEJMP(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
log_game("[key_name(user)] created a new shuttle with a [src] at [AREACOORD(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
message_admins("[ADMIN_LOOKUPFLW(user)] created a new shuttle with a [src] at [ADMIN_VERBOSEJMP(user)] ([GLOB.custom_shuttle_count] custom shuttles)")
log_game("[key_name(user)] created a new shuttle with a [src] at [AREACOORD(user)] ([GLOB.custom_shuttle_count] custom shuttles)")
return TRUE
/obj/item/shuttle_creator/proc/create_shuttle_area(mob/user)
@@ -350,7 +351,7 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
loggedOldArea = get_area(get_turf(user))
loggedTurfs |= turfs
overlay_holder.highlight_area(turfs)
//TODO READD THIS SHIT: icon_state = "rsd_used"
//TODO READD THIS SHIT: icon_state = "rsd_empty"
to_chat(user, "<span class='notice'>You add the area into the buffer of the [src], you made add more areas or select an airlock to act as a docking port to complete the shuttle.</span>")
return turfs
+2 -1
View File
@@ -46,7 +46,8 @@
/obj/item/wrench/medical = 1,
/obj/item/storage/belt/medolier/full = 2,
/obj/item/gun/syringe/dart = 2,
/obj/item/storage/briefcase/medical = 2)
/obj/item/storage/briefcase/medical = 2,
/obj/item/plunger/reinforced = 2)
armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
resistance_flags = FIRE_PROOF
+18
View File
@@ -54,6 +54,24 @@
<h3 class="author">timothyteakettle updated:</h3>
<ul class="changes bgimages16">
<li class="bugfix">two snouts can once again be chosen in customization</li>
<h3 class="author">LetterN updated:</h3>
<ul class="changes bgimages16">
<li class="rscadd">Updates and adds some of the tips</li>
</ul>
<h3 class="author">Putnam3145 updated:</h3>
<ul class="changes bgimages16">
<li class="admin">added reftracking as a compile flag</li>
</ul>
<h3 class="author">SmArtKar updated:</h3>
<ul class="changes bgimages16">
<li class="tweak">RSD limitation is now 500 tiles</li>
<li class="bugfix">Fixed broken RSD sprites</li>
<li class="config">Removed that shuttle limit</li>
</ul>
<h3 class="author">timothyteakettle updated:</h3>
<ul class="changes bgimages16">
<li class="bugfix">two snouts can once again be chosen in customization</li>
<li class="bugfix">lizard snouts work again</li>
</ul>
<h2 class="date">20 August 2020</h2>
+9
View File
@@ -27010,5 +27010,14 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
tiramisuapimancer:
- bugfix: Ethereal hair is now their body color instead of accidentally white
2020-08-21:
LetterN:
- rscadd: Updates and adds some of the tips
Putnam3145:
- admin: added reftracking as a compile flag
SmArtKar:
- tweak: RSD limitation is now 500 tiles
- bugfix: Fixed broken RSD sprites
- config: Removed that shuttle limit
timothyteakettle:
- bugfix: two snouts can once again be chosen in customization
- bugfix: lizard snouts work again
@@ -0,0 +1,5 @@
author: "Time-Green (copypasta'd by lolman360)"
delete-after: True
changes:
- rscadd: "plumbing"
- rscadd: "automatic hydro trays"
Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

+17 -8
View File
@@ -25,7 +25,7 @@ As the Captain, you have absolute access and control over the station, but this
As the Captain, you have a fancy pen that can be used as a holdout dagger or even as a scalpel in surgery!
As the Captain, you can purchase a new emergency shuttle using a communications console. Some require credits, while others give you credits in exchange. Keep in mind that purchasing dangerous shuttles will incur the ire of your crew.
As the Chief Medical Officer, your hypospray is like the ones that your Medical Doctors can buy, except it comes in a fancy box that can hold several more hypovials than the standard, and already comes preloaded with specially-made high-capacity hypovials that hold double the reagents the standard ones do.
As the Chief Medical Officer, coordinate and communicate with your doctors, chemists, and geneticists during a nuclear emergency, blob infestation, or some other crisis to keep people alive and fighting.
As the Chief Medical Officer, coordinate and communicate with your doctors, chemists, and paramedics during a nuclear emergency, blob infestation, or some other crisis to keep people alive and fighting.
As a Medical Doctor, pester Research for improved surgical tools. They work faster, combine the purposes of several tools in one (scalpel/saw, retractor/hemostat, drill/cautery), and don't cost many materials to boot!
As a Medical Doctor, the surgical saw and drill are both powerful weapons, the saw is sharp and can slice and dice, while the drill can quickly blind someone if aimed for the eyes. The laser scalpel is an upgraded version producible with Research's aid, and it has the highest force of most common place weapons, while still remaining sharp.
As a Medical Doctor, your belt can hold a full set of surgical tools. Using sterilizine before each attempt during surgery will reduce your failure chance on tricky steps or when using less-than-optimal equipment.
@@ -34,11 +34,14 @@ As a Medical Doctor, while both heal toxin damage, the difference between charco
As a Medical Doctor, you can surgically implant or extract things from people's chests by performing a cavity implant. This could range from inserting a suicide bomb to embedding the nuke disk into the Captain's chest.
As a Medical Doctor, it's of utmost urgency that you tend to anyone who's been hugged by a facehugger. You only have a couple of minutes from the initial attachment to perform organ manipulation to their chest and remove the rapidly developing alien embryo before it bursts out and immediately kills your patient.
As a Medical Doctor, you must target the correct limb and be on help intent when trying to perform surgery on someone. Using disarm attempt will intentionally fail the surgery step.
As a Medical Doctor, corpses with the "...and their soul has departed" description no longer have a ghost attached to them and aren't usually revivable or cloneable. However it may prove useful to be creative in your revivification techniques with these bodies.
As a Medical Doctor, corpses with the "...and their soul has departed" description no longer have a ghost attached to them and can't be revived. However it may prove useful to be creative in your revivification techniques with these bodies.
As a Medical Doctor, treating plasmamen is not impossible! Salbutamol and epinephrine stops them from suffocating due to lack of internals and showers stop them from burning alive. You can even perform surgery on them by doing the procedure on a roller bed under a shower.
As a Medical Doctor, you can point your penlight at people to create a medical hologram. This lets them know that you're coming to treat them.
As a Medical Doctor, you can extract implants by holding an empty implant case in your offhand while performing the extraction step.
As a Medical Doctor, clone scanning people will implant them with a health tracker that displays their vitals in the clone records. Useful to check on crew members that didn't activate suit sensors!
As a Medical Doctor, you can deal with patients who have absurd amounts of wounds by putting them in cryo. This will slowly treat all of their wounds simultaneously, but is much slower than direct treatment.
As a Medical Doctor, Critical Slash wounds are one of the most dangerous conditions someone can have. Apply gauze, epipens, sutures, cauteries, whatever you can, as soon as possible!
As a Medical Doctor, Saline-Glucose not only acts as a temporary boost to a patient's blood level, it also speeds regeneration! Perfect for drained patients!
As a Medical Doctor, medical gauze is an incredibly underrated tool. It can be used to entirely halt a limb from bleeding or sling one that's been shattered until it can be given proper attention. This even works on the dead, too! Be sure to stop someone's bleeding whether they're in critical condition or a corpse, as dragging someone whom is bleeding will rapidly deplete them of all their blood.
As a Chemist, there are dozens of chemicals that can heal, and even more that can cause harm. See which chemicals have the best synergy, both in healing, and in harming. Experiment!
As a Chemist, some chemicals can only be synthesized by heating up the contents in the chemical heater.
@@ -135,10 +138,10 @@ As a Security Officer, examining someone while wearing your security HUDglasses
As a Security Officer, you can take out the power cell on your baton to replace it with a better or fully charged one. Just use a screwdriver on your baton to remove the old cell.
As a Security Officer, you can just about any firearm on your vest, this even works with other non-standard armor-substitutes like security winter coats!
As the Detective, people leave fingerprints everywhere and on everything. With the exception of white latex, gloves will hide them. All is not lost, however, as gloves leave fibers specific to their kind such as black or nitrile, pointing to a general department.
As the Detective, you can use your forensics scanner from a distance.
As the Detective, you can use your forensics scanner from a distance. Use this to scan boxes or other storage containers.
As the Detective, your revolver can be loaded with .357 ammunition. Use a screwdriver to permanently modify your revolver into using this type of ammunition, be warned however, firing it has a decent chance to cause the revolver to misfire and shoot you in the foot.
As the Lawyer, try to negotiate with the Warden if sentences seem too high for the crime.
As the Lawyer, you can try to convince the captain and Head of Security to hold trials for prisoners in the courtroom.
As the Lawyer, you can try to convince the Captain and Head of Security to hold trials for prisoners in the courtroom.
As the Head of Personnel, you are not higher ranking than other heads of staff, even though you are expected to take the Captain's place first should he go missing. If the situation seems too rough for you, consider allowing another head to become temporary Captain.
As the Head of Personnel, you are just as large a target as the Captain because of the potential power your ID and computer can hand out and your comparative vulnerability.
As the Mime, your invisible wall power blocks people as well as projectiles. You can use it in a pinch to delay your pursuer.
@@ -187,7 +190,7 @@ As a Shaft Miner, every monster on Lavaland has a pattern you can exploit to min
As a Shaft Miner, you can harvest goliath plates from goliaths and upgrade your explorer's suit, mining hardsuits as well as Firefighter APLUs with them, greatly reducing incoming melee damage.
As a Shaft Miner, always have a GPS on you, so a fellow miner or cyborg can come to save you if you die.
As a Shaft Miner, you can craft a variety of equipment from the local fauna. Bone axes, lava boats and ash drake armour are just a few of them!
As a Traitor, the cryptographic sequencer (emag) can not only open doors, but also lockers, crates, APCs and more. It can hack cyborgs, and even cause bots to go berserk. Use it on the right machines, and you may just be able to create some difficult to obtain substances, or contact your employers to request special objectives! Experiment!
As a Traitor, the cryptographic sequencer (emag) can not only open lockers, crates, APCs and more. It can also do things like hack cyborgs, and even cause bots to go berserk. Use it on the right machines, and you can even contact the Syndicate. Experiment!
As a Traitor, subverting the AI to serve you can make it an extremely powerful ally. However, be careful of the wording in the laws you give it, as it may use your poorly written laws against you!
As a Traitor, the Captain and the Head of Security are two of the most difficult to kill targets on the station. If either one is your target, plan carefully.
As a Traitor, you can manufacture and recycle revolver bullets at a hacked autolathe, making the revolver an extremely powerful tool if you manage to nab an autolathe for yourself.
@@ -235,6 +238,7 @@ As a Changeling, absorbing someone will give you their full memory. This can inc
As a Changeling, absorbing another Changeling will permanently boost your chemical reserve, allow you to pick more abilities, and make the victim unable to revive. Be careful when exposing your identity to other Changelings, as they may be out of those wonderful benefits.
As a Changeling, BZ gas will dramatically slow down or even halt your natural chemical regeneration, be sure to avoid it at all costs as some lunatics may try and flood portions of the station to deal with you.
As a Changeling, death is not the end for you! You can revive after two minutes from being dead by triggering your stasis ability, and then waiting for the prompt to resurrect yourself to show up.
As a Changeling, your Regenerate Limbs power will quickly heal all of your wounds, but they'll still leave scars. Changelings can use Fleshmend to get rid of scars, or you can ingest Carpotoxin to get rid of them like a normal person.
As a Cultist, do not cause too much chaos before your objective is completed. If the shuttle gets called too soon, you may not have enough time to win.
As a Cultist, your team starts off very weak, but if necessary can quickly convert everything they have into raw power. Make sure you have the numbers and equipment to support going loud, or the cult will fall flat on its face.
As a Cultist, the Blood Boil rune will deal massive amounts of brute damage to non-cultists, stamina damage to Ratvarian scum, and some damage to fellow cultists of Nar-Sie nearby, but will create a fire where the rune stands on use.
@@ -281,12 +285,17 @@ As a Devil, you gain power for every three souls you control, however you also b
As a Devil, as long as you control at least one other soul, you will automatically resurrect, as long as a banishment ritual is not performed.
At which time a Devil's nameth is spake on the tongue of man, the Devil may appeareth.
You can swap floor tiles by holding a crowbar in one hand and a stack of tiles in the other.
When hacking doors, cutting and mending the "test light wire" will restore power to the door.
When hacking, remote singulars pulse when attached to a wire and pinged. This can allow you to hack things or set traps from far away.
When hacking doors, cutting and mending a "test light wire" will restore power to the door.
When crafting most items, you can either manually combine parts or use the crafting menu.
Suit storage units not only remove blood and dirt from clothing, but also radiation!
Suit storage units entirely purge radiation from any carbon mob put inside of them when cycling, at the cost of some horrific burns, this is a very effective strategy to clean someone up after they bathed in the engine.
Remote devices will work when used through cameras. For example: Bluespace RPEDs and door remotes.
You can light a cigar on a supermatter crystal.
Using sticky tape on items can make them stick to people and walls! Be careful, grenades might stick to your hand during the moment of truth!
In a pinch, stripping yourself naked will give you a sizeable resistance to being tackled. What do you value more, your freedom or your dignity?
Wearing riot armor makes you significantly more effective at performing tackle takedowns, but will use extra stamina with each leap! It will also significantly protect you from other tackles!
Epipens contain a powerful coagulant that drastically reduces bleeding on all bleeding wounds. If you don't have time to properly treat someone with lots of slashes or piercings, stick them with a pen to buy some time!
Anything you can light a cigarette with, you can use to cauterize a bleeding wound. Technically, that includes the supermatter.
Suit storage units entirely purge radiation from any carbon mob put inside of them when cycling, at the cost of some horrific burns, this is a very effective strategy to clean someone up after they bathed in the engine.
Laser pointers can be upgraded by replacing its micro laser with a better one from RnD! Use a screwdriver on it to remove the old laser. Upgrading the laser pointer gives you better odds of stunning a cyborg, and even blinding people with sunglasses.
Being out of combat mode makes makes you deal less damage to people and objects when attacking. This stacks with the penalty incurred by resting.
Resting makes you deal less damage to people and objects when attacking. This stacks with the penalty incurred by being out of combat mode.
+25
View File
@@ -81,6 +81,7 @@
#include "code\__DEFINES\networks.dm"
#include "code\__DEFINES\pinpointers.dm"
#include "code\__DEFINES\pipe_construction.dm"
#include "code\__DEFINES\plumbing.dm"
#include "code\__DEFINES\pool.dm"
#include "code\__DEFINES\power.dm"
#include "code\__DEFINES\preferences.dm"
@@ -301,6 +302,7 @@
#include "code\controllers\subsystem\events.dm"
#include "code\controllers\subsystem\fail2topic.dm"
#include "code\controllers\subsystem\fire_burning.dm"
#include "code\controllers\subsystem\fluid.dm"
#include "code\controllers\subsystem\garbage.dm"
#include "code\controllers\subsystem\holodeck.dm"
#include "code\controllers\subsystem\icon_smooth.dm"
@@ -376,6 +378,7 @@
#include "code\datums\datumvars.dm"
#include "code\datums\dna.dm"
#include "code\datums\dog_fashion.dm"
#include "code\datums\ductnet.dm"
#include "code\datums\emotes.dm"
#include "code\datums\ert.dm"
#include "code\datums\explosion.dm"
@@ -489,6 +492,11 @@
#include "code\datums\components\fantasy\affix.dm"
#include "code\datums\components\fantasy\prefixes.dm"
#include "code\datums\components\fantasy\suffixes.dm"
#include "code\datums\components\plumbing\_plumbing.dm"
#include "code\datums\components\plumbing\chemical_acclimator.dm"
#include "code\datums\components\plumbing\filter.dm"
#include "code\datums\components\plumbing\reaction_chamber.dm"
#include "code\datums\components\plumbing\splitter.dm"
#include "code\datums\components\storage\storage.dm"
#include "code\datums\components\storage\ui.dm"
#include "code\datums\components\storage\concrete\_concrete.dm"
@@ -1282,6 +1290,7 @@
#include "code\game\objects\structures\crates_lockers\crates\secure.dm"
#include "code\game\objects\structures\crates_lockers\crates\wooden.dm"
#include "code\game\objects\structures\icemoon\cave_entrance.dm"
#include "code\game\objects\structures\lavaland\geyser.dm"
#include "code\game\objects\structures\lavaland\necropolis_tendril.dm"
#include "code\game\objects\structures\signs\_signs.dm"
#include "code\game\objects\structures\signs\signs_departments.dm"
@@ -1394,6 +1403,7 @@
#include "code\modules\admin\view_variables\mark_datum.dm"
#include "code\modules\admin\view_variables\mass_edit_variables.dm"
#include "code\modules\admin\view_variables\modify_variables.dm"
#include "code\modules\admin\view_variables\reference_tracking.dm"
#include "code\modules\admin\view_variables\topic.dm"
#include "code\modules\admin\view_variables\topic_basic.dm"
#include "code\modules\admin\view_variables\topic_list.dm"
@@ -2858,6 +2868,21 @@
#include "code\modules\photography\photos\album.dm"
#include "code\modules\photography\photos\frame.dm"
#include "code\modules\photography\photos\photo.dm"
#include "code\modules\plumbing\ducts.dm"
#include "code\modules\plumbing\plumbers\_plumb_machinery.dm"
#include "code\modules\plumbing\plumbers\acclimator.dm"
#include "code\modules\plumbing\plumbers\autohydro.dm"
#include "code\modules\plumbing\plumbers\bottler.dm"
#include "code\modules\plumbing\plumbers\destroyer.dm"
#include "code\modules\plumbing\plumbers\fermenter.dm"
#include "code\modules\plumbing\plumbers\filter.dm"
#include "code\modules\plumbing\plumbers\grinder_chemical.dm"
#include "code\modules\plumbing\plumbers\medipenrefill.dm"
#include "code\modules\plumbing\plumbers\pill_press.dm"
#include "code\modules\plumbing\plumbers\pumps.dm"
#include "code\modules\plumbing\plumbers\reaction_chamber.dm"
#include "code\modules\plumbing\plumbers\splitters.dm"
#include "code\modules\plumbing\plumbers\synthesizer.dm"
#include "code\modules\pool\pool_controller.dm"
#include "code\modules\pool\pool_drain.dm"
#include "code\modules\pool\pool_effects.dm"