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..567db521c2766 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) @@ -633,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/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/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) 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 0000000000000..e8fa45135f1df Binary files /dev/null and b/goon/icons/turfs/outdoors.dmi differ 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 0000000000000..70f93f53b2cce Binary files /dev/null and b/yogstation/icons/obj/effects/liquid.dmi differ diff --git a/yogstation/icons/obj/effects/liquid_overlays.dmi b/yogstation/icons/obj/effects/liquid_overlays.dmi new file mode 100644 index 0000000000000..b47120a5ff791 Binary files /dev/null and b/yogstation/icons/obj/effects/liquid_overlays.dmi differ diff --git a/yogstation/icons/obj/effects/splash.dmi b/yogstation/icons/obj/effects/splash.dmi new file mode 100644 index 0000000000000..f2cb774f59e29 Binary files /dev/null and b/yogstation/icons/obj/effects/splash.dmi differ diff --git a/yogstation/icons/obj/items/tiles.dmi b/yogstation/icons/obj/items/tiles.dmi new file mode 100644 index 0000000000000..2f9c4e81f00b1 Binary files /dev/null and b/yogstation/icons/obj/items/tiles.dmi differ diff --git a/yogstation/icons/obj/structures/drains.dmi b/yogstation/icons/obj/structures/drains.dmi new file mode 100644 index 0000000000000..5c305b7d23760 Binary files /dev/null and b/yogstation/icons/obj/structures/drains.dmi differ diff --git a/yogstation/icons/obj/structures/liquid_pump.dmi b/yogstation/icons/obj/structures/liquid_pump.dmi new file mode 100644 index 0000000000000..012dda8bb5e1d Binary files /dev/null and b/yogstation/icons/obj/structures/liquid_pump.dmi differ diff --git a/yogstation/icons/turf/floors/elevated_iron.dmi b/yogstation/icons/turf/floors/elevated_iron.dmi new file mode 100644 index 0000000000000..422ebaddd9a49 Binary files /dev/null and b/yogstation/icons/turf/floors/elevated_iron.dmi differ diff --git a/yogstation/icons/turf/floors/lowered_iron.dmi b/yogstation/icons/turf/floors/lowered_iron.dmi new file mode 100644 index 0000000000000..8171815033bef Binary files /dev/null and b/yogstation/icons/turf/floors/lowered_iron.dmi differ diff --git a/yogstation/icons/turf/floors/seafloor.dmi b/yogstation/icons/turf/floors/seafloor.dmi new file mode 100644 index 0000000000000..f78eed47f7ddb Binary files /dev/null and b/yogstation/icons/turf/floors/seafloor.dmi differ diff --git a/yogstation/sound/effects/splash.ogg b/yogstation/sound/effects/splash.ogg new file mode 100644 index 0000000000000..22f17c07901c4 Binary files /dev/null and b/yogstation/sound/effects/splash.ogg differ diff --git a/yogstation/sound/effects/water_wade1.ogg b/yogstation/sound/effects/water_wade1.ogg new file mode 100644 index 0000000000000..27527f9d8b8e5 Binary files /dev/null and b/yogstation/sound/effects/water_wade1.ogg differ diff --git a/yogstation/sound/effects/water_wade2.ogg b/yogstation/sound/effects/water_wade2.ogg new file mode 100644 index 0000000000000..f9c15cab64a90 Binary files /dev/null and b/yogstation/sound/effects/water_wade2.ogg differ diff --git a/yogstation/sound/effects/water_wade3.ogg b/yogstation/sound/effects/water_wade3.ogg new file mode 100644 index 0000000000000..3daccae01a1d6 Binary files /dev/null and b/yogstation/sound/effects/water_wade3.ogg differ diff --git a/yogstation/sound/effects/water_wade4.ogg b/yogstation/sound/effects/water_wade4.ogg new file mode 100644 index 0000000000000..7ba705f4991f0 Binary files /dev/null and b/yogstation/sound/effects/water_wade4.ogg differ diff --git a/yogstation/sound/effects/watersplash.ogg b/yogstation/sound/effects/watersplash.ogg new file mode 100644 index 0000000000000..a9a6e48608785 Binary files /dev/null and b/yogstation/sound/effects/watersplash.ogg differ