From 5138fa3453dee906098cc19df3eee8d1ebfbdeeb Mon Sep 17 00:00:00 2001 From: Byemoh Date: Mon, 3 Jun 2024 16:11:59 -0500 Subject: [PATCH 1/2] pee --- code/__DEFINES/atmospherics.dm | 1 + .../signals_atom/signals_atom_x_act.dm | 1 + code/__DEFINES/dcs/signals/signals_turf.dm | 10 + code/__DEFINES/icon_smoothing.dm | 4 + code/__DEFINES/movespeed_modification.dm | 2 + code/__DEFINES/subsystems.dm | 10 + code/__DEFINES/turfs.dm | 2 +- code/__DEFINES/{yogs_defines}/liquids.dm | 86 ++ code/game/area/areas.dm | 2 +- code/game/area/areas/shuttles.dm | 2 +- code/game/machinery/doors/airlock.dm | 2 + code/game/machinery/doors/door.dm | 1 + code/game/turfs/baseturfs.dm | 4 +- code/game/turfs/open/_open.dm | 2 +- code/game/turfs/turf.dm | 1 + code/modules/admin/admin_verbs.dm | 4 +- code/modules/mapping/reader.dm | 2 +- code/modules/mob/living/living.dm | 1 + code/modules/reagents/chemistry/holder.dm | 4 +- code/modules/reagents/chemistry/reagents.dm | 22 +- .../chemistry/reagents/alcohol_reagents.dm | 1 + .../chemistry/reagents/other_reagents.dm | 2 + .../reagents/pyrotechnic_reagents.dm | 1 + goon/icons/turfs/outdoors.dmi | Bin 0 -> 2318 bytes yogstation.dme | 14 + yogstation/code/modules/liquids/drains.dm | 80 ++ .../code/modules/liquids/height_floors.dm | 57 + .../code/modules/liquids/liquid_controller.dm | 233 +++++ .../code/modules/liquids/liquid_effect.dm | 308 ++++++ .../code/modules/liquids/liquid_groups.dm | 983 ++++++++++++++++++ .../code/modules/liquids/liquid_height.dm | 45 + .../modules/liquids/liquid_interaction.dm | 28 + .../code/modules/liquids/liquid_ocean.dm | 332 ++++++ .../code/modules/liquids/liquid_plumbers.dm | 336 ++++++ .../code/modules/liquids/liquid_pump.dm | 101 ++ .../modules/liquids/liquid_status_effect.dm | 46 + .../code/modules/liquids/liquid_turf.dm | 58 ++ yogstation/code/modules/liquids/tools.dm | 69 ++ yogstation/icons/obj/effects/liquid.dmi | Bin 0 -> 11389 bytes .../icons/obj/effects/liquid_overlays.dmi | Bin 0 -> 809 bytes yogstation/icons/obj/effects/splash.dmi | Bin 0 -> 519 bytes yogstation/icons/obj/items/tiles.dmi | Bin 0 -> 502 bytes yogstation/icons/obj/structures/drains.dmi | Bin 0 -> 5414 bytes .../icons/obj/structures/liquid_pump.dmi | Bin 0 -> 929 bytes .../icons/turf/floors/elevated_iron.dmi | Bin 0 -> 417 bytes yogstation/icons/turf/floors/lowered_iron.dmi | Bin 0 -> 352 bytes yogstation/icons/turf/floors/seafloor.dmi | Bin 0 -> 2608 bytes yogstation/sound/effects/splash.ogg | Bin 0 -> 16328 bytes yogstation/sound/effects/water_wade1.ogg | Bin 0 -> 11450 bytes yogstation/sound/effects/water_wade2.ogg | Bin 0 -> 10965 bytes yogstation/sound/effects/water_wade3.ogg | Bin 0 -> 9065 bytes yogstation/sound/effects/water_wade4.ogg | Bin 0 -> 10698 bytes yogstation/sound/effects/watersplash.ogg | Bin 0 -> 9609 bytes 53 files changed, 2845 insertions(+), 12 deletions(-) create mode 100644 code/__DEFINES/{yogs_defines}/liquids.dm create mode 100644 goon/icons/turfs/outdoors.dmi create mode 100644 yogstation/code/modules/liquids/drains.dm create mode 100644 yogstation/code/modules/liquids/height_floors.dm create mode 100644 yogstation/code/modules/liquids/liquid_controller.dm create mode 100644 yogstation/code/modules/liquids/liquid_effect.dm create mode 100644 yogstation/code/modules/liquids/liquid_groups.dm create mode 100644 yogstation/code/modules/liquids/liquid_height.dm create mode 100644 yogstation/code/modules/liquids/liquid_interaction.dm create mode 100644 yogstation/code/modules/liquids/liquid_ocean.dm create mode 100644 yogstation/code/modules/liquids/liquid_plumbers.dm create mode 100644 yogstation/code/modules/liquids/liquid_pump.dm create mode 100644 yogstation/code/modules/liquids/liquid_status_effect.dm create mode 100644 yogstation/code/modules/liquids/liquid_turf.dm create mode 100644 yogstation/code/modules/liquids/tools.dm create mode 100644 yogstation/icons/obj/effects/liquid.dmi create mode 100644 yogstation/icons/obj/effects/liquid_overlays.dmi create mode 100644 yogstation/icons/obj/effects/splash.dmi create mode 100644 yogstation/icons/obj/items/tiles.dmi create mode 100644 yogstation/icons/obj/structures/drains.dmi create mode 100644 yogstation/icons/obj/structures/liquid_pump.dmi create mode 100644 yogstation/icons/turf/floors/elevated_iron.dmi create mode 100644 yogstation/icons/turf/floors/lowered_iron.dmi create mode 100644 yogstation/icons/turf/floors/seafloor.dmi create mode 100644 yogstation/sound/effects/splash.ogg create mode 100644 yogstation/sound/effects/water_wade1.ogg create mode 100644 yogstation/sound/effects/water_wade2.ogg create mode 100644 yogstation/sound/effects/water_wade3.ogg create mode 100644 yogstation/sound/effects/water_wade4.ogg create mode 100644 yogstation/sound/effects/watersplash.ogg diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm index aaa6a71d86d79..90deadaa9ea4d 100644 --- a/code/__DEFINES/atmospherics.dm +++ b/code/__DEFINES/atmospherics.dm @@ -303,6 +303,7 @@ GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0)) #define LAVALAND_DEFAULT_ATMOS "o2=14;n2=23;TEMP=300" #define ICEMOON_DEFAULT_ATMOS "o2=14;n2=23;TEMP=180" #define JUNGLELAND_DEFAULT_ATMOS "o2=44;n2=164;TEMP=300" //yogs edit +#define OCEAN_DEFAULT_ATMOS "o2=10;co2=10;TEMP=293.15" //ATMOSIA GAS MONITOR TAGS #define ATMOS_GAS_MONITOR_INPUT_O2 "o2_in" diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm index 4d3eea4929aae..90e401bdb248b 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm @@ -44,3 +44,4 @@ #define COMSIG_ATOM_SECONDARY_TOOL_ACT(tooltype) "tool_secondary_act_[tooltype]" // We have the same returns here as COMSIG_ATOM_TOOL_ACT // #define COMPONENT_BLOCK_TOOL_ATTACK (1<<0) +#define COMSIG_ATOM_DOOR_OPEN "atom_door_open" diff --git a/code/__DEFINES/dcs/signals/signals_turf.dm b/code/__DEFINES/dcs/signals/signals_turf.dm index a821d243c956a..1f873257c0a66 100644 --- a/code/__DEFINES/dcs/signals/signals_turf.dm +++ b/code/__DEFINES/dcs/signals/signals_turf.dm @@ -47,3 +47,13 @@ #define COMSIG_TURF_IGNITED "turf_ignited" ///Prevents hotspots and turf fires #define SUPPRESS_FIRE (1<<0) + +///called on liquid creation +#define COMSIG_TURF_LIQUIDS_CREATION "turf_liquids_creation" + +#define COMSIG_TURF_MOB_FALL "turf_mob_fall" + +///this is called whenever a turf is destroyed +#define COMSIG_TURF_DESTROY "turf_destroy" +///this is called whenever a turfs air is updated +#define COMSIG_TURF_UPDATE_AIR "turf_air_change" diff --git a/code/__DEFINES/icon_smoothing.dm b/code/__DEFINES/icon_smoothing.dm index 1275dd4b71803..6b5b77537d603 100644 --- a/code/__DEFINES/icon_smoothing.dm +++ b/code/__DEFINES/icon_smoothing.dm @@ -160,6 +160,8 @@ DEFINE_BITFIELD(smoothing_junction, list( #define SMOOTH_GROUP_BAMBOO_WALLS S_TURF(17) //![/turf/closed/wall/mineral/bamboo, /obj/structure/falsewall/bamboo] #define SMOOTH_GROUP_PLASTINUM_WALLS S_TURF(18) //![turf/closed/indestructible/riveted/plastinum] #define SMOOTH_GROUP_CLOCKWORK_WALLS S_TURF(19) //![/turf/closed/wall/clockwork, /obj/structure/falsewall/brass] +#define SMOOTH_GROUP_ELEVATED_PLASTEEL S_TURF(20) +#define SMOOTH_GROUP_LOWERED_PLASTEEL S_TURF(21) #define SMOOTH_GROUP_PAPERFRAME S_OBJ(21) ///obj/structure/window/paperframe, /obj/structure/mineral_door/paperframe @@ -203,6 +205,8 @@ DEFINE_BITFIELD(smoothing_junction, list( #define SMOOTH_GROUP_GAS_TANK S_OBJ(72) +#define SMOOTH_GROUP_WATER S_OBJ(73) ///obj/effect/abstract/liquid_turf + /// Performs the work to set smoothing_groups and canSmoothWith. /// An inlined function used in both turf/Initialize and atom/Initialize. diff --git a/code/__DEFINES/movespeed_modification.dm b/code/__DEFINES/movespeed_modification.dm index e8254f615d5e3..bcc2301850469 100644 --- a/code/__DEFINES/movespeed_modification.dm +++ b/code/__DEFINES/movespeed_modification.dm @@ -83,3 +83,5 @@ #define MOVESPEED_ID_RESIN_FOAM "RESIN_FOAM" #define MOVESPEED_ID_SYNTH_SUSPICION "SYNTH_SUSPICION" + +#define MOVESPEED_ID_LIQUID "LIQUID" diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 678052fbebc13..34efaa265b221 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -332,3 +332,13 @@ //Wardrobe callback master list indexes #define WARDROBE_CALLBACK_INSERT 1 #define WARDROBE_CALLBACK_REMOVE 2 + +///liquid defines +#define SSLIQUIDS_RUN_TYPE_TURFS 1 +#define SSLIQUIDS_RUN_TYPE_GROUPS 2 +#define SSLIQUIDS_RUN_TYPE_IMMUTABLES 3 +#define SSLIQUIDS_RUN_TYPE_EVAPORATION 4 +#define SSLIQUIDS_RUN_TYPE_FIRE 5 +#define SSLIQUIDS_RUN_TYPE_OCEAN 6 +#define SSLIQUIDS_RUN_TYPE_TEMPERATURE 7 +#define SSLIQUIDS_RUN_TYPE_CACHED_EDGES 8 diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm index 7b972abc20cb7..6ff0d233ac0c9 100644 --- a/code/__DEFINES/turfs.dm +++ b/code/__DEFINES/turfs.dm @@ -1,7 +1,7 @@ #define CHANGETURF_DEFER_CHANGE (1<<0) #define CHANGETURF_IGNORE_AIR (1<<1) // This flag prevents changeturf from gathering air from nearby turfs to fill the new turf with an approximation of local air #define CHANGETURF_FORCEOP (1<<2) -#define CHANGETURF_SKIP (1<<3) // A flag for PlaceOnTop to just instance the new turf instead of calling ChangeTurf. Used for uninitialized turfs NOTHING ELSE +#define CHANGETURF_SKIP (1<<3) // A flag for place_on_top to just instance the new turf instead of calling ChangeTurf. Used for uninitialized turfs NOTHING ELSE #define CHANGETURF_INHERIT_AIR (1<<4) // Inherit air from previous turf. Implies CHANGETURF_IGNORE_AIR #define CHANGETURF_RECALC_ADJACENT (1<<5) //Immediately recalc adjacent atmos turfs instead of queuing. #define CHANGETURF_TRAPDOOR_INDUCED (1<<6) // Caused by a trapdoor, for trapdoor to know that this changeturf was caused by itself diff --git a/code/__DEFINES/{yogs_defines}/liquids.dm b/code/__DEFINES/{yogs_defines}/liquids.dm new file mode 100644 index 0000000000000..362543279aec1 --- /dev/null +++ b/code/__DEFINES/{yogs_defines}/liquids.dm @@ -0,0 +1,86 @@ +#define WATER_HEIGH_DIFFERENCE_SOUND_CHANCE 50 +#define WATER_HEIGH_DIFFERENCE_DELTA_SPLASH 7 //Delta needed for the splash effect to be made in 1 go + +#define REQUIRED_MEMBER_PROCESSES 10 + +#define REQUIRED_EVAPORATION_PROCESSES 20 +#define EVAPORATION_CHANCE 50 + +#define REQUIRED_FIRE_PROCESSES 10 +#define REQUIRED_FIRE_POWER_PER_UNIT 5 +#define FIRE_BURN_PERCENT 10 + +#define REQUIRED_OCEAN_PROCESSES 5 + +#define PARTIAL_TRANSFER_AMOUNT 0.3 + +#define LIQUID_MUTUAL_SHARE 1 +#define LIQUID_NOT_MUTUAL_SHARE 2 + +#define LIQUID_GIVER 1 +#define LIQUID_TAKER 2 + +//Required amount of a reagent to be simulated on turf exposures from liquids (to prevent gaming the system with cheap dillutions) +#define LIQUID_REAGENT_THRESHOLD_TURF_EXPOSURE 5 + +//Threshold at which the difference of height makes us need to climb/blocks movement/allows to fall down +#define TURF_HEIGHT_BLOCK_THRESHOLD 20 + +#define LIQUID_HEIGHT_DIVISOR 10 + +#define ONE_LIQUIDS_HEIGHT LIQUID_HEIGHT_DIVISOR + +#define LIQUID_ATTRITION_TO_STOP_ACTIVITY 2 + +//Percieved heat capacity for calculations with atmos sharing +#define REAGENT_HEAT_CAPACITY 5 + +#define LIQUID_STATE_PUDDLE 1 +#define LIQUID_STATE_ANKLES 2 +#define LIQUID_STATE_WAIST 3 +#define LIQUID_STATE_SHOULDERS 4 +#define LIQUID_STATE_FULLTILE 5 +#define TOTAL_LIQUID_STATES 5 +#define LYING_DOWN_SUBMERGEMENT_STATE_BONUS 2 + +#define LIQUID_STATE_FOR_HEAT_EXCHANGERS LIQUID_STATE_WAIST + +#define LIQUID_ANKLES_LEVEL_HEIGHT 8 +#define LIQUID_WAIST_LEVEL_HEIGHT 19 +#define LIQUID_SHOULDERS_LEVEL_HEIGHT 29 +#define LIQUID_FULLTILE_LEVEL_HEIGHT 39 + +#define LIQUID_FIRE_STATE_NONE 0 +#define LIQUID_FIRE_STATE_SMALL 1 +#define LIQUID_FIRE_STATE_MILD 2 +#define LIQUID_FIRE_STATE_MEDIUM 3 +#define LIQUID_FIRE_STATE_HUGE 4 +#define LIQUID_FIRE_STATE_INFERNO 5 + +//Threshold at which we "choke" on the water, instead of holding our breath +#define OXYGEN_DAMAGE_CHOKING_THRESHOLD 15 + +#define IMMUTABLE_LIQUID_SHARE 1 + +#define LIQUID_RECURSIVE_LOOP_SAFETY 255 + +//Height at which we consider the tile "full" and dont drop liquids on it from the upper Z level +#define LIQUID_HEIGHT_CONSIDER_FULL_TILE 50 + +#define LIQUID_GROUP_DECAY_TIME 3 + +//Scaled with how much a person is submerged +#define SUBMERGEMENT_REAGENTS_TOUCH_AMOUNT 60 + +#define CHOKE_REAGENTS_INGEST_ON_FALL_AMOUNT 4 + +#define CHOKE_REAGENTS_INGEST_ON_BREATH_AMOUNT 2 + +#define SUBMERGEMENT_PERCENT(carbon, liquids) min(1,(!MOBILITY_STAND ? liquids.liquid_group.group_overlay_state+LYING_DOWN_SUBMERGEMENT_STATE_BONUS : liquids.liquid_group.group_overlay_state)/TOTAL_LIQUID_STATES) + +#define LIQUID_PROTECTION "liquid_protection" + +GLOBAL_LIST_INIT(liquid_blacklist, list( + /datum/reagent/sorium, + /datum/reagent/liquid_dark_matter +)) diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index b41a91e0ea0b9..c65816553c63f 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -878,7 +878,7 @@ GLOBAL_LIST_EMPTY(teleportlocs) CRASH("Bad op: area/drop_location() called") /// A hook so areas can modify the incoming args (of what??) -/area/proc/PlaceOnTopReact(list/new_baseturfs, turf/fake_turf_type, flags) +/area/proc/place_on_topReact(list/new_baseturfs, turf/fake_turf_type, flags) return flags /// Called when a living mob that spawned here, joining the round, receives the player client. diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm index 37c511734e475..71183599bf9d4 100644 --- a/code/game/area/areas/shuttles.dm +++ b/code/game/area/areas/shuttles.dm @@ -20,7 +20,7 @@ ///list of miners & their mining points from gems to be given once all exports are processed, used by supply shuttles var/list/gem_payout = list() -/area/shuttle/PlaceOnTopReact(list/new_baseturfs, turf/fake_turf_type, flags) +/area/shuttle/place_on_topReact(list/new_baseturfs, turf/fake_turf_type, flags) . = ..() if(length(new_baseturfs) > 1 || fake_turf_type) return // More complicated larger changes indicate this isn't a player diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 444e9d64a047e..27c472b2b0e64 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -1373,6 +1373,7 @@ locked = !locked if(welded) welded = !welded + SEND_SIGNAL(src, COMSIG_AIRLOCK_OPEN, forced) operating = TRUE update_icon(state = AIRLOCK_OPENING, override = TRUE) sleep(0.1 SECONDS) @@ -1389,6 +1390,7 @@ if(delayed_close_requested) delayed_close_requested = FALSE addtimer(CALLBACK(src, PROC_REF(close)), 1) + SEND_SIGNAL(src, COMSIG_ATOM_DOOR_OPEN) /// this is different because we need one that covers all doors return TRUE diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 2993c9b8d9e26..e13a009025dce 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -379,6 +379,7 @@ operating = FALSE air_update_turf() update_freelook_sight() + SEND_SIGNAL(src, COMSIG_ATOM_DOOR_OPEN) if(autoclose) spawn(autoclose) close() diff --git a/code/game/turfs/baseturfs.dm b/code/game/turfs/baseturfs.dm index 6a0fdfd04be50..d5a85213fb4a2 100644 --- a/code/game/turfs/baseturfs.dm +++ b/code/game/turfs/baseturfs.dm @@ -40,11 +40,11 @@ /// Places a turf on top - for map loading /turf/proc/load_on_top(turf/added_layer, flags) var/area/our_area = get_area(src) - flags = our_area.PlaceOnTopReact(list(baseturfs), added_layer, flags) + flags = our_area.place_on_topReact(list(baseturfs), added_layer, flags) if(flags & CHANGETURF_SKIP) // We haven't been initialized if(flags_1 & INITIALIZED_1) - stack_trace("CHANGETURF_SKIP was used in a PlaceOnTop call for a turf that's initialized. This is a mistake. [src]([type])") + stack_trace("CHANGETURF_SKIP was used in a place_on_top call for a turf that's initialized. This is a mistake. [src]([type])") assemble_baseturfs() var/turf/new_turf diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm index 8cd4221d82284..5a056c3ba6323 100644 --- a/code/game/turfs/open/_open.dm +++ b/code/game/turfs/open/_open.dm @@ -93,7 +93,7 @@ * This replaces the current turf if it is plating and is passed plating, is tile and is passed tile. * It places the new turf on top of itself if it is plating and is passed a tile. * It also replaces the turf if it is tile and is passed plating, essentially destroying the over turf. - * Flags argument is passed directly to ChangeTurf or PlaceOnTop + * Flags argument is passed directly to ChangeTurf or place_on_top */ /turf/open/proc/replace_floor(turf/open/new_floor_path, flags) if (!overfloor_placed && initial(new_floor_path.overfloor_placed)) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index a195e07f1a49e..fac9984035c48 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -215,6 +215,7 @@ GLOBAL_LIST_EMPTY(station_turfs) if(length(vis_contents)) vis_contents.Cut() + SEND_SIGNAL(src, COMSIG_TURF_DESTROY) /// WARNING WARNING /// Turfs DO NOT lose their signals when they get replaced, REMEMBER THIS diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 21ade81d0b85d..35298b6d1fe7a 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -132,7 +132,9 @@ GLOBAL_LIST_INIT(admin_verbs_fun, list( /client/proc/admin_away, /client/proc/centcom_podlauncher,/*Open a window to launch a Supplypod and configure it or it's contents*/ /client/proc/load_json_admin_event, - /client/proc/event_role_manager + /client/proc/event_role_manager, + /client/proc/spawn_liquid, + /client/proc/remove_liquid )) GLOBAL_PROTECT(admin_verbs_fun) GLOBAL_LIST_INIT(admin_verbs_spawn, list(/datum/admins/proc/spawn_atom, /datum/admins/proc/podspawn_atom, /datum/admins/proc/spawn_cargo, /datum/admins/proc/spawn_objasmob, /client/proc/respawn_character, /datum/admins/proc/beaker_panel)) diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm index cf3d2c8e56a81..56f6c8252d62c 100644 --- a/code/modules/mapping/reader.dm +++ b/code/modules/mapping/reader.dm @@ -149,7 +149,7 @@ * - y_upper: The maximum y coordinate to load * - z_lower: The minimum z coordinate to load * - z_upper: The maximum z coordinate to load - * - place_on_top: Whether to use /turf/proc/PlaceOnTop rather than /turf/proc/ChangeTurf + * - place_on_top: Whether to use /turf/proc/place_on_top rather than /turf/proc/ChangeTurf * - new_z: If true, a new z level will be created for the map */ /proc/load_map( diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 8370917a5bc80..e40145831bae2 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -42,6 +42,7 @@ return ..() /mob/living/proc/ZImpactDamage(turf/T, levels) + SEND_SIGNAL(T, COMSIG_TURF_MOB_FALL, src) visible_message(span_danger("[src] crashes into [T] with a sickening noise!")) adjustBruteLoss((levels * 5) ** 1.5) Knockdown(levels * 50) diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index 47f0fcd071995..f5c2ee20b1974 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -742,10 +742,10 @@ return TRUE /// Like add_reagent but you can enter a list. Format it like this: list(/datum/reagent/toxin = 10, "beer" = 15) -/datum/reagents/proc/add_reagent_list(list/list_reagents, list/data=null) +/datum/reagents/proc/add_reagent_list(list/list_reagents, list/data=null, _no_react = FALSE) for(var/r_id in list_reagents) var/amt = list_reagents[r_id] - add_reagent(r_id, amt, data) + add_reagent(r_id, amt, data, no_react = _no_react) /// Remove a specific reagent /datum/reagents/proc/remove_reagent(reagent, amount, safety)//Added a safety check for the trans_id_to diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm index 5f08504149147..07f6a456d185c 100644 --- a/code/modules/reagents/chemistry/reagents.dm +++ b/code/modules/reagents/chemistry/reagents.dm @@ -76,8 +76,23 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) var/harmful = FALSE /// The default reagent container for the reagent. Currently only used for crafting icon/displays. var/obj/item/reagent_containers/default_container = /obj/item/reagent_containers/glass/bottle - - /// Are we from a material? We might wanna know that for special stuff. Like metalgen. Is replaced with a ref of the material on New() + + ///Whether it will evaporate if left untouched on a liquids simulated puddle + var/evaporates = TRUE + ///How much fire power does the liquid have, for burning on simulated liquids. Not enough fire power/unit of entire mixture may result in no fire + var/liquid_fire_power = 0 + ///How fast does the liquid burn on simulated turfs, if it does + var/liquid_fire_burnrate = 0 + ///Whether a fire from this requires oxygen in the atmosphere + var/fire_needs_oxygen = TRUE + ///The opacity of the chems used to determine the alpha of liquid turfs + var/opacity = 175 + ///The rate of evaporation in units per call + var/evaporation_rate = 1 + /// do we have a turf exposure (used to prevent liquids doing un-needed processes) + var/turf_exposure = FALSE + /// are we slippery? + var/slippery = TRUE /datum/reagent/Destroy() // This should only be called by the holder, so it's already handled clearing its references . = ..() @@ -149,6 +164,9 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) /datum/reagent/proc/on_ex_act(severity) return +/datum/reagent/proc/evaporate(turf/exposed_turf, reac_volume) + return + /// Called if the reagent has passed the overdose threshold and is set to be triggering overdose effects /datum/reagent/proc/overdose_process(mob/living/M) return diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm index 6796f233468aa..3fb8d0cd5876f 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm @@ -16,6 +16,7 @@ metabolization_rate = 0.5 * REAGENTS_METABOLISM var/boozepwr = 65 //Higher numbers equal higher hardness, higher hardness equals more intense alcohol poisoning accelerant_quality = 5 + liquid_fire_power = 10 /* Boozepwr Chart Note that all higher effects of alcohol poisoning will inherit effects for smaller amounts (i.e. light poisoning inherts from slight poisoning) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 744dbf17e4120..d2b53b5d7aac4 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -1153,6 +1153,7 @@ glass_desc = "Dr. Gibb. Not as dangerous as the glass_name might imply." accelerant_quality = 10 compatible_biotypes = ALL_BIOTYPES + liquid_fire_power = 25 /datum/reagent/fuel/reaction_mob(mob/living/M, methods=TOUCH, reac_volume)//Splashing people with welding fuel to make them easy to ignite! if(methods & (TOUCH|VAPOR)) @@ -1747,6 +1748,7 @@ color = "#C8A5DC" taste_description = "oil" compatible_biotypes = ALL_BIOTYPES + liquid_fire_power = 15 /datum/reagent/oil/on_mob_life(mob/living/carbon/M) M.adjustFireLoss(-2*REM, FALSE, FALSE, BODYPART_ROBOTIC) diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index c1d14964b88e4..0ec385fe130ac 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -164,6 +164,7 @@ self_consuming = TRUE accelerant_quality = 20 compatible_biotypes = ALL_BIOTYPES + liquid_fire_power = 1 /datum/reagent/napalm/on_mob_life(mob/living/carbon/M) M.adjust_fire_stacks(1) diff --git a/goon/icons/turfs/outdoors.dmi b/goon/icons/turfs/outdoors.dmi new file mode 100644 index 0000000000000000000000000000000000000000..e8fa45135f1df8c760f4ae00f782ee9ff8344e1b GIT binary patch literal 2318 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 literal 0 HcmV?d00001 diff --git a/yogstation.dme b/yogstation.dme index 5e5f401d1c98d..d446118b40363 100644 --- a/yogstation.dme +++ b/yogstation.dme @@ -232,6 +232,7 @@ #include "code\__DEFINES\{yogs_defines}\is_helpers.dm" #include "code\__DEFINES\{yogs_defines}\jungle.dm" #include "code\__DEFINES\{yogs_defines}\layers.dm" +#include "code\__DEFINES\{yogs_defines}\liquids.dm" #include "code\__DEFINES\{yogs_defines}\logging.dm" #include "code\__DEFINES\{yogs_defines}\mapping.dm" #include "code\__DEFINES\{yogs_defines}\maps.dm" @@ -4376,6 +4377,19 @@ #include "yogstation\code\modules\jungleland\kinetic_javelin.dm" #include "yogstation\code\modules\language\darkspeak.dm" #include "yogstation\code\modules\language\japanese.dm" +#include "yogstation\code\modules\liquids\drains.dm" +#include "yogstation\code\modules\liquids\height_floors.dm" +#include "yogstation\code\modules\liquids\liquid_controller.dm" +#include "yogstation\code\modules\liquids\liquid_effect.dm" +#include "yogstation\code\modules\liquids\liquid_groups.dm" +#include "yogstation\code\modules\liquids\liquid_height.dm" +#include "yogstation\code\modules\liquids\liquid_interaction.dm" +#include "yogstation\code\modules\liquids\liquid_ocean.dm" +#include "yogstation\code\modules\liquids\liquid_plumbers.dm" +#include "yogstation\code\modules\liquids\liquid_pump.dm" +#include "yogstation\code\modules\liquids\liquid_status_effect.dm" +#include "yogstation\code\modules\liquids\liquid_turf.dm" +#include "yogstation\code\modules\liquids\tools.dm" #include "yogstation\code\modules\mentor\follow.dm" #include "yogstation\code\modules\mentor\mentor.dm" #include "yogstation\code\modules\mentor\mentor_memo.dm" diff --git a/yogstation/code/modules/liquids/drains.dm b/yogstation/code/modules/liquids/drains.dm new file mode 100644 index 0000000000000..563fec37264e2 --- /dev/null +++ b/yogstation/code/modules/liquids/drains.dm @@ -0,0 +1,80 @@ +//Structure as this doesn't need any power to work +/obj/structure/drain + name = "drain" + icon = 'yogstation/icons/obj/structures/drains.dmi' + icon_state = "drain" + desc = "Drainage inlet embedded in the floor to prevent flooding." + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + density = FALSE + plane = FLOOR_PLANE + layer = GAS_SCRUBBER_LAYER + anchored = TRUE + var/processing = FALSE + var/drain_flat = 5 + var/drain_percent = 0.1 + var/welded = FALSE + var/turf/my_turf //need to keep track of it for the signal, if in any bizarre cases something would be moving the drain + +/obj/structure/drain/update_icon() + . = ..() + if(welded) + icon_state = "[initial(icon_state)]_welded" + else + icon_state = "[initial(icon_state)]" + +/obj/structure/drain/welder_act(mob/living/user, obj/item/I) + ..() + if(!I.tool_start_check(user, amount=0)) + return TRUE + + playsound(src, 'sound/items/welder2.ogg', 50, TRUE) + to_chat(user, span_notice("You start [welded ? "unwelding" : "welding"] [src]...")) + if(I.use_tool(src, user, 20)) + to_chat(user, span_notice("You [welded ? "unweld" : "weld"] [src].")) + welded = !welded + update_icon() + if(welded) + if(processing) + STOP_PROCESSING(SSobj, src) + processing = FALSE + else if (my_turf.liquids) + START_PROCESSING(SSobj, src) + processing = TRUE + return TRUE + +/obj/structure/drain/process() + if(!my_turf.liquids) + STOP_PROCESSING(SSobj, src) + processing = FALSE + return + my_turf.liquids.liquid_group.remove_any(my_turf.liquids, drain_flat + (drain_percent * my_turf.liquids.liquid_group.total_reagent_volume)) + +/obj/structure/drain/Initialize() + . = ..() + if(!isturf(loc)) + stack_trace("Drain structure initialized not on a turf") + my_turf = loc + RegisterSignal(my_turf, COMSIG_TURF_LIQUIDS_CREATION, PROC_REF(liquids_signal)) + if(my_turf.liquids) + START_PROCESSING(SSobj, src) + processing = TRUE + +/obj/structure/drain/proc/liquids_signal() + SIGNAL_HANDLER + if(processing || welded) + return + START_PROCESSING(SSobj, src) + processing = TRUE + +/obj/structure/drain/Destroy() + if(processing) + STOP_PROCESSING(SSobj, src) + UnregisterSignal(my_turf, COMSIG_TURF_LIQUIDS_CREATION) + my_turf = null + return ..() + +/obj/structure/drain/big + desc = "Drainage inlet embedded in the floor to prevent flooding. This one seems large." + icon_state = "bigdrain" + drain_percent = 0.3 + drain_flat = 15 diff --git a/yogstation/code/modules/liquids/height_floors.dm b/yogstation/code/modules/liquids/height_floors.dm new file mode 100644 index 0000000000000..61b677892d8da --- /dev/null +++ b/yogstation/code/modules/liquids/height_floors.dm @@ -0,0 +1,57 @@ +/obj/item/stack/tile/elevated + name = "elevated floor tile" + singular_name = "elevated floor tile" + turf_type = /turf/open/floor/elevated + merge_type = /obj/item/stack/tile/elevated + icon = 'yogstation/icons/obj/items/tiles.dmi' + icon_state = "elevated" + +/obj/item/stack/tile/lowered + name = "lowered floor tile" + singular_name = "lowered floor tile" + turf_type = /turf/open/floor/lowered + merge_type = /obj/item/stack/tile/lowered + icon = 'yogstation/icons/obj/items/tiles.dmi' + icon_state = "lowered" + +/obj/item/stack/tile/lowered/iron + name = "lowered floor tile" + singular_name = "lowered floor tile" + turf_type = /turf/open/floor/lowered + merge_type = /obj/item/stack/tile/lowered + icon = 'yogstation/icons/obj/items/tiles.dmi' + icon_state = "lowered" + +/turf/open/floor/iron/pool/rust_heretic_act() + return + +/turf/open/floor/elevated + name = "elevated floor" + floor_tile = /obj/item/stack/tile/elevated + icon = 'yogstation/icons/turf/floors/elevated_iron.dmi' + icon_state = "elevated_plasteel-0" + base_icon_state = "elevated_plasteel-0" + smoothing_flags = SMOOTH_CORNERS + smoothing_groups = SMOOTH_GROUP_ELEVATED_PLASTEEL + canSmoothWith = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_ELEVATED_PLASTEEL + liquid_height = 30 + turf_height = 30 + +/turf/open/floor/elevated/rust_heretic_act() + return + +/turf/open/floor/lowered + name = "lowered floor" + floor_tile = /obj/item/stack/tile/lowered + icon = 'yogstation/icons/turf/floors/lowered_iron.dmi' + icon_state = "lowered_plasteel-0" + base_icon_state = "lowered_plasteel-0" + smoothing_flags = SMOOTH_CORNERS + smoothing_groups = SMOOTH_GROUP_LOWERED_PLASTEEL + canSmoothWith = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_LOWERED_PLASTEEL + liquid_height = -30 + turf_height = -30 + + +/turf/open/floor/lowered/rust_heretic_act() + return diff --git a/yogstation/code/modules/liquids/liquid_controller.dm b/yogstation/code/modules/liquids/liquid_controller.dm new file mode 100644 index 0000000000000..1b21460dcd9be --- /dev/null +++ b/yogstation/code/modules/liquids/liquid_controller.dm @@ -0,0 +1,233 @@ +SUBSYSTEM_DEF(liquids) + name = "Liquid Turfs" + wait = 0.5 SECONDS + flags = SS_KEEP_TIMING | SS_NO_INIT + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + var/list/active_groups = list() + + var/list/evaporation_queue = list() + var/evaporation_counter = 0 //Only process evaporation on intervals + + var/list/temperature_queue = list() + + var/list/active_ocean_turfs = list() + var/list/ocean_turfs = list() + var/list/currentrun_active_ocean_turfs = list() + var/list/unvalidated_oceans = list() + var/ocean_counter = 0 + + var/run_type = SSLIQUIDS_RUN_TYPE_GROUPS + + ///debug variable to toggle evaporation from running + var/debug_evaporation = FALSE + + var/list/burning_turfs = list() + var/fire_counter = 0 + + var/member_counter = 0 + + var/list/arrayed_groups = list() + + ///list of groups to work on for cached edges + var/list/cached_edge_work_queue = list() + ///list of groups we are going to work on in group process + var/list/group_process_work_queue = list() + ///list of all work queue for turf processing + var/list/active_turf_group_queue = list() + + +/datum/controller/subsystem/liquids/stat_entry(msg) + msg += "AG:[length(active_groups)]|BT:[length(burning_turfs)]|EQ:[length(evaporation_queue)]|AO:[length(active_ocean_turfs)]|UO:[length(unvalidated_oceans)]" + return ..() + +/datum/controller/subsystem/liquids/fire(resumed) + if(!length(active_groups) && !length(evaporation_queue) && !length(active_ocean_turfs) && !length(burning_turfs) && !length(unvalidated_oceans)) + return + + listclearnulls(active_groups) + + if(length(unvalidated_oceans)) + for(var/turf/open/floor/plating/ocean/unvalidated_turf in unvalidated_oceans) + if(MC_TICK_CHECK) + return + unvalidated_turf.assume_self() + + if(length(arrayed_groups)) + listclearnulls(arrayed_groups) + for(var/datum/liquid_group/liquid_group as anything in arrayed_groups) + if(QDELETED(liquid_group)) + arrayed_groups -= liquid_group + continue + while(!MC_TICK_CHECK && length(liquid_group?.splitting_array)) // three at a time until we either finish or over-run, this should be done before anything else + liquid_group.work_on_split_queue() + liquid_group.cleanse_members() + + if(!length(temperature_queue)) + for(var/datum/liquid_group/liquid_group as anything in active_groups) + if(MC_TICK_CHECK) + return + if(QDELETED(liquid_group)) + temperature_queue -= active_groups + continue + var/list/turfs = liquid_group.fetch_temperature_queue() + temperature_queue += turfs + + if(run_type == SSLIQUIDS_RUN_TYPE_GROUPS) + if(!length(group_process_work_queue)) + group_process_work_queue |= active_groups + listclearnulls(group_process_work_queue) + if(length(group_process_work_queue)) + var/populate_evaporation = FALSE + if(!length(evaporation_queue)) + populate_evaporation = TRUE + for(var/datum/liquid_group/liquid_group as anything in group_process_work_queue) + if(MC_TICK_CHECK) + return + if(QDELETED(liquid_group)) + group_process_work_queue -= liquid_group + continue + liquid_group.process_group(TRUE) + if(populate_evaporation && (liquid_group.expected_turf_height < LIQUID_STATE_ANKLES) && liquid_group.evaporates) + for(var/turf/listed_turf as anything in liquid_group.members) + if(QDELETED(listed_turf)) + continue + evaporation_queue |= listed_turf + group_process_work_queue -= liquid_group + + + run_type = SSLIQUIDS_RUN_TYPE_TEMPERATURE + + if(run_type == SSLIQUIDS_RUN_TYPE_TEMPERATURE) + listclearnulls(temperature_queue) + if(length(temperature_queue)) + for(var/turf/open/temperature_turf as anything in temperature_queue) + if(MC_TICK_CHECK) + return + temperature_queue -= temperature_turf + if(QDELETED(temperature_turf.liquids)) + continue + if(QDELETED(temperature_turf.liquids.liquid_group)) + QDEL_NULL(temperature_turf.liquids) + continue + temperature_turf.liquids.liquid_group.act_on_queue(temperature_turf) + run_type = SSLIQUIDS_RUN_TYPE_EVAPORATION + + if(run_type == SSLIQUIDS_RUN_TYPE_EVAPORATION && !debug_evaporation) + listclearnulls(evaporation_queue) + evaporation_counter++ + if(evaporation_counter >= REQUIRED_EVAPORATION_PROCESSES) + evaporation_counter = 0 + for(var/datum/liquid_group/liquid_group as anything in active_groups) + if(MC_TICK_CHECK) + return + if(QDELETED(liquid_group)) + active_groups -= liquid_group + continue + liquid_group.check_dead() + if(!length(liquid_group?.splitting_array)) + liquid_group.process_turf_disperse() + for(var/turf/liquid_turf as anything in evaporation_queue) + if(MC_TICK_CHECK) + return + if(!prob(EVAPORATION_CHANCE) || QDELETED(liquid_turf)) + evaporation_queue -= liquid_turf + continue + liquid_turf?.liquids?.process_evaporation() + run_type = SSLIQUIDS_RUN_TYPE_FIRE + + if(run_type == SSLIQUIDS_RUN_TYPE_FIRE) + fire_counter++ + for(var/datum/liquid_group/liquid_group as anything in active_groups) + if(MC_TICK_CHECK) + return + if(length(liquid_group?.burning_members)) + for(var/turf/burning_turf as anything in liquid_group.burning_members) + if(MC_TICK_CHECK) + return + if(!istype(burning_turf) || QDELING(burning_turf)) + liquid_group.burning_members -= burning_turf + continue + liquid_group.process_spread(burning_turf) + + if(fire_counter > REQUIRED_FIRE_PROCESSES) + for(var/datum/liquid_group/liquid_group as anything in active_groups) + if(MC_TICK_CHECK) + return + if(QDELETED(liquid_group)) + active_groups -= liquid_group + continue + if(length(liquid_group.burning_members)) + liquid_group.process_fire() + fire_counter = 0 + run_type = SSLIQUIDS_RUN_TYPE_OCEAN + + if(!length(currentrun_active_ocean_turfs)) + currentrun_active_ocean_turfs = active_ocean_turfs + + if(run_type == SSLIQUIDS_RUN_TYPE_OCEAN) + listclearnulls(currentrun_active_ocean_turfs) + ocean_counter++ + if(ocean_counter >= REQUIRED_OCEAN_PROCESSES) + for(var/turf/open/floor/plating/ocean/active_ocean in currentrun_active_ocean_turfs) + if(MC_TICK_CHECK) + return + active_ocean.process_turf() + ocean_counter = 0 + run_type = SSLIQUIDS_RUN_TYPE_TURFS + + if(run_type == SSLIQUIDS_RUN_TYPE_TURFS) + member_counter++ + if(!length(active_turf_group_queue)) + active_turf_group_queue += active_groups + listclearnulls(active_turf_group_queue) + + if(member_counter > REQUIRED_MEMBER_PROCESSES) + for(var/datum/liquid_group/liquid_group as anything in active_turf_group_queue) + if(MC_TICK_CHECK) + return + if(QDELETED(liquid_group)) + active_turf_group_queue -= liquid_group + continue + liquid_group.build_turf_reagent() + active_turf_group_queue -= liquid_group + if(!liquid_group.exposure) + continue + for(var/turf/member as anything in liquid_group.members) + if(MC_TICK_CHECK) + return + if(!istype(member) || QDELING(member)) + liquid_group.members -= member + continue + liquid_group.process_member(member) + member_counter = 0 + run_type = SSLIQUIDS_RUN_TYPE_CACHED_EDGES + + if(run_type == SSLIQUIDS_RUN_TYPE_CACHED_EDGES) + if(!length(cached_edge_work_queue)) + cached_edge_work_queue |= active_groups + listclearnulls(cached_edge_work_queue) + + if(length(cached_edge_work_queue)) + for(var/datum/liquid_group/liquid_group as anything in cached_edge_work_queue) + if(MC_TICK_CHECK) + return + if(QDELETED(liquid_group)) + cached_edge_work_queue -= liquid_group + continue + + liquid_group.build_turf_reagent() + if(liquid_group.reagents_per_turf > LIQUID_HEIGHT_DIVISOR) + liquid_group.process_cached_edges() + cached_edge_work_queue -= liquid_group + + + run_type = SSLIQUIDS_RUN_TYPE_GROUPS + + +/client/proc/toggle_liquid_debug() + set category = "Debug" + set name = "Liquid Groups Color Debug" + set desc = "Liquid Groups Color Debug." + if(!holder) + return + GLOB.liquid_debug_colors = !GLOB.liquid_debug_colors diff --git a/yogstation/code/modules/liquids/liquid_effect.dm b/yogstation/code/modules/liquids/liquid_effect.dm new file mode 100644 index 0000000000000..b93cdd240d2c8 --- /dev/null +++ b/yogstation/code/modules/liquids/liquid_effect.dm @@ -0,0 +1,308 @@ +/obj/effect/abstract/liquid_turf + name = "liquid" + icon = 'yogstation/icons/obj/effects/liquid.dmi' + icon_state = "water-0" + base_icon_state = "water" + anchored = TRUE + plane = FLOOR_PLANE + color = "#DDF" + alpha = 175 + //For being on fire + light_power = 1 + light_color = LIGHT_COLOR_FIRE + + smoothing_flags = SMOOTH_BITMASK | SMOOTH_OBJ + smoothing_groups = SMOOTH_GROUP_WATER + canSmoothWith = SMOOTH_GROUP_WATER + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS + + mouse_opacity = FALSE + + var/datum/liquid_group/liquid_group + var/turf/my_turf + + var/fire_state = LIQUID_FIRE_STATE_NONE + var/liquid_state = LIQUID_STATE_PUDDLE + var/no_effects = FALSE + + + var/static/obj/effect/abstract/fire/small_fire/small_fire + var/static/obj/effect/abstract/fire/medium_fire/medium_fire + var/static/obj/effect/abstract/fire/big_fire/big_fire + + var/mutable_appearance/displayed_content + /// State-specific message chunks for examine_turf() + var/static/list/liquid_state_messages = list( + "[LIQUID_STATE_PUDDLE]" = "a puddle of $", + "[LIQUID_STATE_ANKLES]" = "$ going [span_warning("up to your ankles")]", + "[LIQUID_STATE_WAIST]" = "$ going [span_warning("up to your waist")]", + "[LIQUID_STATE_SHOULDERS]" = "$ going [span_warning("up to your shoulders")]", + "[LIQUID_STATE_FULLTILE]" = "$ going [span_danger("over your head")]", + ) + + var/temporary_split_key + + +/obj/effect/abstract/liquid_turf/proc/process_evaporation() + if(liquid_group.expected_turf_height > LIQUID_ANKLES_LEVEL_HEIGHT) + SSliquids.evaporation_queue -= my_turf + return + + //See if any of our reagents evaporates + var/any_change = FALSE + var/datum/reagent/R //Faster declaration + for(var/reagent_type in liquid_group.reagents.reagent_list) + R = reagent_type + //We evaporate. bye bye + if(initial(R.evaporates)) + var/remove_amount = min((initial(R.evaporation_rate)), R.volume, (liquid_group.reagents_per_turf / liquid_group.reagents.reagent_list.len)) + liquid_group.remove_specific(src, remove_amount, R) + any_change = TRUE + R.evaporate(src.loc, remove_amount) + + if(!any_change) + SSliquids.evaporation_queue -= my_turf + return + +/obj/effect/abstract/liquid_turf/forceMove(atom/destination, no_tp=FALSE, harderforce = FALSE) + if(harderforce) + . = ..() + +/obj/effect/abstract/liquid_turf/proc/set_new_liquid_state(new_state) + if(no_effects) + return + liquid_state = new_state + + var/number = new_state - 1 + if(number != 0) + icon_state = null + base_icon_state = null + update_appearance() + + else + icon_state = initial(icon_state) + base_icon_state = initial(base_icon_state) + QUEUE_SMOOTH(src) + QUEUE_SMOOTH_NEIGHBORS(src) + +/obj/effect/abstract/liquid_turf/update_overlays() + . = ..() + var/number = liquid_state - 1 + if(number != 0) + . += mutable_appearance('yogstation/icons/obj/effects/liquid_overlays.dmi', "stage[number]_bottom", offset_spokesman = my_turf, plane = ABOVE_GAME_PLANE, layer = ABOVE_MOB_LAYER) + . += mutable_appearance('yogstation/icons/obj/effects/liquid_overlays.dmi', "stage[number]_top", offset_spokesman = my_turf, plane =GAME_PLANE, layer = GATEWAY_UNDERLAY_LAYER) + +/obj/effect/abstract/liquid_turf/proc/set_fire_effect() + if(displayed_content) + vis_contents -= displayed_content + + if(!liquid_group) + return + + switch(liquid_group.group_fire_state) + if(LIQUID_FIRE_STATE_SMALL) + displayed_content = small_fire + if(LIQUID_FIRE_STATE_MILD) + displayed_content = small_fire + if(LIQUID_FIRE_STATE_MEDIUM) + displayed_content = medium_fire + if(LIQUID_FIRE_STATE_HUGE) + displayed_content = big_fire + if(LIQUID_FIRE_STATE_INFERNO) + displayed_content = big_fire + else + displayed_content = null + + if(displayed_content) + vis_contents |= displayed_content + +//Takes a flat of our reagents and returns it, possibly qdeling our liquids +/obj/effect/abstract/liquid_turf/proc/take_reagents_flat(flat_amount) + liquid_group.remove_any(src, flat_amount) + +/obj/effect/abstract/liquid_turf/proc/movable_entered(datum/source, atom/movable/AM) + SIGNAL_HANDLER + if(!liquid_group) + qdel(src) + return + + var/turf/T = source + if(isobserver(AM)) + return //ghosts, camera eyes, etc. don't make water splashy splashy + if(liquid_group.group_overlay_state >= LIQUID_STATE_ANKLES) + if(prob(30)) + var/sound_to_play = pick(list( + 'yogstation/sound/effects/water_wade1.ogg', + 'yogstation/sound/effects/water_wade2.ogg', + 'yogstation/sound/effects/water_wade3.ogg', + 'yogstation/sound/effects/water_wade4.ogg' + )) + playsound(T, sound_to_play, 50, 0) + if(iscarbon(AM)) + var/mob/living/carbon/C = AM + C.apply_status_effect(/datum/status_effect/water_affected) + if(isliving(AM)) + var/mob/living/carbon/human/stepped_human = AM + liquid_group.expose_atom(stepped_human, 1, TOUCH) + else if (isliving(AM)) + var/mob/living/L = AM + if(liquid_group.slippery) + if(prob(7) && !(L.movement_type & FLYING) && L.body_position == STANDING_UP) + L.slip(30, T, NO_SLIP_WHEN_WALKING, 0, TRUE) + + if(fire_state) + AM.fire_act((T20C+50) + (50*fire_state), 125) + +/obj/effect/abstract/liquid_turf/proc/mob_fall(datum/source, mob/M) + SIGNAL_HANDLER + var/turf/T = source + if(liquid_group.group_overlay_state >= LIQUID_STATE_ANKLES && T.has_gravity(T)) + playsound(T, 'yogstation/sound/effects/splash.ogg', 50, 0) + if(iscarbon(M)) + var/mob/living/carbon/C = M + if(C.wear_mask && C.wear_mask.flags_cover & MASKCOVERSMOUTH) + to_chat(C, span_userdanger("You fall in the water!")) + else + liquid_group.transfer_to_atom(src, CHOKE_REAGENTS_INGEST_ON_FALL_AMOUNT, C) + C.adjustOxyLoss(5) + //C.emote("cough") + INVOKE_ASYNC(C, TYPE_PROC_REF(/mob, emote), "cough") + to_chat(C, span_userdanger("You fall in and swallow some water!")) + else + to_chat(M, span_userdanger("You fall in the water!")) + +/obj/effect/abstract/liquid_turf/Initialize(mapload, datum/liquid_group/group_to_add) + . = ..() + if(!small_fire) + small_fire = new + if(!medium_fire) + medium_fire = new + if(!big_fire) + big_fire = new + + if(!my_turf) + my_turf = loc + + if(!my_turf.liquids) + my_turf.liquids = src + + if(group_to_add) + group_to_add.add_to_group(my_turf) + set_new_liquid_state(liquid_group.group_overlay_state) + + if(!liquid_group && !group_to_add) + liquid_group = new(1, src) + + if(!SSliquids) + CRASH("Liquid Turf created with the liquids sybsystem not yet initialized!") + my_turf = loc + RegisterSignal(my_turf, COMSIG_ATOM_ENTERED, PROC_REF(movable_entered)) + RegisterSignal(my_turf, COMSIG_TURF_MOB_FALL, PROC_REF(mob_fall)) + RegisterSignal(my_turf, COMSIG_ATOM_EXAMINE, PROC_REF(examine_turf)) + + SEND_SIGNAL(my_turf, COMSIG_TURF_LIQUIDS_CREATION, src) + + if(z) + QUEUE_SMOOTH(src) + QUEUE_SMOOTH_NEIGHBORS(src) + + +/obj/effect/abstract/liquid_turf/Destroy(force) + UnregisterSignal(my_turf, list(COMSIG_ATOM_ENTERED, COMSIG_TURF_MOB_FALL, COMSIG_ATOM_EXAMINE)) + if(liquid_group) + liquid_group.remove_from_group(my_turf) + if(my_turf in SSliquids.evaporation_queue) + SSliquids.evaporation_queue -= my_turf + if(my_turf in SSliquids.burning_turfs) + SSliquids.burning_turfs -= my_turf + my_turf.liquids = null + my_turf = null + QUEUE_SMOOTH_NEIGHBORS(src) + return ..() + +/obj/effect/abstract/liquid_turf/proc/ChangeToNewTurf(turf/NewT) + if(NewT.liquids) + stack_trace("Liquids tried to change to a new turf, that already had liquids on it!") + + UnregisterSignal(my_turf, list(COMSIG_ATOM_ENTERED, COMSIG_TURF_MOB_FALL)) + if(SSliquids.evaporation_queue[my_turf]) + SSliquids.evaporation_queue -= my_turf + SSliquids.evaporation_queue[NewT] = TRUE + my_turf.liquids = null + my_turf = NewT + liquid_group.move_liquid_group(src) + NewT.liquids = src + loc = NewT + RegisterSignal(my_turf, COMSIG_ATOM_ENTERED, PROC_REF(movable_entered)) + RegisterSignal(my_turf, COMSIG_TURF_MOB_FALL, PROC_REF(mob_fall)) + +/** + * Handles COMSIG_ATOM_EXAMINE for the turf. + * + * Adds reagent info to examine text. + * Arguments: + * * source - the turf we're peekin at + * * examiner - the user + * * examine_text - the examine list + * */ +/obj/effect/abstract/liquid_turf/proc/examine_turf(turf/source, mob/examiner, list/examine_list) + SIGNAL_HANDLER + + if(!liquid_group) + qdel(src) + return + + // This should always have reagents if this effect object exists, but as a sanity check... + if(!length(liquid_group.reagents.reagent_list)) + return + + var/liquid_state_template = liquid_state_messages["[liquid_group.group_overlay_state]"] + + examine_list += "
" + + if(examiner.can_see_reagents()) + examine_list += "
" + + if(length(liquid_group.reagents.reagent_list) == 1) + // Single reagent text. + var/datum/reagent/reagent_type = liquid_group.reagents.reagent_list[1] + var/reagent_name = initial(reagent_type.name) + var/volume = round(reagent_type.volume / length(liquid_group.members), 0.01) + + examine_list += span_notice("There is [replacetext(liquid_state_template, "$", "[volume] units of [reagent_name]")] here.") + else + // Show each individual reagent + examine_list += "There is [replacetext(liquid_state_template, "$", "the following")] here:" + + for(var/datum/reagent/reagent_type as anything in liquid_group.reagents.reagent_list) + var/reagent_name = initial(reagent_type.name) + var/volume = round(reagent_type.volume / length(liquid_group.members), 0.01) + examine_list += "• [volume] units of [reagent_name]" + + examine_list += span_notice("The solution has a temperature of [liquid_group.group_temperature]K.") + examine_list += "
" + return + + // Otherwise, just show the total volume + examine_list += span_notice("There is [replacetext(liquid_state_template, "$", "liquid")] here.") + +/obj/effect/temp_visual/liquid_splash + icon = 'yogstation/icons/obj/effects/splash.dmi' + icon_state = "splash" + layer = FLY_LAYER + randomdir = FALSE + +/obj/effect/abstract/fire + icon = 'yogstation/icons/obj/effects/liquid.dmi' + plane = FLOOR_PLANE + layer = BELOW_MOB_LAYER + mouse_opacity = FALSE + appearance_flags = RESET_COLOR | RESET_ALPHA + +/obj/effect/abstract/fire/small_fire + icon_state = "fire_small" + +/obj/effect/abstract/fire/medium_fire + icon_state = "fire_medium" + +/obj/effect/abstract/fire/big_fire + icon_state = "fire_big" diff --git a/yogstation/code/modules/liquids/liquid_groups.dm b/yogstation/code/modules/liquids/liquid_groups.dm new file mode 100644 index 0000000000000..5390940271d29 --- /dev/null +++ b/yogstation/code/modules/liquids/liquid_groups.dm @@ -0,0 +1,983 @@ +/***************************************************/ +/********************PROPER GROUPING**************/ + +//Whenever you add a liquid cell add its contents to the group, have the group hold the reference to total reagents for processing sake +//Have the liquid turfs point to a partial liquids reference in the group for any interactions +//Have the liquid group handle the total reagents datum, and reactions too (apply fraction?) + +GLOBAL_VAR_INIT(liquid_debug_colors, FALSE) + +/datum/liquid_group + ///the generated color given to the group on creation for debugging + var/color + ///list of all current members of the group saved in true/false format + var/list/members = list() + ///list of all current burning members of our group + var/list/burning_members = list() + ///our reagent holder, where the entire liquid groups reagents are stored + var/datum/reagents/reagents + ///our reagent holder for a single turf + var/datum/reagents/turf_reagents + ///the expected height of all the collective turfs + var/expected_turf_height = 1 + ///A saved variable of the total reagent volumes to avoid calling reagents.total_volume constantly + var/total_reagent_volume = 0 + ///a cached value of our reagents per turf, used to determine liquid height and state + var/reagents_per_turf = 0 + ///the icon state our group currently uses + var/group_overlay_state = LIQUID_STATE_PUDDLE + ///the calculated alpha cache for our group + var/group_alpha = 0 + ///the calculated temperature cache for our group + var/group_temperature = 300 + ///the generated color used to apply coloring to all the members + var/group_color + ///have we failed a process? if so we are added to a death check so it will gracefully die on its own + var/failed_death_check = FALSE + ///the burn power of our group, used to determine how strong we burn each process_fire() + var/group_burn_power = 0 + ///the icon state of our fire + var/group_fire_state = LIQUID_FIRE_STATE_NONE + ///the amount of reagents we attempt to burn each process_fire() + var/group_burn_rate = 0 + ///the viscosity of our group, determines how much we can spread with our total reagent pool, higher means less turfs per reagent + var/group_viscosity = 1 + ///are we currently attempting a merge? if so don't process groups + var/merging = FALSE + ///list of cached edge turfs with a sublist of directions stored + var/list/cached_edge_turfs = list() + ///list of cached spreadable turfs for each burning member + var/list/cached_fire_spreads = list() + ///list of old reagents + var/list/cached_reagent_list = list() + ///cached temperature between turfs recalculated on group_process + var/cached_temperature_shift = 0 + ///does temperature need action + var/temperature_shift_needs_action = FALSE + ///this groups list of currently running turfs, we iterate over this to stop redundancy + var/list/current_temperature_queue = list() + ///do we evaporate + var/evaporates = TRUE + ///can we merge? + var/can_merge = TRUE + ///number in decimal value that acts as a multiplier to the amount of liquids lost in applications + var/loss_precent = 1 + ///do we have any containing expose turf chemicals with volume to look for? + var/exposure = FALSE + ///array generated by bulk splitting + var/list/splitting_array = list() + ///are we slippery + var/slippery = TRUE + +///NEW/DESTROY +/datum/liquid_group/New(height, obj/effect/abstract/liquid_turf/created_liquid) + color = "#[random_short_color()]" + expected_turf_height = height + reagents = new(100000) // this is a random number used on creation it expands based on the turfs in the group + if(!QDELETED(created_liquid)) + add_to_group(created_liquid.my_turf) + cached_edge_turfs[created_liquid.my_turf] = list(NORTH, SOUTH, EAST, WEST) + SSliquids.active_groups |= src + +/datum/liquid_group/Destroy() + SSliquids.active_groups -= src + + if(src in SSliquids.arrayed_groups) + SSliquids.arrayed_groups -= src /// Someone made a massive fucky wucky if this is happening + + for(var/turf/member_turf as anything in members) + member_turf?.liquids?.liquid_group = null + members = list() + burning_members = null + return ..() + + +///GROUP CONTROLLING +/datum/liquid_group/proc/add_to_group(turf/T) + if(QDELETED(T)) + return + if(QDELETED(T.liquids)) + T.liquids = new(T, src) + cached_edge_turfs[T] = list(NORTH, SOUTH, EAST, WEST) + + if(!members) + QDEL_NULL(T.liquids) + return + + members[T] = TRUE + T.liquids.liquid_group = src + + reagents.maximum_volume += 1000 /// each turf will hold 1000 units plus the base amount spread across the group + if(group_color) + T.liquids.color = group_color + process_group() + +/datum/liquid_group/proc/remove_from_group(turf/T) + + if(T in burning_members) + burning_members -= T + + if(T in SSliquids.burning_turfs) + SSliquids.burning_turfs -= T + + members -= T + T.liquids?.liquid_group = null + + if(!length(members)) + qdel(src) + return + process_group() + +/datum/liquid_group/proc/remove_all() + for(var/turf/member in members) + QDEL_NULL(member.liquids) + +/datum/liquid_group/proc/merge_group(datum/liquid_group/otherg) + if(otherg == src) + return + if(!length(members) || !total_reagent_volume) + return + + otherg.merging = TRUE + var/list/created_reagent_list = list() + for(var/datum/reagent/reagent in otherg.reagents.reagent_list) + created_reagent_list |= reagent.type + created_reagent_list[reagent.type] = reagent.volume + + add_reagents(reagent_list = created_reagent_list, chem_temp = otherg.group_temperature) + cached_edge_turfs |= otherg.cached_edge_turfs + + for(var/turf/liquid_turf as anything in otherg.members) + otherg.remove_from_group(liquid_turf) + add_to_group(liquid_turf) + + + total_reagent_volume = reagents.total_volume + reagents_per_turf = total_reagent_volume / length(members) + + qdel(otherg) + process_group() + +/datum/liquid_group/proc/break_group() + qdel(src) + +/datum/liquid_group/proc/check_dead() + if(!members && !total_reagent_volume) + if(failed_death_check) + qdel(src) + return + failed_death_check = TRUE + +///PROCESSING +/datum/liquid_group/proc/process_group(from_SS = FALSE) + if(merging) + return + if(!members || !length(members)) // this ideally shouldn't exist, ideally groups would die before they got to this point but alas here we are + check_dead() + return + + if(group_temperature != reagents.chem_temp) + reagents.chem_temp = group_temperature + + handle_visual_changes() + reagents.my_atom = pick(members) /// change the location of explosions and sounds every group process + + for(var/turf/turf as anything in members) + if(isopenturf(turf)) + continue + remove_from_group(turf) + + var/turf/open/open_turf = pick(members) + var/datum/gas_mixture/math_cache = open_turf.air + + if(math_cache && total_reagent_volume) + if(!(group_temperature <= math_cache.return_temperature() + 5 && math_cache.return_temperature() - 5 <= group_temperature) && !temperature_shift_needs_action) + cached_temperature_shift =((math_cache.return_temperature() * max(1, math_cache.total_moles())) + ((group_temperature * max(1, (total_reagent_volume * 0.025))))) / (max(1, (total_reagent_volume * 0.025)) + max(1, math_cache.total_moles())) + temperature_shift_needs_action = TRUE + + if(from_SS) + total_reagent_volume = reagents.total_volume + reagents.handle_reactions() + + if(!total_reagent_volume || !members) + return + + reagents_per_turf = total_reagent_volume / length(members) + + expected_turf_height = CEILING(reagents_per_turf, 1) / LIQUID_HEIGHT_DIVISOR + var/old_overlay = group_overlay_state + switch(expected_turf_height) + if(0 to LIQUID_ANKLES_LEVEL_HEIGHT-1) + group_overlay_state = LIQUID_STATE_PUDDLE + if(LIQUID_ANKLES_LEVEL_HEIGHT to LIQUID_WAIST_LEVEL_HEIGHT-1) + group_overlay_state = LIQUID_STATE_ANKLES + if(LIQUID_WAIST_LEVEL_HEIGHT to LIQUID_SHOULDERS_LEVEL_HEIGHT-1) + group_overlay_state = LIQUID_STATE_WAIST + if(LIQUID_SHOULDERS_LEVEL_HEIGHT to LIQUID_FULLTILE_LEVEL_HEIGHT-1) + group_overlay_state = LIQUID_STATE_SHOULDERS + if(LIQUID_FULLTILE_LEVEL_HEIGHT to INFINITY) + group_overlay_state = LIQUID_STATE_FULLTILE + + if(old_overlay != group_overlay_state) + for(var/turf/member in members) + if(QDELETED(member.liquids)) + remove_from_group(member) + continue + + member.liquids.set_new_liquid_state(group_overlay_state) + member.liquid_height = expected_turf_height + member.turf_height + +/datum/liquid_group/proc/cleanse_members() + for(var/turf/listed_turf as anything in members) + if(isclosedturf(listed_turf)) + remove_from_group(listed_turf) + qdel(listed_turf.liquids) + +/datum/liquid_group/proc/process_member(turf/member) + if(isspaceturf(member)) + remove_any(member.liquids, reagents_per_turf) + + if(!(member in members)) + return + if(QDELETED(turf_reagents)) + return + turf_reagents.reaction(member, TOUCH) + +/datum/liquid_group/proc/build_turf_reagent() + if(!length(members)) + return + if(!turf_reagents) + turf_reagents = new(100000) + + exposure = FALSE + slippery = FALSE + for(var/reagent_type in reagents.reagent_list) + var/datum/reagent/pulled_reagent = reagent_type + var/amount = pulled_reagent.volume / length(members) + if(!amount) + continue + turf_reagents.add_reagent(pulled_reagent.type, amount) + if(pulled_reagent.turf_exposure && amount > 10) + exposure = TRUE + if(pulled_reagent.slippery) + slippery = TRUE + + +/datum/liquid_group/proc/process_turf_disperse() + if(!total_reagent_volume) + for(var/turf/member in members) + remove_from_group(member) + QDEL_NULL(member.liquids) + return + + var/list/removed_turf = list() + if(reagents_per_turf < 5) + var/turfs_to_remove = round(length(members) - (total_reagent_volume / 6)) + if(turfs_to_remove <= 0) + return + while(turfs_to_remove > 0) + turfs_to_remove-- + if(members && length(members)) + var/turf/picked_turf = pick(members) + if(!QDELETED(picked_turf.liquids)) + remove_from_group(picked_turf) + QDEL_NULL(picked_turf.liquids) + removed_turf |= picked_turf + if(!total_reagent_volume) + reagents_per_turf = 0 + else + reagents_per_turf = total_reagent_volume / length(members) + else + members -= picked_turf + + if(!length(removed_turf)) + return + try_bulk_split(removed_turf) + +///REAGENT ADD/REMOVAL HANDLING +/datum/liquid_group/proc/check_liquid_removal(obj/effect/abstract/liquid_turf/remover, amount) + if(amount >= reagents_per_turf) + remove_from_group(remover.my_turf) + var/turf/remover_turf = remover.my_turf + qdel(remover) + try_split(remover_turf) + + for(var/dir in GLOB.cardinals) + var/turf/open/open_turf = get_step(remover_turf, dir) + if(!isopenturf(open_turf) || QDELETED(open_turf.liquids)) + continue + check_edges(open_turf) + process_group() + +/datum/liquid_group/proc/remove_any(obj/effect/abstract/liquid_turf/remover, amount) + reagents.remove_any(amount, TRUE) + if(!QDELETED(remover)) + check_liquid_removal(remover, amount) + total_reagent_volume = reagents.total_volume + reagents_per_turf = total_reagent_volume / length(members) + expected_turf_height = CEILING(reagents_per_turf, 1) / LIQUID_HEIGHT_DIVISOR + if(!total_reagent_volume && !reagents.total_volume) + remove_all() + qdel(src) + +/datum/liquid_group/proc/remove_specific(obj/effect/abstract/liquid_turf/remover, amount, datum/reagent/reagent_type) + reagents.remove_reagent(reagent_type.type, amount) + if(!QDELETED(remover)) + check_liquid_removal(remover, amount) + total_reagent_volume = reagents.total_volume + +/datum/liquid_group/proc/transfer_to_atom(obj/effect/abstract/liquid_turf/remover, amount, atom/transfer_target, transfer_method = INGEST) + reagents.trans_to(transfer_target, amount) + if(!QDELETED(remover)) + check_liquid_removal(remover, amount) + total_reagent_volume = reagents.total_volume + +/datum/liquid_group/proc/move_liquid_group(obj/effect/abstract/liquid_turf/member) + remove_from_group(member.my_turf) + member.liquid_group = new(1, member) + var/remove_amount = reagents_per_turf / length(reagents.reagent_list) + for(var/datum/reagent/reagent_type in reagents.reagent_list) + member.liquid_group.reagents.add_reagent(reagent_type, remove_amount, no_react = TRUE) + remove_specific(amount = remove_amount, reagent_type = reagent_type) + +/datum/liquid_group/proc/add_reagents(obj/effect/abstract/liquid_turf/member, reagent_list, chem_temp) + reagents.add_reagent_list(reagent_list, _no_react = TRUE) + + var/amount = 0 + for(var/list_item in reagent_list) + amount += reagent_list[list_item] + handle_temperature(amount, chem_temp) + handle_visual_changes() + process_group() + +/datum/liquid_group/proc/add_reagent(obj/effect/abstract/liquid_turf/member, datum/reagent/reagent, amount, temperature) + reagents.add_reagent(reagent, amount, temperature, no_react = TRUE) + + handle_temperature(amount, temperature) + handle_visual_changes() + process_group() + +/datum/liquid_group/proc/transfer_reagents_to_secondary_group(obj/effect/abstract/liquid_turf/member, obj/effect/abstract/liquid_turf/transfer) + if(!total_reagent_volume && !reagents.total_volume) + return + else if(!total_reagent_volume) + total_reagent_volume = reagents.total_volume + + var/total_removed = length(members) + 1 / total_reagent_volume + var/remove_amount = total_removed / length(reagents.reagent_list) + if(QDELETED(transfer)) + transfer = new() + if(QDELETED(transfer.liquid_group)) + transfer.liquid_group = new(1, transfer) + for(var/datum/reagent/reagent_type in reagents.reagent_list) + transfer.liquid_group.reagents.add_reagent(reagent_type.type, remove_amount, no_react = TRUE) + remove_specific(amount = remove_amount, reagent_type = reagent_type) + total_removed += remove_amount + check_liquid_removal(member, total_removed) + handle_visual_changes() + process_group() + +/datum/liquid_group/proc/trans_to_seperate_group(datum/reagents/secondary_reagent, amount, obj/effect/abstract/liquid_turf/remover, merge = FALSE) + reagents.trans_to(secondary_reagent, amount) + if(remover) + check_liquid_removal(remover, amount) + else if(!merge) + process_removal(amount) + + handle_visual_changes() + +/datum/liquid_group/proc/transfer_specific_reagents(datum/reagents/secondary_reagent, amount, list/reagents_to_check, obj/effect/abstract/liquid_turf/remover, merge = FALSE) + if(!length(reagents_to_check)) + return + var/total_hits = 0 + var/total_volume = 0 + for(var/datum/reagent/reagent_type in reagents.reagent_list) + if(!(reagent_type.type in reagents_to_check)) + continue + total_hits++ + total_volume += reagent_type.volume + if(!total_hits) + return + + var/precent = (amount / total_volume) + for(var/datum/reagent/reagent_type in reagents.reagent_list) + if(!(reagent_type.type in reagents_to_check)) + continue + secondary_reagent.add_reagent(reagent_type.type, reagent_type.volume * precent, no_react = TRUE) + if(remover) + remove_specific(remover, amount = reagent_type.volume * precent, reagent_type = reagent_type.type) + else + remove_specific(amount = reagent_type.volume * precent, reagent_type = reagent_type.type) + + process_removal() + handle_visual_changes() + + +/datum/liquid_group/proc/process_removal(amount) + + total_reagent_volume = reagents.total_volume + if(total_reagent_volume && length(members)) //Otherwise we are probably just sending the last of things + reagents_per_turf = total_reagent_volume / length(members) + else + reagents_per_turf = 0 + process_turf_disperse() + process_group() + +/datum/liquid_group/proc/handle_temperature(previous_reagents, temp) + var/baseline_temperature = ((total_reagent_volume * group_temperature) + (previous_reagents * temp)) / (total_reagent_volume + previous_reagents) + group_temperature = baseline_temperature + reagents.chem_temp = group_temperature + +/datum/liquid_group/proc/handle_visual_changes() + var/new_color + var/old_color = group_color + + if(GLOB.liquid_debug_colors) + new_color = color + else if(length(cached_reagent_list) != length(reagents.reagent_list)) + new_color = mix_color_from_reagents(reagents.reagent_list) + cached_reagent_list = list() + cached_reagent_list |= reagents.reagent_list + + var/alpha_setting = 1 + var/alpha_divisor = 1 + + for(var/r in reagents.reagent_list) + var/datum/reagent/R = r + alpha_setting += max((R.opacity * R.volume), 1) + alpha_divisor += max((1 * R.volume), 1) + + var/old_alpha = group_alpha + if(new_color == old_color && group_alpha == old_alpha || !new_color) + return + group_alpha = clamp(round(alpha_setting / alpha_divisor, 1), 120, 255) + group_color = new_color + for(var/turf/member in members) + if(QDELETED(member.liquids)) + continue + member.liquids.alpha = group_alpha + member.liquids.color = group_color + +///Fire Related Procs / Handling + +/datum/liquid_group/proc/get_group_burn() + var/total_burn_power = 0 + var/total_burn_rate = 0 + for(var/datum/reagent/reagent_type in reagents.reagent_list) + var/burn_power = initial(reagent_type.liquid_fire_power) + if(burn_power) + total_burn_power += burn_power * reagent_type.volume + total_burn_rate += burn_power + group_burn_rate = total_burn_rate * 0.5 //half power because reasons + if(!total_burn_power) + if(length(burning_members)) + extinguish_all() + group_burn_power = 0 + return + + total_burn_power /= reagents.total_volume //We get burn power per unit. + if(total_burn_power <= REQUIRED_FIRE_POWER_PER_UNIT) + return FALSE + //Finally, we burn + var/old_burn = group_burn_power + + group_burn_power = total_burn_power + + if(old_burn == group_burn_power) + return + switch(group_burn_power) + if(0 to 7) + group_fire_state = LIQUID_FIRE_STATE_SMALL + if(7 to 8) + group_fire_state = LIQUID_FIRE_STATE_MILD + if(8 to 9) + group_fire_state = LIQUID_FIRE_STATE_MEDIUM + if(9 to 10) + group_fire_state = LIQUID_FIRE_STATE_HUGE + if(10 to INFINITY) + group_fire_state = LIQUID_FIRE_STATE_INFERNO + +/datum/liquid_group/proc/process_fire() + get_group_burn() + + var/reagents_to_remove = group_burn_rate * (length(burning_members)) + + if(!group_burn_power) + extinguish_all() + return + + remove_any(amount = reagents_to_remove) + + if(!reagents_per_turf) + return + + if(group_burn_rate >= reagents_per_turf) + var/list/removed_turf = list() + var/number = round(group_burn_rate / reagents_per_turf) + for(var/num in 1 to number) + if(!length(burning_members)) + break + var/turf/picked_turf = burning_members[1] + extinguish(picked_turf) + remove_from_group(picked_turf) + QDEL_NULL(picked_turf.liquids) + removed_turf |= picked_turf + + + for(var/turf/remover in removed_turf) + for(var/dir in GLOB.cardinals) + var/turf/open/open_turf = get_step(remover, dir) + if(!isopenturf(open_turf) || QDELETED(open_turf.liquids)) + continue + check_edges(open_turf) + + while(length(removed_turf)) + var/turf/picked_turf = pick(removed_turf) + var/list/output = try_split(picked_turf, TRUE) + removed_turf -= picked_turf + for(var/turf/outputted_turf in output) + if(outputted_turf in removed_turf) + removed_turf -= outputted_turf + + +/datum/liquid_group/proc/ignite_turf(turf/member) + get_group_burn() + if(!group_burn_power) + return + + member.liquids.fire_state = group_fire_state + member.liquids.set_fire_effect() + burning_members |= member + SSliquids.burning_turfs |= member + +/datum/liquid_group/proc/build_fire_cache(turf/burning_member) + cached_fire_spreads |= burning_member + var/list/directions = list(NORTH, SOUTH, EAST, WEST) + var/list/spreading_turfs = list() + for(var/dir in directions) + var/turf/open/open_adjacent = get_step(burning_member, dir) + if(QDELETED(open_adjacent) || QDELETED(open_adjacent.liquids)) + continue + spreading_turfs |= open_adjacent + + cached_fire_spreads[burning_member] = spreading_turfs + +/datum/liquid_group/proc/process_spread(turf/member) + if(member.liquids.fire_state <= LIQUID_FIRE_STATE_MEDIUM) // fires to small to worth spreading + return + + if(!cached_fire_spreads[member]) + build_fire_cache(member) + + for(var/turf/open/adjacent_turf in cached_fire_spreads[member]) + if(!QDELETED(adjacent_turf.liquids) && adjacent_turf.liquids.liquid_group == src && adjacent_turf.liquids.fire_state < member.liquids.fire_state) + adjacent_turf.liquids.fire_state = group_fire_state + member.liquids.set_fire_effect() + burning_members |= adjacent_turf + SSliquids.burning_turfs |= adjacent_turf + for(var/atom/movable/movable in adjacent_turf) + movable.fire_act((T20C+50) + (50*adjacent_turf.liquids.fire_state), 125) + +/datum/liquid_group/proc/extinguish_all() + group_burn_power = 0 + group_fire_state = LIQUID_FIRE_STATE_NONE + for(var/turf/member in burning_members) + member.liquids.fire_state = LIQUID_FIRE_STATE_NONE + member.liquids.set_fire_effect() + if(burning_members[member]) + burning_members -= member + if(SSliquids.burning_turfs[member]) + SSliquids.burning_turfs -= member + +/datum/liquid_group/proc/extinguish(turf/member) + if(SSliquids.burning_turfs[member]) + SSliquids.burning_turfs -= member + burning_members -= member + if(QDELETED(member.liquids)) + return + member.liquids.fire_state = LIQUID_FIRE_STATE_NONE + member.liquids.set_fire_effect() + +///EDGE COLLECTION AND PROCESSING + +/datum/liquid_group/proc/check_adjacency(turf/member) + var/adjacent_liquid = 0 + for(var/tur in member.get_atmos_adjacent_turfs()) + var/turf/adjacent_turf = tur + if(!QDELETED(adjacent_turf.liquids)) + if(adjacent_turf.liquids.liquid_group == member.liquids.liquid_group) + adjacent_liquid++ + if(adjacent_liquid < 2) + return FALSE + return TRUE + +/datum/liquid_group/proc/process_cached_edges() + for(var/turf/cached_turf in cached_edge_turfs) + for(var/direction in cached_edge_turfs[cached_turf]) + var/turf/directional_turf = get_step(cached_turf, direction) + if(isclosedturf(directional_turf)) + continue + if(!(directional_turf in cached_turf.atmos_adjacent_turfs)) //i hate that this is needed + continue + if(!cached_turf.atmos_adjacent_turfs[directional_turf]) + continue + if(spread_liquid(directional_turf, cached_turf)) + cached_edge_turfs[cached_turf] -= direction + if(!length(cached_edge_turfs[cached_turf])) + cached_edge_turfs -= cached_turf + +/datum/liquid_group/proc/check_edges(turf/checker) + var/list/passed_directions = list() + for(var/direction in GLOB.cardinals) + var/turf/directional_turf = get_step(checker, direction) + if(!QDELETED(directional_turf.liquids)) + continue + passed_directions.Add(direction) + + if(length(passed_directions)) + cached_edge_turfs |= checker + cached_edge_turfs[checker] = passed_directions + + + +///SPLITING PROCS AND RETURNING CONNECTED PROCS + +/*okay for large groups we need some way to iterate over it without grinding the server to a halt to split them +* A breadth-first search or depth first search, are the most efficent but still cause issues with larger groups +* the easist way around this would be using an index of visted turfs and comparing it for changes to save cycles +* this has the draw back of being multiple times slower on small groups, but massively faster on large groups +* For a unique key the easist way to do so would be either to retrive its member number, or better its position +* key as that will be totally unique. this can be used for things aside from splitting by sucking up large groups +*/ + +/datum/liquid_group/proc/return_connected_liquids(obj/effect/abstract/liquid_turf/source, adjacent_checks = 0) + var/temporary_split_key = source.temporary_split_key + var/turf/first_turf = source.my_turf + var/list/connected_liquids = list() + ///the current queue + var/list/queued_liquids = list(source) + ///the turfs that we have previously visited with unique ids + var/list/previously_visited = list() + ///the turf object the liquid resides on + var/turf/queued_turf + + var/obj/effect/abstract/liquid_turf/current_head + ///compares after each iteration to see if we even need to continue + var/visited_length = 0 + while(length(queued_liquids)) + current_head = queued_liquids[1] + queued_turf = current_head.my_turf + queued_liquids -= current_head + + for(var/turf/adjacent_turf in get_adjacent_open_turfs(queued_turf)) + if(QDELETED(adjacent_turf.liquids) || !members[adjacent_turf]) + continue + if(!(adjacent_turf in queued_turf.atmos_adjacent_turfs)) //i hate that this is needed + continue + visited_length = length(previously_visited) + previously_visited["[adjacent_turf.liquids.x]_[adjacent_turf.liquids.y]"] = adjacent_turf.liquids + if(length(previously_visited) != visited_length) + queued_liquids |= adjacent_turf.liquids + connected_liquids |= adjacent_turf + if(adjacent_checks) + if(temporary_split_key == adjacent_turf.liquids.temporary_split_key && adjacent_turf != first_turf) + adjacent_checks-- + if(adjacent_checks <= 0) + return FALSE + + return connected_liquids + +/datum/liquid_group/proc/return_connected_liquids_in_range(obj/effect/abstract/liquid_turf/source, total_turfs = 0) + var/list/connected_liquids = list() + ///the current queue + var/list/queued_liquids = list(source) + ///the turfs that we have previously visited with unique ids + var/list/previously_visited = list() + ///the turf object the liquid resides on + var/turf/queued_turf + + var/obj/effect/abstract/liquid_turf/current_head + ///compares after each iteration to see if we even need to continue + var/visited_length = 0 + while(length(queued_liquids)) + current_head = queued_liquids[1] + queued_turf = current_head.my_turf + queued_liquids -= current_head + + for(var/turf/adjacent_turf in get_adjacent_open_turfs(queued_turf)) + if(QDELETED(adjacent_turf.liquids) || !members[adjacent_turf]) + continue + + visited_length = length(previously_visited) + previously_visited["[adjacent_turf.liquids.x]_[adjacent_turf.liquids.y]"] = adjacent_turf.liquids + if(length(previously_visited) != visited_length) + queued_liquids |= adjacent_turf.liquids + connected_liquids |= adjacent_turf + if(total_turfs > 0 && length(connected_liquids) >= total_turfs) + return connected_liquids + + +/datum/liquid_group/proc/try_split(turf/source, return_list = FALSE) + if(!length(members)) + return + var/list/connected_liquids = list() + + var/turf/head_turf = source + var/obj/effect/abstract/liquid_turf/current_head + + var/generated_key = "[world.time]_activemembers[length(members)]" + var/adjacent_liquid_count = -1 + for(var/turf/adjacent_turf in get_adjacent_open_turfs(head_turf)) + if(QDELETED(adjacent_turf.liquids) || !members[adjacent_turf]) //empty turf or not our group just skip this + continue + ///the section is a little funky, as if say a cross shaped liquid removal occurs this will leave 3 of them in the same group, not a big deal as this only affects turfs that are like 5 tiles total + current_head = adjacent_turf.liquids + current_head.temporary_split_key = generated_key + adjacent_liquid_count++ + + if(adjacent_liquid_count > 0) ///if there is only 1 adjacent liquid it physically can't split + return FALSE + + if(current_head) + connected_liquids = return_connected_liquids(current_head, adjacent_liquid_count) + + if(!length(connected_liquids) || length(connected_liquids) == length(members)) //yes yes i know if two groups are identical in size this will break but fixing this would add to much processing + if(return_list) + return connected_liquids + return FALSE + + var/amount_to_transfer = length(connected_liquids) * reagents_per_turf + + var/datum/liquid_group/new_group = new(1) + + for(var/turf/connected_liquid in connected_liquids) + new_group.check_edges(connected_liquid) + + if(connected_liquid in burning_members) + new_group.burning_members |= connected_liquid + remove_from_group(connected_liquid, TRUE) + new_group.add_to_group(connected_liquid) + + trans_to_seperate_group(new_group.reagents, amount_to_transfer) + new_group.total_reagent_volume = new_group.reagents.total_volume + new_group.reagents_per_turf = new_group.total_reagent_volume / length(new_group.members) + + ///asses the group to see if it should exist + var/new_group_length = length(new_group.members) + if(new_group.total_reagent_volume == 0 || new_group.reagents_per_turf == 0 || !new_group_length) + qdel(new_group) + return FALSE + for(var/turf/new_turf in new_group.members) + if(new_turf in members) + new_group.members -= new_turf + + if(!length(new_group.members)) + qdel(new_group) + return FALSE + + if(return_list) + return connected_liquids + + return TRUE + +/datum/liquid_group/proc/try_bulk_split(list/input_turfs) + var/list/connected_array = list() + for(var/turf/listed_input in input_turfs) + for(var/turf/cardinal in listed_input.get_atmos_adjacent_turfs()) + var/exists_already = FALSE + for(var/list/arrayed_item in connected_array) + if(cardinal in arrayed_item) + exists_already = TRUE + break + if(!exists_already) + if(!QDELETED(cardinal.liquids)) + var/list/temp = return_connected_liquids(cardinal.liquids) + if(isnull(temp) || !length(temp)) + continue + connected_array += list(list(temp)) + + if(!length(connected_array)) + return + + splitting_array = connected_array + SSliquids.arrayed_groups += src + +///we try and work on the split queue from this point on +/datum/liquid_group/proc/work_on_split_queue() + if(!length(splitting_array)) + SSliquids.arrayed_groups -= src + return + + var/list/plucked_array = list() + var/pick_count = 3 + while(pick_count > 0 && length(splitting_array)) + var/list/temp = pick(splitting_array) + plucked_array += list(temp) + splitting_array -= list(temp) + + if(!length(splitting_array)) + SSliquids.arrayed_groups -= src + + for(var/list/connected_liquids in plucked_array) + if(isnull(connected_liquids) || !length(connected_liquids)) + continue + + var/amount_to_transfer = length(connected_liquids) * reagents_per_turf + + for(var/list/liquid_list as anything in connected_liquids) + var/datum/liquid_group/new_group = new(1) + if(!members) + members = list() + trans_to_seperate_group(new_group.reagents, amount_to_transfer) + for(var/turf/connected_liquid in liquid_list) + + new_group.check_edges(connected_liquid) + + if(connected_liquid in burning_members) + new_group.burning_members |= connected_liquid + remove_from_group(connected_liquid, TRUE) + new_group.add_to_group(connected_liquid) + + new_group.total_reagent_volume = new_group.reagents.total_volume + new_group.reagents_per_turf = new_group.total_reagent_volume / length(new_group.members) + + ///asses the group to see if it should exist + var/new_group_length = length(new_group.members) + if(new_group.total_reagent_volume == 0 || new_group.reagents_per_turf == 0 || !new_group_length) + qdel(new_group) + return + + for(var/turf/new_turf in new_group.members) + if(new_turf in members) + new_group.members -= new_turf + + if(!length(new_group.members)) + qdel(new_group) + return + + + +///EXPOSURE AND SPREADING +/datum/liquid_group/proc/expose_members_turf(obj/effect/abstract/liquid_turf/member) + if(!turf_reagents) + return + var/turf/members_turf = member.my_turf + for(var/atom/movable/target_atom in members_turf) + turf_reagents.reaction(target_atom, TOUCH) + +/datum/liquid_group/proc/expose_atom(atom/target, modifier = 0, method) + if(!turf_reagents) + return + if(HAS_TRAIT(target, LIQUID_PROTECTION)) + return + turf_reagents.reaction(target, method) + +/datum/liquid_group/proc/spread_liquid(turf/new_turf, turf/source_turf) + if(isclosedturf(new_turf) || !source_turf.atmos_adjacent_turfs) + return + if(!(new_turf in source_turf.atmos_adjacent_turfs)) //i hate that this is needed + return + if(!source_turf.atmos_adjacent_turfs[new_turf]) + return + + if(isopenspaceturf(new_turf)) + var/turf/Z_turf_below = GET_TURF_BELOW(new_turf) + if(!Z_turf_below) + return + if(isspaceturf(Z_turf_below)) + return FALSE + if(QDELETED(Z_turf_below.liquids)) + Z_turf_below.liquids = new(Z_turf_below) + if(QDELETED(source_turf.liquids)) + remove_from_group(source_turf) + if(source_turf in cached_edge_turfs) + cached_edge_turfs -= source_turf + return FALSE + source_turf.liquids.liquid_group.transfer_reagents_to_secondary_group(source_turf.liquids, Z_turf_below.liquids) + + var/obj/splashy = new /obj/effect/temp_visual/liquid_splash(Z_turf_below) + if(!QDELETED(Z_turf_below.liquids?.liquid_group)) + splashy.color = Z_turf_below.liquids.liquid_group.group_color + return FALSE + + if(QDELETED(new_turf.liquids) && !istype(new_turf, /turf/open/openspace) && !isspaceturf(new_turf) && !istype(new_turf, /turf/open/floor/plating/ocean) && source_turf.turf_height == new_turf.turf_height) // no space turfs, or oceans turfs, also don't attempt to spread onto a turf that already has liquids wastes processing time + if(reagents_per_turf < LIQUID_HEIGHT_DIVISOR) + return FALSE + if(!length(members)) + return FALSE + reagents_per_turf = total_reagent_volume / length(members) + expected_turf_height = CEILING(reagents_per_turf, 1) / LIQUID_HEIGHT_DIVISOR + new_turf.liquids = new(new_turf, src) + new_turf.liquids.alpha = group_alpha + check_edges(new_turf) + + var/obj/splashy = new /obj/effect/temp_visual/liquid_splash(new_turf) + if(!QDELETED(new_turf.liquids?.liquid_group)) + splashy.color = new_turf.liquids.liquid_group.group_color + + water_rush(new_turf, source_turf) + + else if(!QDELETED(source_turf?.liquids?.liquid_group) && !QDELETED(new_turf?.liquids?.liquid_group) && new_turf.liquids.liquid_group != source_turf.liquids.liquid_group && source_turf.turf_height == new_turf.turf_height && new_turf.liquids.liquid_group.can_merge) + merge_group(new_turf.liquids.liquid_group) + return FALSE + else if(source_turf.turf_height != new_turf.turf_height) + if(new_turf.turf_height < source_turf.turf_height) // your going down + if(QDELETED(new_turf.liquids)) + new_turf.liquids = new(new_turf) + if(new_turf.turf_height + new_turf.liquids.liquid_group.expected_turf_height < source_turf.turf_height) + var/obj/effect/abstract/liquid_turf/turf_liquids = new_turf.liquids + trans_to_seperate_group(turf_liquids.liquid_group.reagents, reagents_per_turf, source_turf) + turf_liquids.liquid_group.process_group() + else if(source_turf.turf_height < new_turf.turf_height) + if(source_turf.turf_height + expected_turf_height < new_turf.turf_height) + return + if(QDELETED(new_turf.liquids)) + new_turf.liquids = new(new_turf) + var/obj/effect/abstract/liquid_turf/turf_liquids = new_turf.liquids + trans_to_seperate_group(turf_liquids.liquid_group.reagents, 10, source_turf) //overflows out + turf_liquids.liquid_group.process_group() + + return TRUE + +/datum/liquid_group/proc/water_rush(turf/new_turf, turf/source_turf) + var/direction = get_dir(source_turf, new_turf) + for(var/atom/movable/target_atom in new_turf) + if(!target_atom.anchored && !target_atom.pulledby && !isobserver(target_atom) && (target_atom.move_resist < INFINITY)) + reagents.reaction(target_atom, TOUCH, (reagents_per_turf * 0.5)) + if(expected_turf_height < LIQUID_ANKLES_LEVEL_HEIGHT) + return + step(target_atom, direction) + if(isliving(target_atom) && prob(60)) + var/mob/living/target_living = target_atom + target_living.Paralyze(6 SECONDS) + to_chat(target_living, span_danger("You are knocked down by the currents!")) + +/datum/liquid_group/proc/fetch_temperature_queue() + if(!cached_temperature_shift) + return list() + + var/list/returned = list() + for(var/tur in members) + var/turf/open/member = tur + returned |= member + + current_temperature_queue = returned + return returned + +/datum/liquid_group/proc/act_on_queue(turf/member) + if(!temperature_shift_needs_action) + return + + var/turf/open/member_open = member + var/datum/gas_mixture/gas = member_open.air + if(!gas) + return + + gas.set_temperature(cached_temperature_shift) + if(group_temperature != cached_temperature_shift) + group_temperature = cached_temperature_shift + reagents.chem_temp = cached_temperature_shift + + current_temperature_queue -= member + if(!length(current_temperature_queue)) + temperature_shift_needs_action = FALSE diff --git a/yogstation/code/modules/liquids/liquid_height.dm b/yogstation/code/modules/liquids/liquid_height.dm new file mode 100644 index 0000000000000..291b4df682731 --- /dev/null +++ b/yogstation/code/modules/liquids/liquid_height.dm @@ -0,0 +1,45 @@ +/** + * Liquid Height element; for dynamically applying liquid blockages. + * + * Used for reinforced tables, sandbags, and the likes. + */ +/datum/element/liquids_height + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + + ///Height applied by this element + var/height_applied + +/datum/element/liquids_height/Attach(datum/target, height_applied) + . = ..() + if(!ismovable(target)) + return ELEMENT_INCOMPATIBLE + + src.height_applied = height_applied + + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_target_move)) + var/atom/movable/movable_target = target + if(isturf(movable_target.loc)) + var/turf/turf_loc = movable_target.loc + turf_loc.turf_height += height_applied + turf_loc.reasses_liquids() + +/datum/element/liquids_height/Detach(atom/movable/target) + . = ..() + UnregisterSignal(target, list(COMSIG_MOVABLE_MOVED)) + var/atom/movable/movable_target = target + if(isturf(movable_target.loc)) + var/turf/turf_loc = movable_target.loc + turf_loc.turf_height -= height_applied + turf_loc.reasses_liquids() + +/datum/element/liquids_height/proc/on_target_move(atom/movable/source, atom/OldLoc, Dir, Forced = FALSE) + SIGNAL_HANDLER + if(isturf(OldLoc)) + var/turf/old_turf = OldLoc + old_turf.turf_height += height_applied + old_turf.reasses_liquids() + if(isturf(source.loc)) + var/turf/new_turf = source.loc + new_turf.turf_height -= height_applied + new_turf.reasses_liquids() diff --git a/yogstation/code/modules/liquids/liquid_interaction.dm b/yogstation/code/modules/liquids/liquid_interaction.dm new file mode 100644 index 0000000000000..0a43b0c9f5125 --- /dev/null +++ b/yogstation/code/modules/liquids/liquid_interaction.dm @@ -0,0 +1,28 @@ +///This element allows for items to interact with liquids on turfs. +/datum/component/liquids_interaction + ///Callback interaction called when the turf has some liquids on it + var/datum/callback/interaction_callback + +/datum/component/liquids_interaction/Initialize(on_interaction_callback) + . = ..() + + if(!istype(parent, /obj/item)) + return COMPONENT_INCOMPATIBLE + + interaction_callback = CALLBACK(parent, on_interaction_callback) + +/datum/component/liquids_interaction/RegisterWithParent() + RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, PROC_REF(AfterAttack)) //The only signal allowing item -> turf interaction + +/datum/component/liquids_interaction/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_ITEM_AFTERATTACK) + +/datum/component/liquids_interaction/proc/AfterAttack(datum/source, atom/victim, mob/caster, proximity_flag, click_parameters) + SIGNAL_HANDLER + var/turf/turf_target = victim + + if(!isturf(turf_target) || !turf_target.liquids) + return NONE + + if(interaction_callback.Invoke(parent, victim, caster, turf_target.liquids)) + return COMPONENT_CANCEL_ATTACK_CHAIN diff --git a/yogstation/code/modules/liquids/liquid_ocean.dm b/yogstation/code/modules/liquids/liquid_ocean.dm new file mode 100644 index 0000000000000..02cabe4a58012 --- /dev/null +++ b/yogstation/code/modules/liquids/liquid_ocean.dm @@ -0,0 +1,332 @@ +GLOBAL_LIST_INIT(initalized_ocean_areas, list()) +/area/ocean + name = "Ocean" + + icon = 'yogstation/icons/obj/effects/liquid.dmi' + base_icon_state = "ocean" + icon_state = "ocean" + alpha = 120 + + requires_power = TRUE + always_unpowered = TRUE + + power_light = FALSE + power_equip = FALSE + power_environ = FALSE + + outdoors = TRUE + ambience_index = AMBIENCE_SPACE + + flags_1 = CAN_BE_DIRTY_1 + sound_environment = SOUND_AREA_SPACE + +/area/ocean/Initialize(mapload) + . = ..() + GLOB.initalized_ocean_areas += src + +/area/ocean/dark + base_lighting_alpha = 0 + +/area/ruin/ocean + has_gravity = TRUE + +/area/ruin/ocean/listening_outpost + unique = TRUE + +/area/ruin/ocean/bunker + unique = TRUE + +/area/ruin/ocean/bioweapon_research + unique = TRUE + +/area/ruin/ocean/mining_site + unique = TRUE + +/area/ocean/near_station_powered + requires_power = FALSE + +/turf/open/openspace/ocean + name = "ocean" + planetary_atmos = TRUE + baseturfs = /turf/open/openspace/ocean + var/replacement_turf = /turf/open/floor/plating/ocean + +/turf/open/openspace/ocean/Initialize() + . = ..() + ChangeTurf(replacement_turf, null, CHANGETURF_IGNORE_AIR) + +/turf/open/floor/plating + ///do we still call parent but dont want other stuff? + var/overwrites_attack_by = FALSE + +/turf/open/floor/plating/ocean + plane = FLOOR_PLANE + layer = TURF_LAYER + force_no_gravity = FALSE + gender = PLURAL + name = "ocean sand" + baseturfs = /turf/open/floor/plating/ocean + icon = 'yogstation/icons/turf/floors/seafloor.dmi' + icon_state = "seafloor" + base_icon_state = "seafloor" + footstep = FOOTSTEP_SAND + barefootstep = FOOTSTEP_SAND + clawfootstep = FOOTSTEP_SAND + heavyfootstep = FOOTSTEP_GENERIC_HEAVY + planetary_atmos = TRUE + initial_gas_mix = OCEAN_DEFAULT_ATMOS + + upgradable = FALSE + attachment_holes = FALSE + + resistance_flags = INDESTRUCTIBLE + + overwrites_attack_by = TRUE + + var/static/obj/effect/abstract/ocean_overlay/static_overlay + var/static/list/ocean_reagents = list(/datum/reagent/water = 10) + var/ocean_temp = T20C + var/list/ocean_turfs = list() + var/list/open_turfs = list() + + ///are we captured, this is easier than having to run checks on turfs for vents + var/captured = FALSE + + var/rand_variants = 0 + var/rand_chance = 30 + + /// Itemstack to drop when dug by a shovel + var/obj/item/stack/dig_result = /obj/item/stack/ore/glass + /// Whether the turf has been dug or not + var/dug = FALSE + + /// do we build a catwalk or plating with rods + var/catwalk = FALSE + +/turf/open/floor/plating/ocean/Initialize() + . = ..() + RegisterSignal(src, COMSIG_ATOM_ENTERED, PROC_REF(movable_entered)) + RegisterSignal(src, COMSIG_TURF_MOB_FALL, PROC_REF(mob_fall)) + if(!static_overlay) + static_overlay = new(null, ocean_reagents) + + vis_contents += static_overlay + light_color = static_overlay.color + SSliquids.unvalidated_oceans |= src + SSliquids.ocean_turfs |= src + + if(rand_variants && prob(rand_chance)) + var/random = rand(1,rand_variants) + icon_state = "[base_icon_state][random]" + base_icon_state = "[base_icon_state][random]" + + +/turf/open/floor/plating/ocean/Destroy() + . = ..() + UnregisterSignal(src, list(COMSIG_ATOM_ENTERED, COMSIG_TURF_MOB_FALL)) + SSliquids.active_ocean_turfs -= src + SSliquids.ocean_turfs -= src + for(var/turf/open/floor/plating/ocean/listed_ocean as anything in ocean_turfs) + listed_ocean.rebuild_adjacent() + +/turf/open/floor/plating/ocean/attackby(obj/item/C, mob/user, params) + if(..()) + return + if(istype(C, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = C + if (R.get_amount() < 2) + to_chat(user, span_warning("You need two rods to make a [catwalk ? "catwalk" : "plating"]!")) + return + else + to_chat(user, span_notice("You begin constructing a [catwalk ? "catwalk" : "plating"]...")) + if(do_after(user, 30, target = src)) + if (R.get_amount() >= 2 && !catwalk) + place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) + playsound(src, 'sound/items/deconstruct.ogg', 80, TRUE) + R.use(2) + to_chat(user, span_notice("You reinforce the [src].")) + else if(R.get_amount() >= 2 && catwalk) + new /obj/structure/lattice/catwalk(src) + playsound(src, 'sound/items/deconstruct.ogg', 80, TRUE) + R.use(2) + to_chat(user, span_notice("You build a catwalk over the [src].")) + +/// Drops itemstack when dug and changes icon +/turf/open/floor/plating/ocean/proc/getDug() + dug = TRUE + new dig_result(src, 5) + +/// If the user can dig the turf +/turf/open/floor/plating/ocean/proc/can_dig(mob/user) + if(!dug) + return TRUE + if(user) + to_chat(user, span_warning("Looks like someone has dug here already!")) + + +/turf/open/floor/plating/ocean/proc/assume_self() + if(!atmos_adjacent_turfs) + immediate_calculate_adjacent_turfs() + for(var/direction in GLOB.cardinals) + var/turf/directional_turf = get_step(src, direction) + if(istype(directional_turf, /turf/open/floor/plating/ocean)) + ocean_turfs |= directional_turf + else + if(isclosedturf(directional_turf)) + RegisterSignal(directional_turf, COMSIG_TURF_DESTROY, PROC_REF(add_turf_direction), TRUE) + continue + else if(!(directional_turf in atmos_adjacent_turfs)) + var/obj/machinery/door/found_door = locate(/obj/machinery/door) in directional_turf + if(found_door) + RegisterSignal(found_door, COMSIG_ATOM_DOOR_OPEN, TYPE_PROC_REF(/turf/open/floor/plating/ocean, door_opened)) + RegisterSignal(directional_turf, COMSIG_TURF_UPDATE_AIR, PROC_REF(add_turf_direction_non_closed), TRUE) + continue + else + open_turfs.Add(direction) + + if(open_turfs.len) + SSliquids.active_ocean_turfs |= src + SSliquids.unvalidated_oceans -= src + +/turf/open/floor/plating/ocean/proc/door_opened(datum/source) + SIGNAL_HANDLER + + var/obj/machinery/door/found_door = source + var/turf/turf = get_turf(found_door) + + if(turf.can_atmos_pass()) + turf.add_liquid_list(ocean_reagents, FALSE, ocean_temp) + +/turf/open/floor/plating/ocean/proc/process_turf() + for(var/direction in open_turfs) + var/turf/directional_turf = get_step(src, direction) + if(isspaceturf(directional_turf) || istype(directional_turf, /turf/open/floor/plating/ocean)) + RegisterSignal(directional_turf, COMSIG_TURF_DESTROY, PROC_REF(add_turf_direction), TRUE) + open_turfs -= direction + if(!open_turfs.len) + SSliquids.active_ocean_turfs -= src + return + else if(!(directional_turf in atmos_adjacent_turfs)) + RegisterSignal(directional_turf, COMSIG_TURF_UPDATE_AIR, PROC_REF(add_turf_direction_non_closed), TRUE) + open_turfs -= direction + if(!open_turfs.len) + SSliquids.active_ocean_turfs -= src + return + + directional_turf.add_liquid_list(ocean_reagents, FALSE, ocean_temp) + +/turf/open/floor/plating/ocean/proc/rebuild_adjacent() + ocean_turfs = list() + open_turfs = list() + for(var/direction in GLOB.cardinals) + var/turf/directional_turf = get_step(src, direction) + if(istype(directional_turf, /turf/open/floor/plating/ocean)) + ocean_turfs |= directional_turf + else + open_turfs.Add(direction) + + if(open_turfs.len) + SSliquids.active_ocean_turfs |= src + else if(src in SSliquids.active_ocean_turfs) + SSliquids.active_ocean_turfs -= src + +/turf/open/floor/plating/ocean/attackby(obj/item/C, mob/user, params) + . = ..() + + if(C.tool_behaviour == TOOL_SHOVEL || C.tool_behaviour == TOOL_MINING) + if(!can_dig(user)) + return TRUE + + if(!isturf(user.loc)) + return + + balloon_alert(user, "digging...") + + if(C.use_tool(src, user, 40, volume=50)) + if(!can_dig(user)) + return TRUE + getDug() + SSblackbox.record_feedback("tally", "pick_used_mining", 1, C.type) + return TRUE + +/obj/effect/abstract/ocean_overlay + icon = 'yogstation/icons/obj/effects/liquid.dmi' + icon_state = "ocean" + base_icon_state = "ocean" + plane = AREA_PLANE //Same as weather, etc. + layer = ABOVE_MOB_LAYER + vis_flags = NONE + mouse_opacity = FALSE + alpha = 120 + +/obj/effect/abstract/ocean_overlay/Initialize(mapload, list/ocean_contents) + . = ..() + var/datum/reagents/fake_reagents = new + fake_reagents.add_reagent_list(ocean_contents) + color = mix_color_from_reagents(fake_reagents.reagent_list) + qdel(fake_reagents) + if(istype(loc, /area/ocean)) + var/area/area_loc = loc + area_loc.base_lighting_color = color + +/obj/effect/abstract/ocean_overlay/proc/mix_colors(list/ocean_contents) + var/datum/reagents/fake_reagents = new + fake_reagents.add_reagent_list(ocean_contents) + color = mix_color_from_reagents(fake_reagents.reagent_list) + qdel(fake_reagents) + if(istype(loc, /area/ocean)) + var/area/area_loc = loc + area_loc.base_lighting_color = color + +/turf/open/floor/plating/ocean/proc/mob_fall(datum/source, mob/M) + SIGNAL_HANDLER + var/turf/T = source + playsound(T, 'yogstation/sound/effects/splash.ogg', 50, 0) + if(iscarbon(M)) + var/mob/living/carbon/C = M + to_chat(C, span_userdanger("You fall in the water!")) + +/turf/open/floor/plating/ocean/proc/movable_entered(datum/source, atom/movable/AM) + SIGNAL_HANDLER + + var/turf/T = source + if(isobserver(AM)) + return //ghosts, camera eyes, etc. don't make water splashy splashy + if(prob(30)) + var/sound_to_play = pick(list( + 'yogstation/sound/effects/water_wade1.ogg', + 'yogstation/sound/effects/water_wade2.ogg', + 'yogstation/sound/effects/water_wade3.ogg', + 'yogstation/sound/effects/water_wade4.ogg' + )) + playsound(T, sound_to_play, 50, 0) + if(isliving(AM)) + var/mob/living/arrived = AM + if(!arrived.has_status_effect(/datum/status_effect/ocean_affected)) + arrived.apply_status_effect(/datum/status_effect/ocean_affected) + + SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WASH) + +/turf/open/floor/plating/ocean/proc/add_turf_direction(datum/source) + SIGNAL_HANDLER + var/turf/direction_turf = source + + if(istype(direction_turf, /turf/open/floor/plating/ocean)) + return + + open_turfs.Add(get_dir(src, direction_turf)) + + if(!(src in SSliquids.active_ocean_turfs)) + SSliquids.active_ocean_turfs |= src + +/turf/open/floor/plating/ocean/proc/add_turf_direction_non_closed(datum/source) + SIGNAL_HANDLER + var/turf/direction_turf = source + + if(!(direction_turf in atmos_adjacent_turfs)) + return + + open_turfs.Add(get_dir(src, direction_turf)) + + if(!(src in SSliquids.active_ocean_turfs)) + SSliquids.active_ocean_turfs |= src diff --git a/yogstation/code/modules/liquids/liquid_plumbers.dm b/yogstation/code/modules/liquids/liquid_plumbers.dm new file mode 100644 index 0000000000000..c324ad9b9a82f --- /dev/null +++ b/yogstation/code/modules/liquids/liquid_plumbers.dm @@ -0,0 +1,336 @@ +/** + * Base class for underfloor plumbing machines that mess with floor liquids. + */ +/obj/machinery/plumbing/floor_pump + icon = 'yogstation/icons/obj/structures/drains.dmi' + base_icon_state = "active_input" + icon_state = "active_input" + anchored = FALSE + density = FALSE + idle_power_usage = 10 + active_power_usage = 1000 + //buffer = 300 + //category="Distribution" + //reagent_flags = NO_REACT + + /// Pump is turned on by engineer, etc. + var/turned_on = FALSE + /// Only pump to this liquid level height. 0 means pump the most possible. + var/height_regulator = 0 + + /// The default duct layer for mapping + var/duct_layer = 0 + + /// Base amount to drain + var/drain_flat = 20 + /// Additional ratio of liquid volume to drain + var/drain_percent = 1 + + /// Currently pumping. + var/is_pumping = FALSE + /// Floor tile is placed down + var/tile_placed = FALSE + + var/processes = 0 + var/processes_required = 25 + +/obj/machinery/plumbing/floor_pump/Initialize(mapload, bolt, layer) + . = ..() + RegisterSignal(src, COMSIG_OBJ_HIDE, PROC_REF(on_hide)) + +/obj/machinery/plumbing/floor_pump/examine(mob/user) + . = ..() + . += span_notice("It's currently turned [turned_on ? "ON" : "OFF"].") + . += span_notice("Its height regulator [height_regulator ? "points at [height_regulator]" : "is disabled"]. Click while unanchored to change.") + +/obj/machinery/plumbing/floor_pump/update_appearance(updates) + . = ..() + layer = tile_placed ? GAS_SCRUBBER_LAYER : BELOW_OBJ_LAYER + plane = tile_placed ? FLOOR_PLANE : GAME_PLANE + +/obj/machinery/plumbing/floor_pump/update_icon_state() + . = ..() + if(panel_open) + icon_state = "[base_icon_state]-open" + else if(is_pumping) + icon_state = "[base_icon_state]-pumping" + else if(is_operational() && turned_on) + icon_state = "[base_icon_state]-idle" + else + icon_state = base_icon_state + +/obj/machinery/plumbing/floor_pump/default_unfasten_wrench(mob/user, obj/item/I, time = 20) + . = ..() + if(. == SUCCESSFUL_UNFASTEN) + turned_on = FALSE + update_icon_state() + +/obj/machinery/plumbing/floor_pump/attack_hand(mob/user) + if(!anchored) + set_regulator(user) + return + balloon_alert(user, "turned [turned_on ? "off" : "on"]") + turned_on = !turned_on + update_icon_state() + +/** + * Change regulator level -- ie. what liquid depth we are OK with, like a thermostat. + */ +/obj/machinery/plumbing/floor_pump/proc/set_regulator(mob/living/user) + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + var/new_height = tgui_input_number(user, + "At what water level should the pump stop pumping from 0 to [LIQUID_HEIGHT_CONSIDER_FULL_TILE]? 0 disables.", + "[src]", + default = height_regulator, + min_value = 0, + max_value = LIQUID_HEIGHT_CONSIDER_FULL_TILE) + if(QDELETED(src) || new_height == null) + return + height_regulator = new_height + +/** + * Handle COMSIG_OBJ_HIDE to toggle whether we're on the floor + */ +/obj/machinery/plumbing/floor_pump/proc/on_hide(atom/movable/AM, should_hide) + tile_placed = should_hide + update_appearance() + +/** + * Can the pump actually run at all? + */ +/obj/machinery/plumbing/floor_pump/proc/can_run() + return is_operational() \ + && turned_on \ + && anchored \ + && !panel_open \ + && isturf(loc) \ + && are_reagents_ready() + +/** + * Is the internal reagents container able to give or take liquid as appropriate? + */ +/obj/machinery/plumbing/floor_pump/proc/are_reagents_ready() + CRASH("are_reagents_ready() must be overriden.") + +/** + * Should we actually be pumping this tile right now? + * Arguments: + * * affected_turf - the turf to check. + */ +/obj/machinery/plumbing/floor_pump/proc/should_pump(turf/affected_turf) + return isturf(affected_turf) \ + && should_regulator_permit(affected_turf) + +/** + * Should the liquid height regulator allow water to be pumped here? + */ +/obj/machinery/plumbing/floor_pump/proc/should_regulator_permit(turf/affected_turf) + CRASH("should_regulator_permit() must be overriden.") + +/obj/machinery/plumbing/floor_pump/process(seconds_per_tick) + var/was_pumping = is_pumping + + if(!can_run()) + is_pumping = FALSE + if(was_pumping) + update_icon_state() + return + + // Determine what tiles should be pumped. We grab from a 3x3 area, + // but overall try to pump the same volume regardless of number of affected tiles + var/turf/local_turf = get_turf(src) + var/list/turf/candidate_turfs = local_turf.get_atmos_adjacent_turfs(alldir = TRUE) + candidate_turfs += local_turf + + var/list/turf/affected_turfs = list() + + for(var/turf/candidate as anything in candidate_turfs) + if(should_pump(candidate)) + affected_turfs += candidate + + // Update state + is_pumping = length(affected_turfs) > 0 + if(is_pumping != was_pumping) + update_icon_state() + if(!is_pumping) + return + + // note that the length was verified to be > 0 directly above and is a local var. + var/multiplier = 1 / length(affected_turfs) + + // We're good, actually pump. + for(var/turf/affected_turf as anything in affected_turfs) + pump_turf(affected_turf, seconds_per_tick, multiplier) + +/** + * Pump out the liquids on a turf. + * + * Arguments: + * * affected_turf - the turf to pump liquids out of. + * * seconds_per_tick - machine process delta time + * * multiplier - Multiplier to apply to final volume we want to pump. + */ +/obj/machinery/plumbing/floor_pump/proc/pump_turf(turf/affected_turf, seconds_per_tick, multiplier) + CRASH("pump_turf() must be overriden.") + + + +/obj/machinery/plumbing/floor_pump/input + name = "liquid input pump" + desc = "Pump used to siphon liquids from a location into the plumbing pipenet." + icon_state = "active_input" + base_icon_state = "active_input" + +/obj/machinery/plumbing/floor_pump/input/Initialize(mapload, bolt, layer) + . = ..() + AddComponent(/datum/component/plumbing/simple_supply, bolt, layer || duct_layer) + +/obj/machinery/plumbing/floor_pump/input/are_reagents_ready() + return reagents.total_volume < reagents.maximum_volume + +/obj/machinery/plumbing/floor_pump/input/should_regulator_permit(turf/affected_turf) + return affected_turf.liquids && affected_turf.liquids.liquid_group.expected_turf_height > height_regulator + +/obj/machinery/plumbing/floor_pump/input/pump_turf(turf/affected_turf, seconds_per_tick, multiplier) + if(processes < processes_required) + processes++ + return + processes = 0 + if(!affected_turf.liquids || !affected_turf.liquids.liquid_group || reagents.total_volume) + return + var/target_value = seconds_per_tick * (drain_flat + (affected_turf.liquids.liquid_group.total_reagent_volume * drain_percent)) * multiplier + //Free space handling + var/free_space = reagents.maximum_volume - reagents.total_volume + if(target_value > free_space) + target_value = free_space + + var/datum/liquid_group/targeted_group = affected_turf.liquids.liquid_group + if(!targeted_group.reagents_per_turf) + return + var/turfs_to_pull = round(target_value / targeted_group.reagents_per_turf,1) + + var/list/removed_turfs = targeted_group.return_connected_liquids_in_range(affected_turf.liquids, turfs_to_pull) + targeted_group.trans_to_seperate_group(reagents, target_value, merge = TRUE) + for(var/turf/listed_turf in removed_turfs) + var/datum/liquid_group/listed_group = listed_turf.liquids.liquid_group + targeted_group.remove_from_group(listed_turf) + qdel(listed_turf.liquids) + for(var/dir in GLOB.cardinals) + var/turf/open/direction_turf = get_step(listed_turf, dir) + if(!isopenturf(direction_turf) || !direction_turf.liquids) + continue + if(!listed_group) + continue + listed_group.check_edges(direction_turf) + + ///recalculate the values here because processing + targeted_group.total_reagent_volume = targeted_group.reagents.total_volume + targeted_group.reagents_per_turf = targeted_group.total_reagent_volume / length(targeted_group.members) + + if(!removed_turfs.len) + return + while(removed_turfs.len) + var/turf/picked_turf = pick(removed_turfs) + var/list/output = targeted_group.try_split(picked_turf, TRUE) + removed_turfs -= picked_turf + for(var/turf/outputted_turf in output) + if(outputted_turf in removed_turfs) + removed_turfs -= outputted_turf + +/obj/machinery/plumbing/floor_pump/input/on + icon_state = "active_input-mapping" + anchored = TRUE + turned_on = TRUE + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/plumbing/floor_pump/input/on, 0) + +/obj/machinery/plumbing/floor_pump/input/on/waste + icon_state = "active_input-mapping2" + duct_layer = SECOND_DUCT_LAYER + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/plumbing/floor_pump/input/on/waste, 0) + +/obj/machinery/plumbing/floor_pump/output + name = "liquid output pump" + desc = "Pump used to dump liquids out from a plumbing pipenet into a location." + icon_state = "active_output" + base_icon_state = "active_output" + + /// Is the turf too full to pump more? + var/over_volume = FALSE + /// Max liquid volume on the turf before we stop pumping. + var/max_ext_volume = LIQUID_HEIGHT_CONSIDER_FULL_TILE + + /// Is the turf too high-pressured to pump more? + var/over_pressure = FALSE + /// Max pressure on the turf before we stop pumping. + var/max_ext_kpa = WARNING_HIGH_PRESSURE + +/obj/machinery/plumbing/floor_pump/output/Initialize(mapload, bolt, layer) + . = ..() + AddComponent(/datum/component/plumbing/simple_demand, bolt, layer || duct_layer) + +/obj/machinery/plumbing/floor_pump/output/examine(mob/user) + . = ..() + if(over_pressure) + . += span_warning("The gas regulator light is blinking.") + if(over_volume) + . += span_warning("The liquid volume regulator light is blinking.") + +/obj/machinery/plumbing/floor_pump/output/are_reagents_ready() + return reagents.total_volume > 0 + +/obj/machinery/plumbing/floor_pump/output/should_regulator_permit(turf/affected_turf) + // 0 means keep pumping forever. + return !height_regulator || affected_turf.liquids.liquid_group.expected_turf_height < height_regulator + +/obj/machinery/plumbing/floor_pump/output/process() + over_pressure = FALSE + return ..() + +/obj/machinery/plumbing/floor_pump/output/should_pump(turf/affected_turf) + . = ..() + if(!.) + return FALSE + + if(affected_turf.liquids?.liquid_group.expected_turf_height >= max_ext_volume) + return FALSE + var/turf/open/open_turf = affected_turf + var/datum/gas_mixture/gas_mix = open_turf?.return_air() + if(gas_mix?.return_pressure() > max_ext_kpa) + over_pressure = TRUE + return FALSE + return TRUE + +/obj/machinery/plumbing/floor_pump/output/pump_turf(turf/affected_turf, seconds_per_tick, multiplier) + var/target_value = seconds_per_tick * (drain_flat + (reagents.total_volume * drain_percent)) * multiplier + if(target_value > reagents.total_volume) + target_value = reagents.total_volume + + var/datum/reagents/tempr = new(10000) + reagents.trans_to(tempr, target_value, no_react = TRUE) + affected_turf.add_liquid_from_reagents(tempr) + qdel(tempr) + +/obj/machinery/plumbing/floor_pump/output/on + icon_state = "active_output-mapping" + anchored = TRUE + turned_on = TRUE + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/plumbing/floor_pump/output/on, 0) + +/obj/machinery/plumbing/floor_pump/output/on/supply + icon_state = "active_output-mapping2" + duct_layer = FOURTH_DUCT_LAYER + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/plumbing/floor_pump/output/on/supply, 0) + +// Helpers for maps +/obj/machinery/duct/supply + duct_color = COLOR_CYAN + duct_layer = FOURTH_DUCT_LAYER + +/obj/machinery/duct/waste + duct_color = COLOR_BROWN + duct_layer = SECOND_DUCT_LAYER diff --git a/yogstation/code/modules/liquids/liquid_pump.dm b/yogstation/code/modules/liquids/liquid_pump.dm new file mode 100644 index 0000000000000..7f0c6dbfa69c4 --- /dev/null +++ b/yogstation/code/modules/liquids/liquid_pump.dm @@ -0,0 +1,101 @@ +//Right now it's a structure that works off of magic, as it'd require an internal power source for what its supposed to do +/obj/structure/liquid_pump + name = "portable liquid pump" + desc = "An industrial grade pump, capable of either siphoning or spewing liquids. Needs to be anchored first to work. Has a limited capacity internal storage." + icon = 'yogstation/icons/obj/structures/liquid_pump.dmi' + icon_state = "liquid_pump" + density = TRUE + max_integrity = 500 + anchored = FALSE + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + /// How many reagents at maximum can it hold + var/max_volume = 10000 + /// Whether spewing reagents out, instead of siphoning them + var/spewing_mode = FALSE + /// Whether its turned on and processing + var/turned_on = FALSE + /// How fast does the pump work, in percentages relative to the volume we're working with + var/pump_speed_percentage = 0.4 + /// How fast does the pump work, in flat values. Flat values on top of percentages to help processing + var/pump_speed_flat = 20 + +/obj/structure/liquid_pump/wrench_act(mob/living/user, obj/item/I) + . = ..() + default_unfasten_wrench(user, I, 40) + if(!anchored && turned_on) + toggle_working() + return TRUE + +/obj/structure/liquid_pump/attack_hand(mob/user) + if(!anchored) + to_chat(user, span_warning("[src] needs to be anchored first!")) + return + to_chat(user, span_notice("You turn [src] [turned_on ? "off" : "on"].")) + toggle_working() + +/obj/structure/liquid_pump/AltClick(mob/living/user) + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + to_chat(user, span_notice("You flick [src]'s spewing mode [spewing_mode ? "off" : "on"].")) + spewing_mode = !spewing_mode + update_icon() + +/obj/structure/liquid_pump/examine(mob/user) + . = ..() + . += span_notice("It's anchor bolts are [anchored ? "down and secured" : "up"].") + . += span_notice("It's currently [turned_on ? "ON" : "OFF"].") + . += span_notice("It's mode currently is set to [spewing_mode ? "SPEWING" : "SIPHONING"]. (Alt-click to switch)") + . += span_notice("The pressure gauge shows [reagents.total_volume]/[reagents.maximum_volume].") + +/obj/structure/liquid_pump/process() + if(!isturf(loc)) + return + var/turf/T = loc + if(spewing_mode) + if(!reagents.total_volume) + return + var/datum/reagents/tempr = new(10000) + reagents.trans_to(tempr, (reagents.total_volume * pump_speed_percentage) + pump_speed_flat, no_react = TRUE) + T.add_liquid_from_reagents(tempr) + qdel(tempr) + else + if(!T.liquids) + return + var/free_space = reagents.maximum_volume - reagents.total_volume + if(!free_space) + return + var/target_siphon_amt = (T.liquids.liquid_group.total_reagent_volume * pump_speed_percentage) + pump_speed_flat + if(target_siphon_amt > free_space) + target_siphon_amt = free_space + var/datum/reagents/tempr = T.liquids.take_reagents_flat(target_siphon_amt) + tempr.trans_to(reagents, tempr.total_volume) + qdel(tempr) + return + +/obj/structure/liquid_pump/update_icon() + . = ..() + if(turned_on) + if(spewing_mode) + icon_state = "[initial(icon_state)]_spewing" + else + icon_state = "[initial(icon_state)]_siphoning" + else + icon_state = "[initial(icon_state)]" + +/obj/structure/liquid_pump/proc/toggle_working() + if(turned_on) + STOP_PROCESSING(SSobj, src) + else + START_PROCESSING(SSobj, src) + turned_on = !turned_on + update_icon() + +/obj/structure/liquid_pump/Initialize() + . = ..() + create_reagents(max_volume) + +/obj/structure/liquid_pump/Destroy() + if(turned_on) + STOP_PROCESSING(SSobj, src) + qdel(reagents) + return ..() diff --git a/yogstation/code/modules/liquids/liquid_status_effect.dm b/yogstation/code/modules/liquids/liquid_status_effect.dm new file mode 100644 index 0000000000000..69d0a931c60ed --- /dev/null +++ b/yogstation/code/modules/liquids/liquid_status_effect.dm @@ -0,0 +1,46 @@ +/datum/status_effect/water_affected + id = "wateraffected" + alert_type = null + duration = -1 + +/datum/status_effect/water_affected/on_apply() + //We should be inside a liquid turf if this is applied + calculate_water_slow() + return TRUE + +/datum/status_effect/water_affected/proc/calculate_water_slow() + //Factor in swimming skill here? + var/turf/T = get_turf(owner) + var/slowdown_amount = T.liquids.liquid_group.group_overlay_state * 0.5 + owner.add_movespeed_modifier(MOVESPEED_ID_LIQUID, multiplicative_slowdown = slowdown_amount) + +/datum/status_effect/water_affected/tick() + var/turf/owner_turf = get_turf(owner) + if(QDELETED(owner_turf) || QDELETED(owner_turf.liquids) || owner_turf.liquids.liquid_group.group_overlay_state == LIQUID_STATE_PUDDLE) + qdel(src) + return + calculate_water_slow() + //Make the reagents touch the person + + var/fraction = SUBMERGEMENT_PERCENT(owner, owner_turf.liquids) + owner_turf.liquids.liquid_group.expose_members_turf(owner_turf.liquids) + owner_turf.liquids.liquid_group.transfer_to_atom(owner_turf.liquids, ((SUBMERGEMENT_REAGENTS_TOUCH_AMOUNT * fraction / 20)), owner) + + return ..() + +/datum/status_effect/water_affected/on_remove() + owner.remove_movespeed_modifier(MOVESPEED_ID_LIQUID) + +/datum/status_effect/ocean_affected + alert_type = null + duration = -1 + +/datum/status_effect/ocean_affected/tick() + var/turf/ocean_turf = get_turf(owner) + if(!istype(ocean_turf, /turf/open/floor/plating/ocean)) + qdel(src) + + if(ishuman(owner)) + var/mob/living/carbon/human/arrived = owner + if(is_species(owner, /datum/species/ipc) && !(arrived.wear_suit?.clothing_flags & STOPSPRESSUREDAMAGE)) + arrived.adjustFireLoss(5) diff --git a/yogstation/code/modules/liquids/liquid_turf.dm b/yogstation/code/modules/liquids/liquid_turf.dm new file mode 100644 index 0000000000000..226d9afd6ecd8 --- /dev/null +++ b/yogstation/code/modules/liquids/liquid_turf.dm @@ -0,0 +1,58 @@ +/turf + var/obj/effect/abstract/liquid_turf/liquids + var/liquid_height = 0 + var/turf_height = 0 + +/turf/proc/reasses_liquids() + if(!liquids) + return + if(!liquids.liquid_group) + liquids.liquid_group = new(1, liquids) + +/turf/proc/liquid_update_turf() + if(!liquids) + return + //Check atmos adjacency to cut off any disconnected groups + if(liquids.liquid_group) + var/assoc_atmos_turfs = list() + for(var/tur in get_atmos_adjacent_turfs()) + assoc_atmos_turfs[tur] = TRUE + //Check any cardinals that may have a matching group + for(var/direction in GLOB.cardinals) + var/turf/T = get_step(src, direction) + if(!T.liquids) + return + +/turf/proc/add_liquid_from_reagents(datum/reagents/giver, no_react = FALSE, chem_temp) + var/list/compiled_list = list() + for(var/r in giver.reagent_list) + var/datum/reagent/R = r + if(!(R.type in GLOB.liquid_blacklist)) + compiled_list[R.type] = R.volume + if(!compiled_list.len) //No reagents to add, don't bother going further + return + if(!liquids) + liquids = new(src) + liquids.liquid_group.add_reagents(liquids, compiled_list, chem_temp) + +//More efficient than add_liquid for multiples +/turf/proc/add_liquid_list(reagent_list, no_react = FALSE, chem_temp) + if(liquids && !liquids.liquid_group) + qdel(liquids) + return + + if(!liquids) + liquids = new(src) + liquids.liquid_group.add_reagents(liquids, reagent_list, chem_temp) + //Expose turf + liquids.liquid_group.expose_members_turf(liquids) + +/turf/proc/add_liquid(reagent, amount, no_react = FALSE, chem_temp = 300) + if(reagent in GLOB.liquid_blacklist) + return + if(!liquids) + liquids = new(src) + + liquids.liquid_group.add_reagent(liquids, reagent, amount, chem_temp) + //Expose turf + liquids.liquid_group.expose_members_turf(liquids) diff --git a/yogstation/code/modules/liquids/tools.dm b/yogstation/code/modules/liquids/tools.dm new file mode 100644 index 0000000000000..c9be5d5e3a6e4 --- /dev/null +++ b/yogstation/code/modules/liquids/tools.dm @@ -0,0 +1,69 @@ +/client/proc/spawn_liquid() + set category = "Admin.Fun" + set name = "Spawn Liquid" + set desc = "Spawns an amount of chosen liquid at your current location." + + var/choice + var/valid_id + while(!valid_id) + choice = stripped_input(usr, "Enter the ID of the reagent you want to add.", "Search reagents") + if(isnull(choice)) //Get me out of here! + break + if (!ispath(text2path(choice))) + choice = pick_closest_path(choice, make_types_fancy(subtypesof(/datum/reagent))) + if (ispath(choice)) + valid_id = TRUE + else + valid_id = TRUE + if(!valid_id) + to_chat(usr, span_warning("A reagent with that ID doesn't exist!")) + if(!choice) + return + var/volume = input(usr, "Volume:", "Choose volume") as num + if(!volume) + return + if(volume >= 100000) + to_chat(usr, span_warning("Please limit the volume to below 100000 units!")) + return + var/turf/epicenter = get_turf(mob) + epicenter.add_liquid(choice, volume, FALSE, 300) + message_admins("[ADMIN_LOOKUPFLW(usr)] spawned liquid at [epicenter.loc] ([choice] - [volume]).") + log_admin("[key_name(usr)] spawned liquid at [epicenter.loc] ([choice] - [volume]).") + +/client/proc/remove_liquid() + set name = "Remove Liquids" + set category = "Admin.Fun" + set desc = "Fixes air in specified radius." + var/turf/epicenter = get_turf(mob) + + var/range = input(usr, "Enter range:", "Range selection", 2) as num + + for(var/obj/effect/abstract/liquid_turf/liquid in range(range, epicenter)) + liquid.liquid_group.remove_any(liquid, liquid.liquid_group.reagents_per_turf) + qdel(liquid) + + message_admins("[key_name_admin(usr)] removed liquids with range [range] in [epicenter.loc.name]") + log_game("[key_name_admin(usr)] removed liquids with range [range] in [epicenter.loc.name]") + + + +/client/proc/change_ocean() + set category = "Admin.Fun" + set name = "Change Ocean Liquid" + set desc = "Changes the reagent of the ocean." + + + var/choice = tgui_input_list(usr, "Choose a reagent", "Ocean Reagent", subtypesof(/datum/reagent)) + if(!choice) + return + var/datum/reagent/chosen_reagent = choice + var/rebuilt = FALSE + for(var/turf/open/floor/plating/ocean/listed_ocean as anything in SSliquids.ocean_turfs) + if(!rebuilt) + listed_ocean.ocean_reagents = list() + listed_ocean.ocean_reagents[chosen_reagent] = 10 + listed_ocean.static_overlay.mix_colors(listed_ocean.ocean_reagents) + for(var/area/ocean/ocean_types in GLOB.initalized_ocean_areas) + ocean_types.base_lighting_color = listed_ocean.static_overlay.color + ocean_types.update_base_lighting() + rebuilt = TRUE diff --git a/yogstation/icons/obj/effects/liquid.dmi b/yogstation/icons/obj/effects/liquid.dmi new file mode 100644 index 0000000000000000000000000000000000000000..70f93f53b2cce97766437f439eb80675be158c77 GIT binary patch literal 11389 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 literal 0 HcmV?d00001 diff --git a/yogstation/icons/obj/effects/liquid_overlays.dmi b/yogstation/icons/obj/effects/liquid_overlays.dmi new file mode 100644 index 0000000000000000000000000000000000000000..b47120a5ff791bf6c9d3d9b1ee5b3f6f440860e7 GIT binary patch literal 809 zcmeAS@N?(olHy`uVBq!ia0vp^3qY8I4M=vMPuB%f^Q%H4N?cNllZ!G7N;32F7#J$% zEIqrCugO5f?cvVvn*R=X^=w%a%X7rwfcA`8VHrwSwwO!ZzjrWDR_C##-#(%CcQ^Vo z+ULE~QqZU5@T%)B9E9dLvCO6+lLT4Wd znTT+{UiXZXH}d4G;%5b?t)7a7rESjQUcKq&GyQL;{|T>K>0-VOXl|Um(WHVUL6s*N z7?@l=T^vIy=DfXg(f6={h(lo0j3(Yq{|)XoSV|o9IJPUV=A8N3Mc*E6@rz%_AGH4Z z>*aQBm(pt)=J+-&Ve#-HnRu1`g?)4J>hHDn=WYI#Y+ir=UGeej@7RBQF;Dnjf8i?k zg>Q^@ci%CtduFVl$v8;}Lacmf_aJL|J@=ZcyA{G5f|yiP2@yZvGrFHq-5%C|RO+;h z!Sp@XzG)}qes8!p_dKeLU#;RlVCKI19{Uz1m}OvVS3a)q3(a?)bB+s(AA3^y*2wkz zKmGZ9&b0S+3m^W_R@^eD$&+)!6b2Y^r9kh;OY@E`yjM@LU(ROAv1FYL^$WsRC@dpU z_fy#HYWvPUXYOm++>-yt|9(!Jx4-OHYs2H`P1(!YUx@gS9{gJS?H{~Lp1dZNm2emm e5zJ5}U%8cab7ls}M0o+zK7*&LpUXO@geCx@bvT&- literal 0 HcmV?d00001 diff --git a/yogstation/icons/obj/effects/splash.dmi b/yogstation/icons/obj/effects/splash.dmi new file mode 100644 index 0000000000000000000000000000000000000000..f2cb774f59e298e6fb8db4c14757de7ca07de3db GIT binary patch literal 519 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 K{VIq{#6JM+Z+epe literal 0 HcmV?d00001 diff --git a/yogstation/icons/obj/structures/liquid_pump.dmi b/yogstation/icons/obj/structures/liquid_pump.dmi new file mode 100644 index 0000000000000000000000000000000000000000..012dda8bb5e1d93e6d5529f76e17dbf755a32e40 GIT binary patch literal 929 zcmV;S177@zP)!mY7~&V`OAx zqo%Xo-rp!QbR~G7EIE!UHFz8*UKT%6z`(%Nkp|nl3#A-xga7~l0d!JMQvg8b*k%9# z0FQc9Sad{Xb7OL8aCB*JZU6vyoKseCa&`CgQ*iP1NXCBC3Ew?K)DGbOXA7$|DU#hF%=n41b= zV^bSnoLP{OpO=}Jj$Iefx|GzM#7dB`4lW}KQp<@oKv}`n&jsv30B6fZD1p%wcK`qa zCrLy>R9J=WmwQjbFcihhuyjKQh&mrF!p6e~!SDa^ytg%>E2CX^XH3-NA4~Y1+|%Bv zIWYFCG_ds@JQ-R#b@aQNbMDnxHepYG97<3 zh!dVq@($cGOlMQ;iYEi|>-lva&#f!YZf1NuM#C*$ap3UD{BAyp=hjQY8+;jtLJWrv z*C&kUtZ4cz(EqMnp5NykI2ay^njU#kA3)<^c;XVol`_wTJAlSn@%$v^awVlwgSB(l{9k`a)czlw zPwfA3^W(oC_(bjh!TH=#WdDzzAKU*!BKQAb0JZ;j3yA3dT>_%|f0ux${@)@XJAi!s z-^beo96msY^Znn;+XtNQ|6blEz^uU2|9!kofO&ac`+rE}{vQq?_Wy7IvHu4HsQtf9 z0IC1C2_W_VkjVW%96;>fFDZ*Bkp zc$`yKaB_9`^iy#0_2eo`Eh^5;&r`5fFwryM;w;ZhDainGjE%TBGg33tGfE(w;*!LY zR3KByShrk>i!&v&s2IpI#WNn@Uhkrmi7DHK{qNWk6<1d_hiP zaY<@wj;;X_b`y3VrnyE&#sqa4>E;okhOq6%x@koCq8MioDl549xq#yr0JMLE=>-ax z+W-Ini%CR5R9J=W*1-*cAP_`REP)mb9ccfxd09vVC+m7b^5+KGeI7gvDSb?~eBB2C z0GJQxB_+!*0?YvH7IpUS(_v9(?;cSh8617kY}f!Dto03ZZh**>XfzZt&300000 LNkvXXu0mjfGgzR8 literal 0 HcmV?d00001 diff --git a/yogstation/icons/turf/floors/lowered_iron.dmi b/yogstation/icons/turf/floors/lowered_iron.dmi new file mode 100644 index 0000000000000000000000000000000000000000..8171815033befc320e9dc8f60dce78d3ccbede3d GIT binary patch literal 352 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 literal 0 HcmV?d00001 diff --git a/yogstation/sound/effects/water_wade1.ogg b/yogstation/sound/effects/water_wade1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..27527f9d8b8e5fe8bc672e07eb0a64e483ac16cd GIT binary patch literal 11450 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 literal 0 HcmV?d00001 diff --git a/yogstation/sound/effects/water_wade2.ogg b/yogstation/sound/effects/water_wade2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..f9c15cab64a90f55452578312b1e6e5b78d1d77e GIT binary patch literal 10965 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 literal 0 HcmV?d00001 diff --git a/yogstation/sound/effects/water_wade4.ogg b/yogstation/sound/effects/water_wade4.ogg new file mode 100644 index 0000000000000000000000000000000000000000..7ba705f4991f01e54a42f1815a8166e423c09a66 GIT binary patch literal 10698 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 V3Q$RRfglV-zVrB+wB=0#{{uJ`$J77- literal 0 HcmV?d00001 diff --git a/yogstation/sound/effects/watersplash.ogg b/yogstation/sound/effects/watersplash.ogg new file mode 100644 index 0000000000000000000000000000000000000000..a9a6e4860878571a96e8a17695268f8da3911491 GIT binary patch literal 9609 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 literal 0 HcmV?d00001 From 6954ffb6d31f5450fb0a0749e34dc5671246c073 Mon Sep 17 00:00:00 2001 From: Byemoh Date: Mon, 3 Jun 2024 19:20:05 -0500 Subject: [PATCH 2/2] strange reagent fix --- code/modules/mob/living/living.dm | 15 +++++++++++++++ .../chemistry/reagents/medicine_reagents.dm | 15 +-------------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index e40145831bae2..567db521c2766 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -634,6 +634,21 @@ cure_fakedeath() SEND_SIGNAL(src, COMSIG_LIVING_POST_FULLY_HEAL) +/mob/living/proc/do_strange_reagent_revival() + if(iscarbon(src)) + var/mob/living/carbon/C = src + for(var/organ in C.internal_organs) + var/obj/item/organ/O = organ + O.setOrganDamage(0) + adjustBruteLoss(-100) + adjustFireLoss(-100) + adjustOxyLoss(-200, 0) + adjustToxLoss(-200, 0, TRUE) + updatehealth() + if(revive()) + emote("gasp") + log_combat(src, src, "revived", src) + //proc called by revive(), to check if we can actually ressuscitate the mob (we don't want to revive him and have him instantly die again) /mob/living/proc/can_be_revived() . = 1 diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 79f0dec4933c7..d103ab0a8ec02 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -934,20 +934,7 @@ M.do_jitter_animation(10) addtimer(CALLBACK(M, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 10), 40) //jitter immediately, then again after 4 and 8 seconds addtimer(CALLBACK(M, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 10), 80) - sleep(10 SECONDS) //so the ghost has time to re-enter - if(iscarbon(M)) - var/mob/living/carbon/C = M - for(var/organ in C.internal_organs) - var/obj/item/organ/O = organ - O.setOrganDamage(0) - M.adjustBruteLoss(-100) - M.adjustFireLoss(-100) - M.adjustOxyLoss(-200, 0) - M.adjustToxLoss(-200, 0, TRUE) - M.updatehealth() - if(M.revive()) - M.emote("gasp") - log_combat(M, M, "revived", src) + addtimer(CALLBACK(M, TYPE_PROC_REF(/mob/living, do_strange_reagent_revival)), 10 SECONDS) ..() /datum/reagent/medicine/strange_reagent/on_mob_life(mob/living/carbon/M)