diff --git a/code/__defines/hoses.dm b/code/__defines/hoses.dm new file mode 100644 index 0000000000..2a7bd4d803 --- /dev/null +++ b/code/__defines/hoses.dm @@ -0,0 +1,3 @@ +#define HOSE_INPUT "Input"// Only pull liquid. +#define HOSE_OUTPUT "Output"// Only push liquid. +#define HOSE_NEUTRAL "Neutral"// Equalize liquids, not super efficient, can waste reagents due to rounding. \ No newline at end of file diff --git a/code/datums/autolathe/tools.dm b/code/datums/autolathe/tools.dm index d0aa2aa196..9e9a87ea71 100644 --- a/code/datums/autolathe/tools.dm +++ b/code/datums/autolathe/tools.dm @@ -42,3 +42,15 @@ /datum/category_item/autolathe/tools/welder_industrial name = "industrial welding tool" path =/obj/item/weapon/weldingtool/largetank + +/datum/category_item/autolathe/tools/spraybottle + name = "spray bottle" + path = /obj/item/weapon/reagent_containers/spray + resources = list(MAT_PLASTIC = 2000) + +/datum/category_item/autolathe/tools/spraynozzle + name = "spray nozzle" + path = /obj/item/weapon/reagent_containers/spray + resources = list(MAT_PLASTIC = 5000, DEFAULT_WALL_MATERIAL = 2000) + hidden = 1 + man_rating = 2 diff --git a/code/datums/beam.dm b/code/datums/beam.dm index a35abf8c37..fc4e696702 100644 --- a/code/datums/beam.dm +++ b/code/datums/beam.dm @@ -6,6 +6,7 @@ var/icon/base_icon = null var/icon var/icon_state = "" //icon state of the main segments of the beam + var/beam_color = null // Color of the beam segments var/max_distance = 0 var/endtime = 0 var/sleep_time = 3 @@ -15,7 +16,7 @@ var/static_beam = 0 var/beam_type = /obj/effect/ebeam //must be subtype -/datum/beam/New(beam_origin,beam_target,beam_icon='icons/effects/beam.dmi',beam_icon_state="b_beam",time=50,maxdistance=10,btype = /obj/effect/ebeam,beam_sleep_time=3) +/datum/beam/New(beam_origin,beam_target,beam_icon='icons/effects/beam.dmi',beam_icon_state="b_beam",time=50,maxdistance=10,btype = /obj/effect/ebeam,beam_sleep_time=3,new_beam_color = null) endtime = world.time+time origin = beam_origin origin_oldloc = get_turf(origin) @@ -28,6 +29,8 @@ base_icon = new(beam_icon,beam_icon_state) icon = beam_icon icon_state = beam_icon_state + if(new_beam_color) + beam_color = new_beam_color beam_type = btype /datum/beam/proc/Start() @@ -68,9 +71,12 @@ var/matrix/rot_matrix = matrix() rot_matrix.Turn(Angle) + var/turf/T_target = get_turf(target) //Turfs are referenced instead of the objects directly so that beams will link between 2 objects inside other objects. + var/turf/T_origin = get_turf(origin) + //Translation vector for origin and target - var/DX = (32*target.x+target.pixel_x)-(32*origin.x+origin.pixel_x) - var/DY = (32*target.y+target.pixel_y)-(32*origin.y+origin.pixel_y) + var/DX = (32*T_target.x+target.pixel_x)-(32*T_origin.x+origin.pixel_x) + var/DY = (32*T_target.y+target.pixel_y)-(32*T_origin.y+origin.pixel_y) var/N = 0 var/length = round(sqrt((DX)**2+(DY)**2)) //hypotenuse of the triangle formed by target and origin's displacement @@ -78,6 +84,10 @@ if(QDELETED(src) || finished) break var/obj/effect/ebeam/X = new beam_type(origin_oldloc) + + if(beam_color) + X.color = beam_color + X.owner = src elements |= X @@ -184,8 +194,8 @@ -/atom/proc/Beam(atom/BeamTarget,icon_state="b_beam",icon='icons/effects/beam.dmi',time=50, maxdistance=10,beam_type=/obj/effect/ebeam,beam_sleep_time=3) - var/datum/beam/newbeam = new(src,BeamTarget,icon,icon_state,time,maxdistance,beam_type,beam_sleep_time) +/atom/proc/Beam(atom/BeamTarget,icon_state="b_beam",icon='icons/effects/beam.dmi',time=50, maxdistance=10,beam_type=/obj/effect/ebeam,beam_sleep_time=3,beam_color = null) + var/datum/beam/newbeam = new(src,BeamTarget,icon,icon_state,time,maxdistance,beam_type,beam_sleep_time,beam_color) spawn(0) newbeam.Start() return newbeam diff --git a/code/datums/supplypacks/misc.dm b/code/datums/supplypacks/misc.dm index 40b8c33670..72d20b0686 100644 --- a/code/datums/supplypacks/misc.dm +++ b/code/datums/supplypacks/misc.dm @@ -179,3 +179,12 @@ cost = 40 containertype = /obj/structure/closet/crate/zenghu containername = "emergency rations" + +/datum/supply_pack/misc/reagentpump + name = "Machine - Pump" + contains = list( + /obj/machinery/pump = 1 + ) + cost = 60 + containertype = /obj/structure/closet/crate/large/xion + containername = "pump crate" diff --git a/code/game/machinery/reagents/pump.dm b/code/game/machinery/reagents/pump.dm new file mode 100644 index 0000000000..04916d4d9b --- /dev/null +++ b/code/game/machinery/reagents/pump.dm @@ -0,0 +1,270 @@ + +/obj/machinery/pump + name = "fluid pump" + desc = "A fluid pumping machine." + + description_info = "A machine that can pump fluid from certain turfs.
\ + Water can be pumped from any body of water. Certain locations or environmental\ + conditions can cause different byproducts to be produced.
\ + Magma or Lava can be pumped to produce mineralized fluid." + + anchored = 0 + density = 1 + + icon = 'icons/obj/machines/reagent.dmi' + icon_state = "pump" + + circuit = /obj/item/weapon/circuitboard/fluidpump + + var/on = 0 + var/obj/item/weapon/cell/cell = null + var/use = 200 + var/efficiency = 1 + var/reagents_per_cycle = 40 + var/unlocked = 0 + var/open = 0 + + var/obj/item/hose_connector/output/Output + +// Overlay cache vars. + var/icon/liquid + var/icon/tank + var/icon/powerlow + var/icon/glass + var/icon/open_overlay + var/icon/cell_overlay + +/obj/machinery/pump/Initialize() + create_reagents(200) + . = ..() + default_apply_parts() + + if(ispath(cell)) + cell = new cell(src) + + Output = new(src) + + RefreshParts() + update_icon() + +/obj/machinery/pump/update_icon() + ..() + if(!tank) + tank = new/icon(icon, "[icon_state]-volume") + + if(!powerlow) + powerlow = new/icon(icon, "[icon_state]-lowpower") + + if(!liquid) + var/icon/cutter = new/icon(icon, "[icon_state]-volume") + + cutter.Blend(rgb(0, 0, 0,), ICON_MULTIPLY) + + liquid = new/icon(icon, "[icon_state]-cutting") + + liquid.Blend(cutter,ICON_AND) + + if(!glass) + glass = new/icon(icon, "[icon_state]-glass") + + glass.Blend(rgb(1,1,1,0.5), ICON_MULTIPLY) + + if(!open_overlay) + open_overlay = new/icon(icon, "[icon_state]-open") + + if(!cell_overlay) + cell_overlay = new/icon(icon, "[icon_state]-cell") + + cut_overlays() + add_overlay(tank) + + if(cell && cell.charge < (use * CELLRATE / efficiency)) + add_overlay(powerlow) + + if(reagents.total_volume >= 1) + var/list/hextorgb = hex2rgb(reagents.get_color()) + liquid.GrayScale() + + liquid.Blend(rgb(hextorgb[1],hextorgb[2],hextorgb[3]),ICON_MULTIPLY) + + add_overlay(liquid) + + add_overlay(glass) + + if(open) + add_overlay(open_overlay) + + if(cell) + add_overlay(cell_overlay) + + if(on) + icon_state = "[initial(icon_state)]-running" + + else + icon_state = "[initial(icon_state)]" + +/obj/machinery/pump/process() + if(Output.get_pairing()) + reagents.trans_to_holder(Output.reagents, Output.reagents.maximum_volume) + if(prob(5)) + visible_message("\The [src] gurgles as it exports fluid.") + + if(!on) + return + + if(!cell || (cell.charge < (use * CELLRATE / efficiency))) + turn_off(TRUE) + return + + handle_pumping() + update_icon() + +/obj/machinery/pump/RefreshParts() + for(var/obj/item/weapon/stock_parts/manipulator/SM in component_parts) + efficiency = SM.rating + + var/total_bin_rating = 0 + for(var/obj/item/weapon/stock_parts/matter_bin/SB in component_parts) + total_bin_rating += SB.rating + + reagents.maximum_volume = round(initial(reagents.maximum_volume) + 100 * total_bin_rating) + + return + +/obj/machinery/pump/power_change() + if(!cell || cell.charge < (use * CELLRATE) || !anchored) + return turn_off(TRUE) + + return FALSE + + +// Returns 0 on failure and 1 on success +/obj/machinery/pump/proc/turn_on(var/loud = 0) + if(!cell) + return 0 + if(cell.charge < (use * CELLRATE)) + return 0 + + on = 1 + update_icon() + if(loud) + visible_message("\The [src] turns on.") + return 1 + +/obj/machinery/pump/proc/turn_off(var/loud = 0) + on = 0 + set_light(0, 0) + update_icon() + if(loud) + visible_message("\The [src] shuts down.") + + if(!on) + return TRUE + + return FALSE + +/obj/machinery/pump/attack_ai(mob/user as mob) + if(istype(user, /mob/living/silicon/robot) && Adjacent(user)) + return attack_hand(user) + + if(on) + turn_off(TRUE) + else + if(!turn_on(1)) + to_chat(user, "You try to turn on \the [src] but it does not work.") + +/obj/machinery/pump/attack_hand(mob/user as mob) + if(open && cell) + if(ishuman(user)) + if(!user.get_active_hand()) + user.put_in_hands(cell) + cell.loc = user.loc + else + cell.loc = src.loc + + cell.add_fingerprint(user) + cell.update_icon() + + cell = null + on = 0 + set_light(0) + to_chat(user, "You remove the power cell.") + update_icon() + return + + if(on) + turn_off(TRUE) + else if(anchored) + if(!turn_on(1)) + to_chat(user, "You try to turn on \the [src] but it does not work.") + + update_icon() + +/obj/machinery/pump/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(W.is_screwdriver()) + if(!open) + if(unlocked) + unlocked = 0 + to_chat(user, "You screw the battery panel in place.") + else + unlocked = 1 + to_chat(user, "You unscrew the battery panel.") + + else if(W.is_crowbar()) + if(unlocked) + if(open) + open = 0 + overlays = null + to_chat(user, "You crowbar the battery panel in place.") + else + if(unlocked) + open = 1 + to_chat(user, "You remove the battery panel.") + + else if(W.is_wrench()) + if(on) + to_chat(user, "\The [src] is active. Turn it off before trying to move it!") + return + default_unfasten_wrench(user, W, 2 SECONDS) + + else if(istype(W, /obj/item/weapon/cell)) + if(open) + if(cell) + to_chat(user, "There is a power cell already installed.") + else + user.drop_item() + W.loc = src + cell = W + to_chat(user, "You insert the power cell.") + else + ..() + RefreshParts() + update_icon() + +/obj/machinery/pump/proc/handle_pumping() + var/turf/T = get_turf(src) + + if(istype(T, /turf/simulated/floor/water)) + cell.use(use * CELLRATE / efficiency) + reagents.add_reagent("water", reagents_per_cycle) + + if(T.temperature <= T0C) + reagents.add_reagent("ice", round(reagents_per_cycle / 2, 0.1)) + + if((istype(T,/turf/simulated/floor/water/pool) || istype(T,/turf/simulated/floor/water/deep/pool))) + reagents.add_reagent("chlorine", round(reagents_per_cycle / 10 * efficiency, 0.1)) + + else if(istype(T,/turf/simulated/floor/water/contaminated)) + reagents.add_reagent("vatstabilizer", round(reagents_per_cycle / 2)) + + if(T.loc.name == "Sea") // Saltwater. + reagents.add_reagent("sodiumchloride", round(reagents_per_cycle / 10 * efficiency, 0.1)) + + for(var/turf/simulated/mineral/MT in range(5)) + if(MT.mineral) + var/obj/effect/mineral/OR = MT.mineral + reagents.add_reagent(OR.ore_reagent, round(reagents_per_cycle / 20 * efficiency, 0.1)) + + else if(istype(T, /turf/simulated/floor/lava)) + cell.use(use * CELLRATE / efficiency * 4) + reagents.add_reagent("mineralizedfluid", round(reagents_per_cycle * efficiency / 2, 0.1)) diff --git a/code/game/objects/items/weapons/circuitboards/machinery/fluidpump.dm b/code/game/objects/items/weapons/circuitboards/machinery/fluidpump.dm new file mode 100644 index 0000000000..3646d7311a --- /dev/null +++ b/code/game/objects/items/weapons/circuitboards/machinery/fluidpump.dm @@ -0,0 +1,14 @@ + +#ifndef T_BOARD +#error T_BOARD macro is not defined but we need it! +#endif + +/obj/item/weapon/circuitboard/fluidpump + name = T_BOARD("fluid pump") + build_path = /obj/machinery/pump + board_type = new /datum/frame/frame_types/machine + origin_tech = list(TECH_DATA = 1) + req_components = list( + /obj/item/weapon/stock_parts/matter_bin = 2, + /obj/item/weapon/stock_parts/motor = 2, + /obj/item/weapon/stock_parts/manipulator = 1) diff --git a/code/modules/materials/material_recipes.dm b/code/modules/materials/material_recipes.dm index b5d2863f4a..e981c1130c 100644 --- a/code/modules/materials/material_recipes.dm +++ b/code/modules/materials/material_recipes.dm @@ -138,6 +138,7 @@ recipes += new/datum/stack_recipe("lampshade", /obj/item/weapon/lampshade, 1, time = 1, pass_stack_color = TRUE) recipes += new/datum/stack_recipe("plastic net", /obj/item/weapon/material/fishing_net, 25, time = 1 MINUTE, pass_stack_color = TRUE) recipes += new/datum/stack_recipe("plastic fishtank", /obj/item/glass_jar/fish/plastic, 2, time = 30 SECONDS) + recipes += new/datum/stack_recipe("reagent tubing", /obj/item/stack/hose, 1, 4, 20, pass_stack_color = TRUE) /material/wood/generate_recipes() ..() diff --git a/code/modules/mining/mineral_effect.dm b/code/modules/mining/mineral_effect.dm index 5f8daed8d1..12781a7ea4 100644 --- a/code/modules/mining/mineral_effect.dm +++ b/code/modules/mining/mineral_effect.dm @@ -7,11 +7,14 @@ anchored = 1 var/ore_key var/image/scanner_image + var/ore_reagent // Reagent from pumping water near this ore. /obj/effect/mineral/New(var/newloc, var/ore/M) ..(newloc) name = "[M.display_name] deposit" ore_key = M.name + if(M.reagent) + ore_reagent = M.reagent icon_state = "rock_[ore_key]" var/turf/T = get_turf(src) layer = T.layer+0.1 diff --git a/code/modules/mining/ore_datum.dm b/code/modules/mining/ore_datum.dm index f888ae3bb6..0bca24db1f 100644 --- a/code/modules/mining/ore_datum.dm +++ b/code/modules/mining/ore_datum.dm @@ -17,6 +17,7 @@ var/global/list/ore_data = list() "million" = 999 ) var/xarch_source_mineral = "iron" + var/reagent = "silicate" /ore/New() . = ..() @@ -36,6 +37,7 @@ var/global/list/ore_data = list() "million" = 704 ) xarch_source_mineral = "potassium" + reagent = "uranium" /ore/hematite name = "hematite" @@ -46,6 +48,7 @@ var/global/list/ore_data = list() spread_chance = 25 ore = /obj/item/weapon/ore/iron scan_icon = "mineral_common" + reagent = "iron" /ore/coal name = "carbon" @@ -57,6 +60,7 @@ var/global/list/ore_data = list() spread_chance = 25 ore = /obj/item/weapon/ore/coal scan_icon = "mineral_common" + reagent = "carbon" /ore/glass name = "sand" @@ -81,6 +85,7 @@ var/global/list/ore_data = list() "billion_lower" = 10 ) xarch_source_mineral = "phoron" + reagent = "phoron" /ore/silver name = "silver" @@ -90,6 +95,7 @@ var/global/list/ore_data = list() spread_chance = 10 ore = /obj/item/weapon/ore/silver scan_icon = "mineral_uncommon" + reagent = "silver" /ore/gold smelts_to = "gold" @@ -105,6 +111,7 @@ var/global/list/ore_data = list() "billion" = 4, "billion_lower" = 3 ) + reagent = "gold" /ore/diamond name = "diamond" @@ -116,6 +123,7 @@ var/global/list/ore_data = list() ore = /obj/item/weapon/ore/diamond scan_icon = "mineral_rare" xarch_source_mineral = "nitrogen" + reagent = "carbon" /ore/platinum name = "platinum" @@ -127,6 +135,7 @@ var/global/list/ore_data = list() spread_chance = 10 ore = /obj/item/weapon/ore/osmium scan_icon = "mineral_rare" + reagent = "platinum" /ore/hydrogen name = "mhydrogen" @@ -134,6 +143,7 @@ var/global/list/ore_data = list() smelts_to = "tritium" compresses_to = "mhydrogen" scan_icon = "mineral_rare" + reagent = "hydrogen" /ore/verdantium name = MAT_VERDANTIUM @@ -156,6 +166,7 @@ var/global/list/ore_data = list() spread_chance = 10 ore = /obj/item/weapon/ore/marble scan_icon = "mineral_common" + reagent = "calciumcarbonate" /ore/lead name = MAT_LEAD @@ -165,3 +176,4 @@ var/global/list/ore_data = list() spread_chance = 20 ore = /obj/item/weapon/ore/lead scan_icon = "mineral_rare" + reagent = "lead" diff --git a/code/modules/mob/_modifiers/medical.dm b/code/modules/mob/_modifiers/medical.dm index 7e150ceb93..3d17071903 100644 --- a/code/modules/mob/_modifiers/medical.dm +++ b/code/modules/mob/_modifiers/medical.dm @@ -50,3 +50,13 @@ evasion = -5 attack_speed_percent = 1.1 disable_duration_percent = 1.05 + +/datum/modifier/clone_stabilizer + name = "clone stabilized" + desc = "Your body's regeneration is highly restricted." + + on_created_text = "You feel nauseous." + on_expired_text = "You feel healthier." + stacks = MODIFIER_STACK_EXTEND + + incoming_healing_percent = 0.1 diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Modifiers.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Modifiers.dm index cd2e6767b4..bed5da5f47 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Modifiers.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Modifiers.dm @@ -57,3 +57,14 @@ var/turf/simulated/S = T S.freeze_floor() return + +/datum/reagent/modapplying/vatstabilizer + name = "clone growth inhibitor" + id = "vatstabilizer" + description = "A compound produced by NanoTrasen using a secret blend of phoron and toxins to stop the rampant growth of a clone beyond intended states." + taste_description = "sour glue" + color = "#060501" + metabolism = REM * 0.2 + + modifier_to_add = /datum/modifier/clone_stabilizer + modifier_duration = 30 SECONDS diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm index ba7dc93d63..18a281783a 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm @@ -524,6 +524,14 @@ reagent_state = LIQUID color = "#DF9FBF" +/datum/reagent/mineralfluid + name = "Mineral-Rich Fluid" + id = "mineralizedfluid" + description = "A warm, mineral-rich fluid." + taste_description = "salt" + reagent_state = LIQUID + color = "#ff205255" + // The opposite to healing nanites, exists to make unidentified hypos implied to have nanites not be 100% safe. /datum/reagent/defective_nanites name = "Defective Nanites" diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm index e18c2e56d9..74abe0b4bf 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm @@ -120,6 +120,13 @@ spawn(rand(30, 60)) M.IgniteMob() +/datum/reagent/toxin/lead + name = "lead" + id = "lead" + description = "Elemental Lead." + color = "#273956" + strength = 4 + /datum/reagent/toxin/spidertoxin name = "Spidertoxin" id = "spidertoxin" diff --git a/code/modules/reagents/hoses/connector.dm b/code/modules/reagents/hoses/connector.dm new file mode 100644 index 0000000000..bac7fc7c6a --- /dev/null +++ b/code/modules/reagents/hoses/connector.dm @@ -0,0 +1,138 @@ + +/obj/attackby(var/obj/item/O, var/mob/user) + . = ..() + + if(locate(/obj/item/hose_connector) in src) + if(O.is_wirecutter()) + var/list/available_sockets = list() + + for(var/obj/item/hose_connector/HC in src) + if(HC.my_hose) + available_sockets |= HC + + if(LAZYLEN(available_sockets)) + if(available_sockets.len == 1) + var/obj/item/hose_connector/AC = available_sockets[1] + var/choice = alert("Are you sure you want to disconnect [AC]?", "Confirm", "Yes", "No") + + if(choice == "Yes" && Adjacent(user)) + visible_message("[user] disconnects \the hose from \the [src].") + AC.my_hose.disconnect() + return + + else + + var/choice = input("Select a target hose connector.", "Socket Disconnect", null) as null|anything in available_sockets + + if(choice) + var/obj/item/hose_connector/AC = choice + var/confirm = alert("Are you sure you want to disconnect [AC]?", "Confirm", "Yes", "No") + + if(confirm == "Yes" && Adjacent(user)) + visible_message("[user] disconnects \the hose from \the [src].") + AC.my_hose.disconnect() + + return + +/obj/item/hose_connector + name = "hose connector" + desc = "A socket for a hose. It.. doesn't do anything on its own." + + var/obj/carrier = null + + var/flow_direction = HOSE_NEUTRAL + + var/datum/hose/my_hose = null + +/obj/item/hose_connector/Destroy() + if(my_hose) + my_hose.disconnect() + my_hose = null + if(carrier) + carrier = null + ..() + +/obj/item/hose_connector/Initialize() + ..() + + create_reagents(100) + + if(!istype(loc, /turf)) + name = "[flow_direction] hose connector ([loc])" + +/obj/item/hose_connector/proc/valid_connection(var/obj/item/hose_connector/C) + if(istype(C)) + if(C.my_hose) + return FALSE + + if(C.flow_direction in list(HOSE_INPUT, HOSE_OUTPUT) - flow_direction) + return TRUE + + return FALSE + +/obj/item/hose_connector/proc/disconnect() + my_hose = null + +/obj/item/hose_connector/proc/connect(var/datum/hose/H = null) + if(istype(H)) + my_hose = H + +/obj/item/hose_connector/proc/setup_hoses(var/obj/item/hose_connector/target) + if(target) + var/datum/hose/H = new() + + H.set_hose(src, target) + +/obj/item/hose_connector/proc/get_pairing() + if(my_hose) + return my_hose.get_pairing(src) + + return + +/* + * Subtypes + */ + +/obj/item/hose_connector/input + name = "hose input" + flow_direction = HOSE_INPUT + +/obj/item/hose_connector/input/active + name = "active hose" + +/obj/item/hose_connector/input/active/Destroy() + STOP_PROCESSING(SSobj, src) + ..() + +/obj/item/hose_connector/input/active/Initialize() + ..() + START_PROCESSING(SSobj, src) + + if(!isturf(loc)) + carrier = loc + +/obj/item/hose_connector/input/active/process() + if(carrier) + reagents.trans_to_obj(carrier, reagents.maximum_volume) + +/obj/item/hose_connector/output + name = "hose output" + flow_direction = HOSE_OUTPUT + +/obj/item/hose_connector/output/active + name = "active hose" + +/obj/item/hose_connector/output/active/Destroy() + STOP_PROCESSING(SSobj, src) + ..() + +/obj/item/hose_connector/output/active/Initialize() + ..() + START_PROCESSING(SSobj, src) + + if(!isturf(loc)) + carrier = loc + +/obj/item/hose_connector/output/active/process() + if(carrier) + carrier.reagents.trans_to_holder(reagents, reagents.maximum_volume) diff --git a/code/modules/reagents/hoses/hose.dm b/code/modules/reagents/hoses/hose.dm new file mode 100644 index 0000000000..dfe0eade79 --- /dev/null +++ b/code/modules/reagents/hoses/hose.dm @@ -0,0 +1,113 @@ + +GLOBAL_LIST_EMPTY(hoses) + +/obj/effect/ebeam/hose + plane = OBJ_PLANE + layer = STAIRS_LAYER + +/datum/hose + var/name = "hose" + + var/obj/item/hose_connector/node1 = null + var/obj/item/hose_connector/node2 = null + + var/hose_color = "#ffffff" + + var/initial_distance = 7 + + var/datum/beam/hose = null + +/datum/hose/proc/get_pairing(var/obj/item/hose_connector/target) + if(target) + if(target == node1) + return node2 + else if(target == node2) + return node1 + + return + +/datum/hose/proc/disconnect() + if(node1) + node1.disconnect() + node1 = null + if(node2) + node2.disconnect() + node2 = null + +/datum/hose/proc/set_hose(var/obj/item/hose_connector/target1, var/obj/item/hose_connector/target2) + if(target1 && target2) + node1 = target1 + node2 = target2 + + node1.connect(src) + node2.connect(src) + + name = "[name] ([node1],[node2])" + + initial_distance = get_dist(get_turf(node1), get_turf(node2)) + + GLOB.hoses |= src + START_PROCESSING(SSobj, src) + +/datum/hose/process() + if(node1 && node2) + if(get_dist(get_turf(node1), get_turf(node2)) > 0) + hose = node1.loc.Beam(node2.loc, icon_state = "hose", beam_color = hose_color, maxdistance = world.view, beam_type = /obj/effect/ebeam/hose) + + if(!hose || get_dist(get_turf(node1), get_turf(node2)) > initial_distance) // The hose didn't form. Something's fucky. + disconnect() + return + + var/datum/reagents/reagent_node1 = node1.reagents + var/datum/reagents/reagent_node2 = node2.reagents + + switch(node1.flow_direction) // Node 1 is the default 'master', interactions are considered in all current possible states in regards to it, however. + if(HOSE_INPUT) + if(node2.flow_direction == HOSE_NEUTRAL) // We're input, they're neutral. Take half of our volume. + reagent_node2.trans_to_holder(reagent_node1, reagent_node1.maximum_volume / 2) + else if(node2.flow_direction == HOSE_OUTPUT) // We're input, they're output. Take all of our volume. + reagent_node2.trans_to_holder(reagent_node1, reagent_node1.maximum_volume) + + if(HOSE_OUTPUT) // We're output, give all of their maximum volume. + reagent_node1.trans_to_holder(reagent_node2, reagent_node2.maximum_volume) + + if(HOSE_NEUTRAL) + switch(node2.flow_direction) + if(HOSE_INPUT) // We're neutral, they're input. Give them half of their volume. + reagent_node1.trans_to_holder(reagent_node2, reagent_node2.maximum_volume / 2) + + if(HOSE_NEUTRAL) // We're neutral, they're neutral. Balance our values. + var/volume_difference_perc = (reagent_node1.total_volume / reagent_node1.maximum_volume) - (reagent_node2.total_volume / reagent_node2.maximum_volume) + var/volume_difference = 0 + + var/pulling = FALSE + if(volume_difference_perc > 0) // They are smaller, so they determine the transfer amount. Half of the difference will equalize. + volume_difference = reagent_node2.maximum_volume * volume_difference_perc / 2 + + else if(volume_difference_perc < 0) // We're smaller, so we determine the transfer amount. Half of the difference will equalize. + volume_difference_perc *= -1 + + pulling = TRUE + + volume_difference = reagent_node1.maximum_volume * volume_difference_perc / 2 + + if(volume_difference) + if(pulling) + reagent_node2.trans_to_holder(reagent_node1, volume_difference) + else + reagent_node1.trans_to_holder(reagent_node2, volume_difference) + + if(HOSE_OUTPUT) + reagent_node2.trans_to_holder(reagent_node1, reagent_node2.maximum_volume) + + else + if(node1) + node1.disconnect() + node1 = null + if(node2) + node2.disconnect() + node2 = null + + STOP_PROCESSING(SSobj, src) + GLOB.hoses -= src + qdel(src) diff --git a/code/modules/reagents/hoses/hose_connector.dm b/code/modules/reagents/hoses/hose_connector.dm new file mode 100644 index 0000000000..4c442cc712 --- /dev/null +++ b/code/modules/reagents/hoses/hose_connector.dm @@ -0,0 +1,92 @@ + +/obj/item/stack/hose + name = "plastic tubing" + singular_name = "plastic tube" + desc = "A non-reusable plastic tube for moving reagents to and fro. It looks flimsy." + + description_info = "This tubing may be used to join two hose sockets, if able.
\ + Clicking on an object with a connector, such as a water tank, will display a list of possible sockets.
\ + Neutral can link to all socket types, and Input/Output sockets can link to all but their own type.

