diff --git a/baystation12.dme b/baystation12.dme index 75cc64228b5..3936f304d8c 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -59,6 +59,7 @@ #include "code\__defines\languages.dm" #include "code\__defines\lazy_value.dm" #include "code\__defines\lighting.dm" +#include "code\__defines\liquids.dm" #include "code\__defines\machinery.dm" #include "code\__defines\mapping.dm" #include "code\__defines\materials.dm" @@ -175,6 +176,7 @@ #include "code\_helpers\time.dm" #include "code\_helpers\turfs.dm" #include "code\_helpers\type2type.dm" +#include "code\_helpers\type_processing.dm" #include "code\_helpers\unsorted.dm" #include "code\_helpers\vector.dm" #include "code\_helpers\warnings.dm" @@ -245,6 +247,7 @@ #include "code\controllers\subsystems\garbage.dm" #include "code\controllers\subsystems\inactivity.dm" #include "code\controllers\subsystems\lighting.dm" +#include "code\controllers\subsystems\liquids.dm" #include "code\controllers\subsystems\machines.dm" #include "code\controllers\subsystems\mapping.dm" #include "code\controllers\subsystems\misc_late.dm" @@ -1953,6 +1956,20 @@ #include "code\modules\lighting\lighting_source.dm" #include "code\modules\lighting\lighting_turf.dm" #include "code\modules\lighting\~lighting_undefs.dm" +#include "code\modules\liquids\drains.dm" +#include "code\modules\liquids\height_floors.dm" +#include "code\modules\liquids\tools.dm" +#include "code\modules\liquids\liquid_systems\liquid_effect.dm" +#include "code\modules\liquids\liquid_systems\liquid_groups.dm" +#include "code\modules\liquids\liquid_systems\liquid_height.dm" +#include "code\modules\liquids\liquid_systems\liquid_interaction.dm" +#include "code\modules\liquids\liquid_systems\liquid_plumbers.dm" +#include "code\modules\liquids\liquid_systems\liquid_pump.dm" +#include "code\modules\liquids\liquid_systems\liquid_status_effect.dm" +#include "code\modules\liquids\liquid_systems\liquid_turf.dm" +#include "code\modules\liquids\reagents\reagent_containers.dm" +#include "code\modules\liquids\reagents\chemistry\holder.dm" +#include "code\modules\liquids\reagents\chemistry\reagents.dm" #include "code\modules\lobby_music\_licenses.dm" #include "code\modules\lobby_music\_lobby_music.dm" #include "code\modules\lobby_music\crocketts_theme.dm" diff --git a/code/__defines/ces/signals.dm b/code/__defines/ces/signals.dm index 0ca3a4c7f75..99ae4702f44 100644 --- a/code/__defines/ces/signals.dm +++ b/code/__defines/ces/signals.dm @@ -13,3 +13,6 @@ #define SIGNAL_ELEMENT_ATTACH "element_attach" /// Fires on the target datum when an element is attached to it (/datum/element). #define SIGNAL_ELEMENT_DETACH "element_detach" + +/// Fires on the afterattack when an element is designed to clean liquids +#define SIGNAL_CLEAN_LIQUIDS "clean_liquids" diff --git a/code/__defines/ces/signals_atom.dm b/code/__defines/ces/signals_atom.dm index 04c83054120..283298ff633 100644 --- a/code/__defines/ces/signals_atom.dm +++ b/code/__defines/ces/signals_atom.dm @@ -25,6 +25,9 @@ /// From base of atom/proc/Initialize(): sent any time a new atom is created in this atom #define SIGNAL_ATOM_INITIALIZED_ON "atom_initialized_on" +/// Called on `/atom/movable/proc/handle_fall` (turf) +#define SIGNAL_ATOM_FALL "atom_fall" + /// Called on 'atom/Move' (/atom, old_turf, new_turf) #define SIGNAL_Z_CHANGED "movable_z_changed" diff --git a/code/__defines/ces/signals_turf.dm b/code/__defines/ces/signals_turf.dm index 066bbb3f53c..2f57934871a 100644 --- a/code/__defines/ces/signals_turf.dm +++ b/code/__defines/ces/signals_turf.dm @@ -1,2 +1,3 @@ /// Called on `/turf/proc/ChangeTurf` (/turf, old_density, density, old_opacity, opacity) #define SIGNAL_TURF_CHANGED "turf_changed" +#define SIGNAL_TURF_LIQUIDS_CREATION "turf_liquids_creation" diff --git a/code/__defines/lighting.dm b/code/__defines/lighting.dm index abd5ff50970..1d3ecf992b8 100644 --- a/code/__defines/lighting.dm +++ b/code/__defines/lighting.dm @@ -30,3 +30,9 @@ #define LIGHTMODE_ALARM "alarm" #define LIGHTMODE_READY "ready" #define LIGHTMODE_RADSTORM "radiation_storm" + +///How many tiles standard fires glow. +#define LIGHT_RANGE_FIRE 3 +#define LIGHT_FIRE_BLOSSOM 2.1 +#define LIGHT_RANGE_FIRE_BLOSSOM_HARVESTED 2.7 +#define LIGHT_POWER_FIRE_BLOSSOM_HARVESTED 1.5 diff --git a/code/__defines/liquids.dm b/code/__defines/liquids.dm new file mode 100644 index 00000000000..d67b6017e10 --- /dev/null +++ b/code/__defines/liquids.dm @@ -0,0 +1,83 @@ +#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_EVAPORATION_PROCESSES 80 +#define EVAPORATION_CHANCE 30 + +/// Portion (out of 1) of reagents that are lost during the transfer from a mop/towel to a container. +#define SQUEEZING_DISPERSAL_RATIO 0.75 + +#define REQUIRED_FIRE_PROCESSES 4 +#define REQUIRED_FIRE_POWER_PER_UNIT 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 100 //Hundred loops at maximum for adjacency checking + +//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 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 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,(carbon.lying ? liquids.liquid_state+LYING_DOWN_SUBMERGEMENT_STATE_BONUS : liquids.liquid_state)/TOTAL_LIQUID_STATES) diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index da375872552..9451205d441 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -101,7 +101,7 @@ #define TEMPLATE_FLAG_ALLOW_DUPLICATES 1 // Lets multiple copies of the template to be spawned #define TEMPLATE_FLAG_SPAWN_GUARANTEED 2 // Makes it ignore away site budget and just spawn (only for away sites) #define TEMPLATE_FLAG_CLEAR_CONTENTS 4 // if it should destroy objects it spawns on top of -#define TEMPLATE_FLAG_NO_RUINS 8 // if it should forbid ruins from spawning on top of it +#define TEMPLATE_FLAG_TURF_FLAG_NORUINS 8 // if it should forbid ruins from spawning on top of it #define CUSTOM_ITEM_OBJ 'icons/obj/custom_items_obj.dmi' #define CUSTOM_ITEM_MOB null diff --git a/code/_global_vars/mapping.dm b/code/_global_vars/mapping.dm index 2b11bdc4778..630dab2eb98 100644 --- a/code/_global_vars/mapping.dm +++ b/code/_global_vars/mapping.dm @@ -1 +1,2 @@ GLOBAL_LIST_EMPTY(ruin_landmarks) +GLOBAL_LIST_EMPTY(areas) diff --git a/code/_helpers/atmospherics.dm b/code/_helpers/atmospherics.dm index 8fa90bd9c65..e7b79c11b49 100644 --- a/code/_helpers/atmospherics.dm +++ b/code/_helpers/atmospherics.dm @@ -54,3 +54,35 @@ return . += SPAN_WARNING("\The [target] has no gases!") + +/turf/proc/get_atmos_adjacent_turfs() + var/list/atmos_adjacent_turfs = list() + var/canpass = CanZASPass(src) + for(var/direction in GLOB.cardinalz) + var/turf/current_turf + if(direction != UP && direction != DOWN) + current_turf = get_step(src, direction) + if(direction == UP) + current_turf = GetAbove(src) + current_turf = istype(current_turf, /turf/simulated/open) ? current_turf : null + + if(direction == DOWN) + current_turf = istype(src, /turf/simulated/open) ? GetBelow(src) : null + + if(!istype(current_turf, /turf/simulated)) // not interested in you brother + continue + + if(canpass && CanZASPass(current_turf) && !(blocks_air || current_turf.blocks_air)) + LAZYINITLIST(current_turf.atmos_adjacent_turfs) + LAZYINITLIST(atmos_adjacent_turfs) + atmos_adjacent_turfs[current_turf] = TRUE + current_turf.atmos_adjacent_turfs[src] = TRUE + else + LAZYREMOVE(atmos_adjacent_turfs, current_turf) + if (current_turf.atmos_adjacent_turfs) + LAZYREMOVE(current_turf.atmos_adjacent_turfs, src) + UNSETEMPTY(current_turf.atmos_adjacent_turfs) + + UNSETEMPTY(atmos_adjacent_turfs) + src.atmos_adjacent_turfs = atmos_adjacent_turfs + return atmos_adjacent_turfs diff --git a/code/_helpers/atom_movables.dm b/code/_helpers/atom_movables.dm index fceac71a518..6836fdeef86 100644 --- a/code/_helpers/atom_movables.dm +++ b/code/_helpers/atom_movables.dm @@ -42,3 +42,34 @@ turfs -= get_turf(src) if(length(turfs)) throw_at(pick(turfs), maxrange, speed, src) + +///Returns a chosen path that is the closest to a list of matches +/proc/pick_closest_path(value, list/matches = get_fancy_list_of_atom_types()) + if (value == FALSE) //nothing should be calling us with a number, so this is safe + value = input("Enter type to find (blank for all, cancel to cancel)", "Search for type") as null|text + if (isnull(value)) + return + value = trim(value) + + var/random = FALSE + if(findtext(value, "?")) + value = replacetext(value, "?", "") + random = TRUE + + if(!isnull(value) && value != "") + matches = filter_fancy_list(matches, value) + + if(matches.len == 0) + return + + var/chosen + if(matches.len == 1) + chosen = matches[1] + else if(random) + chosen = pick(matches) || null + else + chosen = input("Select a type", "Pick Type", matches[1]) as null|anything in sort_list(matches) + if(!chosen) + return + chosen = matches[chosen] + return chosen diff --git a/code/_helpers/type_processing.dm b/code/_helpers/type_processing.dm new file mode 100644 index 00000000000..85a46fd640f --- /dev/null +++ b/code/_helpers/type_processing.dm @@ -0,0 +1,54 @@ +/proc/make_types_fancy(list/types) + if (ispath(types)) + types = list(types) + . = list() + for(var/type in types) + var/typename = "[type]" + var/static/list/TYPES_SHORTCUTS = list( + /obj/effect/decal/cleanable = "CLEANABLE", + /obj/item/device/radio/headset = "HEADSET", + /obj/item/clothing/head/helmet/space = "SPESSHELMET", + /obj/item/reagent_containers/vessel/glass = "DRINK", //longest paths comes first + /obj/item/reagent_containers/food = "FOOD", + /obj/item/reagent_containers = "REAGENT_CONTAINERS", + /obj/machinery/atmospherics = "ATMOS_MECH", + /obj/machinery/portable_atmospherics = "PORT_ATMOS", + /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack = "MECHA_MISSILE_RACK", + /obj/item/mecha_parts/mecha_equipment = "MECHA_EQUIP", + /obj/item/organ = "ORGAN", + /obj/item = "ITEM", + /obj/machinery = "MACHINERY", + /obj/effect = "EFFECT", + /obj = "O", + /datum = "D", + /turf = "T", + /mob/living/carbon = "CARBON", + /mob/living/simple_animal = "SIMPLE", + /mob/living = "LIVING", + /mob = "M" + ) + for (var/tn in TYPES_SHORTCUTS) + if(copytext(typename, 1, length("[tn]/") + 1) == "[tn]/" /*findtextEx(typename,"[tn]/",1,2)*/ ) + typename = TYPES_SHORTCUTS[tn] + copytext(typename, length("[tn]/")) + break + .[typename] = type + +/proc/get_fancy_list_of_atom_types() + var/static/list/pre_generated_list + if (!pre_generated_list) //init + pre_generated_list = make_types_fancy(typesof(/atom)) + return pre_generated_list + +/proc/filter_fancy_list(list/L, filter as text) + var/list/matches = new + var/end_len = -1 + var/list/endcheck = splittext(filter, "!") + if(endcheck.len > 1) + filter = endcheck[1] + end_len = length_char(filter) + + for(var/key in L) + var/value = L[key] + if(findtext("[key]", filter, -end_len) || findtext("[value]", filter, -end_len)) + matches[key] = value + return matches diff --git a/code/controllers/subsystems/liquids.dm b/code/controllers/subsystems/liquids.dm new file mode 100644 index 00000000000..468026606be --- /dev/null +++ b/code/controllers/subsystems/liquids.dm @@ -0,0 +1,145 @@ +SUBSYSTEM_DEF(liquids) + name = "Liquid Turfs" + wait = 1 SECONDS + flags = SS_KEEP_TIMING | SS_NO_INIT + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/list/active_turfs = list() + var/list/currentrun_active_turfs = list() + + var/list/active_groups = list() + + var/list/active_immutables = list() + + var/list/evaporation_queue = list() + var/evaporation_counter = 0 //Only process evaporation on intervals + + var/list/processing_fire = list() + var/fire_counter = 0 //Only process fires on intervals + + var/list/singleton_immutables = list() + + var/run_type = SSLIQUIDS_RUN_TYPE_TURFS + +/datum/controller/subsystem/liquids/proc/get_immutable(type) + if(!singleton_immutables[type]) + var/obj/effect/abstract/liquid_turf/immutable/new_one = new type() + singleton_immutables[type] = new_one + return singleton_immutables[type] + +/datum/controller/subsystem/liquids/Initialize() + fire(FALSE, TRUE) + . = ..() + +/datum/controller/subsystem/liquids/stat_entry(msg) + msg += "AT:[active_turfs.len]|AG:[active_groups.len]|AIM:[active_immutables.len]|EQ:[evaporation_queue.len]|PF:[processing_fire.len]" + return ..() + + +/datum/controller/subsystem/liquids/fire(resumed = FALSE, no_mc_tick = FALSE) + if(run_type == SSLIQUIDS_RUN_TYPE_TURFS) + if(!currentrun_active_turfs.len && active_turfs.len) + currentrun_active_turfs = active_turfs.Copy() + for(var/tur in currentrun_active_turfs) + if (no_mc_tick) + CHECK_TICK + else if (MC_TICK_CHECK) + return + var/turf/T = tur + T.process_liquid_cell() + currentrun_active_turfs -= T //work off of index later + if(!currentrun_active_turfs.len) + run_type = SSLIQUIDS_RUN_TYPE_GROUPS + if (run_type == SSLIQUIDS_RUN_TYPE_GROUPS) + for(var/g in active_groups) + var/datum/liquid_group/LG = g + if(LG.dirty) + LG.share() + LG.dirty = FALSE + else if(!LG.amount_of_active_turfs) + LG.decay_counter++ + if(LG.decay_counter >= LIQUID_GROUP_DECAY_TIME) + //Perhaps check if any turfs in here can spread before removing it? It's not unlikely they would + LG.break_group() + if(MC_TICK_CHECK) + run_type = SSLIQUIDS_RUN_TYPE_IMMUTABLES //No currentrun here for now + return + run_type = SSLIQUIDS_RUN_TYPE_IMMUTABLES + if(run_type == SSLIQUIDS_RUN_TYPE_IMMUTABLES) + for(var/t in active_immutables) + var/turf/T = t + T.process_immutable_liquid() + /* + if (no_mc_tick) + CHECK_TICK + else if (MC_TICK_CHECK) + return + */ + run_type = SSLIQUIDS_RUN_TYPE_EVAPORATION + + if(run_type == SSLIQUIDS_RUN_TYPE_EVAPORATION) + evaporation_counter++ + if(evaporation_counter >= REQUIRED_EVAPORATION_PROCESSES) + for(var/t in evaporation_queue) + var/turf/T = t + if(prob(EVAPORATION_CHANCE)) + T.liquids.process_evaporation() + if(MC_TICK_CHECK) + return + evaporation_counter = 0 + run_type = SSLIQUIDS_RUN_TYPE_FIRE + + if(run_type == SSLIQUIDS_RUN_TYPE_FIRE) + fire_counter++ + if(fire_counter >= REQUIRED_FIRE_PROCESSES) + for(var/t in processing_fire) + var/turf/T = t + T.liquids.process_fire() + if (no_mc_tick) + CHECK_TICK + else if (MC_TICK_CHECK) + return + fire_counter = 0 + run_type = SSLIQUIDS_RUN_TYPE_TURFS + +/datum/controller/subsystem/liquids/proc/add_active_turf(turf/T) + if(!active_turfs[T]) + active_turfs[T] = TRUE + if(T.lgroup) + T.lgroup.amount_of_active_turfs++ + +/datum/controller/subsystem/liquids/proc/remove_active_turf(turf/T) + if(active_turfs[T]) + active_turfs -= T + if(T.lgroup) + T.lgroup.amount_of_active_turfs-- + +/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 + +/proc/mix_color_from_reagent_list(list/reagent_list) + var/mixcolor + var/vol_counter = 0 + var/vol_temp + var/cached_color + var/datum/reagent/raw_reagent + + for(var/reagent_type in reagent_list) + vol_temp = reagent_list[reagent_type] + vol_counter += vol_temp + raw_reagent = reagent_type // Not initialized + cached_color = initial(raw_reagent.color) + + if(!mixcolor) + mixcolor = cached_color + else if (length(mixcolor) >= length(cached_color)) + mixcolor = BlendRGB(mixcolor, cached_color, vol_temp/vol_counter) + else + mixcolor = BlendRGB(cached_color, mixcolor, vol_temp/vol_counter) + + return mixcolor diff --git a/code/controllers/subsystems/mapping.dm b/code/controllers/subsystems/mapping.dm index ed427bfbc3f..7ae27d6b4b3 100644 --- a/code/controllers/subsystems/mapping.dm +++ b/code/controllers/subsystems/mapping.dm @@ -6,6 +6,9 @@ SUBSYSTEM_DEF(mapping) var/list/map_templates = list() var/list/holodeck_templates = list() + ///All possible biomes in assoc list as type || instance + var/list/biomes = list() + /datum/controller/subsystem/mapping/Initialize(timeofday) preloadTemplates() preloadHolodeckTemplates() diff --git a/code/datums/mapgen/_MapGenerator.dm b/code/datums/mapgen/_MapGenerator.dm new file mode 100644 index 00000000000..a3c5fb299fc --- /dev/null +++ b/code/datums/mapgen/_MapGenerator.dm @@ -0,0 +1,8 @@ +///This type is responsible for any map generation behavior that is done in areas, override this to allow for area-specific map generation. This generation is ran by areas in initialize. +/datum/map_generator + +///This proc will be ran by areas on Initialize, and provides the areas turfs as argument to allow for generation. +/datum/map_generator/proc/generate_terrain(list/turfs, area/generate_in) + return + +/proc/perlin_noise(seed, x, y) diff --git a/code/datums/mapgen/biomes/_biome.dm b/code/datums/mapgen/biomes/_biome.dm new file mode 100644 index 00000000000..cf2d68de9e1 --- /dev/null +++ b/code/datums/mapgen/biomes/_biome.dm @@ -0,0 +1,26 @@ +///This datum handles the transitioning from a turf to a specific biome, and handles spawning decorative structures and mobs. +/datum/biome + ///Type of turf this biome creates + var/turf_type + ///Chance of having a structure from the flora types list spawn + var/flora_density = 0 + ///Chance of having a mob from the fauna types list spawn + var/fauna_density = 0 + ///list of type paths of objects that can be spawned when the turf spawns flora + var/list/flora_types = list(/turf/simulated/floor/natural/jungle/sand) + ///list of type paths of mobs that can be spawned when the turf spawns fauna + var/list/fauna_types = list() + +///This proc handles the creation of a turf of a specific biome type +/datum/biome/proc/generate_turf(turf/gen_turf) + gen_turf.ChangeTurf(turf_type) + if(length(fauna_types) && prob(fauna_density)) + var/mob/fauna = pick(fauna_types) + new fauna(gen_turf) + + if(length(flora_types) && prob(flora_density)) + var/obj/structure/flora = pick(flora_types) + new flora(gen_turf) + +/datum/biome/water + turf_type = /turf/simulated/floor/natural/jungle/water diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index 26705fb48bb..704198bb7d0 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -20,10 +20,13 @@ var/importance = 1 var/loyalty = 0 + ///This datum, if set, allows terrain generation behavior to be ran on Initialize() + var/datum/map_generator/map_generator + /area/New() icon_state = "" uid = ++global_uid - + GLOB.areas += src if(!requires_power) power_light = 0 power_equip = 0 diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 43dcf6f72ac..ab0d2562915 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -323,7 +323,6 @@ its easier to just keep the beam vertical. /atom/proc/examine(...) SHOULD_NOT_OVERRIDE(TRUE) - var/content = "
" content += _examine_text(arglist(args)) diff --git a/code/game/objects/items/storage/lockbox.dm b/code/game/objects/items/storage/lockbox.dm index dfa7a134902..847d6d5b239 100644 --- a/code/game/objects/items/storage/lockbox.dm +++ b/code/game/objects/items/storage/lockbox.dm @@ -142,4 +142,3 @@ broken = !broken update_icon() return - diff --git a/code/game/objects/items/weapons/mop.dm b/code/game/objects/items/weapons/mop.dm index 5b05d354eb7..665aeeded22 100644 --- a/code/game/objects/items/weapons/mop.dm +++ b/code/game/objects/items/weapons/mop.dm @@ -14,10 +14,19 @@ /obj/item/mop/New() create_reagents(30) + AddComponent(/datum/component/liquids_interaction, /obj/item/mop/proc/attack_on_liquids_turf) /obj/item/mop/afterattack(atom/A, mob/user, proximity) if(!proximity) return + + var/turf/turf_to_clean = A + + // Disable normal cleaning if there are liquids. + if(isturf(A) && turf_to_clean.liquids) + SEND_SIGNAL(src, SIGNAL_CLEAN_LIQUIDS, turf_to_clean, user) + return FALSE + if(istype(A, /turf) || istype(A, /obj/effect/decal/cleanable) || istype(A, /obj/effect/overlay) || istype(A, /obj/effect/rune)) if(reagents.total_volume < 1) to_chat(user, "Your mop is dry!") @@ -38,3 +47,27 @@ if(istype(I, /obj/item/mop) || istype(I, /obj/item/soap)) return ..() + +/** + * Proc to remove liquids from a turf using a mop. + * + * Arguments: + * * tile - On which tile we're trying to absorb liquids + * * user - Who tries to absorb liquids with this? + * * liquids - Liquids we're trying to absorb. + */ +/obj/item/mop/proc/attack_on_liquids_turf(turf/tile, mob/user, obj/effect/abstract/liquid_turf/liquids) + if(!in_range(user, tile)) + return FALSE + + var/free_space = reagents.maximum_volume - reagents.total_volume + if(free_space <= 0) + to_chat(user, SPAN_WARNING("Your [src] can't absorb any more liquid!")) + return TRUE + + var/datum/reagents/tempr = liquids.take_reagents_flat(free_space) + tempr.trans_to_obj(src, tempr.total_volume) + to_chat(user, SPAN_NOTICE("You soak \the [src] with some liquids.")) + qdel(tempr) + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) + return TRUE diff --git a/code/game/objects/items/weapons/towels.dm b/code/game/objects/items/weapons/towels.dm index 199715e4b4b..29ee323d01e 100644 --- a/code/game/objects/items/weapons/towels.dm +++ b/code/game/objects/items/weapons/towels.dm @@ -15,10 +15,25 @@ drop_sound = SFX_DROP_CLOTH pickup_sound = SFX_PICKUP_CLOTH +/obj/item/towel/Initialize() + . = ..() + AddComponent(/datum/component/liquids_interaction, /obj/item/towel/proc/attack_on_liquids_turf) + /obj/item/towel/attack_self(mob/living/user as mob) user.visible_message(text("[] uses [] to towel themselves off.", user, src)) playsound(user, 'sound/weapons/towelwipe.ogg', 25, 1) +/obj/item/towel/afterattack(atom/A, mob/user, proximity) + if(!proximity) + return + + var/turf/turf_to_clean = A + + // Disable normal cleaning if there are liquids. + if(isturf(A) && turf_to_clean.liquids) + SEND_SIGNAL(src, SIGNAL_CLEAN_LIQUIDS, turf_to_clean, user) + return + /obj/item/towel/random/New() ..() color = get_random_colour() @@ -29,3 +44,33 @@ color = "#ffd700" force = 1 attack_verb = list("smote") + +/** + * The procedure for remove liquids from turf + * + * The object is called from liquid_interaction element. + * The procedure check range of mop owner and tile, then check reagents in mop, if reagents volume < mop capacity - liquids absorbs from tile + * In another way, input a chat about mop capacity + * Arguments: + * * towel - Towel used to absorb liquids + * * tile - On which tile the towel will try to absorb liquids + * * user - Who tries to absorb liquids with the towel + * * liquids - Liquids that user tries to absorb with the towel + */ +/obj/item/towel/proc/attack_on_liquids_turf(turf/tile, mob/user, obj/effect/abstract/liquid_turf/liquids) + if(!in_range(user, tile)) + return FALSE + + var/free_space = reagents.maximum_volume - reagents.total_volume + if(free_space <= 0) + to_chat(user, SPAN_WARNING("Your [src] can't absorb any more liquid!")) + return TRUE + + var/datum/reagents/temp_holder = liquids.take_reagents_flat(free_space) + temp_holder.trans_to_obj(src, temp_holder.total_volume) + + to_chat(user, SPAN_NOTICE("You soak \the [src] with some liquids.")) + + qdel(temp_holder) + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) + return TRUE diff --git a/code/game/objects/structures/mop_bucket.dm b/code/game/objects/structures/mop_bucket.dm index f209cc1da73..8f1fb5c7861 100644 --- a/code/game/objects/structures/mop_bucket.dm +++ b/code/game/objects/structures/mop_bucket.dm @@ -21,6 +21,20 @@ if(get_dist(src, user) <= 1) . += "\n[src] \icon[src] contains [reagents.total_volume] unit\s of water!" +/obj/structure/mopbucket/ShiftClick(mob/user) + . = ..() + var/obj/O = user.get_active_hand() + if(istype(O, /obj/item/mop)) + if(O.reagents.total_volume == 0) + to_chat(user, "[O] is dry, you can't squeeze anything out!") + return + if(reagents.total_volume == reagents.maximum_volume) + to_chat(user, "[src] is full!") + return + O.reagents.remove_any(O.reagents.total_volume * SQUEEZING_DISPERSAL_RATIO) + O.reagents.trans_to(src, O.reagents.total_volume) + to_chat(user, "You squeeze the liquids from [O] to [src].") + /obj/structure/mopbucket/attackby(obj/item/I, mob/user) if(istype(I, /obj/item/mop)) if(reagents.total_volume < 1) diff --git a/code/game/objects/structures/rock.dm b/code/game/objects/structures/rock.dm index 9b35450356d..2a73ff4da32 100644 --- a/code/game/objects/structures/rock.dm +++ b/code/game/objects/structures/rock.dm @@ -9,18 +9,20 @@ var/list/iconlist = list("asteroid_bigstone1","asteroid_bigstone2","asteroid_bigstone3","asteroid_bigstone4") var/health = 40 var/last_act = 0 + var/harvest_amount_low = 2 + var/harvest_amount_high = 6 + var/mineralSpawnChanceList = list(uranium = 10, osmium = 10, iron = 20, coal = 20, diamond = 2, gold = 10, silver = 10, plasma = 20) /obj/structure/rock/New() ..() icon_state = pick(iconlist) /obj/structure/rock/Destroy() - var/mineralSpawnChanceList = list(uranium = 10, osmium = 10, iron = 20, coal = 20, diamond = 2, gold = 10, silver = 10, plasma = 20) if(prob(20)) var/mineral_name = util_pick_weight(mineralSpawnChanceList) //temp mineral name mineral_name = lowertext(mineral_name) var/ore = text2path("/obj/item/ore/[mineral_name]") - for(var/i=1,i <= rand(2,6),i++) + for(var/i=1,i <= rand(harvest_amount_low, harvest_amount_high),i++) new ore(get_turf(src)) return ..() diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index 59e2a7d8f2b..d491c9a1c29 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -425,6 +425,19 @@ for(var/mob/V in viewers(src, null)) V.show_message("[user] washes their hands using \the [src].") +/obj/structure/sink/ShiftClick(mob/user) + . = ..() + var/obj/O = user.get_active_hand() + if(istype(O, /obj/item/mop)) + if(O.reagents.total_volume == 0) + to_chat(user, "[O] is dry, you can't squeeze anything out!") + return + if(reagents.total_volume == reagents.maximum_volume) + to_chat(user, "[src] is full!") + return + O.reagents.remove_any(O.reagents.total_volume * SQUEEZING_DISPERSAL_RATIO) + O.reagents.trans_to(src, O.reagents.total_volume) + to_chat(user, "You squeeze the liquids from [O] to [src].") /obj/structure/sink/attackby(obj/item/O as obj, mob/living/user as mob) if(busy) diff --git a/code/game/turfs/simulated/wall_attacks.dm b/code/game/turfs/simulated/wall_attacks.dm index bba402077fe..ea052fe1f87 100644 --- a/code/game/turfs/simulated/wall_attacks.dm +++ b/code/game/turfs/simulated/wall_attacks.dm @@ -18,6 +18,7 @@ set_opacity(0) for(var/turf/simulated/turf in loc) SSair.mark_for_update(turf) + turf.liquid_update_turf() else can_open = WALL_OPENING //flick("[material.icon_base]fwall_closing", src) @@ -33,6 +34,7 @@ shove_everything() for(var/turf/simulated/turf in loc) SSair.mark_for_update(turf) + turf.liquid_update_turf() can_open = WALL_CAN_OPEN update_icon() @@ -44,6 +46,7 @@ for(var/turf/simulated/turf in loc) update_thermal(turf) SSair.mark_for_update(turf) + turf.liquid_update_turf() /turf/simulated/wall/proc/update_thermal(turf/simulated/source) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 818e23b2943..5d9277dc1ec 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -38,6 +38,8 @@ var/changing_turf var/footstep_sound = SFX_FOOTSTEP_PLATING + ///list of turfs adjacent to us that air can flow onto + var/list/atmos_adjacent_turfs var/turf_height = 0 // "Vertical" offset. Mostly used for mobs and dropped items. @@ -57,6 +59,7 @@ luminosity = 1 RecalculateOpacity() + get_atmos_adjacent_turfs() /turf/Destroy() if(!changing_turf) diff --git a/code/game/turfs/turf_changing.dm b/code/game/turfs/turf_changing.dm index 19c708cec11..14ac4727c30 100644 --- a/code/game/turfs/turf_changing.dm +++ b/code/game/turfs/turf_changing.dm @@ -94,7 +94,7 @@ GLOB.universe.OnTurfChange(W) SSair.mark_for_update(src) //handle the addition of the new turf. - + liquid_update_turf() for(var/turf/space/S in range(W,1)) S.update_starlight() diff --git a/code/modules/ZAS/Atom.dm b/code/modules/ZAS/Atom.dm index 70d4847aacc..07036b95620 100644 --- a/code/modules/ZAS/Atom.dm +++ b/code/modules/ZAS/Atom.dm @@ -50,6 +50,7 @@ /atom/movable/proc/update_nearby_tiles(need_rebuild) for(var/turf/simulated/turf in locs) SSair.mark_for_update(turf) + turf.liquid_update_turf() return 1 diff --git a/code/modules/ZAS/Fire.dm b/code/modules/ZAS/Fire.dm index 94347104ec5..dafc6e36024 100644 --- a/code/modules/ZAS/Fire.dm +++ b/code/modules/ZAS/Fire.dm @@ -34,6 +34,7 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin igniting = 1 create_fire(exposed_temperature) + liquids?.fire_act() return igniting /zone/proc/process_fire() diff --git a/code/modules/ZAS/Zone.dm b/code/modules/ZAS/Zone.dm index 67fe6e15abb..9a3da0d9c0b 100644 --- a/code/modules/ZAS/Zone.dm +++ b/code/modules/ZAS/Zone.dm @@ -135,6 +135,7 @@ Class Procs: continue //don't need to rebuild this edge for(var/turf/T in E.connecting_turfs) SSair.mark_for_update(T) + T.liquid_update_turf() /zone/proc/c_invalidate() invalid = 1 @@ -152,6 +153,7 @@ Class Procs: //T.dbg(invalid_zone) T.needs_air_update = 0 //Reset the marker so that it will be added to the list. SSair.mark_for_update(T) + T.liquid_update_turf() /zone/proc/add_tile_air(datum/gas_mixture/tile_air) //air.volume += CELL_VOLUME diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index d5833b80190..085e96d2fda 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -143,7 +143,9 @@ var/list/admin_verbs_fun = list( /client/proc/create_dungeon, /datum/admins/proc/ai_hologram_set, /client/proc/projectile_basketball, - /client/proc/toggle_possess_mode + /client/proc/toggle_possess_mode, + /client/proc/spawn_liquid, + /client/proc/remove_liquid, ) var/list/admin_verbs_spawn = list( diff --git a/code/modules/liquids/drains.dm b/code/modules/liquids/drains.dm new file mode 100644 index 00000000000..f1ff4d4327a --- /dev/null +++ b/code/modules/liquids/drains.dm @@ -0,0 +1,85 @@ +//Structure as this doesn't need any power to work +/obj/structure/drain + name = "drain" + icon = 'icons/obj/liquids/structures/drains.dmi' + icon_state = "drain" + desc = "Drainage inlet embedded in the floor to prevent flooding." + density = FALSE + plane = TURF_PLANE + layer = EXPOSED_PIPE_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/attackby(obj/item/O, mob/user) + . = ..() + if(isWelder(O)) + welder_act(user, O) + +/obj/structure/drain/proc/welder_act(mob/living/user, obj/item/weldingtool/WT) + if(!WT.use_tool(src, user, delay= 4 SECONDS, amount = 5)) + return + + playsound(src, 'sound/items/welder2.ogg', 50, TRUE) + to_chat(user, SPAN_NOTICE("You start [welded ? "unwelding" : "welding"] [src]...")) + if(do_after(user, 40, src)) + if(!src || !user || !WT.remove_fuel(5, user)) return + to_chat(user, SPAN_NOTICE("You [welded ? "unweld" : "weld"] [src].")) + welded = !welded + update_icon() + if(welded) + if(processing) + processing = FALSE + set_next_think(0) + else if (my_turf.liquids) + set_next_think(world.time+1) + processing = TRUE + return TRUE + +/obj/structure/drain/think() + if(!my_turf.liquids || my_turf.liquids.immutable) + set_next_think(0) + processing = FALSE + return + my_turf.liquids.liquid_simple_delete_flat(drain_flat + (drain_percent * my_turf.liquids.total_reagents)) + set_next_think(world.time+1) + +/obj/structure/drain/Initialize(mapload) + . = ..() + if(!isturf(loc)) + CRASH("Drain structure initialized not on a turf") + my_turf = loc + register_signal(my_turf, SIGNAL_TURF_LIQUIDS_CREATION, .proc/liquids_signal) + if(my_turf.liquids) + set_next_think(world.time+1) + processing = TRUE + +/obj/structure/drain/proc/liquids_signal() + SIGNAL_HANDLER + if(processing || welded) + return + set_next_think(world.time+1) + processing = TRUE + +/obj/structure/drain/Destroy() + if(processing) + set_next_think(0) + unregister_signal(my_turf, SIGNAL_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/code/modules/liquids/height_floors.dm b/code/modules/liquids/height_floors.dm new file mode 100644 index 00000000000..825616954ac --- /dev/null +++ b/code/modules/liquids/height_floors.dm @@ -0,0 +1,27 @@ + +/turf/simulated/floor/smoothable/iron/pool + name = "pool floor" + //tile_type = /obj/item/stack/tile/iron/pool + icon = 'icons/turf/pool_tile.dmi' + base_icon_state = "pool_tile" + icon_state = "pool_tile" + liquid_height = -30 + turf_height = -30 + +/turf/simulated/floor/smoothable/iron/elevated + name = "elevated floor" + //tile_type = /obj/item/stack/tile/iron/elevated + icon = 'icons/turf/elevated_plasteel.dmi' + icon_state = "elevated_plasteel-0" + base_icon_state = "elevated_plasteel" + liquid_height = 30 + turf_height = 30 + +/turf/simulated/floor/smoothable/iron/lowered + name = "lowered floor" + //tile_type = /obj/item/stack/tile/iron/lowered + icon = 'icons/turf/lowered_plasteel.dmi' + icon_state = "lowered_plasteel-0" + base_icon_state = "lowered_plasteel" + liquid_height = -30 + turf_height = -30 diff --git a/code/modules/liquids/liquid_systems/liquid_effect.dm b/code/modules/liquids/liquid_systems/liquid_effect.dm new file mode 100644 index 00000000000..2d852a89093 --- /dev/null +++ b/code/modules/liquids/liquid_systems/liquid_effect.dm @@ -0,0 +1,621 @@ +/obj/effect/abstract/liquid_turf + name = "liquid" + icon = 'icons/effects/liquid.dmi' + icon_state = "water-0" + anchored = TRUE + plane = TURF_PLANE + layer = SHALLOW_FLUID_LAYER + color = "#DDF" + + //For being on fire + var/light_range = 0 + var/light_power = 1 + light_color = "#FAA019" + + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + var/height = 1 + var/only_big_diffs = 1 + var/turf/my_turf + var/liquid_state = LIQUID_STATE_PUDDLE + var/has_cached_share = FALSE + + var/attrition = 0 + + var/immutable = FALSE + + var/list/reagent_list = list() + var/total_reagents = 0 + var/temp = 20 CELSIUS + + var/fire_state = LIQUID_FIRE_STATE_NONE + + var/no_effects = FALSE + + +/obj/effect/abstract/liquid_turf/proc/check_fire(hotspotted = FALSE) + var/my_burn_power = get_burn_power(hotspotted) + if(!my_burn_power) + if(fire_state) + //Set state to 0 + set_fire_state(LIQUID_FIRE_STATE_NONE) + return FALSE + //Calculate appropriate state + var/new_state = LIQUID_FIRE_STATE_SMALL + switch(my_burn_power) + if(0 to 7) + new_state = LIQUID_FIRE_STATE_SMALL + if(7 to 8) + new_state = LIQUID_FIRE_STATE_MILD + if(8 to 9) + new_state = LIQUID_FIRE_STATE_MEDIUM + if(9 to 10) + new_state = LIQUID_FIRE_STATE_HUGE + if(10 to INFINITY) + new_state = LIQUID_FIRE_STATE_INFERNO + + if(fire_state != new_state) + set_fire_state(new_state) + + return TRUE + +/obj/effect/abstract/liquid_turf/proc/set_fire_state(new_state) + fire_state = new_state + switch(fire_state) + if(LIQUID_FIRE_STATE_NONE) + light_outer_range = 0 + if(LIQUID_FIRE_STATE_SMALL) + light_outer_range = LIGHT_RANGE_FIRE + if(LIQUID_FIRE_STATE_MILD) + light_outer_range = LIGHT_RANGE_FIRE + if(LIQUID_FIRE_STATE_MEDIUM) + light_outer_range = LIGHT_RANGE_FIRE + if(LIQUID_FIRE_STATE_HUGE) + light_outer_range = LIGHT_RANGE_FIRE + if(LIQUID_FIRE_STATE_INFERNO) + light_outer_range = LIGHT_RANGE_FIRE + update_light() + update_overlays() + +/obj/effect/abstract/liquid_turf/proc/get_burn_power(hotspotted = FALSE) + //We are not on fire and werent ignited by a hotspot exposure, no fire pls + if(!hotspotted && !fire_state) + return FALSE + var/total_burn_power = 0 + var/datum/reagent/R //Faster declaration + for(var/reagent_type in reagent_list) + R = reagent_type + var/burn_power = initial(R.liquid_fire_power) + if(burn_power) + total_burn_power += burn_power * reagent_list[reagent_type] + if(!total_burn_power) + return FALSE + total_burn_power /= total_reagents //We get burn power per unit. + if(total_burn_power <= REQUIRED_FIRE_POWER_PER_UNIT) + return FALSE + //Finally, we burn + return total_burn_power + +/obj/effect/abstract/liquid_turf/proc/process_fire() + if(!fire_state) + SSliquids.processing_fire -= my_turf + set_fire_state(LIQUID_FIRE_STATE_NONE) + var/old_state = fire_state + if(!check_fire()) + SSliquids.processing_fire -= my_turf + //Try spreading + if(fire_state == old_state) //If an extinguisher made our fire smaller, dont spread, else it's too hard to put out + for(var/turf/T in my_turf.atmos_adjacent_turfs) + if(T.liquids && !T.liquids.fire_state && T.liquids.check_fire(TRUE)) + SSliquids.processing_fire[T] = TRUE + //Burn our resources + var/datum/reagent/R //Faster declaration + var/burn_rate + for(var/reagent_type in reagent_list) + R = reagent_type + burn_rate = initial(R.liquid_fire_burnrate) + if(burn_rate) + var/amt = reagent_list[reagent_type] + if(burn_rate >= amt) + reagent_list -= reagent_type + total_reagents -= amt + else + reagent_list[reagent_type] -= burn_rate + total_reagents -= burn_rate + + my_turf.hotspot_expose((20 CELSIUS+50) + (50*fire_state), 125) + for(var/A in my_turf.contents) + var/atom/AT = A + if(!QDELETED(AT)) + AT.fire_act((20 CELSIUS+50) + (50*fire_state), 125) + + if(reagent_list.len == 0) + qdel(src, TRUE) + else + has_cached_share = FALSE + if(!my_turf.lgroup) + calculate_height() + set_reagent_color_for_liquid() + +/obj/effect/abstract/liquid_turf/proc/process_evaporation() + if(immutable) + SSliquids.evaporation_queue -= my_turf + return + //We're in a group. dont try and evaporate + if(my_turf.lgroup) + SSliquids.evaporation_queue -= my_turf + return + if(liquid_state != LIQUID_STATE_PUDDLE) + 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 reagent_list) + R = reagent_type + //We evaporate. bye bye + if(initial(R.evaporates)) + total_reagents -= reagent_list[reagent_type] + reagent_list -= reagent_type + any_change = TRUE + if(!any_change) + SSliquids.evaporation_queue -= my_turf + return + //No total reagents. Commit death + if(reagent_list.len == 0) + qdel(src, TRUE) + //Reagents still left. Recalculte height and color and remove us from the queue + else + has_cached_share = FALSE + SSliquids.evaporation_queue -= my_turf + calculate_height() + set_reagent_color_for_liquid() + +/** + * Makes and returns the liquid effect overlay. + * + * Arguments: + * * overlay_state - the icon state of the new overlay + * * overlay_layer - the layer + * * overlay_plane - the plane + */ +/obj/effect/abstract/liquid_turf/proc/make_liquid_overlay(overlay_state, overlay_layer, overlay_plane) + PRIVATE_PROC(TRUE) + + return mutable_appearance( + 'icons/effects/liquid_overlays.dmi', + overlay_state + ) + +/** + * Returns a list of over and underlays for different liquid states. + * + * Arguments: + * * state - the stage number. + * * has_top - if this stage has a top. + */ +/obj/effect/abstract/liquid_turf/proc/make_state_layer(state, has_top) + PRIVATE_PROC(TRUE) + + . = list(make_liquid_overlay("stage[state]_bottom", DEEP_FLUID_LAYER, DEFAULT_PLANE)) + + if(!has_top) + return + + . += make_liquid_overlay("stage[state]_top", BELOW_OBJ_LAYER, DEFAULT_PLANE) + +/obj/effect/abstract/liquid_turf/proc/set_new_liquid_state(new_state) + liquid_state = new_state + update_overlays() + +/obj/effect/abstract/liquid_turf/proc/update_overlays() + + if(no_effects) + return + src.overlays.Cut() + var/mutable_appearance/liquid_state_overlay + switch(liquid_state) + if(LIQUID_STATE_ANKLES) + liquid_state_overlay+= make_state_layer(1, has_top = TRUE) + if(LIQUID_STATE_WAIST) + liquid_state_overlay += make_state_layer(2, has_top = TRUE) + if(LIQUID_STATE_SHOULDERS) + liquid_state_overlay += make_state_layer(3, has_top = TRUE) + if(LIQUID_STATE_FULLTILE) + liquid_state_overlay += make_state_layer(4, has_top = FALSE) + + src.overlays += liquid_state_overlay + + var/mutable_appearance/shine = mutable_appearance(icon, "shine", flags = RESET_COLOR|RESET_ALPHA) + shine.alpha = 32 + src.overlays += shine + + //Add a fire overlay too + + if(fire_state == LIQUID_FIRE_STATE_NONE) + return + + var/fire_icon_state + switch(fire_state) + if(LIQUID_FIRE_STATE_SMALL) + fire_icon_state = "fire_small" + if(LIQUID_FIRE_STATE_MILD) + fire_icon_state = "fire_small" + if(LIQUID_FIRE_STATE_MEDIUM) + fire_icon_state = "fire_medium" + if(LIQUID_FIRE_STATE_HUGE) + fire_icon_state = "fire_big" + if(LIQUID_FIRE_STATE_INFERNO) + fire_icon_state = "fire_big" + + src.overlays += mutable_appearance(icon, fire_icon_state, flags = RESET_COLOR|RESET_ALPHA) + +//Takes a flat of our reagents and returns it, possibly qdeling our liquids +/obj/effect/abstract/liquid_turf/proc/take_reagents_flat(flat_amount) + var/datum/reagents/tempr = new /datum/reagents(10000, GLOB.temp_reagents_holder) + if(flat_amount >= total_reagents) + tempr.add_noreact_reagent_list(reagent_list) + qdel(src, TRUE) + else + var/fraction = flat_amount/total_reagents + var/passed_list = list() + for(var/reagent_type in reagent_list) + var/amount = fraction * reagent_list[reagent_type] + reagent_list[reagent_type] -= amount + total_reagents -= amount + passed_list[reagent_type] = amount + tempr.add_noreact_reagent_list(passed_list) + has_cached_share = FALSE + + return tempr + +/obj/effect/abstract/liquid_turf/immutable/take_reagents_flat(flat_amount) + return simulate_reagents_flat(flat_amount) + +//Returns a reagents holder with all the reagents with a higher volume than the threshold +/obj/effect/abstract/liquid_turf/proc/simulate_reagents_threshold(amount_threshold) + var/datum/reagents/tempr = new /datum/reagents(10000, GLOB.temp_reagents_holder) + var/passed_list = list() + for(var/reagent_type in reagent_list) + var/amount = reagent_list[reagent_type] + if(amount_threshold && amount < amount_threshold) + continue + passed_list[reagent_type] = amount + tempr.add_noreact_reagent_list(passed_list) + + return tempr + +//Returns a flat of our reagents without any effects on the liquids +/obj/effect/abstract/liquid_turf/proc/simulate_reagents_flat(flat_amount) + var/datum/reagents/tempr = new /datum/reagents(10000, GLOB.temp_reagents_holder) + if(flat_amount >= total_reagents) + tempr.add_noreact_reagent_list(reagent_list) + else + var/fraction = flat_amount/total_reagents + var/passed_list = list() + for(var/reagent_type in reagent_list) + var/amount = fraction * reagent_list[reagent_type] + passed_list[reagent_type] = amount + tempr.add_noreact_reagent_list(passed_list) + + return tempr + +/obj/effect/abstract/liquid_turf/fire_act(temperature, volume) + if(!fire_state) + if(check_fire(TRUE)) + SSliquids.processing_fire[my_turf] = TRUE + +/obj/effect/abstract/liquid_turf/proc/set_reagent_color_for_liquid() + color = mix_color_from_reagent_list(reagent_list) + +/obj/effect/abstract/liquid_turf/proc/calculate_height() + var/new_height = CEILING(total_reagents, 1)/LIQUID_HEIGHT_DIVISOR + set_height(new_height) + var/determined_new_state + //We add the turf height if it's positive to state calculations + if(my_turf.turf_height > 0) + new_height += my_turf.turf_height + switch(new_height) + if(0 to LIQUID_ANKLES_LEVEL_HEIGHT-1) + determined_new_state = LIQUID_STATE_PUDDLE + if(LIQUID_ANKLES_LEVEL_HEIGHT to LIQUID_WAIST_LEVEL_HEIGHT-1) + determined_new_state = LIQUID_STATE_ANKLES + if(LIQUID_WAIST_LEVEL_HEIGHT to LIQUID_SHOULDERS_LEVEL_HEIGHT-1) + determined_new_state = LIQUID_STATE_WAIST + if(LIQUID_SHOULDERS_LEVEL_HEIGHT to LIQUID_FULLTILE_LEVEL_HEIGHT-1) + determined_new_state = LIQUID_STATE_SHOULDERS + if(LIQUID_FULLTILE_LEVEL_HEIGHT to INFINITY) + determined_new_state = LIQUID_STATE_FULLTILE + if(determined_new_state != liquid_state) + set_new_liquid_state(determined_new_state) + +/obj/effect/abstract/liquid_turf/immutable/calculate_height() + var/new_height = CEILING(total_reagents, 1)/LIQUID_HEIGHT_DIVISOR + set_height(new_height) + var/determined_new_state + switch(new_height) + if(0 to LIQUID_ANKLES_LEVEL_HEIGHT-1) + determined_new_state = LIQUID_STATE_PUDDLE + if(LIQUID_ANKLES_LEVEL_HEIGHT to LIQUID_WAIST_LEVEL_HEIGHT-1) + determined_new_state = LIQUID_STATE_ANKLES + if(LIQUID_WAIST_LEVEL_HEIGHT to LIQUID_SHOULDERS_LEVEL_HEIGHT-1) + determined_new_state = LIQUID_STATE_WAIST + if(LIQUID_SHOULDERS_LEVEL_HEIGHT to LIQUID_FULLTILE_LEVEL_HEIGHT-1) + determined_new_state = LIQUID_STATE_SHOULDERS + if(LIQUID_FULLTILE_LEVEL_HEIGHT to INFINITY) + determined_new_state = LIQUID_STATE_FULLTILE + if(determined_new_state != liquid_state) + set_new_liquid_state(determined_new_state) + +/obj/effect/abstract/liquid_turf/proc/set_height(new_height) + var/prev_height = height + height = new_height + if(abs(height - prev_height) > WATER_HEIGH_DIFFERENCE_DELTA_SPLASH) + //Splash + if(prob(WATER_HEIGH_DIFFERENCE_SOUND_CHANCE)) + var/sound_to_play = pick(list( + 'sound/effects/water_wade1.ogg', + 'sound/effects/water_wade2.ogg', + 'sound/effects/water_wade3.ogg', + 'sound/effects/water_wade4.ogg' + )) + playsound(my_turf, sound_to_play, 60, 0) + var/obj/splashy = new /obj/effect/liquid_splash(my_turf) + splashy.color = color + if(height >= LIQUID_WAIST_LEVEL_HEIGHT) + //Push things into some direction, like space wind + var/turf/dest_turf + var/last_height = height + for(var/turf in my_turf.atmos_adjacent_turfs) + var/turf/T = turf + if(T.z != my_turf.z) + continue + if(!T.liquids) //Automatic winner + dest_turf = T + break + if(T.liquids.height < last_height) + dest_turf = T + last_height = T.liquids.height + if(dest_turf) + var/dir = get_dir(my_turf, dest_turf) + var/atom/movable/AM + for(var/thing in my_turf) + AM = thing + if(!AM.anchored && !AM.pulledby && !isobserver(AM)) + if(ishuman(AM)) + var/mob/living/carbon/human/H = AM + if(!(H.shoes)) + step(H, dir) + if(prob(60) && !H.lying) + to_chat(H, SPAN_DANGER("The current knocks you down!")) + H.Paralyse(60) + else + step(AM, dir) + +/obj/effect/abstract/liquid_turf/immutable/set_height(new_height) + height = new_height + +/obj/effect/abstract/liquid_turf/proc/movable_entered(datum/source, atom/movable/AM) + var/turf/T = source + if(isobserver(AM)) + return //ghosts, camera eyes, etc. don't make water splashy splashy + if(liquid_state >= LIQUID_STATE_ANKLES) + if(prob(30)) + var/sound_to_play = pick(list( + 'sound/effects/water_wade1.ogg', + 'sound/effects/water_wade2.ogg', + 'sound/effects/water_wade3.ogg', + 'sound/effects/water_wade4.ogg' + )) + playsound(T, sound_to_play, 50, 0) + if(iscarbon(AM)) + var/mob/living/carbon/C = AM + C.add_modifier(/datum/modifier/status_effect/water_affected) + else if (isliving(AM)) + var/mob/living/L = AM + if(prob(7)) + L.slip(T, 60) + if(fire_state) + AM.fire_act((20 CELSIUS+50) + (50*fire_state), 125) + +/obj/effect/abstract/liquid_turf/proc/mob_fall(datum/source, mob/M) + var/turf/T = source + if(liquid_state >= LIQUID_STATE_ANKLES && has_gravity(T)) + playsound(T, 'sound/effects/splash.ogg', 50, 0) + if(iscarbon(M)) + var/mob/living/carbon/falling_carbon = M + + // No point in giving reagents to the deceased. It can cause some runtimes. + if(falling_carbon.stat >= DEAD) + return + + if(falling_carbon.wear_mask && falling_carbon.wear_mask.body_parts_covered & FACE) + to_chat(falling_carbon, SPAN_DANGER("You fall in the [reagents_to_text()]!")) + else + var/datum/reagents/tempr = take_reagents_flat(CHOKE_REAGENTS_INGEST_ON_FALL_AMOUNT) + tempr.trans_to_mob(falling_carbon, tempr.total_volume, type = CHEM_INGEST) + + qdel(tempr) + falling_carbon.adjustOxyLoss(5) + falling_carbon.emote("cough") + to_chat(falling_carbon, SPAN_DANGER("You fall in and swallow some [reagents_to_text()]!")) + else + to_chat(M, SPAN_DANGER("You fall in the [reagents_to_text()]!")) + +/obj/effect/abstract/liquid_turf/Initialize(mapload) + . = ..() + if(!SSliquids) + CRASH("Liquid Turf created with the liquids sybsystem not yet initialized!") + if(!immutable) + my_turf = get_turf(src) + register_signal(my_turf, SIGNAL_ENTERED, .proc/movable_entered) + register_signal(my_turf, SIGNAL_ATOM_FALL, .proc/mob_fall) + SSliquids.add_active_turf(my_turf) + + SEND_SIGNAL(my_turf, SIGNAL_TURF_LIQUIDS_CREATION, src) + + update_overlays() + if(z) + src.smooth(get_turf(src)) + src.smooth_neighbors(get_turf(src)) + +/obj/effect/abstract/liquid_turf/Destroy(force) + ..() + if(force) + unregister_signal(my_turf, list(SIGNAL_ENTERED, SIGNAL_ATOM_FALL)) + if(my_turf.lgroup) + my_turf.lgroup.remove_from_group(my_turf) + if(SSliquids.evaporation_queue[my_turf]) + SSliquids.evaporation_queue -= my_turf + if(SSliquids.processing_fire[my_turf]) + SSliquids.processing_fire -= my_turf + //Is added because it could invoke a change to neighboring liquids + SSliquids.add_active_turf(my_turf) + my_turf.liquids = null + src.smooth_neighbors(my_turf) + my_turf = null + else + return QDEL_HINT_LETMELIVE + return + +/obj/effect/abstract/liquid_turf/immutable/Destroy(force) + if(force) + CRASH("Something tried to hard destroy an immutable liquid.") + return ..() + +//Exposes my turf with simulated reagents +/obj/effect/abstract/liquid_turf/proc/ExposeMyTurf() + var/datum/reagents/tempr = simulate_reagents_threshold(LIQUID_REAGENT_THRESHOLD_TURF_EXPOSURE) + tempr.touch_turf(my_turf) + qdel(tempr) + +/obj/effect/abstract/liquid_turf/proc/ChangeToNewTurf(turf/NewT) + if(NewT.liquids) + CRASH("Liquids tried to change to a new turf, that already had liquids on it!") + + unregister_signal(my_turf, list(SIGNAL_ENTERED, SIGNAL_ATOM_FALL)) + if(SSliquids.active_turfs[my_turf]) + SSliquids.active_turfs -= my_turf + SSliquids.active_turfs[NewT] = TRUE + if(SSliquids.evaporation_queue[my_turf]) + SSliquids.evaporation_queue -= my_turf + SSliquids.evaporation_queue[NewT] = TRUE + if(SSliquids.processing_fire[my_turf]) + SSliquids.processing_fire -= my_turf + SSliquids.processing_fire[NewT] = TRUE + my_turf.liquids = null + my_turf = NewT + NewT.liquids = src + loc = NewT + register_signal(my_turf, SIGNAL_ENTERED, .proc/movable_entered) + register_signal(my_turf, SIGNAL_ATOM_FALL, .proc/mob_fall) + + +/** + * Creates a string of the reagents that make up this liquid. + * + * Puts the reagent(s) that make up the liquid into string form e.g. "plasma" or "plasma and water", or 'plasma, milk, and water' depending on how many reagents there are. + * + * Returns the reagents list string or a generic "liquid" if there are no reagents somehow + * */ +/obj/effect/abstract/liquid_turf/proc/reagents_to_text() + /// the total amount of different types of reagents in the liquid + var/total_reagents = length(reagent_list) + /// the amount of different types of reagents that have not been listed yet + var/reagents_remaining = total_reagents + /// the final string to be returned + var/reagents_string = "" + if(!reagents_remaining) + return reagents_string += "liquid" + + do + for(var/datum/reagent/reagent_type as anything in reagent_list) + reagents_string += "[initial(reagent_type.name)]" + reagents_remaining-- + if(!reagents_remaining) + break + // if we are at the last reagent in the list, preface its name with 'and'. + // do not use a comma if there were only two reagents in the list + if(total_reagents == 2) + reagents_string += " and " + else + reagents_string += ", " + if(reagents_remaining == 1) + reagents_string += "and " + while(reagents_remaining) + + return lowertext(reagents_string) + +/obj/effect/liquid_splash + icon = 'icons/effects/splash.dmi' + icon_state = "splash" + layer = FLY_LAYER + +/obj/effect/liquid_splash/Initialize() + . = ..() + QDEL_IN(src, 5 SECONDS) + +/obj/effect/abstract/liquid_turf/immutable + immutable = TRUE + var/list/starting_mixture = list(/datum/reagent/water = 600) + var/starting_temp = 20 CELSIUS + +//STRICTLY FOR IMMUTABLES DESPITE NOT BEING /immutable +/obj/effect/abstract/liquid_turf/proc/add_turf(turf/T) + T.liquids = src + T.vis_contents += src + SSliquids.active_immutables[T] = TRUE + register_signal(T, SIGNAL_ENTERED, .proc/movable_entered) + register_signal(T, SIGNAL_ATOM_FALL, .proc/mob_fall) + +/obj/effect/abstract/liquid_turf/proc/remove_turf(turf/T) + SSliquids.active_immutables -= T + T.liquids = null + T.vis_contents -= src + unregister_signal(T, list(SIGNAL_ENTERED, SIGNAL_ATOM_FALL)) + +/obj/effect/abstract/liquid_turf/immutable/ocean + icon_state = "ocean" + plane = DEFAULT_PLANE //Same as weather, etc. + layer = OBJ_LAYER + starting_temp = 20 CELSIUS-150 + no_effects = TRUE + vis_flags = 0 + +/obj/effect/abstract/liquid_turf/immutable/ocean/warm + starting_temp = 20 CELSIUS+20 + +/obj/effect/abstract/liquid_turf/immutable/Initialize(mapload) + . = ..() + reagent_list = starting_mixture.Copy() + total_reagents = 0 + for(var/key in reagent_list) + total_reagents += reagent_list[key] + temp = starting_temp + calculate_height() + set_reagent_color_for_liquid() + +/obj/effect/abstract/liquid_turf/proc/smooth_neighbors(smoothing_turf) + for(var/direction in GLOB.alldirs) + var/obj/effect/abstract/liquid_turf/L = locate() in get_step(smoothing_turf, direction) + if(L) + L.smooth(L.loc) //so siding get updated properly + + +/obj/effect/abstract/liquid_turf/proc/smooth(smoothing_turf) + ASSERT(!isnull(smoothing_turf)) + var/connectdir = 0 + for(var/direction in GLOB.cardinal) + if(locate(/obj/effect/abstract/liquid_turf) in get_step(smoothing_turf, direction)) + connectdir |= direction + + //Check the diagonal connections for corners, where you have, for example, connections both north and east. In this case it checks for a north-east connection to determine whether to add a corner marker or not. + var/diagonalconnect = 0 //1 = NE; 2 = SE; 4 = NW; 8 = SW + var/dirs = list(1,2,4,8) + var/i = 1 + for(var/diag in list(NORTHEAST, SOUTHEAST,NORTHWEST,SOUTHWEST)) + if((connectdir & diag) == diag) + if(locate(/obj/effect/abstract/liquid_turf) in get_step(smoothing_turf, diag)) + diagonalconnect |= dirs[i] + i += 1 + + src.icon_state = "water-[connectdir]-[diagonalconnect]" diff --git a/code/modules/liquids/liquid_systems/liquid_groups.dm b/code/modules/liquids/liquid_systems/liquid_groups.dm new file mode 100644 index 00000000000..534dfbb6603 --- /dev/null +++ b/code/modules/liquids/liquid_systems/liquid_groups.dm @@ -0,0 +1,271 @@ +/***************************************************/ +/********************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 + var/list/members = list() + var/color + var/next_share = 0 + var/dirty = TRUE + var/amount_of_active_turfs = 0 + var/decay_counter = 0 + var/expected_turf_height = 0 + var/cached_color + var/list/last_cached_fraction_share + var/last_cached_total_volume = 0 + var/last_cached_thermal = 0 + var/last_cached_overlay_state = LIQUID_STATE_PUDDLE + +/datum/liquid_group/proc/add_to_group(turf/T) + members[T] = TRUE + T.lgroup = src + if(SSliquids.active_turfs[T]) + amount_of_active_turfs++ + if(T.liquids) + T.liquids.has_cached_share = FALSE + +/datum/liquid_group/proc/remove_from_group(turf/T) + members -= T + T.lgroup = null + if(SSliquids.active_turfs[T]) + amount_of_active_turfs-- + if(!members.len) + qdel(src) + +/datum/liquid_group/New(height) + SSliquids.active_groups[src] = TRUE + color = "#[rand_hex_color()]" + expected_turf_height = height + +/datum/liquid_group/proc/can_merge_group(datum/liquid_group/otherg) + if(expected_turf_height == otherg.expected_turf_height) + return TRUE + return FALSE + +/datum/liquid_group/proc/merge_group(datum/liquid_group/otherg) + amount_of_active_turfs += otherg.amount_of_active_turfs + for(var/t in otherg.members) + var/turf/T = t + T.lgroup = src + members[T] = TRUE + if(T.liquids) + T.liquids.has_cached_share = FALSE + otherg.members = list() + qdel(otherg) + share() + +/datum/liquid_group/proc/break_group() + //Flag puddles to the evaporation queue + for(var/t in members) + var/turf/T = t + if(T.liquids && T.liquids.liquid_state >= LIQUID_STATE_PUDDLE) + SSliquids.evaporation_queue[T] = TRUE + + share(TRUE) + qdel(src) + +/datum/liquid_group/Destroy() + SSliquids.active_groups -= src + for(var/t in members) + var/turf/T = t + T.lgroup = null + members = null + return ..() + +/datum/liquid_group/proc/check_adjacency(turf/T) + var/list/recursive_adjacent = list() + var/list/current_adjacent = list() + current_adjacent[T] = TRUE + recursive_adjacent[T] = TRUE + var/getting_new_turfs = TRUE + var/indef_loop_safety = 0 + while(getting_new_turfs && indef_loop_safety < LIQUID_RECURSIVE_LOOP_SAFETY) + indef_loop_safety++ + getting_new_turfs = FALSE + var/list/new_adjacent = list() + for(var/t in current_adjacent) + var/turf/T2 = t + for(var/y in T2.get_atmos_adjacent_turfs()) + if(!recursive_adjacent[y]) + new_adjacent[y] = TRUE + recursive_adjacent[y] = TRUE + getting_new_turfs = TRUE + current_adjacent = new_adjacent + //All adjacent, somehow + if(recursive_adjacent.len == members.len) + return + var/datum/liquid_group/new_group = new(expected_turf_height) + for(var/t in members) + if(!recursive_adjacent[t]) + remove_from_group(t) + new_group.add_to_group(t) + +/datum/liquid_group/proc/share(use_liquids_color = FALSE) + var/any_share = FALSE + var/cached_shares = 0 + var/list/cached_add = list() + var/cached_volume = 0 + var/cached_thermal = 0 + + var/turf/T + var/obj/effect/abstract/liquid_turf/cached_liquids + for(var/t in members) + T = t + if(T.liquids) + any_share = TRUE + cached_liquids = T.liquids + + if(cached_liquids.has_cached_share && last_cached_fraction_share) + cached_shares++ + continue + + for(var/r_type in cached_liquids.reagent_list) + if(!cached_add[r_type]) + cached_add[r_type] = 0 + cached_add[r_type] += cached_liquids.reagent_list[r_type] + cached_volume += cached_liquids.total_reagents + cached_thermal += cached_liquids.total_reagents * cached_liquids.temp + if(!any_share) + return + + decay_counter = 0 + + if(cached_shares) + for(var/reagent_type in last_cached_fraction_share) + if(!cached_add[reagent_type]) + cached_add[reagent_type] = 0 + cached_add[reagent_type] += last_cached_fraction_share[reagent_type] * cached_shares + cached_volume += last_cached_total_volume * cached_shares + cached_thermal += cached_shares * last_cached_thermal + + for(var/reagent_type in cached_add) + cached_add[reagent_type] = cached_add[reagent_type] / members.len + cached_volume = cached_volume / members.len + cached_thermal = cached_thermal / members.len + var/temp_to_set = cached_thermal / cached_volume + last_cached_thermal = cached_thermal + last_cached_fraction_share = cached_add + last_cached_total_volume = cached_volume + var/mixed_color = use_liquids_color ? mix_color_from_reagent_list(cached_add) : color + if(use_liquids_color) + mixed_color = mix_color_from_reagent_list(cached_add) + else if (GLOB.liquid_debug_colors) + mixed_color = color + else + if(!cached_color) + cached_color = mix_color_from_reagent_list(cached_add) + mixed_color = cached_color + + var/height = CEILING(cached_volume/LIQUID_HEIGHT_DIVISOR, 1) + + var/determined_new_state + var/state_height = height + if(expected_turf_height > 0) + state_height += expected_turf_height + switch(state_height) + if(0 to LIQUID_ANKLES_LEVEL_HEIGHT-1) + determined_new_state = LIQUID_STATE_PUDDLE + if(LIQUID_ANKLES_LEVEL_HEIGHT to LIQUID_WAIST_LEVEL_HEIGHT-1) + determined_new_state = LIQUID_STATE_ANKLES + if(LIQUID_WAIST_LEVEL_HEIGHT to LIQUID_SHOULDERS_LEVEL_HEIGHT-1) + determined_new_state = LIQUID_STATE_WAIST + if(LIQUID_SHOULDERS_LEVEL_HEIGHT to LIQUID_FULLTILE_LEVEL_HEIGHT-1) + determined_new_state = LIQUID_STATE_SHOULDERS + if(LIQUID_FULLTILE_LEVEL_HEIGHT to INFINITY) + determined_new_state = LIQUID_STATE_FULLTILE + + var/new_liquids = FALSE + for(var/t in members) + T = t + new_liquids = FALSE + if(!T.liquids) + new_liquids = TRUE + T.liquids = new(T) + cached_liquids = T.liquids + + cached_liquids.reagent_list = cached_add.Copy() + cached_liquids.total_reagents = cached_volume + cached_liquids.temp = temp_to_set + + cached_liquids.has_cached_share = TRUE + cached_liquids.attrition = 0 + + cached_liquids.color = mixed_color + cached_liquids.set_height(height) + + if(determined_new_state != cached_liquids.liquid_state) + cached_liquids.set_new_liquid_state(determined_new_state) + + //Only simulate a turf exposure when we had to create a new liquid tile + if(new_liquids) + cached_liquids.ExposeMyTurf() + +/datum/liquid_group/proc/process_cell(turf/T) + if(T.liquids.height <= 1) //Causes a bug when the liquid hangs in the air and is supposed to fall down a level + return FALSE + for(var/tur in T.get_atmos_adjacent_turfs()) + var/turf/T2 = tur + //Immutable check thing + if(T2.liquids && T2.liquids.immutable) + if(T.z != T2.z) + var/turf/Z_turf_below = GetBelow(T) + if(T2 == Z_turf_below) + qdel(T.liquids, TRUE) + return + else + continue + + //CHECK DIFFERENT TURF HEIGHT THING + if(T.liquid_height != T2.liquid_height) + var/my_liquid_height = T.liquid_height + T.liquids.height + var/target_liquid_height = T2.liquid_height + T2.liquids.height + if(my_liquid_height > target_liquid_height+2) + var/coeff = (T.liquids.height / (T.liquids.height + abs(T.liquid_height))) + var/height_diff = min(0.4,abs((target_liquid_height / my_liquid_height)-1)*coeff) + T.liquid_fraction_delete(height_diff) + . = TRUE + continue + + if(T2.liquids.height > T.liquids.height + 1) + SSliquids.active_immutables[T2] = TRUE + . = TRUE + continue + //END OF IMMUTABLE MADNESS + + if(T.z != T2.z) + var/turf/Z_turf_below = GetBelow(T) + if(T2 == Z_turf_below) + if(!(T2.liquids && T2.liquids.height + T2.liquid_height >= LIQUID_HEIGHT_CONSIDER_FULL_TILE)) + T.liquid_fraction_share(T2, 1) + qdel(T.liquids, TRUE) + . = TRUE + continue + //CHECK DIFFERENT TURF HEIGHT THING + if(T.liquid_height != T2.liquid_height) + var/my_liquid_height = T.liquid_height + T.liquids.height + var/target_liquid_height = T2.liquid_height + (T2.liquids ? T2.liquids.height : 0) + if(my_liquid_height > target_liquid_height+1) + var/coeff = (T.liquids.height / (T.liquids.height + abs(T.liquid_height))) + var/height_diff = min(0.4,abs((target_liquid_height / my_liquid_height)-1)*coeff) + T.liquid_fraction_share(T2, height_diff) + . = TRUE + continue + //END OF TURF HEIGHT + if(!T.can_share_liquids_with(T2)) + continue + if(!T2.lgroup) + add_to_group(T2) + //Try merge groups if possible + else if(T2.lgroup != T.lgroup && T.lgroup.can_merge_group(T2.lgroup)) + T.lgroup.merge_group(T2.lgroup) + . = TRUE + SSliquids.add_active_turf(T2) + if(.) + dirty = TRUE + //return //Do we want it to spread once per process or many times? + //Make sure to handle up/down z levels on adjacency properly diff --git a/code/modules/liquids/liquid_systems/liquid_height.dm b/code/modules/liquids/liquid_systems/liquid_height.dm new file mode 100644 index 00000000000..1e5147976fa --- /dev/null +++ b/code/modules/liquids/liquid_systems/liquid_height.dm @@ -0,0 +1,47 @@ +/** + * Liquid Height element; for dynamically applying liquid blockages. + * + * Used for reinforced tables, sandbags, and the likes. + */ +/datum/element/liquids_height + element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH + id_arg_index = 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 + + register_signal(target, SIGNAL_MOVED, .proc/on_target_move) + var/atom/movable/movable_target = target + if(isturf(movable_target.loc)) + var/turf/turf_loc = movable_target.loc + turf_loc.liquid_height += height_applied + turf_loc.reasses_liquids() + +/datum/element/liquids_height/detach(atom/movable/target) + . = ..() + + unregister_signal(target, list(SIGNAL_MOVED)) + var/atom/movable/movable_target = target + if(isturf(movable_target.loc)) + var/turf/turf_loc = movable_target.loc + turf_loc.liquid_height -= height_applied + turf_loc.reasses_liquids() + +/datum/element/liquids_height/proc/on_target_move(atom/movable/source, atom/OldLoc, Dir, Forced = FALSE) + + if(isturf(OldLoc)) + var/turf/old_turf = OldLoc + old_turf.liquid_height += height_applied + old_turf.reasses_liquids() + if(isturf(source.loc)) + var/turf/new_turf = source.loc + new_turf.liquid_height -= height_applied + new_turf.reasses_liquids() diff --git a/code/modules/liquids/liquid_systems/liquid_interaction.dm b/code/modules/liquids/liquid_systems/liquid_interaction.dm new file mode 100644 index 00000000000..7f046f22e1a --- /dev/null +++ b/code/modules/liquids/liquid_systems/liquid_interaction.dm @@ -0,0 +1,27 @@ +///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/register_with_parent() + register_signal(parent, SIGNAL_CLEAN_LIQUIDS, .proc/AfterAttack) //The only signal allowing item -> turf interaction + +/datum/component/liquids_interaction/unregister_from_parent() + unregister_signal(parent, SIGNAL_CLEAN_LIQUIDS) + +/datum/component/liquids_interaction/proc/AfterAttack(turf/turf_target, mob/user) + SIGNAL_HANDLER + + if(!isturf(turf_target) || !turf_target.liquids) + return + + if(interaction_callback.Invoke(turf_target, user, turf_target.liquids)) + return diff --git a/code/modules/liquids/liquid_systems/liquid_plumbers.dm b/code/modules/liquids/liquid_systems/liquid_plumbers.dm new file mode 100644 index 00000000000..9f7d5afb346 --- /dev/null +++ b/code/modules/liquids/liquid_systems/liquid_plumbers.dm @@ -0,0 +1,329 @@ +/* +/** + * Base class for underfloor plumbing machines that mess with floor liquids. + */ +/obj/machinery/plumbing/floor_pump + icon = 'icons/obj/liquids/structures/drains.dmi' + icon_state = "active_input" + anchored = FALSE + density = FALSE + idle_power_usage = 10 + active_power_usage = 1000 + buffer = 300 + + /// 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 = 0.4 + + /// Currently pumping. + var/is_pumping = FALSE + /// Floor tile is placed down + var/tile_placed = FALSE + + ///category for plumbing RCD + category = "Liquids" + +/obj/machinery/plumbing/floor_pump/Initialize(mapload, bolt, layer) + . = ..() + register_signal(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.can_perform_action(src, NEED_DEXTERITY)) + 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) + SIGNAL_HANDLER + + 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() + 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.height > height_regulator + +/obj/machinery/plumbing/floor_pump/input/pump_turf(turf/affected_turf, seconds_per_tick, multiplier) + var/target_value = seconds_per_tick * (drain_flat + (affected_turf.liquids.total_reagents * 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/reagents/tempr = affected_turf.liquids.take_reagents_flat(target_value) + tempr.trans_to(src, tempr.total_volume) + qdel(tempr) + +// So user can intuitively touch-pour liquids down the drain. +/obj/machinery/plumbing/floor_pump/input/expose_reagents(list/reagents, datum/reagents/source, methods, volume_modifier, show_message) + . = ..() + + if(methods == TOUCH) + source.trans_to(src.reagents, min(source.total_volume * volume_modifier, src.reagents.maximum_volume - src.reagents.total_volume)) + +/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.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?.height >= max_ext_volume) + return FALSE + var/turf/simulated/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) + +/obj/item/construction/plumbing/engineering + name = "engineering plumbing constructor" + desc = "A type of plumbing constructor designed to rapidly deploy the machines needed for logistics regarding fluids." + icon_state = "plumberer2" + +/obj/item/construction/plumbing/engineering/set_plumbing_designs() + plumbing_design_types = list( + /obj/machinery/duct = 1, + /obj/machinery/plumbing/input = 5, + /obj/machinery/plumbing/output = 5, + /obj/machinery/plumbing/tank = 20, + /obj/machinery/plumbing/acclimator = 10, + /obj/machinery/plumbing/filter = 5, + /obj/machinery/plumbing/splitter = 5, + /obj/machinery/plumbing/disposer = 10, + /obj/machinery/plumbing/floor_pump/input = 20, + /obj/machinery/plumbing/floor_pump/output = 20, + /obj/structure/drain = 5, + ) + +// 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/code/modules/liquids/liquid_systems/liquid_pump.dm b/code/modules/liquids/liquid_systems/liquid_pump.dm new file mode 100644 index 00000000000..f83f180ac1c --- /dev/null +++ b/code/modules/liquids/liquid_systems/liquid_pump.dm @@ -0,0 +1,112 @@ +//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 = 'icons/obj/liquids/structures/liquid_pump.dmi' + icon_state = "liquid_pump" + density = TRUE + anchored = FALSE + /// 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/attackby(obj/item/O, mob/user) + if(!isWrench(O)) + return ..() + playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) + to_chat(user, "You begin to unfasten \the [src]...") + if (do_after(user, 40, src)) + user.visible_message( \ + "\The [user] unfastens \the [src].", \ + "You have unfastened \the [src].", \ + "You hear ratchet.") + 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(!Adjacent(user, src)) + 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_text(mob/user, infix, suffix) + . = ..() + . += 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"].") + . += SPAN_NOTICE("The pressure gauge shows [reagents.total_volume]/[reagents.maximum_volume].") + +/obj/structure/liquid_pump/think() + if(!isturf(loc)) + set_next_think(world.time+1) + return + var/turf/T = loc + if(spewing_mode) + if(!reagents.total_volume) + set_next_think(world.time+1) + return + var/datum/reagents/tempr = new(10000) + reagents.trans_to(tempr, (reagents.total_volume * pump_speed_percentage) + pump_speed_flat) + T.add_liquid_from_reagents(tempr) + qdel(tempr) + else + if(!T.liquids) + set_next_think(world.time+1) + return + var/free_space = reagents.maximum_volume - reagents.total_volume + if(!free_space) + set_next_think(world.time+1) + return + var/target_siphon_amt = (T.liquids.total_reagents * 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) + set_next_think(world.time+1) + 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) + set_next_think(0) + else + set_next_think(world.time+1) + turned_on = !turned_on + update_icon() + +/obj/structure/liquid_pump/Initialize(mapload) + . = ..() + create_reagents(max_volume) + +/obj/structure/liquid_pump/Destroy() + if(turned_on) + set_next_think(0) + qdel(reagents) + return ..() diff --git a/code/modules/liquids/liquid_systems/liquid_status_effect.dm b/code/modules/liquids/liquid_systems/liquid_status_effect.dm new file mode 100644 index 00000000000..46b8d8b11ab --- /dev/null +++ b/code/modules/liquids/liquid_systems/liquid_status_effect.dm @@ -0,0 +1,30 @@ +/datum/modifier/status_effect/water_affected + name = "wateraffected" + +/datum/modifier/status_effect/water_affected/on_applied() + //We should be inside a liquid turf if this is applied + calculate_water_slow() + return TRUE + +/datum/modifier/status_effect/water_affected/proc/calculate_water_slow() + //Factor in swimming skill here? + var/turf/T = get_turf(holder) + var/slowdown_amount = T.liquids.liquid_state * 0.5 + holder.setMoveCooldown(slowdown_amount) + +/datum/modifier/status_effect/water_affected/tick() + var/turf/T = get_turf(holder) + if(!T || !T.liquids || T.liquids.liquid_state == LIQUID_STATE_PUDDLE) + qdel(src) + return + calculate_water_slow() + //Make the reagents touch the person + var/fraction = SUBMERGEMENT_PERCENT(holder, T.liquids) + var/datum/reagents/tempr = T.liquids.simulate_reagents_flat(SUBMERGEMENT_REAGENTS_TOUCH_AMOUNT*fraction) + tempr.touch_mob(holder) + tempr.trans_to(holder) + qdel(tempr) + return ..() + +/datum/modifier/status_effect/water_affected/on_expire() + holder.setMoveCooldown(holder.movement_delay()) diff --git a/code/modules/liquids/liquid_systems/liquid_turf.dm b/code/modules/liquids/liquid_systems/liquid_turf.dm new file mode 100644 index 00000000000..502d3a025db --- /dev/null +++ b/code/modules/liquids/liquid_systems/liquid_turf.dm @@ -0,0 +1,283 @@ +/turf + var/datum/liquid_group/lgroup + var/obj/effect/abstract/liquid_turf/liquids + var/liquid_height = 0 + +/turf/proc/convert_immutable_liquids() + if(!liquids || !liquids.immutable) + return + var/datum/reagents/tempr = liquids.take_reagents_flat(liquids.total_reagents) + var/cached_height = liquids.height + liquids.remove_turf(src) + liquids = new(src) + liquids.height = cached_height //Prevent height effects + add_liquid_from_reagents(tempr) + qdel(tempr) + +/turf/proc/reasses_liquids() + if(!liquids) + return + if(lgroup) + lgroup.remove_from_group(src) + SSliquids.add_active_turf(src) + +/obj/effect/abstract/liquid_turf/proc/liquid_simple_delete_flat(flat_amount) + if(flat_amount >= total_reagents) + qdel(src, TRUE) + return + var/fraction = flat_amount/total_reagents + for(var/reagent_type in reagent_list) + var/amount = fraction * reagent_list[reagent_type] + reagent_list[reagent_type] -= amount + total_reagents -= amount + has_cached_share = FALSE + if(!my_turf.lgroup) + calculate_height() + +/turf/proc/liquid_fraction_delete(fraction) + for(var/r_type in liquids.reagent_list) + var/volume_change = liquids.reagent_list[r_type] * fraction + liquids.reagent_list[r_type] -= volume_change + liquids.total_reagents -= volume_change + +/turf/proc/liquid_fraction_share(turf/T, fraction) + if(!liquids) + return + if(fraction > 1) + CRASH("Fraction share more than 100%") + for(var/r_type in liquids.reagent_list) + var/volume_change = liquids.reagent_list[r_type] * fraction + liquids.reagent_list[r_type] -= volume_change + liquids.total_reagents -= volume_change + T.add_liquid(r_type, volume_change, TRUE, liquids.temp) + liquids.has_cached_share = FALSE + +/turf/proc/liquid_update_turf() + if(!liquids) + return + + if(liquids && liquids.immutable) + SSliquids.active_immutables[src] = TRUE + return + //Check atmos adjacency to cut off any disconnected groups + if(lgroup) + var/assoc_atmos_turfs = list() + for(var/tur in get_atmos_adjacent_turfs()) + assoc_atmos_turfs[tur] = TRUE + //Check any cardinal that may have a matching group + for(var/direction in GLOB.cardinal) + var/turf/T = get_step(src, direction) + //Same group of which we do not share atmos adjacency + if(!assoc_atmos_turfs[T] && T.lgroup && T.lgroup == lgroup) + T.lgroup.check_adjacency(T) + + SSliquids.add_active_turf(src) + +/turf/proc/add_liquid_from_reagents(datum/reagents/giver, no_react = FALSE, reagent_multiplier = 1) + var/list/compiled_list = list() + for(var/r in giver.reagent_list) + var/datum/reagent/R = r + compiled_list[R.type] = R.volume * reagent_multiplier + if(!compiled_list.len) //No reagents to add, don't bother going further + return + add_liquid_list(compiled_list, no_react) + +//More efficient than add_liquid for multiples +/turf/proc/add_liquid_list(reagent_list, no_react = FALSE) + if(!liquids) + liquids = new(src) + if(liquids.immutable) + return + + var/prev_total_reagents = liquids.total_reagents + var/prev_thermal_energy = prev_total_reagents * liquids.temp + + for(var/reagent in reagent_list) + if(!liquids.reagent_list[reagent]) + liquids.reagent_list[reagent] = 0 + liquids.reagent_list[reagent] += reagent_list[reagent] + liquids.total_reagents += reagent_list[reagent] + + var/recieved_thermal_energy = (liquids.total_reagents - prev_total_reagents) + liquids.temp = (recieved_thermal_energy + prev_thermal_energy) / liquids.total_reagents + + if(!no_react) + //We do react so, make a simulation + create_reagents(10000) //Reagents are on turf level, should they be on liquids instead? + reagents.add_noreact_reagent_list(liquids.reagent_list) + if(reagents.process_reactions())//Any reactions happened, so re-calculate our reagents + liquids.reagent_list = list() + liquids.total_reagents = 0 + for(var/r in reagents.reagent_list) + var/datum/reagent/R = r + liquids.reagent_list[R.type] = R.volume + liquids.total_reagents += R.volume + + if(!liquids.total_reagents) //Our reaction exerted all of our reagents, remove self + qdel(reagents) + qdel(liquids) + return + qdel(reagents) + //Expose turf + liquids.ExposeMyTurf() + + liquids.calculate_height() + liquids.set_reagent_color_for_liquid() + liquids.has_cached_share = FALSE + SSliquids.add_active_turf(src) + if(lgroup) + lgroup.dirty = TRUE + +/turf/proc/add_liquid(reagent, amount, no_react = FALSE) + if(!liquids) + liquids = new(src) + if(liquids.immutable) + return + + var/prev_thermal_energy = liquids.total_reagents * liquids.temp + + if(!liquids.reagent_list[reagent]) + liquids.reagent_list[reagent] = 0 + liquids.reagent_list[reagent] += amount + liquids.total_reagents += amount + + liquids.temp = (amount + prev_thermal_energy) / liquids.total_reagents + + if(!no_react) + //We do react so, make a simulation + create_reagents(10000) + reagents.add_noreact_reagent_list(liquids.reagent_list) + if(reagents.process_reactions())//Any reactions happened, so re-calculate our reagents + liquids.reagent_list = list() + liquids.total_reagents = 0 + for(var/r in reagents.reagent_list) + var/datum/reagent/R = r + liquids.reagent_list[R.type] = R.volume + liquids.total_reagents += R.volume + qdel(reagents) + //Expose turf + liquids.ExposeMyTurf() + + liquids.calculate_height() + liquids.set_reagent_color_for_liquid() + liquids.has_cached_share = FALSE + SSliquids.add_active_turf(src) + if(lgroup) + lgroup.dirty = TRUE + +/turf/proc/can_share_liquids_with(turf/T) + if(T.z != z) //No Z here handling currently + return FALSE + + if(T.liquids && T.liquids.immutable) + return FALSE + + if(istype(T, /turf/space)) //No space liquids - Maybe add an ice system later + return FALSE + + var/my_liquid_height = liquids ? liquids.height : 0 + if(my_liquid_height < 1) + return FALSE + var/target_height = T.liquids ? T.liquids.height : 0 + + //Varied heights handling: + if(liquid_height != T.liquid_height) + if(my_liquid_height+liquid_height < target_height + T.liquid_height + 1) + return FALSE + else + return TRUE + + var/difference = abs(target_height - my_liquid_height) + //The: sand effect or "piling" Very good for performance + if(difference > 1) //SHOULD BE >= 1 or > 1? '>= 1' can lead into a lot of unnessecary processes, while ' > 1' will lead to a "piling" phenomena + return TRUE + return FALSE + +/turf/proc/process_liquid_cell() + if(!liquids) + if(!lgroup) + for(var/tur in get_atmos_adjacent_turfs()) + var/turf/T2 = tur + if(T2.liquids) + if(T2.liquids.immutable) + SSliquids.active_immutables[T2] = TRUE + else if (T2.can_share_liquids_with(src)) + if(T2.lgroup) + lgroup = new(liquid_height) + lgroup.add_to_group(src) + SSliquids.add_active_turf(T2) + SSliquids.remove_active_turf(src) + break + SSliquids.remove_active_turf(src) + return + if(!lgroup) + lgroup = new(liquid_height) + lgroup.add_to_group(src) + var/shared = lgroup.process_cell(src) + if(QDELETED(liquids)) //Liquids may be deleted in process cell + SSliquids.remove_active_turf(src) + return + if(!shared) + liquids.attrition++ + if(liquids.attrition >= LIQUID_ATTRITION_TO_STOP_ACTIVITY) + SSliquids.remove_active_turf(src) + +/turf/proc/process_immutable_liquid() + var/any_share = FALSE + for(var/tur in get_atmos_adjacent_turfs()) + var/turf/T = tur + if(can_share_liquids_with(T)) + //Move this elsewhere sometime later? + if(T.liquids && T.liquids.height > liquids.height) + continue + + any_share = TRUE + T.add_liquid_list(liquids.reagent_list, TRUE, liquids.temp) + if(!any_share) + SSliquids.active_immutables -= src + +//Consider making all of these behaviours a smart component/element? Something that's only applied wherever it needs to be +//Could probably have the variables on the turf level, and the behaviours being activated/deactived on the component level as the vars are updated +/turf/simulated/CanPass(atom/movable/mover, turf/location) + if(isliving(mover)) + var/turf/current_turf = get_turf(mover) + if(current_turf && current_turf.turf_height - turf_height <= -TURF_HEIGHT_BLOCK_THRESHOLD) + return FALSE + return ..() + +/turf/simulated/Exit(atom/movable/mover, atom/newloc) + . = ..() + if(. && isliving(mover) && isturf(newloc)) + var/mob/living/moving_mob = mover + if(moving_mob.mob_has_gravity()) + var/turf/new_turf = get_turf(newloc) + if(new_turf && new_turf.turf_height - turf_height <= -TURF_HEIGHT_BLOCK_THRESHOLD) + moving_mob.handle_fall() + +// Handles climbing up and down between turfs with height differences, as well as manipulating others to do the same. +/turf/simulated/MouseDrop_T(mob/dropped_mob, mob/user) + if(!isliving(dropped_mob) || !isliving(user) || !dropped_mob.mob_has_gravity() || !Adjacent(user) || !dropped_mob.Adjacent(user) || !(user.stat == CONSCIOUS) || user.lying) + return + if(!dropped_mob.mob_has_gravity()) + return + var/turf/mob_turf = get_turf(dropped_mob) + if(!mob_turf) + return + if(mob_turf.turf_height - turf_height <= -TURF_HEIGHT_BLOCK_THRESHOLD) + //Climb up + if(user == dropped_mob) + user.visible_message("climbing...") + else + dropped_mob.visible_message("being pulled up...") + if(do_after(user, 2 SECONDS, dropped_mob)) + dropped_mob.forceMove(src) + return + if(turf_height - mob_turf.turf_height <= -TURF_HEIGHT_BLOCK_THRESHOLD) + //Climb down + if(user == dropped_mob) + user.visible_message("climbing down...") + else + dropped_mob.visible_message("being lowered...") + if(do_after(user, 2 SECONDS, dropped_mob)) + dropped_mob.forceMove(src) + return diff --git a/code/modules/liquids/reagents/chemistry/holder.dm b/code/modules/liquids/reagents/chemistry/holder.dm new file mode 100644 index 00000000000..ac219588959 --- /dev/null +++ b/code/modules/liquids/reagents/chemistry/holder.dm @@ -0,0 +1,5 @@ +/// Like add_reagent but you can enter a list. Adds them with no_react = TRUE. Format it like this: list(/datum/reagent/toxin = 10, "beer" = 15) +/datum/reagents/proc/add_noreact_reagent_list(list/list_reagents, list/data=null) + for(var/r_id in list_reagents) + var/amt = list_reagents[r_id] + add_reagent(r_id, amt, data, safety = TRUE) diff --git a/code/modules/liquids/reagents/chemistry/reagents.dm b/code/modules/liquids/reagents/chemistry/reagents.dm new file mode 100644 index 00000000000..476dc5d7200 --- /dev/null +++ b/code/modules/liquids/reagents/chemistry/reagents.dm @@ -0,0 +1,219 @@ +/datum/reagent + ///Whether it will evaporate if left untouched on a liquids simulated puddle + var/evaporates = FALSE + + ///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.1 + + ///Whether a fire from this requires oxygen in the atmosphere + var/fire_needs_oxygen = TRUE + +/* +* ALCOHOL REAGENTS +*/ +/datum/reagent/ethanol + liquid_fire_power = 10 + liquid_fire_burnrate = 0.1 + +// 0 fire power +/datum/reagent/ethanol/beer/light + liquid_fire_power = 0 + +/datum/reagent/ethanol/threemileisland + liquid_fire_power = 0 + +/datum/reagent/ethanol/grog + liquid_fire_power = 0 + +/datum/reagent/ethanol/fetching_fizz + liquid_fire_power = 0 + +/datum/reagent/ethanol/sugar_rush + liquid_fire_power = 0 + +/datum/reagent/ethanol/crevice_spike + liquid_fire_power = 0 + +/datum/reagent/ethanol/fanciulli + liquid_fire_power = 0 + +// 2 fire power +/datum/reagent/ethanol/beer + liquid_fire_power = 2 + +/datum/reagent/ethanol/wine + liquid_fire_power = 2 + +/datum/reagent/ethanol/lizardwine + liquid_fire_power = 2 + +/datum/reagent/ethanol/amaretto + liquid_fire_power = 2 + +/datum/reagent/ethanol/goldschlager + liquid_fire_power = 2 + +/datum/reagent/ethanol/gintonic + liquid_fire_power = 2 + +/datum/reagent/ethanol/iced_beer + liquid_fire_power = 2 + +/datum/reagent/ethanol/irishcarbomb + liquid_fire_power = 2 + +/datum/reagent/ethanol/hcider + liquid_fire_power = 2 + +/datum/reagent/ethanol/narsour + liquid_fire_power = 2 + +/datum/reagent/ethanol/peppermint_patty + liquid_fire_power = 2 + +/datum/reagent/ethanol/blank_paper + liquid_fire_power = 2 + +/datum/reagent/ethanol/applejack + liquid_fire_power = 2 + +/datum/reagent/ethanol/jack_rose + liquid_fire_power = 2 + +/datum/reagent/ethanol/old_timer + liquid_fire_power = 2 + +/datum/reagent/ethanol/duplex + liquid_fire_power = 2 + +/datum/reagent/ethanol/painkiller + liquid_fire_power = 2 + +// 3 fire power +/datum/reagent/ethanol/longislandicedtea + liquid_fire_power = 3 + +/datum/reagent/ethanol/irishcoffee + liquid_fire_power = 3 + +/datum/reagent/ethanol/margarita + liquid_fire_power = 3 + +/datum/reagent/ethanol/manhattan + liquid_fire_power = 3 + +/datum/reagent/ethanol/snowwhite + liquid_fire_power = 3 + +/datum/reagent/ethanol/bahama_mama + liquid_fire_power = 3 + +/datum/reagent/ethanol/singulo + liquid_fire_power = 3 + +/datum/reagent/ethanol/red_mead + liquid_fire_power = 3 + +/datum/reagent/ethanol/mead + liquid_fire_power = 3 + +/datum/reagent/ethanol/aloe + liquid_fire_power = 3 + +/datum/reagent/ethanol/andalusia + liquid_fire_power = 3 + +/datum/reagent/ethanol/alliescocktail + liquid_fire_power = 3 + +/datum/reagent/ethanol/amasec + liquid_fire_power = 3 + +/datum/reagent/ethanol/erikasurprise + liquid_fire_power = 3 + +/datum/reagent/ethanol/whiskey_sour + liquid_fire_power = 3 + +/datum/reagent/ethanol/triple_sec + liquid_fire_power = 3 + +/datum/reagent/ethanol/creme_de_menthe + liquid_fire_power = 3 + +/datum/reagent/ethanol/creme_de_cacao + liquid_fire_power = 3 + +/datum/reagent/ethanol/creme_de_coconut + liquid_fire_power = 3 + +/datum/reagent/ethanol/quadruple_sec + liquid_fire_power = 3 + +/datum/reagent/ethanol/grasshopper + liquid_fire_power = 3 + +/datum/reagent/ethanol/stinger + liquid_fire_power = 3 + +/datum/reagent/ethanol/bastion_bourbon + liquid_fire_power = 3 + +/datum/reagent/ethanol/squirt_cider + liquid_fire_power = 3 + +/datum/reagent/ethanol/amaretto_alexander + liquid_fire_power = 3 + +/datum/reagent/ethanol/sidecar + liquid_fire_power = 3 + +/datum/reagent/ethanol/mojito + liquid_fire_power = 3 + +/datum/reagent/ethanol/moscow_mule + liquid_fire_power = 3 + +/datum/reagent/ethanol/fruit_wine + liquid_fire_power = 3 + +/datum/reagent/ethanol/champagne + liquid_fire_power = 3 + +/datum/reagent/ethanol/pina_colada + liquid_fire_power = 3 + +/datum/reagent/ethanol/ginger_amaretto + liquid_fire_power = 3 + +// 4 fire power +/datum/reagent/ethanol/rum_coke + liquid_fire_power = 4 + +/datum/reagent/ethanol/booger + liquid_fire_power = 4 + +/datum/reagent/ethanol/tequila_sunrise + liquid_fire_power = 4 + +/* +* PYROTECHNIC REAGENTS +*/ +/datum/reagent/thermite + liquid_fire_power = 20 + liquid_fire_burnrate = 0.3 + +/* +* OTHER +*/ + +/datum/reagent/fuel + liquid_fire_power = 10 + liquid_fire_burnrate = 0.2 + +/datum/reagent/plasma + liquid_fire_power = 15 + liquid_fire_burnrate = 0.2 diff --git a/code/modules/liquids/reagents/reagent_containers.dm b/code/modules/liquids/reagents/reagent_containers.dm new file mode 100644 index 00000000000..28449198ed5 --- /dev/null +++ b/code/modules/liquids/reagents/reagent_containers.dm @@ -0,0 +1,46 @@ +/obj/item/reagent_containers/Initialize(mapload, vol) + . = ..() + + AddComponent(/datum/component/liquids_interaction, /obj/item/reagent_containers/vessel/proc/attack_on_liquids_turf) + +/** + * Proc to remove liquids from a turf using a reagent container. + * + * Arguments: + * * tile - On which tile we're trying to absorb liquids + * * user - Who tries to absorb liquids with this? + * * liquids - Liquids we're trying to absorb. + */ +/obj/item/reagent_containers/vessel/proc/attack_on_liquids_turf(turf/target_turf, mob/living/user, obj/effect/abstract/liquid_turf/liquids) + if(user.a_intent == I_HURT) + return FALSE + + if(!can_be_splashed) + return FALSE + + if(!user.Adjacent(target_turf)) + return FALSE + + if(liquids.fire_state) //Use an extinguisher first + to_chat(user, SPAN_WARNING("You can't scoop up anything while it's on fire!")) + return TRUE + + if(liquids.height == 1) + to_chat(user, SPAN_WARNING("The puddle is too shallow to scoop anything up!")) + return TRUE + + var/free_space = reagents.maximum_volume - reagents.total_volume + if(free_space <= 0) + to_chat(user, SPAN_WARNING("You can't fit any more liquids inside [src]!")) + return TRUE + + var/desired_transfer = amount_per_transfer_from_this + if(desired_transfer > free_space) + desired_transfer = free_space + + var/datum/reagents/tempr = liquids.take_reagents_flat(desired_transfer) + tempr.trans_to(reagents, tempr.total_volume) + to_chat(user, SPAN_NOTICE("You scoop up around [amount_per_transfer_from_this] units of liquids with [src].")) + qdel(tempr) + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) + return TRUE diff --git a/code/modules/liquids/tools.dm b/code/modules/liquids/tools.dm new file mode 100644 index 00000000000..12617dcb09d --- /dev/null +++ b/code/modules/liquids/tools.dm @@ -0,0 +1,41 @@ +/client/proc/spawn_liquid() + set category = "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 = tgui_input_text(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 = tgui_input_number(usr, "Volume:", "Choose volume") + if(!volume) + return + var/turf/epicenter = get_turf(mob) + epicenter.add_liquid(choice, volume) + message_admins("[usr] ([usr.ckey]) spawned liquid at [epicenter.loc] ([choice] - [volume]).") + log_admin("[key_name(usr)] spawned liquid at [epicenter.loc] ([choice] - [volume]).") + +/client/proc/remove_liquid(turf/epicenter in world) + set name = "Remove Liquids" + set category = "Fun" + set desc = "Remove liquids in a range." + + var/range = input(usr, "Enter range:", "Range selection", 2) as num + + for(var/obj/effect/abstract/liquid_turf/liquid in range(range, epicenter)) + qdel(liquid, TRUE) + + 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]") diff --git a/code/modules/maps/map_template.dm b/code/modules/maps/map_template.dm index cdc9ef95f07..d3cd7db4ab1 100644 --- a/code/modules/maps/map_template.dm +++ b/code/modules/maps/map_template.dm @@ -73,7 +73,7 @@ for (var/i in turfs) var/turf/T = i T.post_change() - if(template_flags & TEMPLATE_FLAG_NO_RUINS) + if(template_flags & TEMPLATE_FLAG_TURF_FLAG_NORUINS) T.turf_flags |= TURF_FLAG_NORUINS /datum/map_template/proc/init_shuttles() diff --git a/code/modules/modifier/status_effect.dm b/code/modules/modifier/status_effect.dm new file mode 100644 index 00000000000..caacf4d7bfc --- /dev/null +++ b/code/modules/modifier/status_effect.dm @@ -0,0 +1,16 @@ +/datum/modifier/status_effect + var/duration = 0 + var/alert_type = null + var/obj/screen/movable/alert/linked_alert = null + +/datum/modifier/status_effect/New(new_holder, new_origin) + ..() + if(duration) + expire_at = world.time + duration + + if(alert_type) + linked_alert = holder.throw_alert("\ref[src]",alert_type) + +/datum/modifier/status_effect/on_expire() + . = ..() + holder.clear_alert("\ref[src]") diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index 3516ee09550..f6c0c56e191 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -170,6 +170,7 @@ return species.can_fall(src) /atom/movable/proc/handle_fall(turf/landing) + SEND_SIGNAL(src, SIGNAL_ATOM_FALL) forceMove(landing) if(locate(/obj/structure/stairs) in landing) diff --git a/code/modules/reagents/Chemistry-Holder.dm b/code/modules/reagents/Chemistry-Holder.dm index 1b00363a6fd..3a383188d95 100644 --- a/code/modules/reagents/Chemistry-Holder.dm +++ b/code/modules/reagents/Chemistry-Holder.dm @@ -348,6 +348,9 @@ GLOBAL_DATUM_INIT(temp_reagents_holder, /obj, new) amount -= spill if(spill) splash(target.loc, spill, multiplier, copy, min_spill, max_spill) + if(isturf(target)) + var/turf/T = target + T.add_liquid_from_reagents(src) trans_to(target, amount, multiplier, copy) diff --git a/code/modules/reagents/Chemistry-Reagents/basic.dm b/code/modules/reagents/Chemistry-Reagents/basic.dm index 3047422656e..3a4861a21fd 100644 --- a/code/modules/reagents/Chemistry-Reagents/basic.dm +++ b/code/modules/reagents/Chemistry-Reagents/basic.dm @@ -9,6 +9,7 @@ glass_name = "water" glass_desc = "The father of all refreshments." var/slippery = 1 + evaporates = TRUE /datum/reagent/water/affect_blood(mob/living/carbon/M, alien, removed) if(!istype(M, /mob/living/carbon/metroid) && alien != IS_METROID) diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm index 3a1573f90ea..f624dc579a5 100644 --- a/code/modules/reagents/reagent_containers.dm +++ b/code/modules/reagents/reagent_containers.dm @@ -34,13 +34,23 @@ /obj/item/reagent_containers/attack_self(mob/user) return -/obj/item/reagent_containers/afterattack(obj/target, mob/user, flag) +/obj/item/reagent_containers/afterattack(atom/A, mob/user, proximity) + if(!proximity) + return + + var/turf/turf_to_clean = A + + // Disable normal cleaning if there are liquids. + if(isturf(A) && turf_to_clean.liquids) + SEND_SIGNAL(src, SIGNAL_CLEAN_LIQUIDS, turf_to_clean, user) + return FALSE + if(can_be_splashed && user.a_intent != I_HELP) - if(standard_splash_mob(user,target)) + if(standard_splash_mob(user,A)) return if(reagents && reagents.total_volume) - to_chat(user, SPAN_NOTICE("You splash the contents of \the [src] onto [target].")) // They are not on help intent, aka wanting to spill it. - reagents.splash(target, reagents.total_volume) + to_chat(user, SPAN_NOTICE("You splash the contents of \the [src] onto [A].")) // They are not on help intent, aka wanting to spill it. + reagents.splash(A, reagents.total_volume) return /obj/item/reagent_containers/proc/reagentlist() // For attack logs diff --git a/code/modules/reagents/reagent_containers/vessel/unsorted.dm b/code/modules/reagents/reagent_containers/vessel/unsorted.dm index 08a7778d5af..9ecb0d04d16 100644 --- a/code/modules/reagents/reagent_containers/vessel/unsorted.dm +++ b/code/modules/reagents/reagent_containers/vessel/unsorted.dm @@ -40,7 +40,21 @@ /obj/item/reagent_containers/vessel/bucket/full startswith = list(/datum/reagent/water) -/obj/item/reagent_containers/vessel/bucket/attackby(obj/D, mob/user) +/obj/item/reagent_containers/vessel/bucket/ShiftClick(mob/user) + . = ..() + var/obj/O = user.get_active_hand() + if(istype(O, /obj/item/mop)) + if(O.reagents.total_volume == 0) + to_chat(user, "[O] is dry, you can't squeeze anything out!") + return + if(reagents.total_volume == reagents.maximum_volume) + to_chat(user, "[src] is full!") + return + O.reagents.remove_any(O.reagents.total_volume * SQUEEZING_DISPERSAL_RATIO) + O.reagents.trans_to(src, O.reagents.total_volume) + to_chat(user, "You squeeze the liquids from [O] to [src].") + +/obj/item/reagent_containers/vessel/bucket/attackby(obj/D, mob/user, click_params) if(isprox(D)) to_chat(user, "You add [D] to [src].") qdel(D) diff --git a/icons/effects/liquid.dmi b/icons/effects/liquid.dmi index 258a0fb2e62..71c82c7566c 100644 Binary files a/icons/effects/liquid.dmi and b/icons/effects/liquid.dmi differ diff --git a/icons/effects/liquid_overlays.dmi b/icons/effects/liquid_overlays.dmi new file mode 100644 index 00000000000..cf6ff903337 Binary files /dev/null and b/icons/effects/liquid_overlays.dmi differ diff --git a/icons/effects/splash.dmi b/icons/effects/splash.dmi new file mode 100644 index 00000000000..f2cb774f59e Binary files /dev/null and b/icons/effects/splash.dmi differ diff --git a/icons/obj/flora/rocks.dmi b/icons/obj/flora/rocks.dmi deleted file mode 100644 index a1f6a0df0a9..00000000000 Binary files a/icons/obj/flora/rocks.dmi and /dev/null differ diff --git a/icons/obj/liquids/structures/drains.dmi b/icons/obj/liquids/structures/drains.dmi new file mode 100644 index 00000000000..5c305b7d237 Binary files /dev/null and b/icons/obj/liquids/structures/drains.dmi differ diff --git a/icons/obj/liquids/structures/liquid_pump.dmi b/icons/obj/liquids/structures/liquid_pump.dmi new file mode 100644 index 00000000000..012dda8bb5e Binary files /dev/null and b/icons/obj/liquids/structures/liquid_pump.dmi differ diff --git a/icons/turf/elevated_plasteel.dmi b/icons/turf/elevated_plasteel.dmi new file mode 100644 index 00000000000..43c82bff75a Binary files /dev/null and b/icons/turf/elevated_plasteel.dmi differ diff --git a/icons/turf/lowered_plasteel.dmi b/icons/turf/lowered_plasteel.dmi new file mode 100644 index 00000000000..c64af2d9b68 Binary files /dev/null and b/icons/turf/lowered_plasteel.dmi differ diff --git a/icons/turf/pool_tile.dmi b/icons/turf/pool_tile.dmi new file mode 100644 index 00000000000..b5ccf9e54c3 Binary files /dev/null and b/icons/turf/pool_tile.dmi differ diff --git a/sound/effects/heart_beat_loop3.ogg b/sound/effects/heart_beat_loop3.ogg new file mode 100644 index 00000000000..ca647ff78b9 Binary files /dev/null and b/sound/effects/heart_beat_loop3.ogg differ diff --git a/sound/effects/heart_beat_once.ogg b/sound/effects/heart_beat_once.ogg new file mode 100644 index 00000000000..d8f6f9343b9 Binary files /dev/null and b/sound/effects/heart_beat_once.ogg differ diff --git a/sound/effects/splash.ogg b/sound/effects/splash.ogg new file mode 100644 index 00000000000..22f17c07901 Binary files /dev/null and b/sound/effects/splash.ogg differ diff --git a/sound/effects/water_wade1.ogg b/sound/effects/water_wade1.ogg new file mode 100644 index 00000000000..27527f9d8b8 Binary files /dev/null and b/sound/effects/water_wade1.ogg differ diff --git a/sound/effects/water_wade2.ogg b/sound/effects/water_wade2.ogg new file mode 100644 index 00000000000..f9c15cab64a Binary files /dev/null and b/sound/effects/water_wade2.ogg differ diff --git a/sound/effects/water_wade3.ogg b/sound/effects/water_wade3.ogg new file mode 100644 index 00000000000..3daccae01a1 Binary files /dev/null and b/sound/effects/water_wade3.ogg differ diff --git a/sound/effects/water_wade4.ogg b/sound/effects/water_wade4.ogg new file mode 100644 index 00000000000..7ba705f4991 Binary files /dev/null and b/sound/effects/water_wade4.ogg differ diff --git a/sound/effects/watersplash.ogg b/sound/effects/watersplash.ogg new file mode 100644 index 00000000000..a9a6e486087 Binary files /dev/null and b/sound/effects/watersplash.ogg differ