\ + This hose does not stretch. The maximum distance you can move two objects from eachother\ + without snapping the tube is determined by distance upon connection." + + icon = 'icons/obj/machines/reagent.dmi' + icon_state = "hose" + origin_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 1) + amount = 1 + w_class = ITEMSIZE_SMALL + no_variants = TRUE + + var/obj/item/hose_connector/remembered = null + +/obj/item/stack/hose/Destroy() + remembered = null + ..() + +/obj/item/stack/hose/CtrlClick(mob/user) + if(remembered) + to_chat(user, "You wind \the [src] back up.") + remembered = null + return + +/obj/item/stack/hose/afterattack(var/atom/target, var/mob/living/user, proximity, params) + if(!proximity) + return + + var/list/available_sockets = list() + + for(var/obj/item/hose_connector/HC in target.contents) + if(!HC.my_hose) + if(remembered) + if(HC.flow_direction == HOSE_NEUTRAL || HC.flow_direction != remembered.flow_direction) + available_sockets |= HC + + else + available_sockets |= HC + + if(LAZYLEN(available_sockets)) + if(available_sockets.len == 1) + var/obj/item/hose_connector/AC = available_sockets[1] + if(remembered && remembered.valid_connection(AC)) + var/distancetonode = get_dist(remembered,AC) + if(distancetonode > world.view) + to_chat(user, "\The [src] would probably burst if it were this long.") + else if(distancetonode <= amount) + to_chat(user, "You join \the [remembered] to \the [AC]") + remembered.setup_hoses(AC) + use(distancetonode) + remembered = null + else + to_chat(user, "You do not have enough tubing to connect the sockets.") + + else + remembered = AC + to_chat(user, "You connect one end of tubing to \the [AC].") + + else + var/choice = input("Select a target hose connector.", "Socket Selection", null) as null|anything in available_sockets + + if(choice) + var/obj/item/hose_connector/CC = choice + if(remembered) + if(remembered.valid_connection(CC)) + var/distancetonode = get_dist(remembered, CC) + if(distancetonode > world.view) + to_chat(user, "\The [src] would probably burst if it were this long.") + else if(distancetonode <= amount) + to_chat(user, "You join \the [remembered] to \the [CC]") + remembered.setup_hoses(CC) + use(distancetonode) + remembered = null + + else + to_chat(user, "You do not have enough tubing to connect the sockets.") + + else + remembered = CC + to_chat(user, "You connect one end of tubing to \the [CC].") + + return + + else + ..() diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm index 2a89067e8a..a83c961546 100644 --- a/code/modules/reagents/reagent_containers/spray.dm +++ b/code/modules/reagents/reagent_containers/spray.dm @@ -204,4 +204,97 @@ /obj/item/weapon/reagent_containers/spray/plantbgone/Initialize() . = ..() - reagents.add_reagent("plantbgone", 100) \ No newline at end of file + reagents.add_reagent("plantbgone", 100) + +/obj/item/weapon/reagent_containers/spray/chemsprayer/hosed + name = "hose nozzle" + desc = "A heavy spray nozzle that must be attached to a hose." + icon = 'icons/obj/janitor.dmi' + icon_state = "cleaner-industrial" + item_state = "cleaner" + center_of_mass = list("x" = 16,"y" = 10) + + possible_transfer_amounts = list(5,10,20) + + var/heavy_spray = FALSE + var/spray_particles = 3 + + var/icon/hose_overlay + + var/obj/item/hose_connector/input/active/InputSocket + +/obj/item/weapon/reagent_containers/spray/chemsprayer/hosed/Initialize() + ..() + + InputSocket = new(src) + +/obj/item/weapon/reagent_containers/spray/chemsprayer/hosed/update_icon() + ..() + + overlays.Cut() + + if(!hose_overlay) + hose_overlay = new icon(icon, "[icon_state]+hose") + + if(InputSocket.get_pairing()) + add_overlay(hose_overlay) + +/obj/item/weapon/reagent_containers/spray/chemsprayer/hosed/AltClick(mob/living/carbon/user) + if(++spray_particles > 3) spray_particles = 1 + + to_chat(user, "You turn the dial on \the [src] to [spray_particles].") + return + +/obj/item/weapon/reagent_containers/spray/chemsprayer/hosed/CtrlClick(var/mob/user) + if(loc != get_turf(src)) + heavy_spray = !heavy_spray + else + . = ..() + +/obj/item/weapon/reagent_containers/spray/chemsprayer/hosed/Spray_at(atom/A as mob|obj) + update_icon() + + var/direction = get_dir(src, A) + var/turf/T = get_turf(A) + var/turf/T1 = get_step(T,turn(direction, 90)) + var/turf/T2 = get_step(T,turn(direction, -90)) + var/list/the_targets = list(T, T1, T2) + + if(src.reagents.total_volume < 1) + to_chat(usr, "\The [src] is empty.") + return + + if(!heavy_spray) + for(var/a = 1 to 3) + spawn(0) + if(reagents.total_volume < 1) break + playsound(src, 'sound/effects/spray2.ogg', 50, 1, -6) + var/obj/effect/effect/water/chempuff/D = new/obj/effect/effect/water/chempuff(get_turf(src)) + var/turf/my_target = the_targets[a] + D.create_reagents(amount_per_transfer_from_this) + if(!src) + return + reagents.trans_to_obj(D, amount_per_transfer_from_this) + D.set_color() + D.set_up(my_target, rand(6, 8), 2) + return + + else + playsound(src, 'sound/effects/extinguish.ogg', 75, 1, -3) + + for(var/a = 1 to spray_particles) + spawn(0) + if(!src || !reagents.total_volume) return + + var/obj/effect/effect/water/W = new /obj/effect/effect/water(get_turf(src)) + var/turf/my_target + if(a <= the_targets.len) + my_target = the_targets[a] + else + my_target = pick(the_targets) + W.create_reagents(amount_per_transfer_from_this) + reagents.trans_to_obj(W, amount_per_transfer_from_this) + W.set_color() + W.set_up(my_target) + + return diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index 129af30fe9..ae6ecf8505 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -10,18 +10,33 @@ anchored = 0 pressure_resistance = 2*ONE_ATMOSPHERE + var/obj/item/hose_connector/input/active/InputSocket + var/obj/item/hose_connector/output/active/OutputSocket + var/amount_per_transfer_from_this = 10 var/possible_transfer_amounts = list(10,25,50,100) - attackby(obj/item/weapon/W as obj, mob/user as mob) +/obj/structure/reagent_dispensers/attackby(obj/item/weapon/W as obj, mob/user as mob) return +/obj/structure/reagent_dispensers/Destroy() + QDEL_NULL(InputSocket) + QDEL_NULL(OutputSocket) + + ..() + /obj/structure/reagent_dispensers/Initialize() var/datum/reagents/R = new/datum/reagents(5000) reagents = R R.my_atom = src if (!possible_transfer_amounts) src.verbs -= /obj/structure/reagent_dispensers/verb/set_APTFT + + InputSocket = new(src) + InputSocket.carrier = src + OutputSocket = new(src) + OutputSocket.carrier = src + . = ..() /obj/structure/reagent_dispensers/examine(mob/user) diff --git a/html/changelogs/mechoid - reagenthoses.yml b/html/changelogs/mechoid - reagenthoses.yml new file mode 100644 index 0000000000..6cf9cfe9a9 --- /dev/null +++ b/html/changelogs/mechoid - reagenthoses.yml @@ -0,0 +1,37 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Mechoid + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Reagent pumps added. You can refill water tanks planetside!" + - rscadd: "Reagent hoses added. Only used player-side by tanks, pumps, and spray nozzles." diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi index 4115b01ef2..4d0710885f 100644 Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ diff --git a/icons/obj/janitor.dmi b/icons/obj/janitor.dmi index 607fef4bc7..47974032e0 100644 Binary files a/icons/obj/janitor.dmi and b/icons/obj/janitor.dmi differ diff --git a/icons/obj/machines/reagent.dmi b/icons/obj/machines/reagent.dmi index d11f34270d..7495451380 100644 Binary files a/icons/obj/machines/reagent.dmi and b/icons/obj/machines/reagent.dmi differ diff --git a/polaris.dme b/polaris.dme index 1d4cfa1bbd..98a7ac077c 100644 --- a/polaris.dme +++ b/polaris.dme @@ -38,6 +38,7 @@ #include "code\__defines\flags.dm" #include "code\__defines\gamemode.dm" #include "code\__defines\holomap.dm" +#include "code\__defines\hoses.dm" #include "code\__defines\integrated_circuits.dm" #include "code\__defines\inventory_sizes.dm" #include "code\__defines\is_helpers.dm" @@ -800,6 +801,7 @@ #include "code\game\machinery\pipe\pipe_dispenser.dm" #include "code\game\machinery\pipe\pipe_recipes.dm" #include "code\game\machinery\pipe\pipelayer.dm" +#include "code\game\machinery\reagents\pump.dm" #include "code\game\machinery\telecomms\broadcaster.dm" #include "code\game\machinery\telecomms\logbrowser.dm" #include "code\game\machinery\telecomms\machine_interactions.dm" @@ -1085,6 +1087,7 @@ #include "code\game\objects\items\weapons\circuitboards\machinery\biogenerator.dm" #include "code\game\objects\items\weapons\circuitboards\machinery\cloning.dm" #include "code\game\objects\items\weapons\circuitboards\machinery\engineering.dm" +#include "code\game\objects\items\weapons\circuitboards\machinery\fluidpump.dm" #include "code\game\objects\items\weapons\circuitboards\machinery\jukebox.dm" #include "code\game\objects\items\weapons\circuitboards\machinery\kitchen_appliances.dm" #include "code\game\objects\items\weapons\circuitboards\machinery\mech_recharger.dm" @@ -2718,6 +2721,9 @@ #include "code\modules\reagents\dispenser\supply.dm" #include "code\modules\reagents\distilling\Distilling-Recipes.dm" #include "code\modules\reagents\distilling\distilling.dm" +#include "code\modules\reagents\hoses\connector.dm" +#include "code\modules\reagents\hoses\hose.dm" +#include "code\modules\reagents\hoses\hose_connector.dm" #include "code\modules\reagents\reagent_containers\blood_pack.dm" #include "code\modules\reagents\reagent_containers\borghydro.dm" #include "code\modules\reagents\reagent_containers\dropper.dm"