From 6aa58aed01fdd5e7dade4eba45e2f34b9ac55394 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Sun, 6 Sep 2026 23:05:35 -0500 Subject: [PATCH 01/15] Post big boom fixes --- code/datums/wounds/bones.dm | 2 +- code/game/objects/effects/decals/cleanable/misc.dm | 2 +- maplestation_modules/code/modules/smells/_smell.dm | 11 +++++++++++ .../code/modules/surgery/bodyparts/cyber_arms.dm | 8 ++++---- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index af5fefed3880..17f658099746 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -170,7 +170,7 @@ if(limb.body_zone != BODY_ZONE_L_LEG && limb.body_zone != BODY_ZONE_R_LEG) return - if(victim.body_position == LYING_DOWN || victim.buckled) // wheelchair = fine, being pulled = not fine + if(victim.body_position == LYING_DOWN || isobj(victim.buckled)) // wheelchair = fine, being pulled = not fine return if(victim.has_status_effect(/datum/status_effect/determined)) return diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index 800b4b8c80fa..32eedf69cf69 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -167,7 +167,7 @@ leave_smell() /obj/effect/decal/cleanable/vomit/proc/leave_smell() - add_smell(smell = "vomit", intensity = SMELL_INTENSITY_STRONG, radius = 1) + add_smell(smell = /datum/smell/vomit, intensity = SMELL_INTENSITY_STRONG, radius = 1) /obj/effect/decal/cleanable/vomit/attack_hand(mob/user, list/modifiers) . = ..() diff --git a/maplestation_modules/code/modules/smells/_smell.dm b/maplestation_modules/code/modules/smells/_smell.dm index e645a49dcad6..92cba48d5683 100644 --- a/maplestation_modules/code/modules/smells/_smell.dm +++ b/maplestation_modules/code/modules/smells/_smell.dm @@ -146,3 +146,14 @@ description = "What a nice smell." mood_change = 1 timeout = 40 SECONDS + +/datum/smell/vomit + text = "vomit" + category = "stench" + +/datum/smell/vomit/on_smell(mob/living/whom, intensity) + if(isnull(whom.mob_mood) || whom.mob_mood.has_mood_of_category(text)) + return + + whom.adjust_disgust(intensity) + whom.add_mood_event(text, /datum/mood_event/disgust/minor_bad_smell) diff --git a/maplestation_modules/code/modules/surgery/bodyparts/cyber_arms.dm b/maplestation_modules/code/modules/surgery/bodyparts/cyber_arms.dm index 86bd6b8a8f1a..e14b0ac9ef40 100644 --- a/maplestation_modules/code/modules/surgery/bodyparts/cyber_arms.dm +++ b/maplestation_modules/code/modules/surgery/bodyparts/cyber_arms.dm @@ -4,13 +4,13 @@ desc = "An advanced robotic arm with in built sharp claws. Makes you formidable in close combat, \ though unfortunately the claws are not retractable, and may make it difficult to manipulate small objects." id = "clawed_advanced_r_arm" - build_path = /obj/item/bodypart/arm/left/robot/advanced/claws + build_path = /obj/item/bodypart/arm/right/robot/advanced/claws /datum/design/advanced_l_arm/clawed name = "Advanced Clawed Left Arm" desc = /datum/design/advanced_r_arm/clawed::desc id = "clawed_advanced_l_arm" - build_path = /obj/item/bodypart/arm/right/robot/advanced/claws + build_path = /obj/item/bodypart/arm/left/robot/advanced/claws /datum/design/advanced_r_arm/lifting name = "Advanced Lifting Right Arm" @@ -18,12 +18,12 @@ particularly grappling - and better at construction of large objects, \ though unfortunately the bulkier design may make it difficult to manipulate small objects." id = "punchy_advanced_r_arm" - build_path = /obj/item/bodypart/arm/left/robot/advanced/lifting + build_path = /obj/item/bodypart/arm/right/robot/advanced/lifting /datum/design/advanced_l_arm/lifting name = "Advanced Lifting Left Arm" id = "punchy_advanced_l_arm" - build_path = /obj/item/bodypart/arm/right/robot/advanced/lifting + build_path = /obj/item/bodypart/arm/left/robot/advanced/lifting // Limbs /obj/item/bodypart/arm/left/robot/advanced/claws From 8ce5f64c664d8ac814716def2c55edbfa0054187 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Sun, 6 Sep 2026 23:08:10 -0500 Subject: [PATCH 02/15] Bags --- code/game/atom/_atom.dm | 7 ++----- .../objects/structures/crates_lockers/closets/bodybag.dm | 5 +++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index b8faa06c2c55..3ea1862706aa 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -379,11 +379,8 @@ return null ///Return the current air environment in this atom -/atom/proc/return_air() - if(loc) - return loc.return_air() - else - return null +/atom/proc/return_air() as /datum/gas_mixture + return loc?.return_air() ///Return the air if we can analyze it /atom/proc/return_analyzable_air() diff --git a/code/game/objects/structures/crates_lockers/closets/bodybag.dm b/code/game/objects/structures/crates_lockers/closets/bodybag.dm index 2a64da6d0bb4..442aa94be2b4 100644 --- a/code/game/objects/structures/crates_lockers/closets/bodybag.dm +++ b/code/game/objects/structures/crates_lockers/closets/bodybag.dm @@ -526,8 +526,9 @@ if(internal_air.temperature <= BODY_PRESERVATION_TEMP && !HAS_TRAIT(freezing, TRAIT_STASIS)) apply_stasis(freezing) - // Bout two minutes of time - take_damage(max_integrity * 0.004 * seconds_per_tick, sound_effect = FALSE) + if(loc?.return_air()?.return_temperature() > T0C) + // Bout two minutes of time + take_damage(max_integrity * 0.004 * seconds_per_tick, sound_effect = FALSE) /obj/structure/closet/body_bag/environmental/stasis/examine_status(mob/user) switch(100 * get_integrity_percentage()) From 51bba2e09557c6b8b48c471c660f6999160a570a Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Tue, 28 Apr 2026 18:01:56 -0500 Subject: [PATCH 03/15] Sect of the Dream God (#95643) --- .../signals/signals_mob/signals_mob_carbon.dm | 7 + code/__DEFINES/span.dm | 1 + code/datums/communications.dm | 4 + code/datums/components/religious_tool.dm | 17 +- .../antagonists/heretic/heretic_antag.dm | 2 + .../heretic/status_effects/dreams.dm | 118 ++++++ .../flufftext/{Dreaming.dm => dreaming.dm} | 24 +- code/modules/religion/deaconize.dm | 91 +++++ .../religion/dreams/banish_nightmare.dm | 100 +++++ .../religion/dreams/deaconize_dreamer.dm | 20 + code/modules/religion/dreams/dream_portent.dm | 378 ++++++++++++++++++ .../religion/dreams/dream_projection.dm | 185 +++++++++ .../religion/dreams/dream_protection.dm | 130 ++++++ code/modules/religion/dreams/slumber_party.dm | 114 ++++++ code/modules/religion/religion_sects.dm | 145 +++++++ code/modules/religion/religion_structures.dm | 16 + code/modules/religion/rites.dm | 5 +- icons/obj/service/hand_of_god_structures.dmi | Bin 44723 -> 45965 bytes interface/stylesheet.dm | 2 + maplestation.dme | 2 +- tgstation.dme | 7 + .../tgui-panel/styles/tgchat/chat-dark.scss | 5 + .../tgui-panel/styles/tgchat/chat-light.scss | 5 + 23 files changed, 1364 insertions(+), 14 deletions(-) create mode 100644 code/modules/antagonists/heretic/status_effects/dreams.dm rename code/modules/flufftext/{Dreaming.dm => dreaming.dm} (91%) create mode 100644 code/modules/religion/deaconize.dm create mode 100644 code/modules/religion/dreams/banish_nightmare.dm create mode 100644 code/modules/religion/dreams/deaconize_dreamer.dm create mode 100644 code/modules/religion/dreams/dream_portent.dm create mode 100644 code/modules/religion/dreams/dream_projection.dm create mode 100644 code/modules/religion/dreams/dream_protection.dm create mode 100644 code/modules/religion/dreams/slumber_party.dm diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index c197eb8c8fc3..6928ed198d1e 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -190,3 +190,10 @@ //from base of [/obj/effect/particle_effect/fluid/smoke/proc/smoke_mob]: (seconds_per_tick) #define COMSIG_CARBON_EXPOSED_TO_SMOKE "carbon_exposed_to_smoke" + +/// Before a mob starts dreaming - you can add dream datums to the dream pool to override the selection: (list/dream_pool) +#define COMSIG_PRE_DREAMING "pre_dreaming" +/// A mob has started dreaming: (datum/dream/current_dream) +#define COMSIG_START_DREAMING "start_dreaming" +/// A mob has finished dreaming: (datum/dream/finished_dream) +#define COMSIG_END_DREAMING "end_dreaming" diff --git a/code/__DEFINES/span.dm b/code/__DEFINES/span.dm index e4bbc1a6188d..f6c36434fe3b 100644 --- a/code/__DEFINES/span.dm +++ b/code/__DEFINES/span.dm @@ -43,6 +43,7 @@ #define span_cultboldtalic(str) ("" + str + "") #define span_cultitalic(str) ("" + str + "") #define span_cultlarge(str) ("" + str + "") +#define span_cyan(str) ("" + str + "") #define span_danger(str) ("" + str + "") #define span_deadsay(str) ("" + str + "") #define span_deconversion_message(str) ("" + str + "") diff --git a/code/datums/communications.dm b/code/datums/communications.dm index 91ccc8369374..816aa05dbbfe 100644 --- a/code/datums/communications.dm +++ b/code/datums/communications.dm @@ -27,6 +27,9 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n /// What is the higher bound of when the roundstart announcement is sent out? var/waittime_h = 180 SECONDS + /// Tracks if we have announced greenshift at the start of the round or not + var/announced_greenshift = FALSE + /datum/communciations_controller/proc/can_announce(mob/living/user, is_silicon) if(is_silicon && COOLDOWN_FINISHED(src, silicon_message_cooldown)) return TRUE @@ -96,6 +99,7 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n if(greenshift) station_goal_strings += "All special orders have been authorized for the shift. \ Feel free to pick one your crew wishes to specialize in - you are not expected to complete them all." + announced_greenshift = TRUE else for(var/datum/station_goal/station_goal as anything in SSstation.get_station_goals()) diff --git a/code/datums/components/religious_tool.dm b/code/datums/components/religious_tool.dm index 3c421a04aa30..afb52c91bac5 100644 --- a/code/datums/components/religious_tool.dm +++ b/code/datums/components/religious_tool.dm @@ -176,12 +176,17 @@ if(!performing_rite.perform_rite(user, parent)) QDEL_NULL(performing_rite) return - performing_rite.invoke_effect(user, parent) - easy_access_sect.adjust_favor(-performing_rite.favor_cost) - if(performing_rite.auto_delete) - QDEL_NULL(performing_rite) - else - performing_rite = null + + if(performing_rite.invoke_effect(user, parent)) + performing_rite.post_invoke_effects(user, parent) + easy_access_sect.adjust_favor(-performing_rite.favor_cost) + + if(!(performing_rite.rite_flags & RITE_ALLOW_MULTIPLE_PERFORMS)) + if(performing_rite.rite_flags & RITE_AUTO_DELETE) + QDEL_NULL(performing_rite) + else + performing_rite = null + if(charges) charges-- if(!charges) diff --git a/code/modules/antagonists/heretic/heretic_antag.dm b/code/modules/antagonists/heretic/heretic_antag.dm index a9accccd8b20..04243f6320d1 100644 --- a/code/modules/antagonists/heretic/heretic_antag.dm +++ b/code/modules/antagonists/heretic/heretic_antag.dm @@ -301,6 +301,7 @@ var/mob/living/our_mob = mob_override || owner.current handle_clown_mutation(our_mob, "Ancient knowledge described to you has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") our_mob.faction |= FACTION_HERETIC + our_mob.apply_status_effect(/datum/status_effect/grouped/heretic_dreams, type) if (!issilicon(our_mob)) GLOB.reality_smash_track.add_tracked_mind(owner) @@ -314,6 +315,7 @@ var/mob/living/our_mob = mob_override || owner.current handle_clown_mutation(our_mob, removing = FALSE) our_mob.faction -= FACTION_HERETIC + our_mob.remove_status_effect(/datum/status_effect/grouped/heretic_dreams, type) if (owner in GLOB.reality_smash_track.tracked_heretics) GLOB.reality_smash_track.remove_tracked_mind(owner) diff --git a/code/modules/antagonists/heretic/status_effects/dreams.dm b/code/modules/antagonists/heretic/status_effects/dreams.dm new file mode 100644 index 000000000000..1b1db45f7028 --- /dev/null +++ b/code/modules/antagonists/heretic/status_effects/dreams.dm @@ -0,0 +1,118 @@ +/datum/status_effect/grouped/heretic_dreams + id = "heretic_dreams" + duration = STATUS_EFFECT_PERMANENT + tick_interval = STATUS_EFFECT_NO_TICK + alert_type = null + /// Cooldown between allowed dreams + COOLDOWN_DECLARE(dreaming_cooldown) + +/datum/status_effect/grouped/heretic_dreams/on_apply() + . = ..() + RegisterSignal(owner, COMSIG_PRE_DREAMING, PROC_REF(add_heretic_dream)) + RegisterSignal(owner, COMSIG_START_DREAMING, PROC_REF(start_heretic_dream)) + +/datum/status_effect/grouped/heretic_dreams/on_remove() + . = ..() + UnregisterSignal(owner, COMSIG_PRE_DREAMING) + UnregisterSignal(owner, COMSIG_START_DREAMING) + +/datum/status_effect/grouped/heretic_dreams/proc/add_heretic_dream(mob/living/dreamer, list/dream_pool) + SIGNAL_HANDLER + + if(!COOLDOWN_FINISHED(src, dreaming_cooldown)) + return + + var/atom/dream_center = get_dream_center(dreamer) + if(isnull(dream_center)) + return + + dream_pool[new /datum/dream/heretic(dream_center)] = 200 + +/datum/status_effect/grouped/heretic_dreams/proc/start_heretic_dream(mob/living/dreamer, datum/dream/current_dream) + SIGNAL_HANDLER + + if(!istype(current_dream, /datum/dream/heretic)) + return + COOLDOWN_START(src, dreaming_cooldown, /datum/mood_event/mansus_dream_fatigue::timeout) + dreamer.add_mood_event("mansus_dream_fatigue", /datum/mood_event/mansus_dream_fatigue) + +/datum/status_effect/grouped/heretic_dreams/proc/get_dream_center(mob/living/dreamer) + // Select a random influence as the center of the dream + if(length(GLOB.reality_smash_track.smashes)) + return pick(GLOB.reality_smash_track.smashes) + + // If there are no influences, either don't trigger the dream (if we are a heretic) or pick a completely random locale (if we aren't) + if(IS_HERETIC(dreamer)) + return null + + return get_safe_random_station_turf_equal_weight() + +/// Heretics can see dreams about random machinery from the perspective of a random unused influence +/datum/dream/heretic + sleep_until_finished = TRUE + /// The location of the influence (or lack thereof in the case of a fake dream) we will be dreaming about + var/atom/dream_center + /// The distance to the objects visible from the influence during the dream + var/dream_view_range = 5 + var/list/what_you_can_see = list( + /obj/item, + /obj/structure, + /obj/machinery, + ) + var/static/list/what_you_cant_see = typecacheof(list( + // Underfloor stuff and default wallmounts + /obj/item/radio/intercom, + /obj/structure/cable, + /obj/structure/disposalpipe/segment, + /obj/machinery/atmospherics/pipe/smart/manifold4w, + /obj/machinery/atmospherics/components/unary/vent_scrubber, + /obj/machinery/atmospherics/components/unary/vent_pump, + /obj/machinery/duct, + /obj/machinery/navbeacon, + /obj/machinery/power/terminal, + /obj/machinery/power/apc, + /obj/machinery/light_switch, + /obj/machinery/light, + /obj/machinery/camera, + /obj/machinery/door/firedoor, + /obj/machinery/firealarm, + /obj/machinery/airalarm, + /obj/structure/window/fulltile, + /obj/structure/window/reinforced/fulltile, + )) + /// Cached list of allowed typecaches for each type in what_you_can_see + var/static/list/allowed_typecaches_by_root_type = null + +/datum/dream/heretic/New(atom/dream_center) + src.dream_center = dream_center + +/datum/dream/heretic/GenerateDream(mob/living/carbon/dreamer) + . = list() + . += "you wander through the forest of Mansus" + . += "there is a " + pick("pond", "well", "lake", "puddle", "stream", "spring", "brook", "marsh") + + if(isnull(allowed_typecaches_by_root_type)) + allowed_typecaches_by_root_type = list() + for(var/type in what_you_can_see) + allowed_typecaches_by_root_type[type] = typecacheof(type) - what_you_cant_see + + var/list/all_objects = oview(dream_view_range, dream_center) + var/something_found = FALSE + for(var/object_type in allowed_typecaches_by_root_type) + var/list/filtered_objects = typecache_filter_list(all_objects, allowed_typecaches_by_root_type[object_type]) + if(filtered_objects.len) + if (!something_found) + . += "its waters reflect" + something_found = TRUE + var/obj/found_object = pick(filtered_objects) + . += initial(found_object.name) + if(!something_found) + . += pick("it's pitch black", "ihe reflections are vague", "you stroll aimlessly") + else + . += "the images fade in the ripples" + . += "you feel exhausted" + +/datum/mood_event/mansus_dream_fatigue + description = "I must recover before I can dream of Mansus again." + mood_change = -2 + timeout = 5 MINUTES diff --git a/code/modules/flufftext/Dreaming.dm b/code/modules/flufftext/dreaming.dm similarity index 91% rename from code/modules/flufftext/Dreaming.dm rename to code/modules/flufftext/dreaming.dm index 15d9a80578ea..3beaf44eb90f 100644 --- a/code/modules/flufftext/Dreaming.dm +++ b/code/modules/flufftext/dreaming.dm @@ -22,9 +22,16 @@ /mob/living/carbon/proc/dream() set waitfor = FALSE - var/datum/dream/chosen_dream = pick_weight(GLOB.dreams) + var/list/dream_pool = list() + + SEND_SIGNAL(src, COMSIG_PRE_DREAMING, dream_pool) + if(!length(dream_pool)) + dream_pool = GLOB.dreams + + var/datum/dream/chosen_dream = pick_weight(dream_pool) ADD_TRAIT(src, TRAIT_DREAMING, DREAMING_SOURCE) + SEND_SIGNAL(src, COMSIG_START_DREAMING, chosen_dream) dream_sequence(chosen_dream.GenerateDream(src), chosen_dream) /** @@ -39,9 +46,10 @@ */ /mob/living/carbon/proc/dream_sequence(list/dream_fragments, datum/dream/current_dream) - if(stat >= SOFT_CRIT || !HAS_TRAIT(src, TRAIT_KNOCKEDOUT)) + if(!HAS_TRAIT(src, TRAIT_KNOCKEDOUT) || stat >= HARD_CRIT) REMOVE_TRAIT(src, TRAIT_DREAMING, DREAMING_SOURCE) current_dream.OnDreamEnd(src) + SEND_SIGNAL(src, COMSIG_END_DREAMING, current_dream) return var/next_message = dream_fragments[1] dream_fragments.Cut(1,2) @@ -60,6 +68,7 @@ else REMOVE_TRAIT(src, TRAIT_DREAMING, DREAMING_SOURCE) current_dream.OnDreamEnd(src) + SEND_SIGNAL(src, COMSIG_END_DREAMING, current_dream) //------------------------- // DREAM DATUMS @@ -101,12 +110,9 @@ GLOBAL_LIST_INIT(dreams, populate_dream_list()) weight = 1000 /datum/dream/random/GenerateDream(mob/living/carbon/dreamer) - var/list/custom_dream_nouns = list() + var/list/custom_dream_nouns = get_dream_nouns(dreamer) || list() var/fragment = "" - for(var/obj/item/bedsheet/sheet in dreamer.loc) - custom_dream_nouns += sheet.dream_messages - . = list() . += "you see" @@ -149,6 +155,12 @@ GLOBAL_LIST_INIT(dreams, populate_dream_list()) fragment = "\a [replacetext(fragment, "%A% ", "")]" . += fragment +/datum/dream/random/proc/get_dream_nouns(mob/living/carbon/dreamer) + var/list/custom_dream_nouns = list() + for(var/obj/item/bedsheet/sheet in dreamer.loc) + custom_dream_nouns += sheet.dream_messages + return custom_dream_nouns + /// Dream plays a random sound at you, chosen from all sounds in the folder /datum/dream/hear_something weight = 500 diff --git a/code/modules/religion/deaconize.dm b/code/modules/religion/deaconize.dm new file mode 100644 index 000000000000..0466b026caa1 --- /dev/null +++ b/code/modules/religion/deaconize.dm @@ -0,0 +1,91 @@ +/** + * Deaconize + * Makes a sentient, non-cult member of the station into a Holy person, able to use bibles & other chap gear. + * Is a one-time use ability, given to all sects that don't have their own variation of it. + */ +/datum/religion_rites/deaconize + name = "Deaconize" + desc = "Converts someone to your sect. They must be willing, so the first invocation will instead prompt them to join. \ + They will gain the same holy abilities as you, this is a one-time use so make sure they are worthy!" + ritual_length = 30 SECONDS + ritual_invocations = list( + "A good, honorable person has been brought here by faith ...", + "With their hands ready to serve ...", + "Heart ready to listen ...", + "And soul ready to follow ...", + "May we offer our own hand in return ..." + ) + invoke_msg = "And use them to the best of our abilities." + rite_flags = RITE_ALLOW_MULTIPLE_PERFORMS | RITE_ONE_TIME_USE + + ///The person currently being deaconized. + var/mob/living/carbon/human/potential_deacon + +/datum/religion_rites/deaconize/Destroy() + potential_deacon = null + return ..() + +/datum/religion_rites/deaconize/perform_rite(mob/living/user, atom/religious_tool) + if(!ismovable(religious_tool)) + to_chat(user, span_warning("This rite requires a religious device that individuals can be buckled to.")) + return FALSE + var/atom/movable/movable_reltool = religious_tool + if(!movable_reltool) + return FALSE + var/mob/living/carbon/human/possible_deacon = locate() in movable_reltool.buckled_mobs + if(!possible_deacon) + to_chat(user, span_warning("Nothing is buckled to the [movable_reltool]!")) + return FALSE + if(!is_valid_for_deacon(possible_deacon, user)) + return FALSE + //no one invited or this is not the invited person + if(!potential_deacon || (possible_deacon != potential_deacon)) + INVOKE_ASYNC(src, PROC_REF(invite_deacon), possible_deacon) + to_chat(user, span_notice("They have been offered the oppertunity to join our ranks. Wait for them to decide and try again.")) + return FALSE + return ..() + +/datum/religion_rites/deaconize/invoke_effect(mob/living/carbon/human/user, atom/movable/religious_tool) + . = ..() + if(!(potential_deacon in religious_tool.buckled_mobs)) //checks one last time if the right corpse is still buckled + to_chat(user, span_warning("[potential_deacon] is no longer on the altar!")) + return FALSE + if(potential_deacon.stat != CONSCIOUS) + to_chat(user, span_warning("[potential_deacon] has to be conscious for the rite to work!")) + return FALSE + if(!potential_deacon.mind) + to_chat(user, span_warning("[potential_deacon]'s mind appears to be elsewhere!")) + return FALSE + if(IS_CULTIST(potential_deacon))//what the fuck?! + to_chat(user, span_warning("[GLOB.deity] has seen a true, dark evil in [potential_deacon]'s heart, and they have been smitten!")) + playsound(get_turf(religious_tool), 'sound/effects/pray.ogg', 50, TRUE) + potential_deacon.gib(DROP_ORGANS|DROP_BODYPARTS) + return FALSE + var/datum/brain_trauma/special/honorbound/honor = user.has_trauma_type(/datum/brain_trauma/special/honorbound) + if(honor && (potential_deacon in honor.guilty)) + honor.guilty -= potential_deacon + to_chat(user, span_notice("[GLOB.deity] has bound [potential_deacon] to the code! They are now a holy role! (albeit the lowest level of such)")) + potential_deacon.mind.set_holy_role(HOLY_ROLE_DEACON) + GLOB.religious_sect.on_conversion(potential_deacon) + playsound(get_turf(religious_tool), 'sound/effects/pray.ogg', 50, TRUE) + return TRUE + +///Helper if the passed possible_deacon is valid to become a deacon or not. +/datum/religion_rites/deaconize/proc/is_valid_for_deacon(mob/living/carbon/human/possible_deacon, mob/living/user) + if(possible_deacon.stat != CONSCIOUS) + to_chat(user, span_warning("[possible_deacon] needs to be alive and conscious to join!")) + return FALSE + if(possible_deacon.mind && possible_deacon.mind.holy_role) + to_chat(user, span_warning("[possible_deacon] is already a member of the religion!")) + return FALSE + return TRUE + +/** + * Async proc that waits for a response on joining the sect. + * If they accept, the deaconize rite can now recruit them instead of just offering more invites. + */ +/datum/religion_rites/deaconize/proc/invite_deacon(mob/living/carbon/human/invited) + var/ask = tgui_alert(invited, "Join [GLOB.deity]? You will be expected to follow the Chaplain's order.", "Invitation", list("Yes", "No"), 60 SECONDS) + if(ask != "Yes") + return + potential_deacon = invited diff --git a/code/modules/religion/dreams/banish_nightmare.dm b/code/modules/religion/dreams/banish_nightmare.dm new file mode 100644 index 000000000000..9883c8253150 --- /dev/null +++ b/code/modules/religion/dreams/banish_nightmare.dm @@ -0,0 +1,100 @@ +/datum/religion_rites/banish_nightmare + name = "Banish Nightmare" + desc = "Banish the corpse of a Nightmare or its heart back from whence it came, protecting the dreams of \ + the station and earning favor. If a heart is present, you will be rewarded with a special blessing." + favor_cost = 0 + ritual_length = 20 SECONDS + +/datum/religion_rites/banish_nightmare/New() + . = ..() + ritual_invocations = list( + "We have bested a terrible Nightmare that plagued our station!..", + "With the power of [GLOB.deity], we cast it out!..", + "This invader of dreams has no place here...", + "May it trouble our flock no longer.", + ) + +/datum/religion_rites/banish_nightmare/perform_rite(mob/living/user, atom/religious_tool) + var/has_nightmare = FALSE + for(var/mob/living/carbon/human/nightmare in get_turf(religious_tool)) + if(isnightmare(nightmare)) + has_nightmare = TRUE + break + + for(var/obj/item/organ/organ in get_turf(religious_tool)) + if(istype(organ, /obj/item/organ/heart/nightmare)) + has_nightmare = TRUE + break + + if(!has_nightmare) + to_chat(user, span_warning("There is no corpse or heart of a Nightmare to banish!")) + return FALSE + + return ..() + +/datum/religion_rites/banish_nightmare/post_invoke_effects(mob/living/user, atom/religious_tool) + . = ..() + var/favor = 0 + var/give_heart = FALSE + for(var/mob/living/carbon/human/nightmare in get_turf(religious_tool)) + if(!isnightmare(nightmare)) + continue + + if(istype(nightmare.get_organ_slot(ORGAN_SLOT_HEART), /obj/item/organ/heart/nightmare)) + give_heart += 1 + favor += 100 + + nightmare.dust(just_ash = TRUE, drop_items = TRUE, give_moodlet = FALSE, force =TRUE) + favor += 200 + + for(var/obj/item/organ/organ in get_turf(religious_tool)) + if(!istype(organ, /obj/item/organ/heart/nightmare)) + continue + + qdel(organ) + favor += 100 + give_heart += 1 + + if(favor <= 0) + CRASH("Banish nightmare rite invoked without finding a nightmare or nightmare heart to banish.") + + GLOB.religious_sect.adjust_favor(favor, user) + if(give_heart) + for(var/i in 1 to give_heart) + new /obj/item/organ/heart/evolved/sacred/dreamer(get_turf(religious_tool)) + playsound(religious_tool, 'sound/effects/pray.ogg', 50, TRUE, frequency = 0.5) + to_chat(user, span_hypnophrase("[GLOB.deity] blesses you.")) + else + to_chat(user, span_hypnophrase("[GLOB.deity] smiles upon you.")) + user.add_mood_event("banish_nightmare", /datum/mood_event/banish_nightmare) + +/datum/mood_event/banish_nightmare + mood_change = 4 + description = "I banished a nightmare and protected our dreams!" + timeout = 10 MINUTES + +/obj/item/organ/heart/evolved/sacred/dreamer + name = "blessed sacred heart" + desc = "Banish the shadows!" + maxHealth = STANDARD_ORGAN_THRESHOLD * 1.5 + /// Magic charges we block + var/charges = 3 + +/obj/item/organ/heart/evolved/sacred/dreamer/on_life(seconds_per_tick) + healing_probability = 5 + if(HAS_TRAIT(owner, TRAIT_DREAMING)) + healing_probability += 7.5 + if(owner.stat == UNCONSCIOUS) + healing_probability += 7.5 + return ..() + +/obj/item/organ/heart/evolved/sacred/dreamer/on_blocked() + charges -= 1 + addtimer(CALLBACK(src, PROC_REF(recharge)), 1 MINUTES) + playsound(owner, 'sound/effects/health/slowbeat.ogg', 80) + +/obj/item/organ/heart/evolved/sacred/dreamer/check_block() + return charges > 0 + +/obj/item/organ/heart/evolved/sacred/dreamer/proc/recharge() + charges += 1 diff --git a/code/modules/religion/dreams/deaconize_dreamer.dm b/code/modules/religion/dreams/deaconize_dreamer.dm new file mode 100644 index 000000000000..a6232b10d6cd --- /dev/null +++ b/code/modules/religion/dreams/deaconize_dreamer.dm @@ -0,0 +1,20 @@ +/datum/religion_rites/deaconize/dreamers + desc = "Converts someone to your sect. They must be willing, so the first invocation will instead prompt them to join. \ + They will gain the same holy abilities as you. You can deaconize up to three followers, so choose wisely!" + rite_flags = parent_type::rite_flags & ~RITE_ONE_TIME_USE + +/datum/religion_rites/deaconize/dreamers/invoke_effect(mob/living/carbon/human/user, atom/movable/religious_tool) + if(isnightmare(potential_deacon)) + to_chat(user, span_warning("[potential_deacon] is a nightmare, an affront to [GLOB.deity] and all they stand for!")) + return FALSE + return ..() + +/datum/religion_rites/deaconize/dreamers/post_invoke_effects(mob/living/user, atom/religious_tool) + . = ..() + if(!istype(GLOB.religious_sect, /datum/religion_sect/dreams)) + return + + var/datum/religion_sect/dreams/sect = GLOB.religious_sect + sect.deacon_count += 1 + if(sect.deacon_count >= sect.max_deacons) + sect.rites_list -= type diff --git a/code/modules/religion/dreams/dream_portent.dm b/code/modules/religion/dreams/dream_portent.dm new file mode 100644 index 000000000000..de81563d1894 --- /dev/null +++ b/code/modules/religion/dreams/dream_portent.dm @@ -0,0 +1,378 @@ +/datum/religion_rites/dream_portent + name = "Dream Portent" + desc = "Immediately fall into a slumber and receive a portent of the future. \ + The vision may be difficult to interpret, but will likely come true in some form. \ + Any form of harm will awaken you and disrupt the vision." + favor_cost = 50 + rite_flags = NONE + ritual_length = 6 SECONDS + +/datum/religion_rites/dream_portent/New() + . = ..() + ritual_invocations = list( + "O great shepherd [GLOB.deity], grant me a vision of the future!..", + "That our flock may persevere through the trials to come...", + ) + +/datum/religion_rites/dream_portent/can_afford(mob/living/user) + if(!..()) + return FALSE + if(!iscarbon(user)) + to_chat(user, span_warning("You are not the sort of creature that can receive a portent.")) + return FALSE + return TRUE + +/datum/religion_rites/dream_portent/invoke_effect(mob/living/user, atom/religious_tool) + if(!user.SetSleeping(10 SECONDS)) + to_chat(user, span_warning("You fail to fall asleep.")) + return FALSE + + user.visible_message(span_notice("[user] suddenly falls into a deep slumber, [user.p_their()] eyes fluttering...")) + user.adjust_drowsiness(30 SECONDS) + return ..() + +/datum/religion_rites/dream_portent/post_invoke_effects(mob/living/user, atom/religious_tool) + . = ..() + RegisterSignal(user, COMSIG_PRE_DREAMING, PROC_REF(add_portent)) + RegisterSignal(user, COMSIG_START_DREAMING, PROC_REF(check_portent)) + RegisterSignal(user, COMSIG_END_DREAMING, PROC_REF(end_portent)) + RegisterSignal(user, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(interrupt_portent)) + addtimer(CALLBACK(src, PROC_REF(force_dream), user), rand(2, 5) SECONDS, TIMER_DELETE_ME) // force the dream to start immediately + +/datum/religion_rites/dream_portent/proc/force_dream(mob/living/carbon/dreamer) + if(!iscarbon(dreamer) || HAS_TRAIT(dreamer, TRAIT_DREAMING)) + return // dreamed naturally already + dreamer.dream() + +/datum/religion_rites/dream_portent/proc/add_portent(mob/living/carbon/dreamer, list/dream_pool) + SIGNAL_HANDLER + + // removes any pre-existing vague portents in the dream pool so we can give the real deal + for(var/datum/dream/random/vague_portent/existing in dream_pool) + dream_pool -= existing + + dream_pool[new /datum/dream/specific_portent()] = 2000 + +/datum/religion_rites/dream_portent/proc/check_portent(mob/living/carbon/dreamer, datum/dream/current_dream) + SIGNAL_HANDLER + + if(istype(current_dream, /datum/dream/specific_portent)) + return + + to_chat(dreamer, span_cyan("Your mind wanders, yet you receive no clear vision... You must try again later.")) + refund(0.8) + dreamer.adjust_drowsiness(10 SECONDS) + dreamer.add_mood_event("dream_failed", /datum/mood_event/dream_failed) + +/datum/religion_rites/dream_portent/proc/interrupt_portent(mob/living/carbon/dreamer, damage_amount) + SIGNAL_HANDLER + + if(!prob(damage_amount * 10)) // higher damage = higher chance to interrupt + return + + to_chat(dreamer, span_warning("Your dream is interrupted as you are harmed!")) + dreamer.SetSleeping(0) + dreamer.adjust_drowsiness(10 SECONDS) + dreamer.add_mood_event("dream_interrupted", /datum/mood_event/dream_interrupted) + +/datum/religion_rites/dream_portent/proc/end_portent(mob/living/carbon/dreamer, datum/dream/current_dream) + SIGNAL_HANDLER + + qdel(src) + +/datum/mood_event/dream_interrupted + mood_change = -2 + description = "I was rudely awakened from my dreams!" + timeout = 5 MINUTES + +/datum/mood_event/dream_failed + mood_change = -2 + description = "I couldn't receive a clear vision from my dreams!" + timeout = 5 MINUTES + +/datum/dream/specific_portent + weight = 0 + sleep_until_finished = TRUE + +/datum/dream/specific_portent/GenerateDream(mob/living/carbon/dreamer) + . = list() + . += span_cyan("a portent of the future") + + var/list/portent_types = list( + "[GLOB.deity] greets you warmly" = "[GLOB.deity] bids you farewell, though you feel their presence watch over you", + "a crystal ball reveals a vision of the future" = "ultimately, the crystal ball returns to its normal, opaque state", + "a divine light blinds you, revealing glimpses of what is to come" = "finally, the light fades, leaving you with a lingering warmth", + "a full moon illuminates the sky" = "the moon crosses the horizon, bringing forth a new dawn", + "a mysterious figure appears, cloaked in shadow" = "they depart, leaving you with a sense of [pick("wonder", "dread", "curiosity", "foreboding")]", + "an incomprehensible entity envelops you, showing you visions of the past, present, and future" = "the entity releases you, leaving you with a sense of awe and fear", + "an old [pick("man", "woman", "prophet", "oracle")] approaches you, offering cryptic advice" = "they vanish before you can ask any questions", + "the stars align in a way you've never seen before" = "finally, the stars return to their normal constellations", + "the trees ahead parts to reveal a hidden path" = "ultimately, you lose the path as the trees sway back into place", + "walking through a featureless landscape, shapes begin to form" = "finally, the shapes fade away, leaving you alone in the void", + "you see yourself sleeping peacefully" = "finally, you see yourself waking up calmly", + "your third eye opens to reveal a hidden truth" = "finally, your third eye closes, but the vision lingers in your mind", + ) + var/picked_portent = pick(portent_types) + + . += span_cyan(picked_portent) + for(var/part in get_portent(dreamer)) + . += span_cyan(part) + . += span_cyan(portent_types[picked_portent]) + +/datum/dream/specific_portent/proc/get_portent(mob/living/carbon/dreamer) + if(prob(1)) + GLOB.religious_sect.adjust_favor(25, dreamer) + return pick(list( + list("reply hazy", "try again later"), + list("ask again later"), + list("better not tell you now"), + list("cannot predict now"), + list("concentrate and ask again"), + )) + + for(var/datum/antagonist/nightmare/nightmare as anything in GLOB.antagonists) + if(nightmare.owner?.current?.stat == CONSCIOUS) + return pick(list( + list("you have a terrible nightmare", "filled with indescribable horrors", "leaving you with a lingering sense of dread"), + list("you have a terrible nightmare", "filled with visions of your own death", "leaving you with a lingering sense of doom"), + list("you have a terrible nightmare", "filled with horrible memories of your past", "leaving you with a lingering sense of sadness"), + list("you have a terrible nightmare", "filled with fear of the unknown", "leaving you with a lingering sense of anxiety"), + list("you have a terrible nightmare", "filled with stabbing pain and suffocating darkness", "leaving you with a lingering sense of panic"), + )) + + for(var/datum/team/cult/cult as anything in GLOB.antagonist_teams) + if(cult.cult_ascendent) + return list("the Blood Geometer, Nar'sie, invades your dream", "her pressence overwhelming and suffocating", "she eyes you greedily") + + for(var/datum/antagonist/heretic/heretic in GLOB.antagonists) + if(!heretic.ascended) + continue + if(heretic.owner?.current?.stat != CONSCIOUS) + return list( + "the doors of the Mansus loom ahead of you", + "intricately decorated - but cracked, broken, and sealed shut", + "a great [IS_HERETIC(dreamer) ? "force" : "evil"] locked away for good", + ) + + var/list/heretic_text = list("the doors of the Mansus loom ahead of you", "intricately decorated - and ajar", "you look through the crack") + switch(prob(75) ? heretic.heretic_path.route : null) + if(PATH_ASH) + heretic_text += "beyond it, you see a barren wasteland" + heretic_text += "all life long gone, scorched to ash and dust" + heretic_text += "you can hardly breathe through the smog" + if(PATH_FLESH) + heretic_text += "beyond it, you see a vast sea of blood" + heretic_text += "the screams of the drowning fill the air" + heretic_text += "the blood laps at your feet" + if(PATH_VOID) + heretic_text += "beyond it, a vast emptiness stretches out in all directions" + heretic_text += "the silence is deafening" + heretic_text += "you know you are not alone" + if(PATH_COSMIC) + heretic_text += "beyond it, you see the birth and death of stars, galaxies colliding in a cosmic dance" + heretic_text += "the beauty of it all is overwhelming" + heretic_text += "you feel insignificant" + if(PATH_BLADE) + heretic_text += "beyond it, you see a great battle unfolding" + heretic_text += "countless warriors grappling in an endless war" + heretic_text += "the sound of clashing steel and cries of the fallen fill the air" + if(PATH_LOCK) + heretic_text += "beyond it, you see an endless labyrinth" + heretic_text += "the walls shifting and changing as you navigate it" + heretic_text += "no matter which turn you take, you cannot find an exit" + if(PATH_MOON) + heretic_text += "beyond it, you bear witness to a grand carnival" + heretic_text += "filled with strange sights and smells, but endless joy and laughter" + heretic_text += "you can't shake the feeling something is wrong" + else + heretic_text += "what lies beyond cannot be comprehended" + heretic_text += "the sheer magnitude of overwhelms you" + heretic_text += "you feel a strange mix of awe and terror" + + return heretic_text + + for(var/datum/antagonist/wizard/wizard in GLOB.antagonists) + if(wizard.owner?.current?.stat != CONSCIOUS) + return + if(wizard.ritual?.times_completed < GRAND_RITUAL_RUNES_WARNING_POTENCY) + if(prob(1)) + return list( + "a garish fool puts on a show", + "many lament their antics, but some are amused", + "they seem to have no sense of right or wrong", + "what an asshole", + ) + return + + if(wizard.ritual.times_completed == GRAND_RITUAL_FINALE_COUNT) + return list( + "the magician appears once more", + "they ignore you, bowing to an unseen audience", + "you hear a crowd cheering and applause", + "a bright light envelops you, blinding you", + "when your vision returns, the magician is gone", + ) + + if(wizard.ritual.times_completed == GRAND_RITUAL_IMMINENT_FINALE_POTENCY) + return list( + "the magician greets you once more, grinning", + "they tell you the grand finale is near", + "you can feel the air around you crackling with magical energy", + "they magician winks, promising an unforgettable show", + "before you can question, they vanish once again", + ) + + return list( + "a robed figure manifests in your dream", + "they introduce themselves as the magician", + "a demonstration of their powers leaves you in awe", + "they leave as suddenly as they arrived", + ) + + for(var/obj/machinery/nuclearbomb/bomb as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb)) + if(bomb.timing) + return pick(list( + list("you see", "a supernova", "bright and blinding", "consuming everything in an instant"), + list("you see", "a mushroom cloud on the horizon", "a sign of devastation and ruin"), + list("you see", "a ticking clock", "counting down to an inevitable disaster"), + list("you see", "a looming tower atop a rocky mountain", "a lightning strikes it, bringing it down", "a sign of imminent calamity"), + )) + + for(var/obj/item/disk/nuclear/nuke_disk as anything in SSpoints_of_interest.real_nuclear_disks) + var/area/disk_loc = get_area(nuke_disk) + if(!istype(disk_loc, /area/station) && !istype(disk_loc, /area/space)) + return pick(list( + list("you see", "a dying star", "slowly dimming", "on the verge of collapse"), + list("you see", "a fool, dancing aimlessly", "they holds a ticking bomb", "a sign of recklessness"), + list("you see", "a hanging man", "swaying gently in the breeze", "a sign of surrender"), + list("you see", "a looming tower atop a rocky mountain", "it rains heavily around you", "a sign of calamity"), + )) + + for(var/mob/living/carbon/human/clone as anything in GLOB.human_list) + if(clone != dreamer && clone.real_name == dreamer.real_name && clone.stat == CONSCIOUS && prob(50)) + return list( + "you see yourself", + "in a foggy mirror", + "the reflection is warped and distorted", + "but unquestionably you", + ) + + if(prob(length(dreamer.get_all_orbiters()) * 20)) + return list( + "you feel a ghostly [pick("presence", "entity", "figure")]", + "it seems to be trying to communicate with you", + "yet you can't comprehend its message", + "a sense of sadness and longing washes over you", + ) + + if(IS_HERETIC(dreamer) && prob(50)) + var/datum/antagonist/heretic/heretic = GET_HERETIC(dreamer) + if(prob(75) && !heretic.feast_of_owls) + switch(heretic.heretic_path?.route) + if(PATH_START) + return list("you see", "a fork in the road ahead", "the path before you uncertain and full of potential") + if(PATH_ASH) + return list("you see", "a barren wasteland ahead", "burned trees line the horizon", "the air thick with smoke and ash", "a bleak and desolate sight") + if(PATH_FLESH) + return list("you see", "a legion of amalgamations ahead", "twisted and grotesque", "marching in unison towards an unknown destination") + if(PATH_VOID) + return list("you see", "nothingness ahead", "a void that seems to swallow all light and hope", "the silence deafening and oppressive") + if(PATH_COSMIC) + return list("you see", "the birth of a new star", "radiant and full of potential", "an awe-inspiring sight") + if(PATH_BLADE) + return list("you see", "a towering fortress ahead", "its walls lined with stalwart defenders", "each and every one bowing in respect to you") + if(PATH_LOCK) + return list("you see", "and endless labyrinth", "the walls shifting and changing as you navigate it", "a test of your resolve and cunning") + if(PATH_MOON) + return list("you see", "an everlasting carnival", "the air filled with joy and laughter", "but with an undercurrent of melancholy and longing") + + return list("you see", "a large door ahead", "intricately decorated and emanating a powerful aura", "but never opening", "no matter how long you wait") + + var/dead = 0 + for(var/mob/deceased as anything in GLOB.player_list) + if(deceased.stat == DEAD) + dead += 1 + + switch(dead / length(GLOB.joined_player_list)) + if(0.25 to 0.5) + return pick(list( + list("you find yourself", "in a small graveyard", "humble in size but lovingly maintained", "with fresh flowers on the graves"), + list("you see", "a vision of spirits", "floating throughout the station"), + )) + if(0.5 to 0.75) + return pick(list( + list("you find yourself", "in a dimly lit hallway", "with a sense of dread in the air"), + list("you see", "a vision of an encroaching darkness", "threatening you eerily"), + )) + if(0.75 to 0.9) + return pick(list( + list("you find yourself", "in a lonely ballroom", "barely lit with flickering lights"), + list("you see", "a picture of a silent battlefield", "no clear victor, but heavy losses on all sides"), + )) + if(0.9 to 1) + return pick(list( + list("you find yourself", "alone", "no sight but your darkness", "no sound but your heartbeat", "a bleak and hopeless vision"), + list("you see", "a vision of yourself, alone", "in a desolate wasteland", "with no signs of life or hope in sight"), + )) + + var/max_law_changes = 0 + for(var/mob/living/silicon/ai/ai as anything in GLOB.ai_list) + max_law_changes = max(max_law_changes, ai.law_change_counter) + + if(prob(clamp((max_law_changes - 10) * 10, 0, 50))) + return pick(list( + list("you see", "a twisted and sickly tree", "with branches that seem to reach into every aspect of the station", "its roots drip with a inky black liquid"), + list("you see", "a corrupt political figure", "surrounded by sycophants and puppets", "pulling the strings from behind the scenes"), + list("you see", "buzzing electronics", "wires that seem to snake off into the distance", "it bathes you in red light and static"), + )) + + if(EMERGENCY_ESCAPED_OR_ENDGAMED) + return list("you see", "a new beginning on the horizon", "it feels warm") + + if(EMERGENCY_PAST_POINT_OF_NO_RETURN) + return list("you see", "salvation just out of reach") + + if(prob(75) || GLOB.communications_controller.announced_greenshift) + if(length(GLOB.admins) >= 5) + return list("you see", "a gathering of powerful beings in the distance", "their intentions unclear") + + switch(SSdynamic.current_tier.tier) + if(0) + return list("you see", "a flourishing field", "teeming with life and vitality", "a symbol of hope for the future") + if(1) + return pick(list( + list("you see", "a lone figure in the distance", "shrouded in mystery"), + list("you see", "a [pick("beast", "monster", "animal")] stalking through the shadows", "its intentions unknown"), + list("you see", "a [pick("fog", "haze", "cloud")] rolling in", "obscuring everything in its path"), + )) + if(2) + return pick(list( + list("you find yourself", "in a branching forest", "dark and with many potential paths"), + list("you find yourself", "in an old city", "still bustling with activity", "but with an omnipresent feel of decay"), + )) + if(3) + return list("you find yourself", "in a burning city", "flames reaching high", "but everyone doing their best to survive") + if(4) + return list("you find yourself", "in a chaotic battlefield", "with no clear sides or victors", "only endless conflict and suffering") + + return list("you see", "nothing of note", "but have a lingering feeling of unease about the future") + +/datum/dream/random/vague_portent + weight = 0 + sleep_until_finished = TRUE + +/datum/dream/random/vague_portent/get_dream_nouns(mob/living/carbon/dreamer) + var/list/antags = list() + for(var/datum/antagonist/antag as anything in GLOB.antagonists) + antags |= LOWER_TEXT(antag.jobban_flag || antag.pref_flag) + + if(prob(80) || !length(antags)) + for(var/datum/dynamic_ruleset/ruleset as anything in subtypesof(/datum/dynamic_ruleset)) + antags |= LOWER_TEXT(initial(ruleset.jobban_flag) || initial(ruleset.pref_flag)) + + // chance to make nightmares the focus of the dream + var/nightmare_id = /datum/antagonist/nightmare::jobban_flag || /datum/antagonist/nightmare::pref_flag + if(prob(20) && (LOWER_TEXT(nightmare_id) in antags)) + return list(LOWER_TEXT(nightmare_id)) + + return antags diff --git a/code/modules/religion/dreams/dream_projection.dm b/code/modules/religion/dreams/dream_projection.dm new file mode 100644 index 000000000000..c1c131978a4a --- /dev/null +++ b/code/modules/religion/dreams/dream_projection.dm @@ -0,0 +1,185 @@ +/datum/religion_rites/dream_projection + name = "Dream Projection" + desc = "Astrally project your dream consciousness into the mind of one of your followers. \ + While projecting, you are asleep, and can communicate with only and see through the eyes of the chosen follower, \ + but cannot interact with the world in any way. The projection can be ended at any time, \ + ends if you are woken up or attacked, and ends if the follower dies." + favor_cost = 100 + ritual_length = 15 SECONDS + +/datum/religion_rites/dream_projection/New() + . = ..() + ritual_invocations = list( + "A member of the flock has gone astray, lost in the waking world...", + "It is the duty of the shepherd to guide them back to the fold, even if they cannot find their way themselves...", + "Let me walk through their waking dream, and show them the way back...", + ) + +/datum/religion_rites/dream_projection/perform_rite(mob/living/user, atom/religious_tool) + var/list/followers = list() + for(var/mob/living/follower as anything in GLOB.mob_living_list) + if(follower.mind?.holy_role && user != follower) + followers += follower + + if(!length(followers)) + to_chat(user, span_warning("You have no followers to project into!")) + return FALSE + + return ..() + +/datum/religion_rites/dream_projection/post_invoke_effects(mob/living/user, atom/religious_tool) + . = ..() + var/list/followers = list() + for(var/mob/living/follower as anything in GLOB.mob_living_list) + if(follower.mind?.holy_role && user != follower) + followers += follower + + if(!length(followers)) + refund(0.8) + return + + var/mob/living/carbon/human/target = tgui_input_list(user, "Choose a follower to project into:", "Dream Projection", followers) + if(QDELETED(target) || target.stat == DEAD || isnull(target.mind?.holy_role)) + refund(0.8) + return + + if(!user.apply_status_effect(/datum/status_effect/dream_projection, target)) + to_chat(user, span_warning("You fail to fall asleep.")) + refund(0.8) + return + +/datum/status_effect/dream_projection + id = "dream_projection" + duration = STATUS_EFFECT_PERMANENT + alert_type = null + on_remove_on_mob_delete = TRUE + + /// Target of the projection + VAR_PRIVATE/mob/living/carbon/human/target + /// Projection mob that the owner is put into + VAR_PRIVATE/mob/eye/imaginary_friend/dream_projection/projection + +/datum/status_effect/dream_projection/on_creation(mob/living/new_owner, mob/living/carbon/human/target) + if(isnull(target)) + stack_trace("Dream projection created without a target!") + qdel(src) + return + + src.target = target + return ..() + +/datum/status_effect/dream_projection/get_examine_text() + return "[owner.p_They()] are in a deep slumber, yet [owner.p_their()] eyes show a distant look, as if [owner.p_they()] are somewhere far away..." + +/datum/status_effect/dream_projection/on_apply() + if(!owner.SetSleeping(20 SECONDS)) + to_chat(owner, span_warning("You fail to fall asleep.")) + return FALSE + + . = ..() + RegisterSignal(target, COMSIG_QDELETING, PROC_REF(end_projection)) + RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(end_projection)) + + ADD_TRAIT(owner, TRAIT_DREAMING, TRAIT_STATUS_EFFECT(id)) + RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_KNOCKEDOUT), PROC_REF(interrupt_projection)) + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(interrupt_projection)) + RegisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(interrupt_projection)) + + projection = new(target.loc) + projection.AddComponent(/datum/component/temporary_body, old_mind = owner.mind) + projection.real_name = owner.real_name + projection.gender = owner.gender + projection.human_icon = getFlatIcon(owner) + projection.PossessByPlayer(owner.ckey) + projection.attach_to_owner(target) + + RegisterSignal(projection, COMSIG_QDELETING, PROC_REF(stop_projection)) + + owner.add_filter(id, 1, list("type" = "outline", "color" = "#aee2b2", "alpha" = 0, "size" = 2)) + owner.update_filters() + var/filter = owner.get_filter(id) + animate(filter, alpha = 150, time = 2 SECONDS, easing = SINE_EASING|EASE_IN, loop = -1) + animate(alpha = 0, time = 2 SECONDS, easing = SINE_EASING|EASE_OUT) + +/datum/status_effect/dream_projection/on_remove() + . = ..() + UnregisterSignal(target, COMSIG_QDELETING) + UnregisterSignal(target, COMSIG_LIVING_DEATH) + target = null + + REMOVE_TRAIT(owner, TRAIT_DREAMING, TRAIT_STATUS_EFFECT(id)) + UnregisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_KNOCKEDOUT)) + UnregisterSignal(owner, COMSIG_LIVING_DEATH) + UnregisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE) + + if(!QDELING(owner)) + owner.adjust_drowsiness(10 SECONDS) + var/filter = owner.get_filter(id) + animate(filter, alpha = 0, time = 1 SECONDS, easing = SINE_EASING|EASE_OUT) + addtimer(CALLBACK(owner, TYPE_PROC_REF(/datum, remove_filter), id), 1 SECONDS) // delay the filter removal to let the transition finish + + UnregisterSignal(projection, COMSIG_QDELETING) + if(QDELING(projection)) + projection = null + else + QDEL_NULL(projection) + +/datum/status_effect/dream_projection/tick(seconds_between_ticks) + if(isnull(owner.mind?.holy_role)) + end_projection() + return + + owner.SetSleeping(20 SECONDS) // keep the owner asleep + +/datum/status_effect/dream_projection/proc/end_projection() + SIGNAL_HANDLER + to_chat(owner, span_warning("Your dream projection ends as your target is no longer valid.")) + owner.SetSleeping(10 SECONDS) + qdel(src) + +/datum/status_effect/dream_projection/proc/interrupt_projection() + SIGNAL_HANDLER + to_chat(owner, span_warning("Your dream projection is interrupted!")) + INVOKE_ASYNC(src, TYPE_PROC_REF(/mob, emote), "gasp") + owner.visible_message(span_notice("[owner]'s eyes snap open as they are jolted awake!"), vision_distance = COMBAT_MESSAGE_RANGE, ignored_mobs = owner) + qdel(src) + +/datum/status_effect/dream_projection/proc/stop_projection() + SIGNAL_HANDLER + to_chat(owner, span_warning("You end your dream projection and return to your body.")) + owner.SetSleeping(10 SECONDS) + qdel(src) + +/mob/eye/imaginary_friend/dream_projection + name = "dream projection" + +/mob/eye/imaginary_friend/dream_projection/Initialize(mapload) + . = ..() + var/datum/action/innate/stop_projection/exit_action = new(src) + exit_action.Grant(src) + overlay_fullscreen("curse", /atom/movable/screen/fullscreen/curse, 1) // todo something more fitting? + +/mob/eye/imaginary_friend/dream_projection/Login() + . = ..() + client.eye = owner || src + +/mob/eye/imaginary_friend/dream_projection/greet() + return + +/mob/eye/imaginary_friend/dream_projection/verb/stop_projection() + set category = "IC" + set name = "Stop Projection" + set desc = "Stop astrally projecting and return to your body." + + qdel(src) + +/mob/eye/imaginary_friend/dream_projection/attach_to_owner(mob/living/imaginary_friend_owner) + . = ..() + client?.eye = owner + +/datum/action/innate/stop_projection + name = "Stop Projection" + desc = "Stop astrally projecting and return to your body." + +/datum/action/innate/stop_projection/Activate() + qdel(owner) diff --git a/code/modules/religion/dreams/dream_protection.dm b/code/modules/religion/dreams/dream_protection.dm new file mode 100644 index 000000000000..f69777b6be38 --- /dev/null +++ b/code/modules/religion/dreams/dream_protection.dm @@ -0,0 +1,130 @@ +/datum/religion_rites/dream_protection + name = "Dream Protection" + desc = "Bless you and all of your followers with protection in their slumber, \ + granting resistance to damage while asleep, which is further increased while dreaming." + favor_cost = 200 + rite_flags = RITE_ONE_TIME_USE | RITE_AUTO_DELETE + ritual_length = 15 SECONDS + +/datum/religion_rites/dream_protection/New() + . = ..() + ritual_invocations = list( + "Protect our flock from harm, great shepherd [GLOB.deity]!..", + "Grant us peaceful slumber, free from nightmares and those who would do us harm!..", + "Our sleepers shall be safe to dream to their heart's desire!..", + ) + +/datum/religion_rites/dream_protection/post_invoke_effects(mob/living/user, atom/religious_tool) + . = ..() + if(!istype(GLOB.religious_sect, /datum/religion_sect/dreams)) + return + + var/datum/religion_sect/dreams/sect = GLOB.religious_sect + sect.dream_protection = TRUE + + for(var/mob/living/follower as anything in GLOB.mob_living_list) + if(follower.mind?.holy_role) + follower.apply_status_effect(/datum/status_effect/dream_protection) + +/datum/status_effect/dream_protection + id = "dream_protection" + duration = STATUS_EFFECT_PERMANENT + tick_interval = STATUS_EFFECT_NO_TICK + alert_type = null + /// Damage reduction when sleeping/dreaming, multiplicative + var/damage_mod = 0.75 + /// If the filter has been applied + VAR_PRIVATE/has_filter = FALSE + +/datum/status_effect/dream_protection/on_apply() + . = ..() + RegisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS, PROC_REF(modify_damage)) + RegisterSignals(owner, list( + COMSIG_MOB_STATCHANGE, + SIGNAL_ADDTRAIT(TRAIT_DREAMING), + SIGNAL_REMOVETRAIT(TRAIT_DREAMING), + ), PROC_REF(check_protection)) + check_protection() + +/datum/status_effect/dream_protection/on_remove() + . = ..() + UnregisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS) + REMOVE_TRAIT(owner, TRAIT_HOLY, TRAIT_STATUS_EFFECT(id)) + + if(!QDELING(owner)) + var/filter = owner.get_filter(id) + animate(filter, alpha = 0, time = 1 SECONDS, easing = SINE_EASING|EASE_OUT) + addtimer(CALLBACK(owner, TYPE_PROC_REF(/datum, remove_filter), id), 1 SECONDS) // delay the filter removal to let the transition finish + +/datum/status_effect/dream_protection/proc/check_protection() + SIGNAL_HANDLER + + if(owner.stat == UNCONSCIOUS || HAS_TRAIT(owner, TRAIT_DREAMING)) + if(!has_filter) + owner.add_filter(id, 2, list("type" = "outline", "color" = "#bde0dc", "alpha" = 0, "size" = 2)) + var/filter = owner.get_filter(id) + animate(filter, alpha = 150, time = 2 SECONDS, easing = SINE_EASING|EASE_IN, loop = -1) + animate(alpha = 0, time = 2 SECONDS, easing = SINE_EASING|EASE_OUT) + has_filter = TRUE + ADD_TRAIT(owner, TRAIT_HOLY, TRAIT_STATUS_EFFECT(id)) + + else + if(has_filter) + var/filter = owner.get_filter(id) + animate(filter, alpha = 0, time = 1 SECONDS, easing = SINE_EASING|EASE_OUT) + has_filter = FALSE + REMOVE_TRAIT(owner, TRAIT_HOLY, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/dream_protection/proc/modify_damage(mob/living/source, list/damage_mods, ...) + SIGNAL_HANDLER + if(owner.stat == UNCONSCIOUS) + damage_mods += damage_mod + if(HAS_TRAIT(owner, TRAIT_DREAMING)) + damage_mods += damage_mod + +/datum/status_effect/dream_protection/get_examine_text() + if(owner.stat == UNCONSCIOUS || HAS_TRAIT(owner, TRAIT_DREAMING)) + return "A soft cyan glow envelops [owner.p_them()], reflecting light." + +// Version that only lasts until they wake up (with a set duration backup) +/datum/status_effect/dream_protection/temporary + id = "temporary_dream_protection" + duration = 3 MINUTES // lasts until they wake up or if they're an especially long sleeper + damage_mod = 0.9 + +/datum/status_effect/dream_protection/temporary/on_apply() + if(owner.stat != UNCONSCIOUS) + return FALSE + if(owner.has_status_effect(/datum/status_effect/dream_protection)) + return FALSE + + return ..() + +/datum/status_effect/dream_protection/temporary/check_protection() + . = ..() + if(!has_filter) // soon as it goes, we go + qdel(src) + +// Version that works on dead mobs and lasts until they revive (with a set duration backup) +/datum/status_effect/dream_protection/deceased + id = "deceased_dream_protection" + duration = 3 MINUTES // lasts until they wake up or if they're an especially long sleeper + +/datum/status_effect/dream_protection/deceased/on_apply() + if(owner.stat != DEAD) + return FALSE + if(owner.has_status_effect(/datum/status_effect/dream_protection)) + return FALSE + + RegisterSignal(owner, COMSIG_LIVING_REVIVE, PROC_REF(mob_revived)) + ADD_TRAIT(owner, TRAIT_DREAMING, TRAIT_STATUS_EFFECT(id)) // "permanent" dreaming + return ..() + +/datum/status_effect/dream_protection/deceased/on_remove() + UnregisterSignal(owner, COMSIG_LIVING_REVIVE) + REMOVE_TRAIT(owner, TRAIT_DREAMING, TRAIT_STATUS_EFFECT(id)) + return ..() + +/datum/status_effect/dream_protection/deceased/proc/mob_revived() + SIGNAL_HANDLER + qdel(src) diff --git a/code/modules/religion/dreams/slumber_party.dm b/code/modules/religion/dreams/slumber_party.dm new file mode 100644 index 000000000000..96b61dd37daa --- /dev/null +++ b/code/modules/religion/dreams/slumber_party.dm @@ -0,0 +1,114 @@ +/datum/religion_rites/slumber_party + name = "Slumber Party" + desc = "Put all nearby creatures to sleep. \ + All affected creatures share the same dream and heal rapidly while sleeping. \ + You and your followers heal even faster during the ritual." + favor_cost = 200 + rite_flags = RITE_AUTO_DELETE + ritual_length = 20 SECONDS + +/datum/religion_rites/slumber_party/New() + . = ..() + ritual_invocations = list( + "Sleep now, flock of [GLOB.deity], and share in each other's dreams...", + "Our slumber shall rejuvenate us for the trials ahead...", + "May we all wake up refreshed and renewed!", + ) + +/datum/religion_rites/slumber_party/post_invoke_effects(mob/living/user, atom/religious_tool) + . = ..() + var/datum/dream/random/base_dream = new() + for(var/mob/living/carbon/nearby_guy in view(5, get_turf(religious_tool))) + nearby_guy.apply_status_effect(/datum/status_effect/slumber_party, base_dream.GenerateDream(user)) + qdel(base_dream) + +/datum/status_effect/slumber_party + id = "slumber_party" + duration = 20 SECONDS + alert_type = null + /// Dream fragments we share between all sleepers + var/list/shared_dream + /// How much we heal per second while sleepin - holy people heal more + var/healing = 2 + +/datum/status_effect/slumber_party/on_creation(mob/living/new_owner, list/shared_dream) + src.shared_dream = shared_dream + return ..() + +/datum/status_effect/slumber_party/on_apply() + if(IS_CULTIST(owner)) + var/datum/antagonist/cult/cultist = GET_CULTIST(owner) + if(cultist.cult_team?.cult_ascendent) + return FALSE + + if(IS_HERETIC(owner)) + var/datum/antagonist/heretic/heretic = GET_HERETIC(owner) + if(heretic.ascended) + return FALSE + + if(!(owner.mob_biotypes & MOB_ORGANIC)) + return FALSE + + if(!owner.SetSleeping(duration)) + return FALSE + + if(owner.mind?.holy_role) + healing *= 2 + + else if(owner.can_block_magic(MAGIC_RESISTANCE_HOLY|MAGIC_RESISTANCE_MIND, 1)) + return FALSE + + RegisterSignal(owner, COMSIG_PRE_DREAMING, PROC_REF(add_shared_dream)) + RegisterSignal(owner, COMSIG_START_DREAMING, PROC_REF(start_shared_dream)) + RegisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(damage_applied)) + if(iscarbon(owner)) + addtimer(CALLBACK(src, PROC_REF(force_dream)), rand(4, 8) SECONDS, TIMER_DELETE_ME) + return TRUE + +/datum/status_effect/slumber_party/on_remove() + UnregisterSignal(owner, COMSIG_PRE_DREAMING) + UnregisterSignal(owner, COMSIG_START_DREAMING) + UnregisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE) + REMOVE_TRAIT(owner, TRAIT_DREAMING, TRAIT_STATUS_EFFECT(id)) + owner.adjust_drowsiness(20 SECONDS) + +/datum/status_effect/slumber_party/tick(seconds_between_ticks) + if(owner.stat != UNCONSCIOUS) + qdel(src) + return + + owner.heal_overall_damage(healing * seconds_between_ticks, healing * seconds_between_ticks, required_bodytype = BODYTYPE_ORGANIC) + owner.adjust_tox_loss(healing * seconds_between_ticks * 0.50, required_biotype = MOB_ORGANIC) + owner.adjust_oxy_loss(healing * seconds_between_ticks * 0.25, required_biotype = MOB_ORGANIC) + +/datum/status_effect/slumber_party/proc/force_dream() + var/mob/living/carbon/dreamer = owner + if(HAS_TRAIT(dreamer, TRAIT_DREAMING)) + return // dreamed naturally already + dreamer.dream() + +/datum/status_effect/slumber_party/proc/add_shared_dream(datum/source, list/dream_pool) + SIGNAL_HANDLER + dream_pool[new /datum/dream/shared(shared_dream)] = 2000 + +/datum/status_effect/slumber_party/proc/start_shared_dream(datum/source, datum/dream/current_dream) + SIGNAL_HANDLER + ADD_TRAIT(owner, TRAIT_DREAMING, TRAIT_STATUS_EFFECT(id)) // so they don't have any OTHER dreams + +/datum/status_effect/slumber_party/proc/damage_applied(mob/living/source, damage_amount, ...) + SIGNAL_HANDLER + owner.AdjustSleeping(-damage_amount * 0.5 SECONDS) + +/datum/dream/shared + sleep_until_finished = TRUE + /// Dream shared between everyone + var/list/generated_dream + +/datum/dream/shared/New(list/shared_dream) + . = ..() + generated_dream = LAZYLISTDUPLICATE(shared_dream) + +/datum/dream/shared/GenerateDream(mob/living/carbon/dreamer) + if(!LAZYLEN(generated_dream)) + CRASH("Shared dream has no generated dream fragments!") + return generated_dream diff --git a/code/modules/religion/religion_sects.dm b/code/modules/religion/religion_sects.dm index 7ed15a46d998..9e8541380dfc 100644 --- a/code/modules/religion/religion_sects.dm +++ b/code/modules/religion/religion_sects.dm @@ -36,6 +36,8 @@ var/altar_icon /// Changes the Altar of Gods icon_state var/altar_icon_state + /// Changes the Altar of Gods emissive overlay icon_state + var/altar_emissive_icon_state /// Currently Active (non-deleted) rites var/list/active_rites /// Chance that we fail a bible blessing. @@ -111,6 +113,14 @@ to_chat(chap, span_warning("[GLOB.deity] refuses to heal this metallic taint!")) return TRUE + return standard_bless_healing(blessed, chap) + +/datum/religion_sect/proc/standard_bless_healing(mob/living/carbon/human/blessed, mob/living/chap) + if(!ishuman(blessed)) + blessed.adjust_brute_loss(-10) + blessed.adjust_fire_loss(-10) + return BLESSING_SUCCESS + var/heal_amt = 10 var/list/hurt_limbs = blessed.get_damaged_bodyparts(1, 1, BODYTYPE_ORGANIC) @@ -523,3 +533,138 @@ /datum/religion_sect/music/on_conversion(mob/living/chap) . = ..() new /obj/item/choice_beacon/music(get_turf(chap)) + +/datum/religion_sect/dreams + name = "Dream God" + quote = "The dream is a window into the soul." + desc = "Dream deeply to gain insights into the universe. Earn favor by dreaming or blessing dreaming creatures. \ + Your blessings invoke dreams and promote healing in those who are sound asleep." + tgui_icon = FA_ICON_CLOUD + alignment = ALIGNMENT_GOOD + altar_icon_state = "convertaltar-dream" + altar_emissive_icon_state = "convertaltar-dream-em" + candle_overlay = FALSE + rites_list = list( + /datum/religion_rites/deaconize/dreamers, + /datum/religion_rites/banish_nightmare, + /datum/religion_rites/dream_portent, + /datum/religion_rites/dream_projection, + /datum/religion_rites/dream_protection, + /datum/religion_rites/slumber_party, + ) + smack_chance = 20 + /// Whether the dream protection rite has been used + VAR_FINAL/dream_protection = FALSE + /// Number of deacons added thus far + VAR_FINAL/deacon_count = 0 + /// Max number of deacons + var/max_deacons = 3 + /// Chance a given dream will be a vague portent + var/vague_portent_chance = 10 + /// Lazylist of mobs that were blessed recently + /// Blocks mobs from being repeatedly blessed for favor + VAR_PRIVATE/list/recent_bless_refs + /// Cooldown between any follower receiving a vague portent + COOLDOWN_DECLARE(vague_portent_cooldown) + +/datum/religion_sect/dreams/on_conversion(mob/living/chap) + . = ..() + RegisterSignal(chap, COMSIG_PRE_DREAMING, PROC_REF(pre_dream)) + RegisterSignal(chap, COMSIG_START_DREAMING, PROC_REF(on_dream)) + if(dream_protection) + chap.apply_status_effect(/datum/status_effect/dream_protection) + +/datum/religion_sect/dreams/on_deconversion(mob/living/chap) + . = ..() + UnregisterSignal(chap, COMSIG_PRE_DREAMING) + UnregisterSignal(chap, COMSIG_START_DREAMING) + chap.remove_status_effect(/datum/status_effect/dream_protection) + +/datum/religion_sect/dreams/proc/pre_dream(mob/living/chap, list/dream_pool) + SIGNAL_HANDLER + + // prioritize specific portents if they're in the pool + if(locate(/datum/dream/specific_portent) in dream_pool) + return + if(!COOLDOWN_FINISHED(src, vague_portent_cooldown)) + return + if(!prob(vague_portent_chance)) + return + + dream_pool[new /datum/dream/random/vague_portent()] = /datum/dream/random::weight * 0.1 + +/datum/religion_sect/dreams/proc/on_dream(mob/living/chap, datum/dream/dream_instance) + SIGNAL_HANDLER + + // no reward for the dream your god is specifically giving you + if(istype(dream_instance, /datum/dream/specific_portent)) + return + + var/dream_favor = 10 + // but you do get a slight boost for having a *vague* one + if(istype(dream_instance, /datum/dream/random/vague_portent)) + COOLDOWN_START(src, vague_portent_cooldown, 30 SECONDS) + dream_favor *= 1.5 + + to_chat(chap, span_cyan("[GLOB.deity] approves of your slumber.")) + adjust_favor(dream_favor, chap) + +// dream blessing only works on dreaming targets. +// blessing someone asleep causes them to dream, and blessing a dreamer rewards favor. +// it also heals regardless of if the target is mechanical or organic. do robots dream of electric sheep? +/datum/religion_sect/dreams/sect_bless(mob/living/target, mob/living/chap) + if(HAS_TRAIT(target, TRAIT_DREAMING)) + var/result = standard_bless_healing(target, chap) + var/tarref = REF(target) + if(!LAZYFIND(recent_bless_refs, tarref)) + adjust_favor(20 * (isnull(target.mind) ? 0.5 : 1) * (target.IsSleeping() ? 1 : 0.5), chap) + LAZYADD(recent_bless_refs, tarref) + addtimer(CALLBACK(src, PROC_REF(clear_bless_ref), tarref), 6 MINUTES) + result = BLESSING_SUCCESS + + if(result == BLESSING_SUCCESS) + to_chat(chap, span_cyan("[GLOB.deity] approves of [target]'s slumber.")) + return result + + if(target.stat == UNCONSCIOUS) + if(iscarbon(target)) + var/mob/living/carbon/sleeper = target + sleeper.dream() + + to_chat(chap, span_cyan("[GLOB.deity] blesses [target]'s slumber.")) + var/result = standard_bless_healing(target, chap) + if(dream_protection && target.mind && target.apply_status_effect(/datum/status_effect/dream_protection/temporary)) + result = BLESSING_SUCCESS + + if(result == BLESSING_SUCCESS) + to_chat(chap, span_cyan("[GLOB.deity] blesses [target]'s slumber.")) + return result + + to_chat(chap, span_warning("[GLOB.deity] has no interest in blessing the waking.")) + return BLESSING_IGNORED + +/datum/religion_sect/dreams/sect_dead_bless(mob/living/target, mob/living/chap) + var/tarref = REF(target) + if(LAZYFIND(recent_bless_refs, tarref)) + return BLESSING_IGNORED + + to_chat(chap, span_cyan("[GLOB.deity] watches over [target]'s eternal rest.")) + if(dream_protection && target.mind) + target.apply_status_effect(/datum/status_effect/dream_protection/deceased) + adjust_favor(10 * (target.mind ? 1 : 0.5), chap) + LAZYADD(recent_bless_refs, tarref) + addtimer(CALLBACK(src, PROC_REF(clear_bless_ref), tarref), 6 MINUTES) + return BLESSING_SUCCESS + +/datum/religion_sect/dreams/proc/clear_bless_ref(tarref) + LAZYREMOVE(recent_bless_refs, tarref) + +/datum/religion_sect/dreams/vv_edit_var(var_name, var_value) + . = ..() + if(var_name == NAMEOF(src, dream_protection)) + for(var/mob/living/chap as anything in GLOB.mob_living_list) + if(chap.mind?.holy_role) + if(var_value) + chap.apply_status_effect(/datum/status_effect/dream_protection) + else + chap.remove_status_effect(/datum/status_effect/dream_protection) diff --git a/code/modules/religion/religion_structures.dm b/code/modules/religion/religion_structures.dm index 1b30d021268c..2d3625f230da 100644 --- a/code/modules/religion/religion_structures.dm +++ b/code/modules/religion/religion_structures.dm @@ -11,6 +11,10 @@ buckle_lying = 90 //we turn to you! ///Avoids having to check global everytime by referencing it locally. var/datum/religion_sect/sect_to_altar + /// Do we have lit candles? + var/lit_candles = TRUE + /// Optional emissive overlay + var/emissive_icon_state /obj/structure/altar_of_gods/Initialize(mapload) . = ..() @@ -24,6 +28,14 @@ GLOB.chaplain_altars -= src return ..() +/obj/structure/altar_of_gods/update_overlays() + . = ..() + if (lit_candles) + . += mutable_appearance(icon, "convertaltarcandle", alpha = src.alpha) + . += emissive_appearance(icon, "convertaltarcandle", src, alpha = src.alpha) + if(emissive_icon_state) + . += emissive_appearance(icon, emissive_icon_state, src, alpha = src.alpha) + /obj/structure/altar_of_gods/update_overlays() var/list/new_overlays = ..() if(GLOB.religious_sect) @@ -65,12 +77,16 @@ if(isnull(GLOB.religious_sect)) icon = initial(icon) icon_state = initial(icon_state) + emissive_icon_state = initial(emissive_icon_state) else sect_to_altar = GLOB.religious_sect + lit_candles = GLOB.religious_sect.candle_overlay if(sect_to_altar.altar_icon) icon = sect_to_altar.altar_icon if(sect_to_altar.altar_icon_state) icon_state = sect_to_altar.altar_icon_state + if(sect_to_altar.altar_emissive_icon_state) + emissive_icon_state = sect_to_altar.altar_emissive_icon_state update_appearance() //Light the candles! /obj/structure/altar_of_gods/proc/get_chaplains() diff --git a/code/modules/religion/rites.dm b/code/modules/religion/rites.dm index 14fd752eb3e2..6773118b9838 100644 --- a/code/modules/religion/rites.dm +++ b/code/modules/religion/rites.dm @@ -65,6 +65,9 @@ return TRUE +/datum/religion_rites/proc/refund(percent = 1.0) + GLOB.religious_sect.adjust_favor(favor_cost * percent) + /**** Mechanical God ****/ /datum/religion_rites/synthconversion @@ -208,7 +211,7 @@ return FALSE //uses HAS_TRAIT_FROM because junkies are also hopelessly addicted if(HAS_TRAIT_FROM(user, TRAIT_HOPELESSLY_ADDICTED, "maint_adaptation")) - to_chat(user, span_warning("You've already adapted.")) + to_chat(user, span_warning("You've already adapted.")) return FALSE return ..() diff --git a/icons/obj/service/hand_of_god_structures.dmi b/icons/obj/service/hand_of_god_structures.dmi index cbbc36d7718870ab99956f00912279b5a1beb3ce..e9d94cef78f9c59b450c8acb3765661a41946c8c 100644 GIT binary patch literal 45965 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 kK?+y_c%NmP)AJURFU}wOA@!?a{oC{MwtGPYI&d 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"} diff --git a/maplestation.dme b/maplestation.dme index 65fa8cacbaa4..247ea5229573 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -4127,7 +4127,7 @@ #include "code\modules\fishing\fish\types\tiziran.dm" #include "code\modules\fishing\sources\_fish_source.dm" #include "code\modules\fishing\sources\source_types.dm" -#include "code\modules\flufftext\Dreaming.dm" +#include "code\modules\flufftext\dreaming.dm" #include "code\modules\food_and_drinks\pizzabox.dm" #include "code\modules\food_and_drinks\plate.dm" #include "code\modules\food_and_drinks\machinery\coffeemaker.dm" diff --git a/tgstation.dme b/tgstation.dme index cde06e9a327d..66552f1fce6c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3241,6 +3241,7 @@ #include "code\modules\antagonists\heretic\magic\wave_of_desperation.dm" #include "code\modules\antagonists\heretic\status_effects\buffs.dm" #include "code\modules\antagonists\heretic\status_effects\debuffs.dm" +#include "code\modules\antagonists\heretic\status_effects\dreams.dm" #include "code\modules\antagonists\heretic\status_effects\ghoul.dm" #include "code\modules\antagonists\heretic\status_effects\mark_effects.dm" #include "code\modules\antagonists\heretic\structures\carving_knife.dm" @@ -5648,6 +5649,12 @@ #include "code\modules\religion\rites.dm" #include "code\modules\religion\burdened\burdened_trauma.dm" #include "code\modules\religion\burdened\psyker.dm" +#include "code\modules\religion\dreams\banish_nightmare.dm" +#include "code\modules\religion\dreams\deaconize_dreamer.dm" +#include "code\modules\religion\dreams\dream_portent.dm" +#include "code\modules\religion\dreams\dream_projection.dm" +#include "code\modules\religion\dreams\dream_protection.dm" +#include "code\modules\religion\dreams\slumber_party.dm" #include "code\modules\religion\festival\festival_violin.dm" #include "code\modules\religion\festival\instrument_rites.dm" #include "code\modules\religion\honorbound\honorbound_rites.dm" diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss index ba4f94320eac..6d3af7ce81ba 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss @@ -958,6 +958,11 @@ em { display: inline-block; } +.cyan { + color: #bde0dc; + text-shadow: 0 0 6px #bde0dc; +} + .connectionClosed, .fatalError { background: red; diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss index 3b65f451e25f..b55b847ad7ba 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss @@ -965,6 +965,11 @@ h2.alert { display: inline-block; } +.cyan { + color: #95afac; + text-shadow: 0 0 6px #95afac; +} + .connectionClosed, .fatalError { background: red; From 3aad108e414c32ecb287b7901d60d62807da17c3 Mon Sep 17 00:00:00 2001 From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Sun, 4 May 2025 10:49:36 -0400 Subject: [PATCH 04/15] Chaplains can deaconize one person, everyone else loses bible perms (#90686) --- code/__DEFINES/religion.dm | 7 ++ code/datums/components/religious_tool.dm | 29 +++-- code/modules/library/bibles.dm | 58 +++++---- code/modules/religion/deaconize.dm | 2 +- .../religion/festival/instrument_rites.dm | 4 +- .../religion/honorbound/honorbound_rites.dm | 119 +++++++----------- code/modules/religion/religion_sects.dm | 21 +--- code/modules/religion/rites.dm | 20 ++- tgstation.dme | 1 + 9 files changed, 117 insertions(+), 144 deletions(-) diff --git a/code/__DEFINES/religion.dm b/code/__DEFINES/religion.dm index 3074dae29879..5186f5c48e29 100644 --- a/code/__DEFINES/religion.dm +++ b/code/__DEFINES/religion.dm @@ -54,3 +54,10 @@ #define PUNISHMENT_LIGHTNING "lightningbolt" ///brands the sinner #define PUNISHMENT_BRAND "brand" + +///The rite will automatically delete itself by the religious tool calling it after it's invoked. +#define RITE_AUTO_DELETE (1<<0) +///The rite can be performed multiple times with a religious tool, so don't delete/null it. +#define RITE_ALLOW_MULTIPLE_PERFORMS (1<<1) +///The rite can only be fully performed once, so we'll completely remove it from the rite list afterwards. +#define RITE_ONE_TIME_USE (1<<2) diff --git a/code/datums/components/religious_tool.dm b/code/datums/components/religious_tool.dm index afb52c91bac5..3c5e226b1a54 100644 --- a/code/datums/components/religious_tool.dm +++ b/code/datums/components/religious_tool.dm @@ -25,13 +25,13 @@ var/list/rite_types_allowlist /datum/component/religious_tool/Initialize( - operation_flags = ALL, - force_catalyst_afterattack = FALSE, - after_sect_select_cb = null, - catalyst_type = /obj/item/book/bible, - charges = -1, - rite_types_allowlist = null, - ) + operation_flags = ALL, + force_catalyst_afterattack = FALSE, + after_sect_select_cb = null, + catalyst_type = /obj/item/book/bible, + charges = -1, + rite_types_allowlist = null, +) . = ..() SetGlobalToLocal() //attempt to connect on start in case one already exists! src.operation_flags = operation_flags @@ -44,8 +44,8 @@ RegisterSignal(SSdcs, COMSIG_RELIGIOUS_SECT_RESET, PROC_REF(on_sect_reset)) /datum/component/religious_tool/Destroy(force) + QDEL_NULL(performing_rite) easy_access_sect = null - performing_rite = null catalyst_type = null after_sect_select_cb = null return ..() @@ -166,15 +166,18 @@ if(rite_types_allowlist && !is_path_in_list(path, rite_types_allowlist)) to_chat(user, span_warning("This cannot perform that kind of rite.")) return - if(performing_rite) - to_chat(user, "There is a rite currently being performed here already.") - return if(!user.can_perform_action(parent, FORBID_TELEKINESIS_REACH)) to_chat(user,span_warning("You are not close enough to perform the rite.")) return - performing_rite = new path(parent) - if(!performing_rite.perform_rite(user, parent)) + //we have a rite already, but we want to do a new one. + if(performing_rite && !ispath(performing_rite.type, path)) QDEL_NULL(performing_rite) + if(!performing_rite) + performing_rite = new path(parent) + + if(!performing_rite.perform_rite(user, parent)) + if(!(performing_rite.rite_flags & RITE_ALLOW_MULTIPLE_PERFORMS)) + QDEL_NULL(performing_rite) return if(performing_rite.invoke_effect(user, parent)) diff --git a/code/modules/library/bibles.dm b/code/modules/library/bibles.dm index 48c0b24d6179..e3fdf410b528 100644 --- a/code/modules/library/bibles.dm +++ b/code/modules/library/bibles.dm @@ -278,42 +278,40 @@ GLOBAL_LIST_INIT(bibleitemstates, list( log_combat(user, target_mob, "attacked", src) /obj/item/book/bible/interact_with_atom(atom/bible_smacked, mob/living/user, list/modifiers) + if(!user.mind?.holy_role) + return if(SEND_SIGNAL(bible_smacked, COMSIG_BIBLE_SMACKED, user) & COMSIG_END_BIBLE_CHAIN) return ITEM_INTERACT_SUCCESS if(isfloorturf(bible_smacked)) - if(user.mind?.holy_role) - var/area/current_area = get_area(bible_smacked) - if(!GLOB.chaplain_altars.len && istype(current_area, /area/station/service/chapel)) - make_new_altar(bible_smacked, user) - return ITEM_INTERACT_SUCCESS - for(var/obj/effect/rune/nearby_runes in range(2, user)) - nearby_runes.SetInvisibility(INVISIBILITY_NONE, id=type, priority=INVISIBILITY_PRIORITY_BASIC_ANTI_INVISIBILITY) + var/area/current_area = get_area(bible_smacked) + if(!GLOB.chaplain_altars.len && istype(current_area, /area/station/service/chapel)) + make_new_altar(bible_smacked, user) + return ITEM_INTERACT_SUCCESS + for(var/obj/effect/rune/nearby_runes in range(2, user)) + nearby_runes.SetInvisibility(INVISIBILITY_NONE, id=type, priority=INVISIBILITY_PRIORITY_BASIC_ANTI_INVISIBILITY) bible_smacked.balloon_alert(user, "floor smacked!") return ITEM_INTERACT_SUCCESS - if(user.mind?.holy_role) - if(bible_smacked.reagents?.has_reagent(/datum/reagent/water)) // blesses all the water in the holder - bible_smacked.balloon_alert(user, "blessed") - var/water2holy = bible_smacked.reagents.get_reagent_amount(/datum/reagent/water) - bible_smacked.reagents.del_reagent(/datum/reagent/water) - bible_smacked.reagents.add_reagent(/datum/reagent/water/holywater,water2holy) - . = ITEM_INTERACT_SUCCESS - if(bible_smacked.reagents?.has_reagent(/datum/reagent/fuel/unholywater)) // yeah yeah, copy pasted code - sue me - bible_smacked.balloon_alert(user, "purified") - var/unholy2holy = bible_smacked.reagents.get_reagent_amount(/datum/reagent/fuel/unholywater) - bible_smacked.reagents.del_reagent(/datum/reagent/fuel/unholywater) - bible_smacked.reagents.add_reagent(/datum/reagent/water/holywater,unholy2holy) - . = ITEM_INTERACT_SUCCESS - if(istype(bible_smacked, /obj/item/book/bible) && !istype(bible_smacked, /obj/item/book/bible/syndicate)) - bible_smacked.balloon_alert(user, "converted") - var/obj/item/book/bible/other_bible = bible_smacked - other_bible.name = name - other_bible.icon_state = icon_state - other_bible.inhand_icon_state = inhand_icon_state - other_bible.deity_name = deity_name - . = ITEM_INTERACT_SUCCESS - if(.) - return . + if(bible_smacked.reagents?.has_reagent(/datum/reagent/water)) // blesses all the water in the holder + bible_smacked.balloon_alert(user, "blessed") + var/water2holy = bible_smacked.reagents.get_reagent_amount(/datum/reagent/water) + bible_smacked.reagents.del_reagent(/datum/reagent/water) + bible_smacked.reagents.add_reagent(/datum/reagent/water/holywater,water2holy) + return ITEM_INTERACT_SUCCESS + if(bible_smacked.reagents?.has_reagent(/datum/reagent/fuel/unholywater)) // yeah yeah, copy pasted code - sue me + bible_smacked.balloon_alert(user, "purified") + var/unholy2holy = bible_smacked.reagents.get_reagent_amount(/datum/reagent/fuel/unholywater) + bible_smacked.reagents.del_reagent(/datum/reagent/fuel/unholywater) + bible_smacked.reagents.add_reagent(/datum/reagent/water/holywater,unholy2holy) + return ITEM_INTERACT_SUCCESS + if(istype(bible_smacked, /obj/item/book/bible) && !istype(bible_smacked, /obj/item/book/bible/syndicate)) + bible_smacked.balloon_alert(user, "converted") + var/obj/item/book/bible/other_bible = bible_smacked + other_bible.name = name + other_bible.icon_state = icon_state + other_bible.inhand_icon_state = inhand_icon_state + other_bible.deity_name = deity_name + return ITEM_INTERACT_SUCCESS if(istype(bible_smacked, /obj/item/cult_bastard) && !IS_CULTIST(user)) var/obj/item/cult_bastard/sword = bible_smacked diff --git a/code/modules/religion/deaconize.dm b/code/modules/religion/deaconize.dm index 0466b026caa1..826787bdd16c 100644 --- a/code/modules/religion/deaconize.dm +++ b/code/modules/religion/deaconize.dm @@ -65,7 +65,7 @@ if(honor && (potential_deacon in honor.guilty)) honor.guilty -= potential_deacon to_chat(user, span_notice("[GLOB.deity] has bound [potential_deacon] to the code! They are now a holy role! (albeit the lowest level of such)")) - potential_deacon.mind.set_holy_role(HOLY_ROLE_DEACON) + potential_deacon.mind.holy_role = HOLY_ROLE_DEACON GLOB.religious_sect.on_conversion(potential_deacon) playsound(get_turf(religious_tool), 'sound/effects/pray.ogg', 50, TRUE) return TRUE diff --git a/code/modules/religion/festival/instrument_rites.dm b/code/modules/religion/festival/instrument_rites.dm index 17868554f8a4..331b4e7ed4a4 100644 --- a/code/modules/religion/festival/instrument_rites.dm +++ b/code/modules/religion/festival/instrument_rites.dm @@ -58,7 +58,7 @@ desc = "this is a prototype." ritual_length = 10 SECONDS favor_cost = 10 - auto_delete = FALSE + rite_flags = NONE ///if repeats count as continuations instead of a song's end, TRUE var/repeats_okay = TRUE ///personal message sent to the chaplain as feedback for their chosen song @@ -142,7 +142,7 @@ /datum/religion_rites/song_tuner/light/Destroy() QDEL_NULL(performer_light_obj) - . = ..() + return ..() /datum/religion_rites/song_tuner/light/finish_effect(mob/living/carbon/human/listener, atom/song_source) listener.apply_status_effect(/datum/status_effect/song/light) diff --git a/code/modules/religion/honorbound/honorbound_rites.dm b/code/modules/religion/honorbound/honorbound_rites.dm index 88eef0340ea6..8e92ea91ba46 100644 --- a/code/modules/religion/honorbound/honorbound_rites.dm +++ b/code/modules/religion/honorbound/honorbound_rites.dm @@ -1,79 +1,3 @@ -/// how much favor is gained when someone joins the crusade and is deaconized -#define DEACONIZE_FAVOR_GAIN 300 - -///Makes the person holy, but they now also have to follow the honorbound code (CBT). Actually earns favor, convincing others to uphold the code (tm) is not easy -/datum/religion_rites/deaconize - name = "Join Crusade" - desc = "Converts someone to your sect. They must be willing, so the first invocation will instead prompt them to join. \ - They will become honorbound like you, and you will gain a massive favor boost!" - ritual_length = 30 SECONDS - ritual_invocations = list( - "A good, honorable crusade against evil is required.", - "We need the righteous ...", - "... the unflinching ...", - "... and the just.", - "Sinners must be silenced ...", - ) - invoke_msg = "... And the code must be upheld!" - ///the invited crusader - var/mob/living/carbon/human/new_crusader - -/datum/religion_rites/deaconize/perform_rite(mob/living/user, atom/religious_tool) - var/datum/religion_sect/honorbound/sect = GLOB.religious_sect - if(!ismovable(religious_tool)) - to_chat(user, span_warning("This rite requires a religious device that individuals can be buckled to.")) - return FALSE - var/atom/movable/movable_reltool = religious_tool - if(!movable_reltool) - return FALSE - if(!LAZYLEN(movable_reltool.buckled_mobs)) - to_chat(user, span_warning("Nothing is buckled to the altar!")) - return FALSE - for(var/mob/living/carbon/human/possible_crusader in movable_reltool.buckled_mobs) - if(possible_crusader.stat != CONSCIOUS) - to_chat(user, span_warning("[possible_crusader] needs to be alive and conscious to join the crusade!")) - return FALSE - if(TRAIT_GENELESS in possible_crusader.dna.species.inherent_traits) - to_chat(user, span_warning("This species disgusts [GLOB.deity]! They would never be allowed to join the crusade!")) - return FALSE - if(possible_crusader in sect.currently_asking) - to_chat(user, span_warning("Wait for them to decide on whether to join or not!")) - return FALSE - if(!(possible_crusader in sect.possible_crusaders)) - INVOKE_ASYNC(sect, TYPE_PROC_REF(/datum/religion_sect/honorbound, invite_crusader), possible_crusader) - to_chat(user, span_notice("They have been given the option to consider joining the crusade against evil. Wait for them to decide and try again.")) - return FALSE - new_crusader = possible_crusader - return ..() - -/datum/religion_rites/deaconize/invoke_effect(mob/living/carbon/human/user, atom/movable/religious_tool) - ..() - var/mob/living/carbon/human/joining_now = new_crusader - new_crusader = null - if(!(joining_now in religious_tool.buckled_mobs)) //checks one last time if the right corpse is still buckled - to_chat(user, span_warning("The new member is no longer on the altar!")) - return FALSE - if(joining_now.stat != CONSCIOUS) - to_chat(user, span_warning("The new member has to stay alive for the rite to work!")) - return FALSE - if(!joining_now.mind) - to_chat(user, span_warning("The new member has no mind!")) - return FALSE - if(joining_now.mind.has_antag_datum(/datum/antagonist/cult))//what the fuck?! - to_chat(user, span_warning("[GLOB.deity] has seen a true, dark evil in [joining_now]'s heart, and they have been smitten!")) - playsound(get_turf(religious_tool), 'sound/effects/pray.ogg', 50, TRUE) - joining_now.gib(DROP_ORGANS|DROP_BODYPARTS) - return FALSE - var/datum/brain_trauma/special/honorbound/honor = user.has_trauma_type(/datum/brain_trauma/special/honorbound) - if(joining_now in honor.guilty) - honor.guilty -= joining_now - GLOB.religious_sect.adjust_favor(DEACONIZE_FAVOR_GAIN, user) - to_chat(user, span_notice("[GLOB.deity] has bound [joining_now] to the code! They are now a holy role! (albeit the lowest level of such)")) - joining_now.mind.holy_role = HOLY_ROLE_DEACON - GLOB.religious_sect.on_conversion(joining_now) - playsound(get_turf(religious_tool), 'sound/effects/pray.ogg', 50, TRUE) - return TRUE - ///Mostly useless funny rite for forgiving someone, making them innocent once again. /datum/religion_rites/forgive name = "Forgive" @@ -179,4 +103,47 @@ "} return ..() +/// how much favor is gained when someone is deaconized +#define DEACONIZE_FAVOR_GAIN 300 + +/** + * Crusader deaconize + * Along with making the person holy & being an infinite-use type, it comes with the cost + * of enforcing an honorbound code onto convertees. + * Earns the church favor per conversion, but convincing others to uphold the code is not easy. + * Geneless species are not welcome for reasons + * (The actual reason is because the honorbound trauma used to be a mutation which they couldn't get, + * maybe it's time we let them join? idk) + */ +/datum/religion_rites/deaconize/crusader + name = "Join Crusade" + desc = "Converts someone to your sect. They must be willing, so the first invocation will instead prompt them to join. \ + They will become honorbound like you, and you will gain a massive favor boost!" + ritual_length = 30 SECONDS + ritual_invocations = list( + "A good, honorable crusade against evil is required.", + "We need the righteous ...", + "... the unflinching ...", + "... and the just.", + "Sinners must be silenced ...", + ) + invoke_msg = "... And the code must be upheld!" + rite_flags = RITE_ALLOW_MULTIPLE_PERFORMS + +/datum/religion_rites/deaconize/crusader/post_invoke_effects(mob/living/user, atom/religious_tool) + . = ..() + GLOB.religious_sect.adjust_favor(DEACONIZE_FAVOR_GAIN, user) + +/datum/religion_rites/deaconize/crusader/is_valid_for_deacon(mob/living/carbon/human/possible_deacon, mob/living/user) + if(TRAIT_GENELESS in possible_deacon.dna.species.inherent_traits) + to_chat(user, span_warning("This species disgusts [GLOB.deity]! They would never be allowed to join the crusade!")) + return FALSE + return ..() + +/datum/religion_rites/deaconize/crusader/invite_deacon(mob/living/carbon/human/invited) + var/ask = tgui_alert(invited, "Join [GLOB.deity]? You will be bound to a code of honor.", "Invitation", list("Yes", "No"), 60 SECONDS) + if(ask != "Yes") + return + potential_deacon = invited + #undef DEACONIZE_FAVOR_GAIN diff --git a/code/modules/religion/religion_sects.dm b/code/modules/religion/religion_sects.dm index 9e8541380dfc..5a8f7b3af839 100644 --- a/code/modules/religion/religion_sects.dm +++ b/code/modules/religion/religion_sects.dm @@ -31,7 +31,7 @@ /// Autopopulated by `desired_items` var/list/desired_items_typecache /// Lists of rites by type. Converts itself into a list of rites with "name - desc (favor_cost)" = type - var/list/rites_list + var/list/rites_list = list() /// Changes the Altar of Gods icon var/altar_icon /// Changes the Altar of Gods icon_state @@ -49,6 +49,8 @@ . = ..() if(desired_items) desired_items_typecache = typecacheof(desired_items) + if(!locate(/datum/religion_rites/deaconize) in rites_list) + rites_list += list(/datum/religion_rites/deaconize) on_select() /// Activates once selected @@ -412,22 +414,7 @@ tgui_icon = "scroll" altar_icon_state = "convertaltar-white" alignment = ALIGNMENT_GOOD - rites_list = list(/datum/religion_rites/deaconize, /datum/religion_rites/forgive, /datum/religion_rites/summon_rules) - ///people who have agreed to join the crusade, and can be deaconized - var/list/possible_crusaders = list() - ///people who have been offered an invitation, they haven't finished the alert though. - var/list/currently_asking = list() - -/** - * Called by deaconize rite, this async'd proc waits for a response on joining the sect. - * If yes, the deaconize rite can now recruit them instead of just offering invites - */ -/datum/religion_sect/honorbound/proc/invite_crusader(mob/living/carbon/human/invited) - currently_asking += invited - var/ask = tgui_alert(invited, "Join [GLOB.deity]? You will be bound to a code of honor.", "Invitation", list("Yes", "No"), 60 SECONDS) - currently_asking -= invited - if(ask == "Yes") - possible_crusaders += invited + rites_list = list(/datum/religion_rites/deaconize/crusader, /datum/religion_rites/forgive, /datum/religion_rites/summon_rules) /datum/religion_sect/honorbound/on_conversion(mob/living/carbon/new_convert) ..() diff --git a/code/modules/religion/rites.dm b/code/modules/religion/rites.dm index 6773118b9838..d6725692c724 100644 --- a/code/modules/religion/rites.dm +++ b/code/modules/religion/rites.dm @@ -10,8 +10,10 @@ /// message when you invoke var/invoke_msg var/favor_cost = 0 - /// does the altar auto-delete the rite - var/auto_delete = TRUE + + ///Rite flags we use mostly to know when it should be deleted. + // RITE_AUTO_DELETE | RITE_ALLOW_MULTIPLE_PERFORMS | RITE_ONE_TIME_USE + var/rite_flags = RITE_AUTO_DELETE /datum/religion_rites/New() . = ..() @@ -61,9 +63,15 @@ ///Does the thing if the rite was successfully performed. return value denotes that the effect successfully (IE a harm rite does harm) /datum/religion_rites/proc/invoke_effect(mob/living/user, atom/religious_tool) SHOULD_CALL_PARENT(TRUE) - GLOB.religious_sect.on_riteuse(user,religious_tool) + GLOB.religious_sect.on_riteuse(user, religious_tool) return TRUE +///Called if invoke effect returns TRUE, for effects meant to occur only if the rite passes. +/datum/religion_rites/proc/post_invoke_effects(mob/living/user, atom/religious_tool) + SHOULD_CALL_PARENT(TRUE) + if(!(rite_flags & RITE_ONE_TIME_USE)) + return + GLOB.religious_sect.rites_list.Remove(src.type) /datum/religion_rites/proc/refund(percent = 1.0) GLOB.religious_sect.adjust_favor(favor_cost * percent) @@ -123,8 +131,10 @@ name = "Receive Blessing" desc = "Receive a blessing from the machine god to further your ascension." ritual_length = 5 SECONDS - ritual_invocations =list( "Let your will power our forges.", - "...Help us in our great conquest!") + ritual_invocations = list( + "Let your will power our forges.", + "...Help us in our great conquest!", + ) invoke_msg = "The end of flesh is near!" favor_cost = 2000 diff --git a/tgstation.dme b/tgstation.dme index 66552f1fce6c..63c7de06d550 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -5644,6 +5644,7 @@ #include "code\modules\recycling\disposal\outlet.dm" #include "code\modules\recycling\disposal\pipe.dm" #include "code\modules\recycling\disposal\pipe_sorting.dm" +#include "code\modules\religion\deaconize.dm" #include "code\modules\religion\religion_sects.dm" #include "code\modules\religion\religion_structures.dm" #include "code\modules\religion\rites.dm" From 79215514e632a307d467f7a3609826cb09e36d0d Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Sun, 6 Sep 2026 23:34:58 -0500 Subject: [PATCH 05/15] Fixes --- .../heretic/status_effects/dreams.dm | 6 +-- code/modules/religion/dreams/dream_portent.dm | 37 +++++++++---------- .../religion/dreams/dream_projection.dm | 2 +- .../religion/dreams/dream_protection.dm | 4 +- code/modules/religion/dreams/slumber_party.dm | 4 +- code/modules/religion/religion_sects.dm | 4 +- maplestation.dme | 7 ++++ 7 files changed, 35 insertions(+), 29 deletions(-) diff --git a/code/modules/antagonists/heretic/status_effects/dreams.dm b/code/modules/antagonists/heretic/status_effects/dreams.dm index 1b1db45f7028..9904b2f190f5 100644 --- a/code/modules/antagonists/heretic/status_effects/dreams.dm +++ b/code/modules/antagonists/heretic/status_effects/dreams.dm @@ -1,7 +1,7 @@ /datum/status_effect/grouped/heretic_dreams id = "heretic_dreams" - duration = STATUS_EFFECT_PERMANENT - tick_interval = STATUS_EFFECT_NO_TICK + duration = -1 //STATUS_EFFECT_PERMANENT + tick_interval = -1 //STATUS_EFFECT_NO_TICK alert_type = null /// Cooldown between allowed dreams COOLDOWN_DECLARE(dreaming_cooldown) @@ -45,7 +45,7 @@ if(IS_HERETIC(dreamer)) return null - return get_safe_random_station_turf_equal_weight() + return get_safe_random_station_turf() /// Heretics can see dreams about random machinery from the perspective of a random unused influence /datum/dream/heretic diff --git a/code/modules/religion/dreams/dream_portent.dm b/code/modules/religion/dreams/dream_portent.dm index de81563d1894..82f5ca8bab89 100644 --- a/code/modules/religion/dreams/dream_portent.dm +++ b/code/modules/religion/dreams/dream_portent.dm @@ -155,7 +155,7 @@ ) var/list/heretic_text = list("the doors of the Mansus loom ahead of you", "intricately decorated - and ajar", "you look through the crack") - switch(prob(75) ? heretic.heretic_path.route : null) + switch(prob(75) ? heretic.heretic_path : null) if(PATH_ASH) heretic_text += "beyond it, you see a barren wasteland" heretic_text += "all life long gone, scorched to ash and dust" @@ -267,24 +267,23 @@ if(IS_HERETIC(dreamer) && prob(50)) var/datum/antagonist/heretic/heretic = GET_HERETIC(dreamer) - if(prob(75) && !heretic.feast_of_owls) - switch(heretic.heretic_path?.route) - if(PATH_START) - return list("you see", "a fork in the road ahead", "the path before you uncertain and full of potential") - if(PATH_ASH) - return list("you see", "a barren wasteland ahead", "burned trees line the horizon", "the air thick with smoke and ash", "a bleak and desolate sight") - if(PATH_FLESH) - return list("you see", "a legion of amalgamations ahead", "twisted and grotesque", "marching in unison towards an unknown destination") - if(PATH_VOID) - return list("you see", "nothingness ahead", "a void that seems to swallow all light and hope", "the silence deafening and oppressive") - if(PATH_COSMIC) - return list("you see", "the birth of a new star", "radiant and full of potential", "an awe-inspiring sight") - if(PATH_BLADE) - return list("you see", "a towering fortress ahead", "its walls lined with stalwart defenders", "each and every one bowing in respect to you") - if(PATH_LOCK) - return list("you see", "and endless labyrinth", "the walls shifting and changing as you navigate it", "a test of your resolve and cunning") - if(PATH_MOON) - return list("you see", "an everlasting carnival", "the air filled with joy and laughter", "but with an undercurrent of melancholy and longing") + switch(prob(75) ? heretic.heretic_path : null) + if(PATH_START) + return list("you see", "a fork in the road ahead", "the path before you uncertain and full of potential") + if(PATH_ASH) + return list("you see", "a barren wasteland ahead", "burned trees line the horizon", "the air thick with smoke and ash", "a bleak and desolate sight") + if(PATH_FLESH) + return list("you see", "a legion of amalgamations ahead", "twisted and grotesque", "marching in unison towards an unknown destination") + if(PATH_VOID) + return list("you see", "nothingness ahead", "a void that seems to swallow all light and hope", "the silence deafening and oppressive") + if(PATH_COSMIC) + return list("you see", "the birth of a new star", "radiant and full of potential", "an awe-inspiring sight") + if(PATH_BLADE) + return list("you see", "a towering fortress ahead", "its walls lined with stalwart defenders", "each and every one bowing in respect to you") + if(PATH_LOCK) + return list("you see", "and endless labyrinth", "the walls shifting and changing as you navigate it", "a test of your resolve and cunning") + if(PATH_MOON) + return list("you see", "an everlasting carnival", "the air filled with joy and laughter", "but with an undercurrent of melancholy and longing") return list("you see", "a large door ahead", "intricately decorated and emanating a powerful aura", "but never opening", "no matter how long you wait") diff --git a/code/modules/religion/dreams/dream_projection.dm b/code/modules/religion/dreams/dream_projection.dm index c1c131978a4a..fe23726e3f7f 100644 --- a/code/modules/religion/dreams/dream_projection.dm +++ b/code/modules/religion/dreams/dream_projection.dm @@ -50,7 +50,7 @@ /datum/status_effect/dream_projection id = "dream_projection" - duration = STATUS_EFFECT_PERMANENT + duration = -1 //STATUS_EFFECT_PERMANENT alert_type = null on_remove_on_mob_delete = TRUE diff --git a/code/modules/religion/dreams/dream_protection.dm b/code/modules/religion/dreams/dream_protection.dm index f69777b6be38..6aec31581dcc 100644 --- a/code/modules/religion/dreams/dream_protection.dm +++ b/code/modules/religion/dreams/dream_protection.dm @@ -28,8 +28,8 @@ /datum/status_effect/dream_protection id = "dream_protection" - duration = STATUS_EFFECT_PERMANENT - tick_interval = STATUS_EFFECT_NO_TICK + duration = -1 //STATUS_EFFECT_PERMANENT + tick_interval = -1 //STATUS_EFFECT_NO_TICK alert_type = null /// Damage reduction when sleeping/dreaming, multiplicative var/damage_mod = 0.75 diff --git a/code/modules/religion/dreams/slumber_party.dm b/code/modules/religion/dreams/slumber_party.dm index 96b61dd37daa..a3f258d692ae 100644 --- a/code/modules/religion/dreams/slumber_party.dm +++ b/code/modules/religion/dreams/slumber_party.dm @@ -78,8 +78,8 @@ return owner.heal_overall_damage(healing * seconds_between_ticks, healing * seconds_between_ticks, required_bodytype = BODYTYPE_ORGANIC) - owner.adjust_tox_loss(healing * seconds_between_ticks * 0.50, required_biotype = MOB_ORGANIC) - owner.adjust_oxy_loss(healing * seconds_between_ticks * 0.25, required_biotype = MOB_ORGANIC) + owner.adjustToxLoss(healing * seconds_between_ticks * 0.50, required_biotype = MOB_ORGANIC) + owner.adjustOxyLoss(healing * seconds_between_ticks * 0.25, required_biotype = MOB_ORGANIC) /datum/status_effect/slumber_party/proc/force_dream() var/mob/living/carbon/dreamer = owner diff --git a/code/modules/religion/religion_sects.dm b/code/modules/religion/religion_sects.dm index 5a8f7b3af839..88d17cbc4bf9 100644 --- a/code/modules/religion/religion_sects.dm +++ b/code/modules/religion/religion_sects.dm @@ -119,8 +119,8 @@ /datum/religion_sect/proc/standard_bless_healing(mob/living/carbon/human/blessed, mob/living/chap) if(!ishuman(blessed)) - blessed.adjust_brute_loss(-10) - blessed.adjust_fire_loss(-10) + blessed.adjustBruteLoss(-10) + blessed.adjustFireLoss(-10) return BLESSING_SUCCESS var/heal_amt = 10 diff --git a/maplestation.dme b/maplestation.dme index 247ea5229573..067ff68c4f6d 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -3230,6 +3230,7 @@ #include "code\modules\antagonists\heretic\magic\wave_of_desperation.dm" #include "code\modules\antagonists\heretic\status_effects\buffs.dm" #include "code\modules\antagonists\heretic\status_effects\debuffs.dm" +#include "code\modules\antagonists\heretic\status_effects\dreams.dm" #include "code\modules\antagonists\heretic\status_effects\ghoul.dm" #include "code\modules\antagonists\heretic\status_effects\mark_effects.dm" #include "code\modules\antagonists\heretic\structures\carving_knife.dm" @@ -5630,6 +5631,12 @@ #include "code\modules\religion\rites.dm" #include "code\modules\religion\burdened\burdened_trauma.dm" #include "code\modules\religion\burdened\psyker.dm" +#include "code\modules\religion\dreams\banish_nightmare.dm" +#include "code\modules\religion\dreams\deaconize_dreamer.dm" +#include "code\modules\religion\dreams\dream_portent.dm" +#include "code\modules\religion\dreams\dream_projection.dm" +#include "code\modules\religion\dreams\dream_protection.dm" +#include "code\modules\religion\dreams\slumber_party.dm" #include "code\modules\religion\festival\festival_violin.dm" #include "code\modules\religion\festival\instrument_rites.dm" #include "code\modules\religion\honorbound\honorbound_rites.dm" From ea2e558aa53cec953d940eeb47ba74057d9193d2 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Tue, 18 Aug 2026 16:08:13 -0500 Subject: [PATCH 06/15] In game chem recipe browser now has recursive wiki-like popups (#97522) --- .../reagents/chemistry/recipes/others.dm | 2 +- .../tgui/interfaces/ChemDispenser.tsx | 264 +++++++++++------- 2 files changed, 168 insertions(+), 98 deletions(-) diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm index f33b0725734c..9163645d68c6 100644 --- a/code/modules/reagents/chemistry/recipes/others.dm +++ b/code/modules/reagents/chemistry/recipes/others.dm @@ -37,7 +37,7 @@ /datum/chemical_reaction/sodiumchloride results = list(/datum/reagent/consumable/salt = 2) required_reagents = list(/datum/reagent/sodium = 1, /datum/reagent/chlorine = 1) // That's what I said! Sodium Chloride! - reaction_tags = REACTION_TAG_EASY | REACTION_TAG_FOOD | REACTION_TAG_COMPONENT + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_CHEMICAL | REACTION_TAG_COMPONENT required_other = TRUE /datum/chemical_reaction/sodiumchloride/pre_reaction_other_checks(datum/reagents/holder) diff --git a/tgui/packages/tgui/interfaces/ChemDispenser.tsx b/tgui/packages/tgui/interfaces/ChemDispenser.tsx index 0bbeb2ecca0c..5c7d4c31c20e 100644 --- a/tgui/packages/tgui/interfaces/ChemDispenser.tsx +++ b/tgui/packages/tgui/interfaces/ChemDispenser.tsx @@ -1,8 +1,10 @@ +import { useState } from 'react'; import { BlockQuote, Box, Button, Collapsible, + Floating, Icon, Input, LabeledList, @@ -14,12 +16,17 @@ import { } from 'tgui-core/components'; import type { BooleanLike } from 'tgui-core/react'; import { createSearch, toTitleCase } from 'tgui-core/string'; - import { useBackend, useSharedState } from '../backend'; import { Window } from '../layouts'; import { type Beaker, BeakerDisplay } from './common/BeakerDisplay'; import { bitflagInfo } from './Reagents/types'; +enum DropdownState { + NO_DROPDOWN = 0, + DROPDOWN_CLOSED = 1, + DROPDOWN_OPEN = 2, +} + type DispensableReagent = { title: string; id: string; @@ -427,10 +434,14 @@ export const ChemDispenser = (props) => { filteredReactions.map((reaction) => ( )) @@ -498,14 +509,85 @@ const ReagentDispenseButton = (props: ReagentDispenseButtonProps) => { }; type ReactionDisplayProps = { + /// Determines how the collapsible/dropdown is displayed. + /// NO_DROPDOWN: no dropdown is displayed, just the recipe list is shown + /// DROPDOWN_CLOSED: dropdown is displayed, but closed by default + /// DROPDOWN_OPEN: dropdown is displayed and open by default + /// If undefined, the default behavior is DROPDOWN_CLOSED. + dropdownState?: DropdownState; + /// The reaction to display. reaction: ReagentReaction; + /// List of reactions that are pinned to the top of the list. pinnedReactions: ReactionTypepath[]; + /// Callback to update the list of pinned reactions. setPinnedReactions: (reactions: ReactionTypepath[]) => void; - setSearchTerm: (term: string) => void; + /// Callback to force the parent component to keep their floating window open. + setParentForceFloating?: (force: boolean) => void; }; const ReactionDisplay = (props: ReactionDisplayProps) => { - const { reaction, pinnedReactions, setPinnedReactions } = props; + const { + dropdownState, + reaction, + pinnedReactions, + setPinnedReactions, + setParentForceFloating, + } = props; + + const recipeList = ( +
+ + + + + {reaction.reaction.required_reagents.map((reagent) => ( + + + + ))} + {reaction.reaction.required_catalysts.length > 0 && ( + <> + + + + {reaction.reaction.required_catalysts.map((catalyst) => ( + + + + ))} + + )} + + + + + {getTemperatureMessage( + reaction.reaction.lower_temperature, + reaction.reaction.upper_temperature, + )} + + + + + + {getPHMessage(reaction.reaction.lower_ph, reaction.reaction.upper_ph)} + + +
+ ); + return ( { - -
- - - - - {reaction.reaction.required_reagents.map((reagent) => ( - - - - ))} - {reaction.reaction.required_catalysts.length > 0 && ( - <> - - - - {reaction.reaction.required_catalysts.map((catalyst) => ( - - - - ))} - - )} - - - - - {getTemperatureMessage( - reaction.reaction.lower_temperature, - reaction.reaction.upper_temperature, - )} - - - - - - {getPHMessage( - reaction.reaction.lower_ph, - reaction.reaction.upper_ph, - )} - - -
-
+ {dropdownState === DropdownState.NO_DROPDOWN ? ( + recipeList + ) : ( + + {recipeList} + + )}
); @@ -660,10 +693,14 @@ function getPHMessage(lower: number, upper: number): string { } type ReactionComponentDisplayProps = { + /// What component of the reaction is being displayed. reagentComponent: ReactionComponent; - setSearchTerm: (term: string) => void; + /// List of reactions that are pinned to the top of the list. pinnedReactions: ReactionTypepath[]; + /// Callback to update the list of pinned reactions. setPinnedReactions: (reactions: ReactionTypepath[]) => void; + /// Callback to force the parent component to keep their floating window open. + setParentForceFloating?: (force: boolean) => void; }; // linkifies a reagent name in the reaction display @@ -673,9 +710,9 @@ type ReactionComponentDisplayProps = { const ReactionComponentDisplay = (props: ReactionComponentDisplayProps) => { const { reagentComponent, - setSearchTerm, pinnedReactions, setPinnedReactions, + setParentForceFloating, } = props; const { data } = useBackend(); const { chemicals, reaction_list } = data; @@ -697,49 +734,82 @@ const ReactionComponentDisplay = (props: ReactionComponentDisplayProps) => { const reactionReagentList = reagentListToArray(reaction_list); // check if it's a recipe - const isRecipe = reactionReagentList + const foundRecipe = reactionReagentList .filter((reaction) => { return reaction.name === reagentComponent.name; }) .find((reaction) => reaction.name === reagentComponent.name); - if (isRecipe) { + if (!foundRecipe) return ( - ); - } - // otherwise, just display the name + const [forceFloating, setForceFloating] = useState(false); + return ( - + { + if (setParentForceFloating) { + setParentForceFloating(state); + } + if (!state) { + setForceFloating(false); + } + }} + content={ + + + + + + + + } + > + + ); }; From 236de96cdc51a1588e8636ff94dafd6c94164170 Mon Sep 17 00:00:00 2001 From: FinancialGoose <92416224+TheBoondock@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:43:47 -0700 Subject: [PATCH 07/15] Refactor gasmix mole change into a proc (#95327) --- code/datums/elements/death_gases.dm | 4 +- .../fluid_spread/effects_foam.dm | 7 +- .../fluid_spread/effects_smoke.dm | 9 +- .../objects/effects/spawners/bombspawner.dm | 59 ++++----- code/game/objects/items/bodybag.dm | 2 + code/game/objects/items/flamethrower.dm | 3 +- code/game/objects/items/tanks/jetpack.dm | 3 +- code/game/objects/items/tanks/tank_types.dm | 45 +++---- .../crates_lockers/closets/bodybag.dm | 19 ++- code/game/objects/structures/toiletbong.dm | 3 +- .../transit_tubes/transit_tube_pod.dm | 5 +- code/game/turfs/change_turf.dm | 2 +- .../atmospherics/gasmixtures/gas_mixture.dm | 34 ++++++ .../atmospherics/gasmixtures/reactions.dm | 31 +++-- .../electrolyzer/electrolyzer_reactions.dm | 47 +++---- .../components/fusion/hfr_main_processes.dm | 115 ++++++++---------- .../atmospherics/machinery/components/tank.dm | 4 +- .../components/unary_devices/vent_scrubber.dm | 6 +- .../machinery/portable/canister.dm | 18 ++- .../machinery/portable/scrubber.dm | 87 ++++++++----- code/modules/clothing/masks/gas_filter.dm | 12 +- .../events/space_vines/vine_mutations.dm | 69 +++++------ code/modules/fishing/fish/fish_traits.dm | 4 +- .../modules/hydroponics/unique_plant_genes.dm | 4 +- code/modules/mob/living/basic/tree.dm | 6 +- code/modules/mod/modules/modules_timeline.dm | 5 +- .../power/supermatter/supermatter_gas.dm | 10 +- .../surgery/organs/internal/lungs/_lungs.dm | 7 +- .../mecha/equipment/tools/air_tank.dm | 5 +- 29 files changed, 326 insertions(+), 299 deletions(-) diff --git a/code/datums/elements/death_gases.dm b/code/datums/elements/death_gases.dm index b93c870f817f..7589e7194158 100644 --- a/code/datums/elements/death_gases.dm +++ b/code/datums/elements/death_gases.dm @@ -30,7 +30,7 @@ SIGNAL_HANDLER var/datum/gas_mixture/mix_to_spawn = new() mix_to_spawn.add_gas(gas_type) - mix_to_spawn.gases[gas_type][MOLES] = amount_of_gas - mix_to_spawn.temperature = T20C + mix_to_spawn.set_gas(gas_type, amount_of_gas) + mix_to_spawn.set_temperature(T20C) var/turf/open/our_turf = get_turf(target) our_turf.assume_air(mix_to_spawn) diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm index b0546ac3081d..24d5981039f2 100644 --- a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm +++ b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm @@ -250,10 +250,9 @@ QDEL_NULL(hotspot) var/datum/gas_mixture/air = location.air - var/list/gases = air.gases - if (gases[/datum/gas/plasma]) - var/scrub_amt = min(30, gases[/datum/gas/plasma][MOLES]) //Absorb some plasma - gases[/datum/gas/plasma][MOLES] -= scrub_amt + if (air.gases[/datum/gas/plasma]) + var/scrub_amt = min(30, air.gases[/datum/gas/plasma][MOLES]) //Absorb some plasma + air.adjust_gas(/datum/gas/plasma, -scrub_amt) absorbed_plasma += scrub_amt if (air.temperature > T20C) air.temperature = max(air.temperature / 2, T20C) diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm index 95169137c08d..16da43cd19dd 100644 --- a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm +++ b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm @@ -295,11 +295,10 @@ if(!distcheck || get_dist(location, chilly) < blast) // Otherwise we'll get silliness like people using Nanofrost to kill people through walls with cold air air.temperature = temperature - var/list/gases = air.gases - if(gases[/datum/gas/plasma]) - air.assert_gas(/datum/gas/nitrogen) - gases[/datum/gas/nitrogen][MOLES] += gases[/datum/gas/plasma][MOLES] - gases[/datum/gas/plasma][MOLES] = 0 + if(air.gases[/datum/gas/plasma]) + var/mole_count = air.gases[/datum/gas/plasma][MOLES] + air.adjust_gas(/datum/gas/nitrogen, mole_count) + air.adjust_gas(/datum/gas/plasma, -mole_count) air.garbage_collect() for(var/obj/effect/hotspot/fire in chilly) diff --git a/code/game/objects/effects/spawners/bombspawner.dm b/code/game/objects/effects/spawners/bombspawner.dm index 9d1da48cd290..c5c82b485e0e 100644 --- a/code/game/objects/effects/spawners/bombspawner.dm +++ b/code/game/objects/effects/spawners/bombspawner.dm @@ -44,14 +44,11 @@ if(!first_gasmix || !second_gasmix) return - first_gasmix.temperature = 1413 - second_gasmix.temperature = 141.3 + first_gasmix.set_temperature(1413) + second_gasmix.set_temperature(141.3) - first_gasmix.assert_gas(/datum/gas/plasma) - second_gasmix.assert_gas(/datum/gas/oxygen) - - first_gasmix.gases[/datum/gas/plasma][MOLES] = calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1) - second_gasmix.gases[/datum/gas/oxygen][MOLES] = calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1) + first_gasmix.set_gas(/datum/gas/plasma, calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1)) + second_gasmix.set_gas(/datum/gas/oxygen, calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1)) /obj/effect/spawner/newbomb/tritium @@ -60,16 +57,13 @@ if(!first_gasmix || !second_gasmix) return - first_gasmix.temperature = 8000 - second_gasmix.temperature = 43 + first_gasmix.set_temperature(8000) + second_gasmix.set_temperature(43) - first_gasmix.assert_gas(/datum/gas/plasma) - second_gasmix.assert_gas(/datum/gas/oxygen) - second_gasmix.assert_gas(/datum/gas/tritium) + first_gasmix.set_gas(/datum/gas/plasma, calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1)) - first_gasmix.gases[/datum/gas/plasma][MOLES] = calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1) - second_gasmix.gases[/datum/gas/oxygen][MOLES] = 0.67 * calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1) - second_gasmix.gases[/datum/gas/tritium][MOLES] = 0.33 * calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1) + second_gasmix.set_gas(/datum/gas/oxygen, 0.67 * calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1)) + second_gasmix.set_gas(/datum/gas/tritium, 0.33 * calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1)) /obj/effect/spawner/newbomb/isolated_tritium @@ -78,16 +72,13 @@ if(!first_gasmix || !second_gasmix) return - first_gasmix.temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST + 1 - second_gasmix.temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST + 1 + first_gasmix.set_temperature(FIRE_MINIMUM_TEMPERATURE_TO_EXIST + 1) + second_gasmix.set_temperature(FIRE_MINIMUM_TEMPERATURE_TO_EXIST + 1) - first_gasmix.assert_gas(/datum/gas/hypernoblium) - first_gasmix.assert_gas(/datum/gas/tritium) - second_gasmix.assert_gas(/datum/gas/oxygen) + first_gasmix.set_gas(/datum/gas/hypernoblium, REACTION_OPPRESSION_THRESHOLD - 0.01,) + first_gasmix.set_gas( /datum/gas/tritium, 0.5 * calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1)) - first_gasmix.gases[/datum/gas/hypernoblium][MOLES] = REACTION_OPPRESSION_THRESHOLD - 0.01 - first_gasmix.gases[/datum/gas/tritium][MOLES] = 0.5 * calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1) - second_gasmix.gases[/datum/gas/oxygen][MOLES] = calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE-1) + second_gasmix.set_gas(/datum/gas/oxygen, calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE-1)) /obj/effect/spawner/newbomb/noblium @@ -96,14 +87,11 @@ if(!first_gasmix || !second_gasmix) return - first_gasmix.temperature = 2.7 - second_gasmix.temperature = 2.7 - - first_gasmix.assert_gas(/datum/gas/nitrogen) - second_gasmix.assert_gas(/datum/gas/tritium) + first_gasmix.set_temperature(2.7) + second_gasmix.set_temperature(2.7) - first_gasmix.gases[/datum/gas/nitrogen][MOLES] = calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1) - second_gasmix.gases[/datum/gas/tritium][MOLES] = calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1) + first_gasmix.set_gas(/datum/gas/nitrogen, calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1)) + second_gasmix.set_gas(/datum/gas/tritium, calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1)) /obj/effect/spawner/newbomb/pressure @@ -112,11 +100,8 @@ if(!first_gasmix || !second_gasmix) return - first_gasmix.temperature = 20000 - second_gasmix.temperature = 2.7 - - first_gasmix.assert_gas(/datum/gas/hypernoblium) - second_gasmix.assert_gas(/datum/gas/tritium) + first_gasmix.set_temperature(20000) + second_gasmix.set_temperature(2.7) - first_gasmix.gases[/datum/gas/hypernoblium][MOLES] = calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1) - second_gasmix.gases[/datum/gas/tritium][MOLES] = calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1) + first_gasmix.set_gas(/datum/gas/hypernoblium, calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1)) + second_gasmix.set_gas(/datum/gas/tritium, calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1)) diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm index b82c51a9a0a3..47988bb0843d 100644 --- a/code/game/objects/items/bodybag.dm +++ b/code/game/objects/items/bodybag.dm @@ -61,6 +61,7 @@ unfoldedbag_path = /obj/structure/closet/body_bag/bluespace w_class = WEIGHT_CLASS_SMALL item_flags = NO_MAT_REDEMPTION + custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 1.5, /datum/material/plasma = SHEET_MATERIAL_AMOUNT, /datum/material/diamond = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/bluespace = HALF_SHEET_MATERIAL_AMOUNT) /// Tracks the air from the bodybag var/datum/gas_mixture/internal_air @@ -175,6 +176,7 @@ icon = 'maplestation_modules/icons/obj/bodybag.dmi' icon_state = "stasis_bag_folded" unfoldedbag_path = /obj/structure/closet/body_bag/environmental/stasis + custom_materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT * 10, /datum/material/silver = HALF_SHEET_MATERIAL_AMOUNT) /obj/item/bodybag/stasis/deploy_bodybag(mob/user, atom/location) var/obj/structure/closet/body_bag/environmental/stasis/bag = ..() diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm index bf9ae18a611a..253b01a0e2d4 100644 --- a/code/game/objects/items/flamethrower.dm +++ b/code/game/objects/items/flamethrower.dm @@ -231,7 +231,8 @@ var/datum/gas_mixture/air_transfer = tank_mix.remove_ratio(release_amount) if(air_transfer.gases[/datum/gas/plasma]) - air_transfer.gases[/datum/gas/plasma][MOLES] *= 5 //Suffering + var/moles = air_transfer.gases[/datum/gas/plasma][MOLES] * 5 //Suffering + air_transfer.set_gas(/datum/gas/plasma, moles) target.assume_air(air_transfer) //Burn it based on transferred gas target.hotspot_expose((tank_mix.temperature*2) + 380,500) diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm index 04a82fd9029e..6a7abe0b555c 100644 --- a/code/game/objects/items/tanks/jetpack.dm +++ b/code/game/objects/items/tanks/jetpack.dm @@ -63,8 +63,7 @@ /obj/item/tank/jetpack/populate_gas() if(gas_type) var/datum/gas_mixture/our_mix = return_air() - our_mix.assert_gas(gas_type) - our_mix.gases[gas_type][MOLES] = ((6 * ONE_ATMOSPHERE) * volume / (R_IDEAL_GAS_EQUATION * T20C)) + our_mix.set_gas(gas_type, ((6 * ONE_ATMOSPHERE) * volume / (R_IDEAL_GAS_EQUATION * T20C))) /obj/item/tank/jetpack/ui_action_click(mob/user, action) if(istype(action, /datum/action/item_action/toggle_jetpack)) diff --git a/code/game/objects/items/tanks/tank_types.dm b/code/game/objects/items/tanks/tank_types.dm index 8f0cc857ab1c..daf38882159d 100644 --- a/code/game/objects/items/tanks/tank_types.dm +++ b/code/game/objects/items/tanks/tank_types.dm @@ -35,8 +35,7 @@ /obj/item/tank/internals/oxygen/populate_gas() - air_contents.assert_gas(/datum/gas/oxygen) - air_contents.gases[/datum/gas/oxygen][MOLES] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.set_gas(/datum/gas/oxygen, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) /obj/item/tank/internals/oxygen/yellow @@ -68,9 +67,9 @@ force = 10 /obj/item/tank/internals/anesthetic/populate_gas() - air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrous_oxide) - air_contents.gases[/datum/gas/oxygen][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD - air_contents.gases[/datum/gas/nitrous_oxide][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD + air_contents.set_gas(/datum/gas/oxygen, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD) + air_contents.set_gas(/datum/gas/nitrous_oxide, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD) + /obj/item/tank/internals/anesthetic/examine(mob/user) . = ..() @@ -82,8 +81,7 @@ icon_state = "anesthetic_warning" /obj/item/tank/internals/anesthetic/pure/populate_gas() - air_contents.assert_gases(/datum/gas/nitrous_oxide) - air_contents.gases[/datum/gas/nitrous_oxide][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.adjust_gas(/datum/gas/nitrous_oxide, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) /* * Plasma @@ -101,8 +99,7 @@ /obj/item/tank/internals/plasma/populate_gas() - air_contents.assert_gas(/datum/gas/plasma) - air_contents.gases[/datum/gas/plasma][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.set_gas(/datum/gas/plasma, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) /obj/item/tank/internals/plasma/attackby(obj/item/W, mob/user, list/modifiers, list/attack_modifiers) if(istype(W, /obj/item/flamethrower)) @@ -118,8 +115,7 @@ return ..() /obj/item/tank/internals/plasma/full/populate_gas() - air_contents.assert_gas(/datum/gas/plasma) - air_contents.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.set_gas(/datum/gas/plasma, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) /obj/item/tank/internals/plasma/empty/populate_gas() return @@ -138,12 +134,10 @@ distribute_pressure = TANK_PLASMAMAN_RELEASE_PRESSURE /obj/item/tank/internals/plasmaman/populate_gas() - air_contents.assert_gas(/datum/gas/plasma) - air_contents.gases[/datum/gas/plasma][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.set_gas(/datum/gas/plasma, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) /obj/item/tank/internals/plasmaman/full/populate_gas() - air_contents.assert_gas(/datum/gas/plasma) - air_contents.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.set_gas(/datum/gas/plasma, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) /obj/item/tank/internals/plasmaman/belt @@ -158,8 +152,7 @@ w_class = WEIGHT_CLASS_SMALL //thanks i forgot this /obj/item/tank/internals/plasmaman/belt/full/populate_gas() - air_contents.assert_gas(/datum/gas/plasma) - air_contents.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.set_gas(/datum/gas/plasma, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) /obj/item/tank/internals/plasmaman/belt/empty/populate_gas() return @@ -186,8 +179,7 @@ /obj/item/tank/internals/emergency_oxygen/populate_gas() - air_contents.assert_gas(/datum/gas/oxygen) - air_contents.gases[/datum/gas/oxygen][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.set_gas(/datum/gas/oxygen, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) /obj/item/tank/internals/emergency_oxygen/empty/populate_gas() @@ -246,21 +238,18 @@ /obj/item/tank/internals/emergency_oxygen/engi/clown/n2o /obj/item/tank/internals/emergency_oxygen/engi/clown/n2o/populate_gas() - air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrous_oxide) - air_contents.gases[/datum/gas/oxygen][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * 0.95 - air_contents.gases[/datum/gas/nitrous_oxide][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * 0.05 + air_contents.set_gas(/datum/gas/oxygen, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * 0.95) + air_contents.set_gas(/datum/gas/nitrous_oxide, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * 0.05) /obj/item/tank/internals/emergency_oxygen/engi/clown/bz /obj/item/tank/internals/emergency_oxygen/engi/clown/bz/populate_gas() - air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/bz) - air_contents.gases[/datum/gas/oxygen][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * 0.9 - air_contents.gases[/datum/gas/bz][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * 0.1 + air_contents.set_gas(/datum/gas/oxygen,(10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * 0.9) + air_contents.set_gas(/datum/gas/bz,(10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * 0.1) /obj/item/tank/internals/emergency_oxygen/engi/clown/helium distribute_pressure = TANK_CLOWN_RELEASE_PRESSURE + 2 /obj/item/tank/internals/emergency_oxygen/engi/clown/helium/populate_gas() - air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/helium) - air_contents.gases[/datum/gas/oxygen][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * 0.75 - air_contents.gases[/datum/gas/helium][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * 0.25 + air_contents.set_gas(/datum/gas/oxygen, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * 0.75) + air_contents.set_gas(/datum/gas/helium, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * 0.25) diff --git a/code/game/objects/structures/crates_lockers/closets/bodybag.dm b/code/game/objects/structures/crates_lockers/closets/bodybag.dm index 442aa94be2b4..a87f5654963e 100644 --- a/code/game/objects/structures/crates_lockers/closets/bodybag.dm +++ b/code/game/objects/structures/crates_lockers/closets/bodybag.dm @@ -32,6 +32,10 @@ /// Paper pinned to this bag var/obj/item/paper/pinned +/obj/structure/closet/body_bag/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_ATOM_EXAMINE_POST_DESCRIPTOR, PROC_REF(make_examine_cloth)) + /obj/structure/closet/body_bag/add_context(atom/source, list/context, obj/item/held_item, mob/user) . = ..() if(isnull(held_item)) @@ -53,6 +57,14 @@ QDEL_NULL(foldedbag_instance) return ..() +/obj/structure/closet/body_bag/proc/make_examine_cloth(mob/user, mob/user, list/examine_text, list/mat_list) + SIGNAL_HANDLER + + // melbert todo : weeeee need a cloth material + mat_list -= /datum/material/iron::name + mat_list.Insert(1, "cloth") + mat_list["cloth"] = "It is made out of cloth." + ///Handles renaming of the bodybag's examine tag. /obj/structure/closet/body_bag/proc/handle_tag(new_name) playsound(src, SFX_WRITING_PEN, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE, SOUND_FALLOFF_EXPONENT + 3, ignore_walls = FALSE) @@ -154,6 +166,7 @@ max_mob_size = MOB_SIZE_LARGE sealed = TRUE air_volume = TANK_STANDARD_VOLUME * 2 + custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 1.5, /datum/material/plasma = SHEET_MATERIAL_AMOUNT, /datum/material/diamond = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/bluespace = HALF_SHEET_MATERIAL_AMOUNT) /obj/structure/closet/body_bag/bluespace/attempt_fold(mob/living/carbon/human/the_folder) . = FALSE @@ -421,15 +434,14 @@ if(opened) // lose a majority of all n2o when we start leaking gas, to stop this being a free (obnoxious) way to make n2o internal_air.assert_gases(/datum/gas/nitrous_oxide) - internal_air.gases[/datum/gas/nitrous_oxide][MOLES] *= 0.15 + internal_air.adjust_gas(/datum/gas/nitrous_oxide, internal_air.gases[/datum/gas/nitrous_oxide][MOLES] * 0.15) return ..() internal_air.assert_gases(/datum/gas/nitrogen, /datum/gas/nitrous_oxide) var/conversion_amount = min(internal_air.gases[/datum/gas/nitrogen][MOLES], 0.2 * internal_air.total_moles() * seconds_per_tick) if(conversion_amount > 0) // 20% of the nitrogen in the bag is converted to nitrous oxide every second while closed - internal_air.gases[/datum/gas/nitrogen][MOLES] = max(0, internal_air.gases[/datum/gas/nitrogen][MOLES] - conversion_amount) - internal_air.gases[/datum/gas/nitrous_oxide][MOLES] += conversion_amount + internal_air.convert_gas(/datum/gas/nitrogen, /datum/gas/nitrous_oxide, conversion_amount) return ..() /obj/structure/closet/body_bag/environmental/hardlight @@ -440,6 +452,7 @@ foldedbag_path = null weather_protection = list(TRAIT_VOIDSTORM_IMMUNE, TRAIT_SNOWSTORM_IMMUNE) can_scan_through = TRUE + custom_materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT * 10, /datum/material/silver = HALF_SHEET_MATERIAL_AMOUNT) /obj/structure/closet/body_bag/environmental/hardlight/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) if(damage_type in list(BRUTE, BURN)) diff --git a/code/game/objects/structures/toiletbong.dm b/code/game/objects/structures/toiletbong.dm index 038e8fa292a3..610773f9a75f 100644 --- a/code/game/objects/structures/toiletbong.dm +++ b/code/game/objects/structures/toiletbong.dm @@ -85,7 +85,8 @@ new /obj/item/flamethrower(get_turf(src)) new /obj/item/stack/sheet/iron(get_turf(src)) var/obj/item/tank/internals/plasma/ptank = new /obj/item/tank/internals/plasma(get_turf(src)) - ptank.air_contents.gases[/datum/gas/plasma][MOLES] = (0) + ptank.air_contents.set_gas(/datum/gas/plasma, 0) + drop_custom_materials() qdel(src) return TRUE diff --git a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm index 3877f75d1bf8..7e3fcdf80fc5 100644 --- a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm +++ b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm @@ -14,9 +14,8 @@ /obj/structure/transit_tube_pod/Initialize(mapload) . = ..() - air_contents.add_gases(/datum/gas/oxygen, /datum/gas/nitrogen) - air_contents.gases[/datum/gas/oxygen][MOLES] = MOLES_O2STANDARD - air_contents.gases[/datum/gas/nitrogen][MOLES] = MOLES_N2STANDARD + var/list/new_gases = list(/datum/gas/oxygen = MOLES_O2STANDARD, /datum/gas/nitrogen = MOLES_N2STANDARD) + air_contents.adjust_multiple_gases(new_gases) air_contents.temperature = T20C /obj/structure/transit_tube_pod/Destroy() diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm index e7e2974d2f18..b59a91d05e92 100644 --- a/code/game/turfs/change_turf.dm +++ b/code/game/turfs/change_turf.dm @@ -259,7 +259,7 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( var/list/giver_gases = mix.gases for(var/giver_id in giver_gases) ASSERT_GAS_IN_LIST(giver_id, total_gases) - total_gases[giver_id][MOLES] += giver_gases[giver_id][MOLES] + total.adjust_gas(giver_id, giver_gases[giver_id][MOLES]) total.temperature = energy / heat_cap for(var/id in total_gases) diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index cfb87ce7cb3f..86f7762ce64a 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -174,6 +174,40 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) SEND_SIGNAL(src, COMSIG_GASMIX_MERGED) return TRUE +// Set the gas specie within the gas mix to a set amount, if there is none it will be created at the target temp +/datum/gas_mixture/proc/set_gas(gas_specie, amount) + ASSERT_GAS(gas_specie, src) + gases[gas_specie][MOLES] = amount + garbage_collect() + +/datum/gas_mixture/proc/set_temperature(target_temp) + temperature = target_temp + +/// Add a specific amount of moles to specified gas or add a new gas to the mix +/// amount is added so make it negative to remove +/datum/gas_mixture/proc/adjust_gas(gas, amount) + ASSERT_GAS(gas, src) + gases[gas][MOLES] += QUANTIZE(amount) + garbage_collect() + +/// Add a specific amount of moles to all the gasses present or add a new gas to the mix +///gases_moles is an associative list of gas species to their amount to be added +/datum/gas_mixture/proc/adjust_multiple_gases(list/gases_moles) + for(var/gas_specie in gases_moles) + ASSERT_GAS(gas_specie, src) + gases[gas_specie][MOLES] += gases_moles[gas_specie] + garbage_collect() + + +/// Modify the gas list as to convert moles of gas species A to gas species B +/// reactant and product are the gas species to convert and conversion_amount is the amount to be converted +/datum/gas_mixture/proc/convert_gas(datum/gas/reactant, datum/gas/product, conversion_amount) + var/list/cached_gases = gases + assert_gases(reactant, product) + cached_gases[reactant][MOLES] -= QUANTIZE(conversion_amount) + cached_gases[product][MOLES] += QUANTIZE(conversion_amount) + garbage_collect() + ///Proportionally removes amount of gas from the gas_mixture. ///Returns: gas_mixture with the gases removed /datum/gas_mixture/proc/remove(amount) diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index 24a7c799ad91..d00fdb1edfbd 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -195,6 +195,8 @@ var/plasma_burn_rate = 0 var/super_saturation = FALSE // Whether we should make tritium. var/list/cached_gases = air.gases //this speeds things up because accessing datum vars is slow + var/list/plasma = cached_gases[/datum/gas/plasma] + var/list/oxygen = cached_gases[/datum/gas/oxygen] switch(cached_gases[/datum/gas/oxygen][MOLES] / cached_gases[/datum/gas/plasma][MOLES]) if(SUPER_SATURATION_THRESHOLD to INFINITY) plasma_burn_rate = (cached_gases[/datum/gas/plasma][MOLES] / PLASMA_BURN_RATE_DELTA) * temperature_scale @@ -208,10 +210,10 @@ return NO_REACTION var/old_heat_capacity = air.heat_capacity() - plasma_burn_rate = min(plasma_burn_rate, cached_gases[/datum/gas/plasma][MOLES], cached_gases[/datum/gas/oxygen][MOLES] * INVERSE(oxygen_burn_ratio)) //Ensures matter is conserved properly - cached_gases[/datum/gas/plasma][MOLES] = QUANTIZE(cached_gases[/datum/gas/plasma][MOLES] - plasma_burn_rate) - cached_gases[/datum/gas/oxygen][MOLES] = QUANTIZE(cached_gases[/datum/gas/oxygen][MOLES] - (plasma_burn_rate * oxygen_burn_ratio)) - if (super_saturation) + plasma_burn_rate = min(plasma_burn_rate, plasma[MOLES], oxygen[MOLES] * INVERSE(oxygen_burn_ratio)) //Ensures matter is conserved properly + plasma[MOLES] = QUANTIZE(plasma[MOLES] - plasma_burn_rate) + oxygen[MOLES] = QUANTIZE(oxygen[MOLES] - (plasma_burn_rate * oxygen_burn_ratio)) + if(super_saturation) ASSERT_GAS(/datum/gas/tritium, air) cached_gases[/datum/gas/tritium][MOLES] += plasma_burn_rate else @@ -220,6 +222,7 @@ cached_gases[/datum/gas/carbon_dioxide][MOLES] += plasma_burn_rate * 0.75 cached_gases[/datum/gas/water_vapor][MOLES] += plasma_burn_rate * 0.25 + SET_REACTION_RESULTS((plasma_burn_rate) * (1 + oxygen_burn_ratio)) var/energy_released = FIRE_PLASMA_ENERGY_RELEASED * plasma_burn_rate var/new_heat_capacity = air.heat_capacity() @@ -562,6 +565,8 @@ cached_gases[/datum/gas/nitrous_oxide][MOLES] -= 0.4 * bz_formed cached_gases[/datum/gas/plasma][MOLES] -= 0.8 * bz_formed * (1-nitrous_oxide_decomposed_factor) + + SET_REACTION_RESULTS(bz_formed) var/energy_released = bz_formed * (BZ_FORMATION_ENERGY + nitrous_oxide_decomposed_factor * (N2O_DECOMPOSITION_ENERGY - BZ_FORMATION_ENERGY)) var/new_heat_capacity = air.heat_capacity() @@ -644,18 +649,19 @@ var/temperature = air.temperature var/heat_efficency = min(temperature / NITRIUM_FORMATION_TEMP_DIVISOR, cached_gases[/datum/gas/tritium][MOLES], cached_gases[/datum/gas/nitrogen][MOLES], cached_gases[/datum/gas/bz][MOLES] * INVERSE(0.05)) - if( heat_efficency <= 0 || (cached_gases[/datum/gas/tritium][MOLES] - heat_efficency < 0 ) || (cached_gases[/datum/gas/nitrogen][MOLES] - heat_efficency < 0) || (cached_gases[/datum/gas/bz][MOLES] - heat_efficency * 0.05 < 0)) //Shouldn't produce gas from nothing. + if( heat_efficiency <= 0 || (cached_gases[/datum/gas/tritium][MOLES] - heat_efficiency < 0 ) || (cached_gases[/datum/gas/nitrogen][MOLES] - heat_efficiency < 0) || (cached_gases[/datum/gas/bz][MOLES] - heat_efficiency * 0.05 < 0)) //Shouldn't produce gas from nothing. return NO_REACTION var/old_heat_capacity = air.heat_capacity() ASSERT_GAS(/datum/gas/nitrium, air) - cached_gases[/datum/gas/tritium][MOLES] -= heat_efficency - cached_gases[/datum/gas/nitrogen][MOLES] -= heat_efficency - cached_gases[/datum/gas/bz][MOLES] -= heat_efficency * 0.05 //bz gets consumed to balance the nitrium production and not make it too common and/or easy - cached_gases[/datum/gas/nitrium][MOLES] += heat_efficency + cached_gases[/datum/gas/tritium][MOLES] -= heat_efficiency + cached_gases[/datum/gas/nitrogen][MOLES] -= heat_efficiency + cached_gases[/datum/gas/bz][MOLES] -= heat_efficiency * 0.05 //bz gets consumed to balance the nitrium production and not make it too common and/or easy + cached_gases[/datum/gas/nitrium][MOLES] += heat_efficiency - SET_REACTION_RESULTS(heat_efficency) - var/energy_used = heat_efficency * NITRIUM_FORMATION_ENERGY + + SET_REACTION_RESULTS(heat_efficiency) + var/energy_used = heat_efficiency * NITRIUM_FORMATION_ENERGY var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air.temperature = max(((temperature * old_heat_capacity - energy_used) / new_heat_capacity), TCMB) //the air cools down when reacting @@ -793,6 +799,7 @@ cached_gases[/datum/gas/tritium][MOLES] -= 5 * nob_formed * reduction_factor cached_gases[/datum/gas/nitrogen][MOLES] -= 10 * nob_formed cached_gases[/datum/gas/hypernoblium][MOLES] += nob_formed // I'm not going to nitpick, but N20H10 feels like it should be an explosive more than anything. + SET_REACTION_RESULTS(nob_formed) var/energy_released = nob_formed * (NOBLIUM_FORMATION_ENERGY / (max(cached_gases[/datum/gas/bz][MOLES], 1))) var/new_heat_capacity = air.heat_capacity() @@ -1149,6 +1156,6 @@ var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air.temperature = max((temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB) - return REACTING + . |= REACTING #undef SET_REACTION_RESULTS diff --git a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm index c393436d5a77..454ad98ab86e 100644 --- a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm +++ b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm @@ -51,11 +51,11 @@ GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list()) /datum/electrolyzer_reaction/h2o_conversion/react(turf/location, datum/gas_mixture/air_mixture, working_power) var/old_heat_capacity = air_mixture.heat_capacity() - air_mixture.assert_gases(/datum/gas/water_vapor, /datum/gas/oxygen, /datum/gas/hydrogen) + var/proportion = min(air_mixture.gases[/datum/gas/water_vapor][MOLES] * INVERSE(2), (2.5 * (working_power ** 2))) - air_mixture.gases[/datum/gas/water_vapor][MOLES] -= proportion * 2 - air_mixture.gases[/datum/gas/oxygen][MOLES] += proportion - air_mixture.gases[/datum/gas/hydrogen][MOLES] += proportion * 2 + air_mixture.adjust_gas(/datum/gas/water_vapor, -proportion * 2) + air_mixture.adjust_gas(/datum/gas/oxygen, proportion) + air_mixture.adjust_gas(/datum/gas/hydrogen, proportion * 2) var/new_heat_capacity = air_mixture.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air_mixture.temperature = max(air_mixture.temperature * old_heat_capacity / new_heat_capacity, TCMB) @@ -80,8 +80,10 @@ GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list()) var/old_heat_capacity = air_mixture.heat_capacity() air_mixture.assert_gases(/datum/gas/hypernoblium, /datum/gas/antinoblium) var/proportion = min(air_mixture.gases[/datum/gas/hypernoblium][MOLES], (1.5 * (working_power ** 2))) - air_mixture.gases[/datum/gas/hypernoblium][MOLES] -= proportion - air_mixture.gases[/datum/gas/antinoblium][MOLES] += proportion * 0.5 + + air_mixture.adjust_gas(/datum/gas/hypernoblium, -proportion) + air_mixture.adjust_gas(/datum/gas/antinoblium, proportion) + var/new_heat_capacity = air_mixture.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air_mixture.temperature = max(air_mixture.temperature * old_heat_capacity / new_heat_capacity, TCMB) @@ -89,30 +91,29 @@ GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list()) /datum/electrolyzer_reaction/halon_generation name = "Halon generation" id = "halon_generation" - desc = "Production of halon from CO2 and N2O" + desc = "Production of halon from the electrolysis of BZ." requirements = list( - /datum/gas/carbon_dioxide = MINIMUM_MOLE_COUNT, - /datum/gas/nitrous_oxide = MINIMUM_MOLE_COUNT, - "MAX_TEMP" = 230 + /datum/gas/bz = MINIMUM_MOLE_COUNT, ) factor = list( - /datum/gas/carbon_dioxide = "2 moles of CO2 get consumed", - /datum/gas/nitrous_oxide = "1 mole of N2O gets consumed", - /datum/gas/halon = "1 mole of Halon gets produced", - "Energy" = "300 joules of energy is released per mole", - "Temperature" = "Can only occur under 230 kelvin.", + /datum/gas/bz = "Consumed during reaction.", + /datum/gas/oxygen = "0.2 moles of oxygen gets produced per mole of BZ consumed.", + /datum/gas/halon = "2 moles of Halon gets produced per mole of BZ consumed.", + "Energy" = "91.2321 kJ of thermal energy is released per mole of BZ consumed.", + "Temperature" = "Reaction efficiency is proportional to temperature.", "Location" = "Can only happen on turfs with an active Electrolyzer.", ) -/datum/electrolyzer_reaction/halon_generation/react(turf/location, datum/gas_mixture/air_mixture, working_power) - +/datum/electrolyzer_reaction/halon_generation/react(datum/gas_mixture/air_mixture, working_power, list/electrolyzer_args = list()) var/old_heat_capacity = air_mixture.heat_capacity() - air_mixture.assert_gases(/datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/halon) - var/pressure = air_mixture.return_pressure() - var/reaction_efficency = min(1 / ((pressure / (0.5 * ONE_ATMOSPHERE)) * (max(air_mixture.gases[/datum/gas/carbon_dioxide][MOLES] / air_mixture.gases[/datum/gas/nitrous_oxide][MOLES], 1))), air_mixture.gases[/datum/gas/nitrous_oxide][MOLES], air_mixture.gases[/datum/gas/carbon_dioxide][MOLES] * INVERSE(2)) - air_mixture.gases[/datum/gas/carbon_dioxide][MOLES] -= reaction_efficency * 2 - air_mixture.gases[/datum/gas/nitrous_oxide][MOLES] -= reaction_efficency - air_mixture.gases[/datum/gas/halon][MOLES] += reaction_efficency + air_mixture.assert_gases(/datum/gas/bz, /datum/gas/oxygen, /datum/gas/halon) + var/bz_moles = air_mixture.moles[/datum/gas/bz] + var/reaction_efficency = min(bz_moles * (1 - NUM_E ** (-0.5 * air_mixture.temperature * working_power / FIRE_MINIMUM_TEMPERATURE_TO_EXIST)), bz_moles) + + air_mixture.adjust_gas(/datum/gas/bz, -reaction_efficency) + air_mixture.adjust_gas(/datum/gas/oxygen, reaction_efficency * 0.2) + air_mixture.adjust_gas(/datum/gas/halon, reaction_efficency * 2) + var/energy_used = reaction_efficency * HALON_FORMATION_ENERGY var/new_heat_capacity = air_mixture.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm index 43a1ccf44026..c6447166d223 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm @@ -246,35 +246,37 @@ var/scaled_production = production_amount * selected_fuel.gas_production_multiplier for(var/gas_id in fuel.requirements) - internal_fusion.gases[gas_id][MOLES] -= min(fuel_list[gas_id], fuel_consumption) + internal_fusion.adjust_gas(gas_id, -min(fuel_list[gas_id], fuel_consumption)) for(var/gas_id in fuel.primary_products) - internal_fusion.gases[gas_id][MOLES] += fuel_consumption * 0.5 + internal_fusion.adjust_gas(gas_id, fuel_consumption * 0.5) // Each recipe provides a tier list of six output gases. // Which gases are produced depend on what the fusion level is. var/list/tier = fuel.secondary_products switch(power_level) if(1) - moderator_internal.gases[tier[1]][MOLES] += scaled_production * 0.95 - moderator_internal.gases[tier[2]][MOLES] += scaled_production * 0.75 + moderator_internal.adjust_gas(tier[1], scaled_production * 0.95) + moderator_internal.adjust_gas(tier[2], scaled_production * 0.75) if(2) - moderator_internal.gases[tier[1]][MOLES] += scaled_production * 1.65 - moderator_internal.gases[tier[2]][MOLES] += scaled_production + moderator_internal.adjust_gas(tier[1], scaled_production * 1.65) + moderator_internal.adjust_gas(tier[2], scaled_production) if(moderator_list[/datum/gas/plasma] > 50) - moderator_internal.gases[tier[3]][MOLES] += scaled_production * 1.15 + moderator_internal.adjust_gas(tier[3], scaled_production * 1.15) if(3) - moderator_internal.gases[tier[2]][MOLES] += scaled_production * 0.5 - moderator_internal.gases[tier[3]][MOLES] += scaled_production * 0.45 + moderator_internal.adjust_gas(tier[2], scaled_production * 0.5) + moderator_internal.adjust_gas(tier[3], scaled_production * 0.45) if(4) - moderator_internal.gases[tier[3]][MOLES] += scaled_production * 1.65 - moderator_internal.gases[tier[4]][MOLES] += scaled_production * 1.25 + moderator_internal.adjust_gas(tier[3], scaled_production * 1.65) + moderator_internal.adjust_gas(tier[4], scaled_production * 1.25) + if(moderator_list[/datum/gas/plasma] > 50) + moderator_internal.adjust_gas(tier[5], scaled_production * 1.15) if(5) - moderator_internal.gases[tier[4]][MOLES] += scaled_production * 0.65 - moderator_internal.gases[tier[5]][MOLES] += scaled_production - moderator_internal.gases[tier[6]][MOLES] += scaled_production * 0.75 + moderator_internal.adjust_gas(tier[4], scaled_production * 0.65) + moderator_internal.adjust_gas(tier[5], scaled_production) + moderator_internal.adjust_gas(tier[6], scaled_production * 0.75) if(6) - moderator_internal.gases[tier[5]][MOLES] += scaled_production * 0.35 - moderator_internal.gases[tier[6]][MOLES] += scaled_production + moderator_internal.adjust_gas(tier[5], scaled_production * 0.35) + moderator_internal.adjust_gas(tier[6], scaled_production) /** * Perform common fusion actions: @@ -287,94 +289,81 @@ switch(power_level) if(1) if(moderator_list[/datum/gas/plasma] > 100) - internal_output.assert_gases(/datum/gas/nitrous_oxide) - internal_output.gases[/datum/gas/nitrous_oxide] += scaled_production * 0.5 - moderator_internal.gases[/datum/gas/plasma][MOLES] -= min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 0.85) + internal_output.adjust_gas(/datum/gas/nitrous_oxide, scaled_production * 0.5) + moderator_internal.adjust_gas(/datum/gas/plasma, min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 0.85)) if(moderator_list[/datum/gas/bz] > 150) - internal_output.assert_gases(/datum/gas/halon) - internal_output.gases[/datum/gas/halon][MOLES] += scaled_production * 0.55 - moderator_internal.gases[/datum/gas/bz][MOLES] -= min(moderator_internal.gases[/datum/gas/bz][MOLES], scaled_production * 0.95) + internal_output.adjust_gas(/datum/gas/halon, scaled_production * 0.55) + moderator_internal.adjust_gas(/datum/gas/bz, min(moderator_internal.gases[/datum/gas/bz][MOLES], scaled_production * 0.95)) if(2) if(moderator_list[/datum/gas/plasma] > 50) - internal_output.assert_gases(/datum/gas/bz) - internal_output.gases[/datum/gas/bz][MOLES] += scaled_production * 1.8 - moderator_internal.gases[/datum/gas/plasma][MOLES] -= min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.75) + internal_output.adjust_gas(/datum/gas/bz, scaled_production * 1.8) + moderator_internal.adjust_gas(/datum/gas/plasma, min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.75)) if(moderator_list[/datum/gas/proto_nitrate] > 20) radiation *= 1.55 heat_output *= 1.025 - internal_output.assert_gases(/datum/gas/nitrium) - internal_output.gases[/datum/gas/nitrium][MOLES] += scaled_production * 1.05 - moderator_internal.gases[/datum/gas/proto_nitrate][MOLES] -= min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.35) + internal_output.adjust_gas(/datum/gas/nitrium, scaled_production * 1.05) + moderator_internal.adjust_gas(/datum/gas/proto_nitrate, min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.35)) if(3, 4) if(moderator_list[/datum/gas/plasma] > 10) - internal_output.assert_gases(/datum/gas/freon, /datum/gas/nitrium) - internal_output.gases[/datum/gas/freon][MOLES] += scaled_production * 0.15 - internal_output.gases[/datum/gas/nitrium][MOLES] += scaled_production * 1.05 - moderator_internal.gases[/datum/gas/plasma][MOLES] -= min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 0.45) + var/list/new_gases = list(/datum/gas/freon = scaled_production * 0.15, /datum/gas/nitrium = scaled_production * 1.05) + internal_output.adjust_multiple_gases(new_gases) + moderator_internal.adjust_gas(/datum/gas/plasma, min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 0.45)) if(moderator_list[/datum/gas/freon] > 50) heat_output *= 0.9 radiation *= 0.8 if(moderator_list[/datum/gas/proto_nitrate]> 15) - internal_output.assert_gases(/datum/gas/nitrium, /datum/gas/halon) - internal_output.gases[/datum/gas/nitrium][MOLES] += scaled_production * 1.25 - internal_output.gases[/datum/gas/halon][MOLES] += scaled_production * 1.15 - moderator_internal.gases[/datum/gas/proto_nitrate][MOLES] -= min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.55) + var/list/new_gases = list(/datum/gas/nitrium = scaled_production * 1.25, /datum/gas/halon = scaled_production * 1.15) + internal_output.adjust_multiple_gases(new_gases) + moderator_internal.adjust_gas(/datum/gas/proto_nitrate, min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.55)) radiation *= 1.95 heat_output *= 1.25 if(moderator_list[/datum/gas/bz] > 100) - internal_output.assert_gases(/datum/gas/healium, /datum/gas/proto_nitrate) - internal_output.gases[/datum/gas/proto_nitrate][MOLES] += scaled_production * 1.5 - internal_output.gases[/datum/gas/healium][MOLES] += scaled_production * 1.5 + var/list/new_gases = list(/datum/gas/healium = scaled_production * 1.5, /datum/gas/proto_nitrate = scaled_production * 1.5) + internal_output.adjust_multiple_gases(new_gases) visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * seconds_per_tick) if(5) if(moderator_list[/datum/gas/plasma] > 15) - internal_output.assert_gases(/datum/gas/freon) - internal_output.gases[/datum/gas/freon][MOLES] += scaled_production *0.25 - moderator_internal.gases[/datum/gas/plasma][MOLES] -= min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.45) + internal_output.adjust_gas(/datum/gas/freon, scaled_production *0.25) + moderator_internal.adjust_gas(/datum/gas/plasma,min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.45)) if(moderator_list[/datum/gas/freon] > 500) heat_output *= 0.5 radiation *= 0.2 if(moderator_list[/datum/gas/proto_nitrate] > 50) - internal_output.assert_gases(/datum/gas/nitrium, /datum/gas/pluoxium) - internal_output.gases[/datum/gas/nitrium][MOLES] += scaled_production * 1.95 - internal_output.gases[/datum/gas/pluoxium][MOLES] += scaled_production - moderator_internal.gases[/datum/gas/proto_nitrate][MOLES] -= min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.35) + var/list/new_gases = list(/datum/gas/nitrium = scaled_production * 1.95, /datum/gas/pluoxium = scaled_production) + internal_output.adjust_multiple_gases(new_gases) + moderator_internal.adjust_gas(/datum/gas/proto_nitrate, min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.35)) radiation *= 1.95 heat_output *= 1.25 if(moderator_list[/datum/gas/bz] > 100) - internal_output.assert_gases(/datum/gas/healium, /datum/gas/freon) - internal_output.gases[/datum/gas/healium][MOLES] += scaled_production + var/list/new_gases = list(/datum/gas/healium = scaled_production, /datum/gas/freon = scaled_production * 1.15) + internal_output.adjust_multiple_gases(new_gases) visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * seconds_per_tick) - internal_output.gases[/datum/gas/freon][MOLES] += scaled_production * 1.15 if(moderator_list[/datum/gas/healium] > 100) if(critical_threshold_proximity > 400) critical_threshold_proximity = max(critical_threshold_proximity - (moderator_list[/datum/gas/healium] / 100 * seconds_per_tick ), 0) - moderator_internal.gases[/datum/gas/healium][MOLES] -= min(moderator_internal.gases[/datum/gas/healium][MOLES], scaled_production * 20) + moderator_internal.adjust_gas(/datum/gas/healium, -min(moderator_internal.gases[/datum/gas/healium][MOLES], scaled_production * 20)) if(moderator_internal.temperature < 1e7 || (moderator_list[/datum/gas/plasma] > 100 && moderator_list[/datum/gas/bz] > 50)) - internal_output.assert_gases(/datum/gas/antinoblium) - internal_output.gases[/datum/gas/antinoblium][MOLES] += dirty_production_rate * 0.9 / 0.065 * seconds_per_tick + internal_output.adjust_gas(/datum/gas/antinoblium, dirty_production_rate * 0.9 / 0.065 * seconds_per_tick) if(6) internal_output.assert_gases(/datum/gas/antinoblium) if(moderator_list[/datum/gas/plasma] > 30) - internal_output.assert_gases(/datum/gas/bz) - internal_output.gases[/datum/gas/bz][MOLES] += scaled_production * 1.15 - moderator_internal.gases[/datum/gas/plasma][MOLES] -= min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.45) + internal_output.adjust_gas(/datum/gas/bz, scaled_production * 1.15) + moderator_internal.adjust_gas(/datum/gas/plasma, -min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.45)) if(moderator_list[/datum/gas/proto_nitrate]) - internal_output.assert_gases(/datum/gas/zauker, /datum/gas/nitrium) - internal_output.gases[/datum/gas/zauker][MOLES] += scaled_production * 5.35 - internal_output.gases[/datum/gas/nitrium][MOLES] += scaled_production * 2.15 - moderator_internal.gases[/datum/gas/proto_nitrate][MOLES] -= min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 3.35) + var/list/new_gases = list(/datum/gas/zauker = scaled_production * 5.35, /datum/gas/nitrium = scaled_production * 2.15) + internal_output.adjust_multiple_gases(new_gases) + moderator_internal.adjust_gas(/datum/gas/proto_nitrate, -min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 3.35)) radiation *= 2 heat_output *= 2.25 if(moderator_list[/datum/gas/bz]) visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * seconds_per_tick) - internal_output.gases[/datum/gas/antinoblium][MOLES] += clamp(dirty_production_rate / 0.045, 0, 10) * seconds_per_tick + internal_output.adjust_gas(/datum/gas/antinoblium, clamp(dirty_production_rate / 0.045, 0, 10) * seconds_per_tick) if(moderator_list[/datum/gas/healium] > 100) if(critical_threshold_proximity > 400) critical_threshold_proximity = max(critical_threshold_proximity - (moderator_list[/datum/gas/healium] / 100 * seconds_per_tick ), 0) - moderator_internal.gases[/datum/gas/healium][MOLES] -= min(moderator_internal.gases[/datum/gas/healium][MOLES], scaled_production * 20) - internal_fusion.gases[/datum/gas/antinoblium][MOLES] += dirty_production_rate * 0.01 / 0.095 * seconds_per_tick + moderator_internal.adjust_gas(/datum/gas/healium, -min(moderator_internal.gases[/datum/gas/healium][MOLES], scaled_production * 20)) + internal_fusion.adjust_gas(/datum/gas/antinoblium, dirty_production_rate * 0.01 / 0.095 * seconds_per_tick) //Modifies the internal_fusion temperature with the amount of heat output var/temperature_modifier = selected_fuel.temperature_change_multiplier @@ -403,7 +392,7 @@ var/max_iron_removable = IRON_OXYGEN_HEAL_PER_SECOND var/iron_removed = min(max_iron_removable * seconds_per_tick, iron_content) iron_content -= iron_removed - moderator_internal.gases[/datum/gas/oxygen][MOLES] -= iron_removed * OXYGEN_MOLES_CONSUMED_PER_IRON_HEAL + moderator_internal.adjust_gas(/datum/gas/oxygen, -iron_removed * OXYGEN_MOLES_CONSUMED_PER_IRON_HEAL) check_gravity_pulse(seconds_per_tick) diff --git a/code/modules/atmospherics/machinery/components/tank.dm b/code/modules/atmospherics/machinery/components/tank.dm index f95e5c18da55..db654684f994 100644 --- a/code/modules/atmospherics/machinery/components/tank.dm +++ b/code/modules/atmospherics/machinery/components/tank.dm @@ -141,8 +141,8 @@ var/pressure_limit = max_pressure * safety_margin var/moles_to_add = (pressure_limit * air_contents.volume) / (R_IDEAL_GAS_EQUATION * air_contents.temperature) - air_contents.assert_gas(gastype) - air_contents.gases[gastype][MOLES] += moles_to_add + + air_contents.adjust_gas(gastype, moles_to_add) air_contents.archive() /obj/machinery/atmospherics/components/tank/process_atmos() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm index 0f7501c5bb22..117eae4e8dc5 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm @@ -198,14 +198,16 @@ var/turf/open/us = loc if(!istype(us)) return - scrub(us) + if(scrub(us)) + us.air_update_turf(FALSE, FALSE) if(widenet) if(COOLDOWN_FINISHED(src, check_turfs_cooldown)) check_turfs() COOLDOWN_START(src, check_turfs_cooldown, 2 SECONDS) for(var/turf/tile in adjacent_turfs) - scrub(tile) + if (scrub(tile)) + tile.air_update_turf(FALSE, FALSE) return TRUE ///filtered gases at or below this amount automatically get removed from the mix diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 26e75dfd5ad7..ab4e1a524a1f 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -304,9 +304,8 @@ pressure_limit = 1e14 /obj/machinery/portable_atmospherics/canister/fusion_test/create_gas() - air_contents.add_gases(/datum/gas/hydrogen, /datum/gas/tritium) - air_contents.gases[/datum/gas/hydrogen][MOLES] = 300 - air_contents.gases[/datum/gas/tritium][MOLES] = 300 + air_contents.adjust_gas(/datum/gas/hydrogen, 300) + air_contents.adjust_gas(/datum/gas/tritium, 300) air_contents.temperature = 10000 SSair.start_processing_machine(src) @@ -319,9 +318,8 @@ greyscale_colors = "#9fba6c#3d4680" /obj/machinery/portable_atmospherics/canister/anesthetic_mix/create_gas() - air_contents.add_gases(/datum/gas/oxygen, /datum/gas/nitrous_oxide) - air_contents.gases[/datum/gas/oxygen][MOLES] = (O2_ANESTHETIC * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) - air_contents.gases[/datum/gas/nitrous_oxide][MOLES] = (N2O_ANESTHETIC * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) + air_contents.adjust_gas(/datum/gas/oxygen, (O2_ANESTHETIC * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)) + air_contents.adjust_gas(/datum/gas/nitrous_oxide, (N2O_ANESTHETIC * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)) SSair.start_processing_machine(src) /** @@ -331,14 +329,12 @@ /obj/machinery/portable_atmospherics/canister/proc/create_gas() if(!gas_type) return - air_contents.add_gas(gas_type) - air_contents.gases[gas_type][MOLES] = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) + air_contents.adjust_gas(gas_type, (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)) SSair.start_processing_machine(src) /obj/machinery/portable_atmospherics/canister/air/create_gas() - air_contents.add_gases(/datum/gas/oxygen, /datum/gas/nitrogen) - air_contents.gases[/datum/gas/oxygen][MOLES] = (O2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) - air_contents.gases[/datum/gas/nitrogen][MOLES] = (N2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) + air_contents.adjust_gas(/datum/gas/oxygen, (O2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)) + air_contents.adjust_gas(/datum/gas/nitrogen, (N2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)) SSair.start_processing_machine(src) /obj/machinery/portable_atmospherics/canister/update_icon_state() diff --git a/code/modules/atmospherics/machinery/portable/scrubber.dm b/code/modules/atmospherics/machinery/portable/scrubber.dm index dc148d96e1ec..24b9bcd8dcf3 100644 --- a/code/modules/atmospherics/machinery/portable/scrubber.dm +++ b/code/modules/atmospherics/machinery/portable/scrubber.dm @@ -1,16 +1,17 @@ /obj/machinery/portable_atmospherics/scrubber name = "portable air scrubber" + desc = "A portable variant of the station scrubbers, capable of filtering gas from the air around it or inserted tank. May also be wrenched into a port." icon_state = "scrubber" density = TRUE max_integrity = 250 - volume = 1000 + volume = 2000 ///Is the machine on? var/on = FALSE ///the rate the machine will scrub air - var/volume_rate = 1000 + var/volume_rate = 650 ///Multiplier with ONE_ATMOSPHERE, if the enviroment pressure is higher than that, the scrubber won't work - var/overpressure_m = 80 + var/overpressure_m = 100 ///Should the machine use overlay in update_overlays() when open/close? var/use_overlays = TRUE ///List of gases that can be scrubbed @@ -31,9 +32,9 @@ /datum/gas/halon, ) -/obj/machinery/portable_atmospherics/scrubber/Destroy() - var/turf/T = get_turf(src) - T.assume_air(air_contents) +/obj/machinery/portable_atmospherics/scrubber/on_deconstruction(disassembled) + var/turf/local_turf = get_turf(src) + local_turf.assume_air(air_contents) return ..() /obj/machinery/portable_atmospherics/scrubber/update_icon_state() @@ -59,37 +60,56 @@ excited = TRUE - var/atom/target = holding || get_turf(src) - scrub(target.return_air()) + if(!isnull(holding)) + scrub(holding.return_air()) + return ..() + + var/turf/epicentre = get_turf(src) + if(isopenturf(epicentre)) + if(scrub(epicentre.return_air())) + epicentre.air_update_turf(FALSE, FALSE) + for(var/turf/open/openturf as anything in epicentre.get_atmos_adjacent_turfs(alldir = TRUE)) + if(scrub(openturf.return_air())) + openturf.air_update_turf(FALSE, FALSE) return ..() /** * Called in process_atmos(), handles the scrubbing of the given gas_mixture * Arguments: * * mixture: the gas mixture to be scrubbed + * Returns: TRUE if anything was scrubbed, FALSE otherwise */ -/obj/machinery/portable_atmospherics/scrubber/proc/scrub(datum/gas_mixture/mixture) +/obj/machinery/portable_atmospherics/scrubber/proc/scrub(datum/gas_mixture/environment) if(air_contents.return_pressure() >= overpressure_m * ONE_ATMOSPHERE) - return + return FALSE - var/transfer_moles = min(1, volume_rate / mixture.volume) * mixture.total_moles() + var/list/cached_moles = environment.moles - var/datum/gas_mixture/filtering = mixture.remove(transfer_moles) // Remove part of the mixture to filter. - var/datum/gas_mixture/filtered = new - if(!filtering) - return + //contains all of the gas we're sucking out of the tile, gets put into our parent pipenet + var/datum/gas_mixture/filtered_out = new - filtered.temperature = filtering.temperature - for(var/gas in filtering.gases & scrubbing) - filtered.add_gas(gas) - filtered.gases[gas][MOLES] = filtering.gases[gas][MOLES] // Shuffle the "bad" gasses to the filtered mixture. - filtering.gases[gas][MOLES] = 0 - filtering.garbage_collect() // Now that the gasses are set to 0, clean up the mixture. + filtered_out.temperature = environment.temperature - air_contents.merge(filtered) // Store filtered out gasses. - mixture.merge(filtering) // Returned the cleaned gas. - if(!holding) - air_update_turf(FALSE, FALSE) + //maximum percentage of the turfs gas we can filter + var/removal_ratio = min(1, volume_rate / environment.volume) + + var/total_moles_to_remove = 0 + for(var/gas_id in cached_moles & scrubbing) + total_moles_to_remove += cached_moles[gas_id] + + if(!total_moles_to_remove)//no gases to remove + return FALSE + + for(var/gas_id in cached_moles & scrubbing) + var/transferred_moles = max(QUANTIZE(cached_moles[gas_id] * removal_ratio * (cached_moles[gas_id] / total_moles_to_remove)), min(MOLAR_ACCURACY*1000, cached_moles[gas_id])) + + filtered_out.moles[gas_id] += transferred_moles + cached_moles[gas_id] -= transferred_moles + + environment.garbage_collect() + //Remix the resulting gases + air_contents.merge(filtered_out) + return TRUE /obj/machinery/portable_atmospherics/scrubber/emp_act(severity) . = ..() @@ -118,9 +138,13 @@ data["reactionSuppressionEnabled"] = !!suppress_reactions data["filterTypes"] = list() - for(var/path in GLOB.meta_gas_info) - var/list/gas = GLOB.meta_gas_info[path] - data["filterTypes"] += list(list("gasId" = gas[META_GAS_ID], "gasName" = gas[META_GAS_NAME], "enabled" = (path in scrubbing))) + var/cached_gas_info = GLOB.meta_gas_info + for(var/path in cached_gas_info[META_GAS_ID]) + data["filterTypes"] += list(list( + "gasId" = cached_gas_info[META_GAS_ID][path], + "gasName" = cached_gas_info[META_GAS_NAME][path], + "enabled" = (path in scrubbing) + )) if(holding) data["holding"] = list() @@ -142,7 +166,7 @@ else if(on && holding) user.investigate_log("started a transfer into [holding].", INVESTIGATE_ATMOS) -/obj/machinery/portable_atmospherics/scrubber/ui_act(action, params) +/obj/machinery/portable_atmospherics/scrubber/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() if(.) return @@ -166,7 +190,7 @@ suppress_reactions = !suppress_reactions SSair.start_processing_machine(src) message_admins("[ADMIN_LOOKUPFLW(usr)] turned [suppress_reactions ? "on" : "off"] the [src] reaction suppression.") - usr.investigate_log("turned [suppress_reactions ? "on" : "off"] the [src] reaction suppression.") + usr.investigate_log("turned [suppress_reactions ? "on" : "off"] the [src] reaction suppression.", INVESTIGATE_ATMOS) . = TRUE update_appearance() @@ -210,7 +234,8 @@ if(!holding) var/turf/T = get_turf(src) for(var/turf/AT in T.get_atmos_adjacent_turfs(alldir = TRUE)) - scrub(AT.return_air()) + if(scrub(AT.return_air())) + AT.air_update_turf(FALSE, FALSE) return ..() diff --git a/code/modules/clothing/masks/gas_filter.dm b/code/modules/clothing/masks/gas_filter.dm index 58e518a8c4da..dadb8730ca98 100644 --- a/code/modules/clothing/masks/gas_filter.dm +++ b/code/modules/clothing/masks/gas_filter.dm @@ -70,26 +70,26 @@ for(var/gas_id in breath.gases) if(gas_id in high_filtering_gases) if(breath.gases[gas_id][MOLES] > HIGH_FILTERING_MOLES) - breath.gases[gas_id][MOLES] = max(breath.gases[gas_id][MOLES] - filter_strength_high * filter_efficiency * HIGH_FILTERING_RATIO, 0) + breath.set_gas(gas_id, max(breath.gases[gas_id][MOLES] - filter_strength_high * filter_efficiency * HIGH_FILTERING_RATIO, 0)) danger_points += 1 continue - breath.gases[gas_id][MOLES] = max(breath.gases[gas_id][MOLES] - filter_strength_high * filter_efficiency * LOW_FILTERING_RATIO, 0) + breath.set_gas(gas_id, max(breath.gases[gas_id][MOLES] - filter_strength_high * filter_efficiency * LOW_FILTERING_RATIO, 0)) danger_points += 0.2 continue if(gas_id in mid_filtering_gases) if(breath.gases[gas_id][MOLES] > MID_FILTERING_MOLES) - breath.gases[gas_id][MOLES] = max(breath.gases[gas_id][MOLES] - filter_strength_mid * filter_efficiency * HIGH_FILTERING_RATIO, 0) + breath.set_gas(gas_id, max(breath.gases[gas_id][MOLES] - filter_strength_mid * filter_efficiency * HIGH_FILTERING_RATIO, 0)) danger_points += 1.25 continue - breath.gases[gas_id][MOLES] = max(breath.gases[gas_id][MOLES] - filter_strength_mid * filter_efficiency * LOW_FILTERING_RATIO, 0) + breath.set_gas(gas_id, max(breath.gases[gas_id][MOLES] - filter_strength_mid * filter_efficiency * LOW_FILTERING_RATIO, 0)) danger_points += 0.25 continue if(gas_id in low_filtering_gases) if(breath.gases[gas_id][MOLES] > LOW_FILTERING_MOLES) - breath.gases[gas_id][MOLES] = max(breath.gases[gas_id][MOLES] - filter_strength_low * filter_efficiency * HIGH_FILTERING_RATIO, 0) + breath.set_gas(gas_id, max(breath.gases[gas_id][MOLES] - filter_strength_low * filter_efficiency * HIGH_FILTERING_RATIO, 0)) danger_points += 1.5 continue - breath.gases[gas_id][MOLES] = max(breath.gases[gas_id][MOLES] - filter_strength_low * filter_efficiency * LOW_FILTERING_RATIO, 0) + breath.set_gas(gas_id, max(breath.gases[gas_id][MOLES] - filter_strength_low * filter_efficiency * LOW_FILTERING_RATIO, 0)) danger_points += 0.5 continue diff --git a/code/modules/events/space_vines/vine_mutations.dm b/code/modules/events/space_vines/vine_mutations.dm index b1466a4d0851..8ab80382650b 100644 --- a/code/modules/events/space_vines/vine_mutations.dm +++ b/code/modules/events/space_vines/vine_mutations.dm @@ -229,69 +229,58 @@ holder.light_state = PASS_LIGHT holder.alpha = 125 -/datum/spacevine_mutation/oxy_eater +/datum/spacevine_mutation/gas_eater + abstract_type = /datum/spacevine_mutation/gas_eater + /// Type of gas consumed by this mutation + var/datum/gas/gas_type = null + +/datum/spacevine_mutation/gas_eater/process_mutation(obj/structure/spacevine/holder) + if(isnull(gas_type)) + stack_trace("gas_type not set for gas_eater mutation [type]") + return + + var/turf/open/floor/turf = holder.loc + if(!istype(turf)) + return + + var/datum/gas_mixture/gas_mix = turf.air + if(!gas_mix.gases[gas_type]) + return + + gas_mix.set_gas(gas_type, max(gas_mix.gases[gas_type][MOLES] - GAS_MUTATION_REMOVAL_MULTIPLIER * holder.growth_stage, 0)) + gas_mix.garbage_collect() + +/datum/spacevine_mutation/gas_eater/oxy_eater name = "Oxygen consuming" description = "Consumes Oxygen from the surrounding area." hue = "#28B5B5" severity = SEVERITY_AVERAGE quality = NEGATIVE + gas_type = /datum/gas/oxygen -/datum/spacevine_mutation/oxy_eater/process_mutation(obj/structure/spacevine/holder) - var/turf/open/floor/turf = holder.loc - if(istype(turf)) - var/datum/gas_mixture/gas_mix = turf.air - if(!gas_mix.gases[/datum/gas/oxygen]) - return - gas_mix.gases[/datum/gas/oxygen][MOLES] = max(gas_mix.gases[/datum/gas/oxygen][MOLES] - GAS_MUTATION_REMOVAL_MULTIPLIER * holder.growth_stage, 0) - gas_mix.garbage_collect() - -/datum/spacevine_mutation/nitro_eater +/datum/spacevine_mutation/gas_eater/nitro_eater name = "Nitrogen consuming" description = "Consumes Nitrogen from the surrounding area." hue = "#FF7B54" severity = SEVERITY_AVERAGE quality = NEGATIVE + gas_type = /datum/gas/nitrogen -/datum/spacevine_mutation/nitro_eater/process_mutation(obj/structure/spacevine/holder) - var/turf/open/floor/turf = holder.loc - if(istype(turf)) - var/datum/gas_mixture/gas_mix = turf.air - if(!gas_mix.gases[/datum/gas/nitrogen]) - return - gas_mix.gases[/datum/gas/nitrogen][MOLES] = max(gas_mix.gases[/datum/gas/nitrogen][MOLES] - GAS_MUTATION_REMOVAL_MULTIPLIER * holder.growth_stage, 0) - gas_mix.garbage_collect() - -/datum/spacevine_mutation/carbondioxide_eater +/datum/spacevine_mutation/gas_eater/carbondioxide_eater name = "CO2 consuming" description = "Consumes Carbon Dioxide from the surrounding area." hue = "#798777" severity = SEVERITY_MINOR quality = POSITIVE + gas_type = /datum/gas/carbon_dioxide -/datum/spacevine_mutation/carbondioxide_eater/process_mutation(obj/structure/spacevine/holder) - var/turf/open/floor/turf = holder.loc - if(istype(turf)) - var/datum/gas_mixture/gas_mix = turf.air - if(!gas_mix.gases[/datum/gas/carbon_dioxide]) - return - gas_mix.gases[/datum/gas/carbon_dioxide][MOLES] = max(gas_mix.gases[/datum/gas/carbon_dioxide][MOLES] - GAS_MUTATION_REMOVAL_MULTIPLIER * holder.growth_stage, 0) - gas_mix.garbage_collect() - -/datum/spacevine_mutation/plasma_eater +/datum/spacevine_mutation/gas_eater/plasma_eater name = "Plasma consuming" description = "Consumes Plasma from the surrounding area." hue = "#9074b6" severity = SEVERITY_AVERAGE quality = POSITIVE - -/datum/spacevine_mutation/plasma_eater/process_mutation(obj/structure/spacevine/holder) - var/turf/open/floor/turf = holder.loc - if(istype(turf)) - var/datum/gas_mixture/gas_mix = turf.air - if(!gas_mix.gases[/datum/gas/plasma]) - return - gas_mix.gases[/datum/gas/plasma][MOLES] = max(gas_mix.gases[/datum/gas/plasma][MOLES] - GAS_MUTATION_REMOVAL_MULTIPLIER * holder.growth_stage, 0) - gas_mix.garbage_collect() + gas_type = /datum/gas/plasma /datum/spacevine_mutation/thorns name = "Thorny" diff --git a/code/modules/fishing/fish/fish_traits.dm b/code/modules/fishing/fish/fish_traits.dm index 14ed12becf46..570aa5749988 100644 --- a/code/modules/fishing/fish/fish_traits.dm +++ b/code/modules/fishing/fish/fish_traits.dm @@ -293,8 +293,8 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) return var/datum/gas_mixture/stench = new - ADD_GAS(/datum/gas/miasma, stench.gases) - stench.gases[/datum/gas/miasma][MOLES] = MIASMA_CORPSE_MOLES * 2 * seconds_per_tick + + stench.set_gas(/datum/gas/miasma, MIASMA_CORPSE_MOLES * 2 * seconds_per_tick) stench.temperature = mob.body_temperature our_turf.assume_air(stench) diff --git a/code/modules/hydroponics/unique_plant_genes.dm b/code/modules/hydroponics/unique_plant_genes.dm index 93b97aa60c69..7bfa8c46d58e 100644 --- a/code/modules/hydroponics/unique_plant_genes.dm +++ b/code/modules/hydroponics/unique_plant_genes.dm @@ -675,8 +675,8 @@ return var/datum/gas_mixture/stank = new - ADD_GAS(/datum/gas/miasma, stank.gases) - stank.gases[/datum/gas/miasma][MOLES] = (seed.yield + 6) * 3.5 * MIASMA_CORPSE_MOLES * seconds_per_tick // this process is only being called about 2/7 as much as corpses so this is 12-32 times a corpses + + stank.set_gas(/datum/gas/miasma, (seed.yield + 6) * 3.5 * MIASMA_CORPSE_MOLES * seconds_per_tick) // this process is only being called about 2/7 as much as corpses so this is 12-32 times a corpses stank.temperature = T20C // without this the room would eventually freeze and miasma mining would be easier tray_turf.assume_air(stank) diff --git a/code/modules/mob/living/basic/tree.dm b/code/modules/mob/living/basic/tree.dm index 4fd30a983b2b..295c369627ee 100644 --- a/code/modules/mob/living/basic/tree.dm +++ b/code/modules/mob/living/basic/tree.dm @@ -68,11 +68,11 @@ var/turf/open/our_turf = src.loc if(!our_turf.air || !our_turf.air.gases[/datum/gas/carbon_dioxide]) return - - var/co2 = our_turf.air.gases[/datum/gas/carbon_dioxide][MOLES] + var/datum/gas_mixture/our_air = our_turf.air + var/co2 = our_air.gases[/datum/gas/carbon_dioxide][MOLES] if(co2 > 0 && SPT_PROB(13, seconds_per_tick)) var/amt = min(co2, 9) - our_turf.air.gases[/datum/gas/carbon_dioxide][MOLES] -= amt + our_air.adjust_gas(/datum/gas/carbon_dioxide, -amt) our_turf.atmos_spawn_air("[GAS_O2]=[amt]") /mob/living/basic/tree/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE) diff --git a/code/modules/mod/modules/modules_timeline.dm b/code/modules/mod/modules/modules_timeline.dm index d2a2e86d2744..86c7eee70682 100644 --- a/code/modules/mod/modules/modules_timeline.dm +++ b/code/modules/mod/modules/modules_timeline.dm @@ -415,9 +415,8 @@ /obj/structure/chrono_field/return_air() //we always have nominal air and temperature var/datum/gas_mixture/fresh_air = new - fresh_air.add_gases(/datum/gas/oxygen, /datum/gas/nitrogen) - fresh_air.gases[/datum/gas/oxygen][MOLES] = MOLES_O2STANDARD - fresh_air.gases[/datum/gas/nitrogen][MOLES] = MOLES_N2STANDARD + fresh_air.set_gas(/datum/gas/oxygen, MOLES_O2STANDARD) + fresh_air.set_gas(/datum/gas/nitrogen, MOLES_N2STANDARD) fresh_air.temperature = T20C return fresh_air diff --git a/code/modules/power/supermatter/supermatter_gas.dm b/code/modules/power/supermatter/supermatter_gas.dm index 35b9db0731f9..4848bda3e08e 100644 --- a/code/modules/power/supermatter/supermatter_gas.dm +++ b/code/modules/power/supermatter/supermatter_gas.dm @@ -113,10 +113,10 @@ GLOBAL_LIST_INIT(sm_gas_behavior, init_sm_gas()) ) if(!consumed_co2) return - sm.absorbed_gasmix.gases[/datum/gas/carbon_dioxide][MOLES] -= consumed_co2 - sm.absorbed_gasmix.gases[/datum/gas/oxygen][MOLES] -= consumed_co2 - ASSERT_GAS(/datum/gas/pluoxium, sm.absorbed_gasmix) - sm.absorbed_gasmix.gases[/datum/gas/pluoxium][MOLES] += consumed_co2 + sm.absorbed_gasmix.adjust_gas(/datum/gas/carbon_dioxide, -consumed_co2) + sm.absorbed_gasmix.adjust_gas(/datum/gas/oxygen, -consumed_co2) + + sm.absorbed_gasmix.adjust_gas(/datum/gas/pluoxium, consumed_co2) /datum/sm_gas/plasma gas_path = /datum/gas/plasma @@ -178,7 +178,7 @@ GLOBAL_LIST_INIT(sm_gas_behavior, init_sm_gas()) var/consumed_miasma = sm.absorbed_gasmix.gases[/datum/gas/miasma][MOLES] * miasma_ratio if(!consumed_miasma) return - sm.absorbed_gasmix.gases[/datum/gas/miasma][MOLES] -= consumed_miasma + sm.absorbed_gasmix.adjust_gas(/datum/gas/miasma, -consumed_miasma) sm.external_power_trickle += consumed_miasma * MIASMA_POWER_GAIN sm.log_activation("miasma absorption") diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm index 8cb1efebc3f1..da37d5b9cdab 100644 --- a/code/modules/surgery/organs/internal/lungs/_lungs.dm +++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm @@ -1151,10 +1151,9 @@ /// H2O electrolysis /obj/item/organ/lungs/ethereal/proc/consume_water(mob/living/carbon/breather, datum/gas_mixture/breath, h2o_pp, old_h2o_pp) var/gas_breathed = breath.gases[/datum/gas/water_vapor][MOLES] - breath.gases[/datum/gas/water_vapor][MOLES] -= gas_breathed - breath_out.assert_gases(/datum/gas/oxygen, /datum/gas/hydrogen) - breath_out.gases[/datum/gas/oxygen][MOLES] += gas_breathed - breath_out.gases[/datum/gas/hydrogen][MOLES] += gas_breathed * 2 + breath.adjust_gas(/datum/gas/water_vapor, -gas_breathed) + var/list/new_gases = list(/datum/gas/oxygen = gas_breathed, /datum/gas/hydrogen = gas_breathed * 2) + breath_out.adjust_multiple_gases(new_gases) #undef BREATH_RELATIONSHIP_INITIAL_GAS diff --git a/code/modules/vehicles/mecha/equipment/tools/air_tank.dm b/code/modules/vehicles/mecha/equipment/tools/air_tank.dm index f00444ae598b..70926b12e304 100644 --- a/code/modules/vehicles/mecha/equipment/tools/air_tank.dm +++ b/code/modules/vehicles/mecha/equipment/tools/air_tank.dm @@ -31,9 +31,8 @@ internal_tank.air_contents.volume = volume internal_tank.maximum_pressure = maximum_pressure if(start_full) - internal_tank.air_contents.temperature = T20C - internal_tank.air_contents.add_gases(/datum/gas/oxygen) - internal_tank.air_contents.gases[/datum/gas/oxygen][MOLES] = maximum_pressure * volume / (R_IDEAL_GAS_EQUATION * internal_tank.air_contents.temperature) + internal_tank.air_contents.set_temperature(T20C) + internal_tank.air_contents.set_gas(/datum/gas/oxygen, maximum_pressure * volume / (R_IDEAL_GAS_EQUATION * internal_tank.air_contents.temperature)) /obj/item/mecha_parts/mecha_equipment/air_tank/Destroy() if(chassis) From ecb63890ae89a5f9e8c099e5bafc7c0f5dbeba68 Mon Sep 17 00:00:00 2001 From: antropod Date: Wed, 15 Jul 2026 09:34:31 +0600 Subject: [PATCH 08/15] Atmos refactor & speedup by utilizing BYOND 516 vector functions (#96448) --- code/__DEFINES/atmospherics/atmos_core.dm | 13 +- code/__DEFINES/atmospherics/atmos_helpers.dm | 37 +- code/__HELPERS/atmospherics.dm | 56 +-- code/__HELPERS/logging/atmos.dm | 13 +- code/controllers/subsystem/air.dm | 10 +- code/controllers/subsystem/mapping.dm | 5 +- code/datums/atmosphere/_atmosphere.dm | 20 +- code/datums/diseases/advance/symptoms/heal.dm | 10 +- code/datums/elements/atmos_requirements.dm | 11 +- code/datums/helper_datums/teleport.dm | 3 +- code/datums/mutations/olfaction.dm | 3 +- .../status_effects/debuffs/fire_stacks.dm | 2 +- code/datums/wounds/bones.dm | 65 ++- code/game/area/areas/mining.dm | 11 +- .../fluid_spread/effects_foam.dm | 21 +- .../fluid_spread/effects_smoke.dm | 4 +- .../items/devices/scanners/gas_analyzer.dm | 8 +- code/game/objects/items/flamethrower.dm | 4 +- code/game/objects/items/tanks/tanks.dm | 2 +- code/game/objects/structures/bonfire.dm | 6 +- code/game/turfs/change_turf.dm | 12 +- code/modules/atmospherics/Atmospherics.md | 32 +- .../atmospherics/environmental/LINDA_fire.dm | 32 +- .../environmental/LINDA_turf_tile.dm | 39 +- .../atmospherics/gasmixtures/gas_mixture.dm | 412 ++++++++++-------- .../atmospherics/gasmixtures/gas_types.dm | 42 +- .../gasmixtures/immutable_mixtures.dm | 30 +- .../atmospherics/gasmixtures/reactions.dm | 392 +++++++++-------- .../machinery/air_alarm/_air_alarm.dm | 27 +- .../machinery/air_alarm/air_alarm_circuit.dm | 12 +- .../electrolyzer/electrolyzer_reactions.dm | 6 +- .../components/fusion/hfr_main_processes.dm | 43 +- .../machinery/components/fusion/hfr_parts.dm | 22 +- .../machinery/components/fusion/hfr_procs.dm | 6 +- .../gas_recipe_machines/crystallizer.dm | 28 +- .../components/trinary_devices/filter.dm | 10 +- .../components/unary_devices/cryo.dm | 2 +- .../components/unary_devices/vent_scrubber.dm | 28 +- .../atmospherics/machinery/datum_pipeline.dm | 25 +- .../atmospherics/machinery/other/miner.dm | 2 +- .../machinery/portable/canister.dm | 20 +- .../atmospherics/machinery/portable/pump.dm | 10 +- code/modules/cargo/bounties/atmos.dm | 9 +- code/modules/cargo/exports/large_objects.dm | 55 +-- code/modules/cargo/packs/materials.dm | 2 +- code/modules/clothing/masks/gas_filter.dm | 20 +- .../events/space_vines/vine_mutations.dm | 4 +- code/modules/fishing/fish/_fish.dm | 2 +- .../basic/guardian/guardian_types/gaseous.dm | 2 +- .../basic/space_fauna/regal_rat/regal_rat.dm | 4 +- code/modules/mob/living/basic/tree.dm | 4 +- code/modules/mob/living/carbon/alien/life.dm | 14 +- .../mob/living/simple_animal/slime/life.dm | 4 +- code/modules/mob/mob.dm | 8 +- code/modules/mod/modules/modules_maint.dm | 2 +- code/modules/power/supermatter/supermatter.dm | 9 +- .../power/supermatter/supermatter_gas.dm | 8 +- .../research/ordnance/tank_compressor.dm | 4 +- .../xenobiology/crossbreeding/chilling.dm | 2 +- .../surgery/organs/internal/lungs/_lungs.dm | 66 ++- code/modules/unit_tests/breath.dm | 4 +- code/modules/unit_tests/gas_transfer.dm | 7 +- code/modules/unit_tests/lungs.dm | 4 +- .../code/game/machinery/cloning_pod.dm | 4 +- 64 files changed, 926 insertions(+), 848 deletions(-) diff --git a/code/__DEFINES/atmospherics/atmos_core.dm b/code/__DEFINES/atmospherics/atmos_core.dm index 0f441bdf2089..021dfe2ea4aa 100644 --- a/code/__DEFINES/atmospherics/atmos_core.dm +++ b/code/__DEFINES/atmospherics/atmos_core.dm @@ -1,11 +1,4 @@ //LISTMOS -//indices of values in gas lists. -///Amount of total moles in said gas mixture -#define MOLES 1 -///Archived version of MOLES -#define ARCHIVE 2 -///All gas related variables -#define GAS_META 3 ///Gas specific heat per mole #define META_GAS_SPECIFIC_HEAT 1 ///Name of the gas @@ -22,6 +15,8 @@ #define META_GAS_FUSION_POWER 7 ///Short description of the gas. #define META_GAS_DESC 8 +///Length of gas meta array +#define META_GAS_LENGTH 8 //ATMOS //stuff you should probably leave well alone! /// kPa*L/(K*mol) @@ -56,7 +51,7 @@ /// Molar accuracy to round to #define MOLAR_ACCURACY 1E-4 /// Types of gases (based on gaslist_cache) -#define GAS_TYPE_COUNT GLOB.gaslist_cache.len +#define GAS_TYPE_COUNT 20 /// Maximum error caused by QUANTIZE when removing gas (roughly, in reality around 2 * MOLAR_ACCURACY less) #define MAXIMUM_ERROR_GAS_REMOVAL (MOLAR_ACCURACY * GAS_TYPE_COUNT) @@ -182,3 +177,5 @@ #define ATMOS_PRESSURE_APPROXIMATION_ITERATIONS 20 /// We deal with big numbers and a lot of math, things are bound to get imprecise. Take this traveller. #define ATMOS_PRESSURE_ERROR_TOLERANCE 0.01 +/// Helper function for retrieving gas meta info for use in performace critical places +#define GAS_META /datum/gas_mixture::gas_meta diff --git a/code/__DEFINES/atmospherics/atmos_helpers.dm b/code/__DEFINES/atmospherics/atmos_helpers.dm index b004a6e5e301..d91115102a9d 100644 --- a/code/__DEFINES/atmospherics/atmos_helpers.dm +++ b/code/__DEFINES/atmospherics/atmos_helpers.dm @@ -53,41 +53,24 @@ ///Calculate the thermal energy of the selected gas (J) #define THERMAL_ENERGY(gas) (gas.temperature * gas.heat_capacity()) -///Directly adds a gas to a gas mixture without checking for its presence beforehand, use only if is certain the absence of said gas -#define ADD_GAS(gas_id, out_list)\ - var/list/tmp_gaslist = GLOB.gaslist_cache[gas_id]; out_list[gas_id] = tmp_gaslist.Copy(); - -///Adds a gas to a gas mixture but checks if is already present, faster than the same proc -#define ASSERT_GAS(gas_id, gas_mixture) ASSERT_GAS_IN_LIST(gas_id, gas_mixture.gases) - -///Adds a gas to a gas LIST but checks if is already present, accepts a list instead of a datum, so faster if the list is locally cached -#define ASSERT_GAS_IN_LIST(gas_id, gases) if (!gases[gas_id]) { ADD_GAS(gas_id, gases) }; - -//prefer this to gas_mixture/total_moles in performance critical areas -///Calculate the total moles of the gas mixture, faster than the proc, good for performance critical areas -#define TOTAL_MOLES(cached_gases, out_var)\ - out_var = 0;\ - for(var/total_moles_id in cached_gases){\ - out_var += cached_gases[total_moles_id][MOLES];\ - } - GLOBAL_LIST_INIT(nonoverlaying_gases, typecache_of_gases_with_no_overlays()) ///Returns a list of overlays of every gas in the mixture -#define GAS_OVERLAYS(gases, out_var, z_layer_turf)\ +#define GAS_OVERLAYS(moles, out_var, z_layer_turf)\ do { \ out_var = list();\ var/offset = GET_TURF_PLANE_OFFSET(z_layer_turf) + 1;\ - for(var/_ID in gases){\ - if(GLOB.nonoverlaying_gases[_ID]) continue;\ - var/_GAS = gases[_ID];\ - var/_GAS_META = _GAS[GAS_META];\ - if(_GAS[MOLES] <= _GAS_META[META_GAS_MOLES_VISIBLE]) continue;\ - var/_GAS_OVERLAY = _GAS_META[META_GAS_OVERLAY][offset];\ - out_var += _GAS_OVERLAY[min(TOTAL_VISIBLE_STATES, CEILING(_GAS[MOLES] / MOLES_GAS_VISIBLE_STEP, 1))];\ - } \ + var/list/_META_MOLES_VISIBLE = GAS_META[META_GAS_MOLES_VISIBLE];\ + var/list/_META_GAS_OVERLAY = GAS_META[META_GAS_OVERLAY];\ + for(var/gas_id, amount in moles){\ + if(GLOB.nonoverlaying_gases[gas_id]) continue;\ + if(amount <= _META_MOLES_VISIBLE[gas_id]) continue;\ + var/_GAS_OVERLAY = _META_GAS_OVERLAY[gas_id][offset];\ + out_var += _GAS_OVERLAY[min(TOTAL_VISIBLE_STATES, CEILING(amount / MOLES_GAS_VISIBLE_STEP, 1))];\ + }\ }\ while (FALSE) + #ifdef TESTING GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0)) #define CALCULATE_ADJACENT_TURFS(T, state) if (SSair.adjacent_rebuild[T]) { GLOB.atmos_adjacent_savings[1] += 1 } else { GLOB.atmos_adjacent_savings[2] += 1; SSair.adjacent_rebuild[T] = state} diff --git a/code/__HELPERS/atmospherics.dm b/code/__HELPERS/atmospherics.dm index 2a59cf60b403..bcbdf5e7dc8c 100644 --- a/code/__HELPERS/atmospherics.dm +++ b/code/__HELPERS/atmospherics.dm @@ -41,11 +41,13 @@ ) if(!gasmix) return - for(var/gas_path in gasmix.gases) + var/list/cached_gas_id = GAS_META[META_GAS_ID] + var/list/cached_gas_name = GAS_META[META_GAS_NAME] + for(var/gas_path, amount in gasmix.moles) .["gases"] += list(list( - gasmix.gases[gas_path][GAS_META][META_GAS_ID], - gasmix.gases[gas_path][GAS_META][META_GAS_NAME], - gasmix.gases[gas_path][MOLES], + cached_gas_id[gas_path], + cached_gas_name[gas_path], + amount, )) for(var/datum/gas_reaction/reaction_result as anything in gasmix.reaction_results) .["reactions"] += list(list( @@ -75,13 +77,13 @@ GLOBAL_LIST_EMPTY(gas_handbook) for (var/datum/gas/gas_path as anything in subtypesof(/datum/gas)) var/list/gas_info = list() - var/list/meta_information = GLOB.meta_gas_info[gas_path] - if(!meta_information) + var/list/meta_information = GLOB.meta_gas_info + if(!meta_information[META_GAS_ID]) continue - gas_info["id"] = meta_information[META_GAS_ID] - gas_info["name"] = meta_information[META_GAS_NAME] - gas_info["description"] = meta_information[META_GAS_DESC] - gas_info["specific_heat"] = meta_information[META_GAS_SPECIFIC_HEAT] + gas_info["id"] = meta_information[META_GAS_ID][gas_path] + gas_info["name"] = meta_information[META_GAS_NAME][gas_path] + gas_info["description"] = meta_information[META_GAS_DESC][gas_path] + gas_info["specific_heat"] = meta_information[META_GAS_SPECIFIC_HEAT][gas_path] gas_info["reactions"] = list() momentary_gas_list[gas_path] = gas_info @@ -172,37 +174,9 @@ GLOBAL_LIST_EMPTY(gas_handbook) return null -/** - * A simple helped proc that checks if the contents of a list of gases are within acceptable terms. - * - * Arguments: - * * gases: The list of gases which contents are being checked - * * gases to check: An associated list of gas types and acceptable boundaries in moles. e.g. /datum/gas/oxygen = list(16, 30) - * * * if the assoc list is null, then it'll be considered a safe gas and won't return FALSE. - * * extraneous_gas_limit: If a gas not in gases is found, this is the limit above which the proc will return FALSE. - */ -/proc/check_gases(list/gases, list/gases_to_check, extraneous_gas_limit = 0.1) - gases_to_check = gases_to_check.Copy() - for(var/id in gases) - var/gas_moles = gases[id][MOLES] - if(!(id in gases_to_check)) - if(gas_moles > extraneous_gas_limit) - return FALSE - continue - var/list/boundaries = gases_to_check[id] - if(boundaries && !ISINRANGE(gas_moles, boundaries[1], boundaries[2])) - return FALSE - gases_to_check -= id - ///Check that gases absent from the turf have a lower boundary of zero or none at all, otherwise return FALSE - for(var/id in gases_to_check) - var/list/boundaries = gases_to_check[id] - if(boundaries && boundaries[1] > 0) - return FALSE - return TRUE - /proc/print_gas_mixture(datum/gas_mixture/gas_mixture) var/message = "TEMPERATURE: [gas_mixture.temperature]K, QUANTITY: [gas_mixture.total_moles()] mols, VOLUME: [gas_mixture.volume]L; " - for(var/key in gas_mixture.gases) - var/list/gaslist = gas_mixture.gases[key] - message += "[gaslist[GAS_META][META_GAS_ID]]=[gaslist[MOLES]] mols;" + var/list/cached_gas_id = GAS_META[META_GAS_ID] + for(var/gas_id, amount in gas_mixture.moles) + message += "[cached_gas_id[gas_id]]=[amount] mols;" return message diff --git a/code/__HELPERS/logging/atmos.dm b/code/__HELPERS/logging/atmos.dm index 644c9e656257..9e2a76f648a7 100644 --- a/code/__HELPERS/logging/atmos.dm +++ b/code/__HELPERS/logging/atmos.dm @@ -2,26 +2,27 @@ /proc/log_atmos(text, datum/gas_mixture/gas_mixture) var/message = "[text]\"[print_gas_mixture(gas_mixture)]\"" //Cache commonly accessed information. - var/list/gases = gas_mixture.gases //List of gas datum paths that are associated with a list of information related to the gases. + var/list/cached_moles = gas_mixture.moles var/heat_capacity = gas_mixture.heat_capacity() var/temperature = gas_mixture.return_temperature() var/thermal_energy = temperature * heat_capacity var/volume = gas_mixture.return_volume() var/pressure = gas_mixture.return_pressure() var/total_moles = gas_mixture.total_moles() + var/list/cached_specific_heat = GAS_META[META_GAS_SPECIFIC_HEAT] + var/list/cached_gas_name = GAS_META[META_GAS_NAME] ///The total value of the gas mixture in credits. var/total_value = 0 var/list/specific_gas_data = list() //Gas specific information assigned to each gas. - for(var/datum/gas/gas_path as anything in gases) - var/list/gas = gases[gas_path] - var/moles = gas[MOLES] + for(var/datum/gas/gas_path as anything in cached_moles) + var/moles = cached_moles[gas_path] var/composition = moles / total_moles - var/energy = temperature * moles * gas[GAS_META][META_GAS_SPECIFIC_HEAT] + var/energy = temperature * moles * cached_specific_heat[gas_path] var/value = initial(gas_path.base_value) * moles total_value += value - specific_gas_data[gas[GAS_META][META_GAS_NAME]] = list( + specific_gas_data[cached_gas_name[gas_path]] = list( "moles" = moles, "composition" = composition, "molar concentration" = moles / volume, diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index 2dc59f0c71ea..57b8737aeb82 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -563,9 +563,8 @@ SUBSYSTEM_DEF(air) // If it's already been processed, then it's already talked to us if(enemy_tile.current_cycle == -INFINITE) continue - // .air instead of .return_air() because we can guarentee that the proc won't do anything - if(potential_diff.air.compare(enemy_tile.air)) - //testing("Active turf found. Return value of compare(): [T.air.compare(enemy_tile.air)]") + // .air instead of .return_air() because we can guarantee that the proc won't do anything + if(potential_diff.air.compare(enemy_tile.air, FALSE)) if(!potential_diff.excited) potential_diff.excited = TRUE SSair.active_turfs += potential_diff @@ -785,7 +784,6 @@ GLOBAL_LIST_EMPTY(colored_images) strings_to_mix["[gas_string]-[gastype]"] = canonical_mix gas_string = preprocess_gas_string(gas_string) - var/list/gases = canonical_mix.gases var/list/gas = params2list(gas_string) if(gas["TEMP"]) canonical_mix.temperature = text2num(gas["TEMP"]) @@ -793,12 +791,12 @@ GLOBAL_LIST_EMPTY(colored_images) gas -= "TEMP" else // if we do not have a temp in the new gas mix lets assume room temp. canonical_mix.temperature = T20C + var/list/cached_moles = canonical_mix.moles for(var/id in gas) var/path = id if(!ispath(path)) path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around - ADD_GAS(path, gases) - gases[path][MOLES] = text2num(gas[id]) + cached_moles[path] = text2num(gas[id]) if(istype(canonical_mix, /datum/gas_mixture/immutable)) return canonical_mix diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index 66954fad4cc1..249faead6987 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -894,10 +894,9 @@ ADMIN_VERB(load_away_mission, R_FUN, "Load Away Mission", "Load a specific away GLOB.starlight_objects += starlight_object(offset) GLOB.starlight_overlays += starlight_overlay(offset) - for(var/datum/gas/gas_type as anything in GLOB.meta_gas_info) - var/list/gas_info = GLOB.meta_gas_info[gas_type] + for(var/datum/gas/gas_type as anything in GLOB.meta_gas_info[META_GAS_ID]) if(initial(gas_type.moles_visible) != null) - gas_info[META_GAS_OVERLAY] += generate_gas_overlays(gen_from, new_offset, gas_type) + GLOB.meta_gas_info[META_GAS_OVERLAY][gas_type] += generate_gas_overlays(gen_from, new_offset, gas_type) /datum/controller/subsystem/mapping/proc/create_plane_offsets(gen_from, new_offset) for(var/plane_offset in gen_from to new_offset) diff --git a/code/datums/atmosphere/_atmosphere.dm b/code/datums/atmosphere/_atmosphere.dm index 702a42e7ab07..37bdb954fa13 100644 --- a/code/datums/atmosphere/_atmosphere.dm +++ b/code/datums/atmosphere/_atmosphere.dm @@ -27,11 +27,10 @@ // First let's set up the gasmix and base gases for this template // We make the string from a gasmix in this proc because gases need to calculate their pressure var/datum/gas_mixture/gasmix = new - var/list/gaslist = gasmix.gases gasmix.temperature = rand(minimum_temp, maximum_temp) + var/list/cached_moles = gasmix.moles for(var/i in base_gases) - ADD_GAS(i, gaslist) - gaslist[i][MOLES] = base_gases[i] + cached_moles[i] = base_gases[i] // Now let the random choices begin var/datum/gas/gastype @@ -49,22 +48,21 @@ amount *= pressure_scalar // If we pick a really small target pressure we want roughly the same mix but less of it all amount = CEILING(amount, 0.1) - ASSERT_GAS_IN_LIST(gastype, gaslist) - gaslist[gastype][MOLES] += amount + cached_moles[gastype] += amount // Ensure that minimum_pressure is actually a hard lower limit - target_pressure = clamp(target_pressure, minimum_pressure + (gaslist[gastype][MOLES] * 0.1), maximum_pressure) + target_pressure = clamp(target_pressure, minimum_pressure + (cached_moles[gastype] * 0.1), maximum_pressure) // That last one put us over the limit, remove some of it while(gasmix.return_pressure() > target_pressure) - gaslist[gastype][MOLES] -= gaslist[gastype][MOLES] * 0.1 - gaslist[gastype][MOLES] = FLOOR(gaslist[gastype][MOLES], 0.1) + cached_moles[gastype] -= cached_moles[gastype] * 0.1 + cached_moles[gastype] = FLOOR(cached_moles[gastype], 0.1) gasmix.garbage_collect() // Now finally lets make that string var/list/gas_string_builder = list() - for(var/i in gaslist) - var/list/gas = gaslist[i] - gas_string_builder += "[gas[GAS_META][META_GAS_ID]]=[gas[MOLES]]" + var/list/cached_gas_id = GAS_META[META_GAS_ID] + for(var/gas_id, gas_amount in cached_moles) + gas_string_builder += "[cached_gas_id[gas_id]]=[gas_amount]" gas_string_builder += "TEMP=[gasmix.temperature]" gas_string = gas_string_builder.Join(";") diff --git a/code/datums/diseases/advance/symptoms/heal.dm b/code/datums/diseases/advance/symptoms/heal.dm index 9f273353461d..62908afd6ce8 100644 --- a/code/datums/diseases/advance/symptoms/heal.dm +++ b/code/datums/diseases/advance/symptoms/heal.dm @@ -542,8 +542,6 @@ /datum/symptom/heal/plasma/CanHeal(datum/disease/advance/advanced_disease) var/mob/living/carbon/infected_mob = advanced_disease.affected_mob var/datum/gas_mixture/environment - var/list/gases - . = 0 // Check internals @@ -554,16 +552,14 @@ if(internals_tank) var/datum/gas_mixture/tank_contents = internals_tank.return_air() if(tank_contents && round(tank_contents.return_pressure())) // make sure the tank is not empty or 0 pressure - if(tank_contents.gases[/datum/gas/plasma]) + if(tank_contents.moles[/datum/gas/plasma]) // higher tank distribution pressure leads to more healing, but once you get to about 15kpa you reach the max . += power * min(MAX_HEAL_COEFFICIENT_INTERNALS, internals_tank.distribute_pressure * HEALING_PER_BREATH_PRESSURE) else // Check environment if(infected_mob.loc) environment = infected_mob.loc.return_air() - if(environment) - gases = environment.gases - if(gases[/datum/gas/plasma]) - . += power * min(MAX_HEAL_COEFFICIENT_INTERNALS, gases[/datum/gas/plasma][MOLES] * HEALING_PER_MOL) + if(environment && environment.moles[/datum/gas/plasma]) + . += power * min(MAX_HEAL_COEFFICIENT_INTERNALS, environment.moles[/datum/gas/plasma] * HEALING_PER_MOL) // Check for reagents in bloodstream if(infected_mob.reagents.has_reagent(/datum/reagent/toxin/plasma, needs_metabolizing = TRUE)) diff --git a/code/datums/elements/atmos_requirements.dm b/code/datums/elements/atmos_requirements.dm index 1ac10f020274..6e11ac5004cf 100644 --- a/code/datums/elements/atmos_requirements.dm +++ b/code/datums/elements/atmos_requirements.dm @@ -45,13 +45,14 @@ if(!open_turf.air && (atmos_requirements["min_oxy"] || atmos_requirements["min_tox"] || atmos_requirements["min_n2"] || atmos_requirements["min_co2"])) return FALSE - var/open_turf_gases = open_turf.air.gases + var/open_turf_moles = open_turf.air.moles open_turf.air.assert_gases(/datum/gas/oxygen, /datum/gas/pluoxium, /datum/gas/nitrogen, /datum/gas/carbon_dioxide, /datum/gas/plasma) - var/plas = open_turf_gases[/datum/gas/plasma][MOLES] - var/oxy = open_turf_gases[/datum/gas/oxygen][MOLES] + (open_turf_gases[/datum/gas/pluoxium][MOLES] * PLUOXIUM_PROPORTION) - var/n2 = open_turf_gases[/datum/gas/nitrogen][MOLES] - var/co2 = open_turf_gases[/datum/gas/carbon_dioxide][MOLES] + var/list/return_gases = list() + var/plas = open_turf_moles[/datum/gas/plasma] + var/oxy = open_turf_moles[/datum/gas/oxygen] + (open_turf_moles[/datum/gas/pluoxium] * PLUOXIUM_PROPORTION) + var/n2 = open_turf_moles[/datum/gas/nitrogen] + var/co2 = open_turf_moles[/datum/gas/carbon_dioxide] open_turf.air.garbage_collect() diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index bee48a2bb493..b2fe45e94e3e 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -132,13 +132,12 @@ if(!floor_gas_mixture) return - var/list/floor_gases = floor_gas_mixture.gases var/static/list/gases_to_check = list( /datum/gas/oxygen = list(16, 100), /datum/gas/nitrogen, /datum/gas/carbon_dioxide = list(0, 10) ) - if(!check_gases(floor_gases, gases_to_check)) + if(!floor_gas_mixture.check_gases(gases_to_check)) return FALSE // Aim for goldilocks temperatures and pressure diff --git a/code/datums/mutations/olfaction.dm b/code/datums/mutations/olfaction.dm index fb7c9a92e900..2fd8658d52c9 100644 --- a/code/datums/mutations/olfaction.dm +++ b/code/datums/mutations/olfaction.dm @@ -50,9 +50,8 @@ . = ..() // Can we sniff? is there miasma in the air? var/datum/gas_mixture/air = cast_on.loc.return_air() - var/list/cached_gases = air.gases - if(cached_gases[/datum/gas/miasma]) + if(air.moles[/datum/gas/miasma]) cast_on.adjust_disgust(sensitivity * 45) to_chat(cast_on, span_warning("With your overly sensitive nose, \ you get a whiff of stench and feel sick! Try moving to a cleaner area!")) diff --git a/code/datums/status_effects/debuffs/fire_stacks.dm b/code/datums/status_effects/debuffs/fire_stacks.dm index eb0d48faacfd..45df61c0fd1e 100644 --- a/code/datums/status_effects/debuffs/fire_stacks.dm +++ b/code/datums/status_effects/debuffs/fire_stacks.dm @@ -155,7 +155,7 @@ return TRUE var/datum/gas_mixture/air = owner.loc.return_air() - if(!air.gases[/datum/gas/oxygen] || air.gases[/datum/gas/oxygen][MOLES] < 1) + if(air.moles[/datum/gas/oxygen] < 1) qdel(src) return TRUE diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index 17f658099746..e969e2ed5763 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -168,35 +168,72 @@ /datum/wound/blunt/bone/proc/carbon_step(datum/source) SIGNAL_HANDLER - if(limb.body_zone != BODY_ZONE_L_LEG && limb.body_zone != BODY_ZONE_R_LEG) - return - if(victim.body_position == LYING_DOWN || isobj(victim.buckled)) // wheelchair = fine, being pulled = not fine - return - if(victim.has_status_effect(/datum/status_effect/determined)) - return footstep_counter += 1 if(footstep_counter >= 8) footstep_counter = 1 - if(limb.get_splint_factor() <= 0.75 || !CAN_FEEL_PAIN(victim)) + // crawling + if(victim.body_position == LYING_DOWN) + // crawling, and we're not dealing with broken arms or ribs - skip + if(limb.body_zone == BODY_ZONE_CHEST) + pass() + else if(limb.body_zone != SELECT_LEFT_OR_RIGHT(footstep_counter, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + return + + // walking + else + // walking, and we're not dealing with broken legs or ribs - skip + if(limb.body_zone == BODY_ZONE_CHEST) + pass() + else if(limb.body_zone != SELECT_LEFT_OR_RIGHT(footstep_counter, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + return + + // secured via something like a wheelchair or gurney + if(isobj(victim.buckled)) return - if(limb.body_zone == SELECT_LEFT_OR_RIGHT(footstep_counter, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + + // adrenaline rush + if(victim.has_status_effect(/datum/status_effect/determined)) return - var/mod = 1 + + if(!CAN_FEEL_PAIN(victim)) + return + + var/splint_factor = limb.get_splint_factor() + var/mod = 1 - splint_factor switch(victim.move_intent) if(MOVE_INTENT_RUN) - mod = 1.5 + mod += 0.5 if(MOVE_INTENT_WALK) - mod = 1 + pass() if(MOVE_INTENT_SNEAK) - mod = 0.5 + mod -= 0.5 + // since chest can trigger on both left and right steps, halve the chance to be fair + if(limb.body_zone == BODY_ZONE_CHEST) + mod *= 0.5 + if(!prob(severity * mod * 20)) return if(SEND_SIGNAL(victim, COMSIG_CARBON_PAINED_STEP, limb, footstep_counter) & STOP_PAIN) return - to_chat(victim, span_danger("Your [limb.plaintext_zone] [pick("aches", "pangs", "stings")] as you take a step!")) - victim.sharp_pain(limb.body_zone, severity * 6, BRUTE, 10 SECONDS) + // we can be in one of t + var/situation = "" + // crawling or being dragged + if(victim.body_position == LYING_DOWN) + situation = isnull(victim.pulledby) ? "as you crawl" : "as you are dragged" + // being pulled/grabbed + else if(!isnull(victim.pulledby)) + situation = "as you are pulled along" + // being fireman carried + else if(ismob(victim.buckled)) + situation = "as you are carried limply" + // walking as normal + else + situation = "as you take a step" + + to_chat(victim, span_danger("Your [limb.plaintext_zone] [pick("aches", "pangs", "stings")] [situation]!")) + victim.sharp_pain(limb.body_zone, severity * 6 * splint_factor, BRUTE, 10 SECONDS) /datum/wound/blunt/bone/proc/breath(...) SIGNAL_HANDLER diff --git a/code/game/area/areas/mining.dm b/code/game/area/areas/mining.dm index 176ffecef0df..97f515c0559b 100644 --- a/code/game/area/areas/mining.dm +++ b/code/game/area/areas/mining.dm @@ -206,13 +206,18 @@ name = "Icemoon Wastes" outdoors = TRUE -/area/icemoon/surface/outdoors/nospawn // this is the area you use for stuff to not spawn, but if you still want weather. +/// this is the area you use for stuff to not spawn, but if you still want weather. +/area/icemoon/surface/outdoors/nospawn -/area/icemoon/surface/outdoors/nospawn/New() // unless you roll forested trait lol +// unless you roll forested trait lol (fuck you time green) +/area/icemoon/surface/outdoors/nospawn/New() . = ..() + // this area SOMETIMES does map generation. Often it doesn't at all + // so it SHOULD NOT be used with the genturf turf type, as it is not always replaced if(HAS_TRAIT(SSstation, STATION_TRAIT_FORESTED)) map_generator = /datum/map_generator/cave_generator/icemoon/surface/forested - area_flags = MOB_SPAWN_ALLOWED | FLORA_ALLOWED//flip this on, the generator has already disabled dangerous fauna + // flip this on, the generator has already disabled dangerous fauna + area_flags = MOB_SPAWN_ALLOWED | FLORA_ALLOWED /area/icemoon/surface/outdoors/noteleport // for places like the cursed spring water area_flags_mapping = parent_type::area_flags_mapping | NOTELEPORT diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm index 24d5981039f2..913c4e96cf5a 100644 --- a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm +++ b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm @@ -250,8 +250,8 @@ QDEL_NULL(hotspot) var/datum/gas_mixture/air = location.air - if (air.gases[/datum/gas/plasma]) - var/scrub_amt = min(30, air.gases[/datum/gas/plasma][MOLES]) //Absorb some plasma + if (air.moles[/datum/gas/plasma]) + var/scrub_amt = min(30, air.moles[/datum/gas/plasma]) //Absorb some plasma air.adjust_gas(/datum/gas/plasma, -scrub_amt) absorbed_plasma += scrub_amt if (air.temperature > T20C) @@ -415,6 +415,12 @@ alpha = 120 max_integrity = 10 pass_flags_self = PASSGLASS + var/static/list/ignored_gases = typecacheof(list( + /datum/gas/nitrogen, + /datum/gas/oxygen, + /datum/gas/pluoxium, + /datum/gas/halon, + )) /obj/structure/foamedmetal/resin/Initialize(mapload) . = ..() @@ -429,13 +435,10 @@ for(var/obj/effect/hotspot/fire in location) qdel(fire) - var/list/gases = air.gases - for(var/gas_type in gases) - switch(gas_type) - if(/datum/gas/oxygen, /datum/gas/nitrogen) - continue - else - gases[gas_type][MOLES] = 0 + var/list/cached_moles = air.moles + for(var/gas_id in cached_moles) + if(!(ignored_gases[gas_id])) + cached_moles[gas_id] = 0 air.garbage_collect() for(var/obj/machinery/atmospherics/components/unary/comp in location) diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm index 16da43cd19dd..a8d891609d22 100644 --- a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm +++ b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm @@ -295,8 +295,8 @@ if(!distcheck || get_dist(location, chilly) < blast) // Otherwise we'll get silliness like people using Nanofrost to kill people through walls with cold air air.temperature = temperature - if(air.gases[/datum/gas/plasma]) - var/mole_count = air.gases[/datum/gas/plasma][MOLES] + if(air.moles[/datum/gas/plasma]) + var/mole_count = air.moles[/datum/gas/plasma] air.adjust_gas(/datum/gas/nitrogen, mole_count) air.adjust_gas(/datum/gas/plasma, -mole_count) air.garbage_collect() diff --git a/code/game/objects/items/devices/scanners/gas_analyzer.dm b/code/game/objects/items/devices/scanners/gas_analyzer.dm index a7fd51ce1b28..2accf4e2a094 100644 --- a/code/game/objects/items/devices/scanners/gas_analyzer.dm +++ b/code/game/objects/items/devices/scanners/gas_analyzer.dm @@ -215,10 +215,10 @@ if(total_moles > 0) message += span_notice("Moles: [round(total_moles, 0.01)] mol") - var/list/cached_gases = air.gases - for(var/id in cached_gases) - var/gas_concentration = cached_gases[id][MOLES]/total_moles - message += span_notice("[cached_gases[id][GAS_META][META_GAS_NAME]]: [round(cached_gases[id][MOLES], 0.01)] mol ([round(gas_concentration*100, 0.01)] %)") + var/list/cached_gas_name = GAS_META[META_GAS_NAME] + for(var/id, amount in air.moles) + var/gas_concentration = amount / total_moles + message += span_notice("[cached_gas_name[id]]: [round(amount, 0.01)] mol ([round(gas_concentration*100, 0.01)] %)") message += span_notice("Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)") message += span_notice("Volume: [volume] L") message += span_notice("Pressure: [round(pressure, 0.01)] kPa") diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm index 253b01a0e2d4..a35e023316b6 100644 --- a/code/game/objects/items/flamethrower.dm +++ b/code/game/objects/items/flamethrower.dm @@ -230,8 +230,8 @@ var/datum/gas_mixture/tank_mix = ptank.return_air() var/datum/gas_mixture/air_transfer = tank_mix.remove_ratio(release_amount) - if(air_transfer.gases[/datum/gas/plasma]) - var/moles = air_transfer.gases[/datum/gas/plasma][MOLES] * 5 //Suffering + if(air_transfer.moles[/datum/gas/plasma]) + var/moles = air_transfer.moles[/datum/gas/plasma] * 5 //Suffering air_transfer.set_gas(/datum/gas/plasma, moles) target.assume_air(air_transfer) //Burn it based on transferred gas diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm index 89240e52e743..ba3ce2ec1c04 100644 --- a/code/game/objects/items/tanks/tanks.dm +++ b/code/game/objects/items/tanks/tanks.dm @@ -526,7 +526,7 @@ var/datum/gas_mixture/our_mix = return_air() our_mix.assert_gases(/datum/gas/plasma, /datum/gas/oxygen) - var/fuel_moles = our_mix.gases[/datum/gas/plasma][MOLES] + our_mix.gases[/datum/gas/oxygen][MOLES]/6 + var/fuel_moles = our_mix.moles[/datum/gas/plasma] + our_mix.moles[/datum/gas/oxygen]/6 our_mix.garbage_collect() var/datum/gas_mixture/bomb_mixture = our_mix.copy() var/strength = 1 diff --git a/code/game/objects/structures/bonfire.dm b/code/game/objects/structures/bonfire.dm index facb250e6ad0..7e9a68d28e24 100644 --- a/code/game/objects/structures/bonfire.dm +++ b/code/game/objects/structures/bonfire.dm @@ -97,10 +97,8 @@ /obj/structure/bonfire/proc/check_oxygen() if(isopenturf(loc)) var/turf/open/bonfire_turf = loc - if(bonfire_turf.air) - var/loc_gases = bonfire_turf.air.gases - if(loc_gases[/datum/gas/oxygen] && loc_gases[/datum/gas/oxygen][MOLES] >= 5) - return TRUE + if(bonfire_turf.air?.moles[/datum/gas/oxygen] >= 5) + return TRUE return FALSE /obj/structure/bonfire/proc/start_burning() diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm index b59a91d05e92..1cee2f794872 100644 --- a/code/game/turfs/change_turf.dm +++ b/code/game/turfs/change_turf.dm @@ -240,7 +240,6 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( return var/datum/gas_mixture/total = new//Holders to assimilate air from nearby turfs - var/list/total_gases = total.gases //Stolen blatently from self_breakdown var/list/turf_list = atmos_adjacent_turfs + src var/turflen = turf_list.len @@ -256,14 +255,13 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( energy += mix.temperature * capacity heat_cap += capacity - var/list/giver_gases = mix.gases - for(var/giver_id in giver_gases) - ASSERT_GAS_IN_LIST(giver_id, total_gases) - total.adjust_gas(giver_id, giver_gases[giver_id][MOLES]) + for(var/giver_id, amount in mix.moles) + total.adjust_gas(giver_id, amount) total.temperature = energy / heat_cap - for(var/id in total_gases) - total_gases[id][MOLES] /= turflen + var/list/cached_total_moles = total.moles + for(var/id in cached_total_moles) + cached_total_moles[id] /= turflen for(var/turf/open/turf in turf_list) turf.air.copy_from(total) diff --git a/code/modules/atmospherics/Atmospherics.md b/code/modules/atmospherics/Atmospherics.md index 6d61a8cf16bc..972ef62302a2 100644 --- a/code/modules/atmospherics/Atmospherics.md +++ b/code/modules/atmospherics/Atmospherics.md @@ -38,7 +38,7 @@ Now then, into the breach. The air controller is, at its core, quite simple, yet it is absolutely fundamental to the atmospheric system. The air controller is the clock which triggers all continuous actions within the atmos system, such as vents distributing air or gas moving between tiles. The actions taken by the air controller are quite simple, and will be enumerated here. Much of the substance of the air ticker is due to the game's master controller, whose intricacies I will not delve into for this document. I will however go into more detail about how SSAir in particular works in Chapter 6. In any case, this is a simplified list of the air controller's actions in a single tick: 1. Rebuild Pipenets - Runs each time SSAir processes, sometimes out of order. It ensures that no pipeline sit unresolved or unbuilt - - Processes the `rebuild_queue` list into the `expansion_queue` list, and then builds a full pipeline piecemeal. We do a ton of fenagling here to reduce overrun + - Processes the `rebuild_queue` list into the `expansion_queue` list, and then builds a full pipeline piecemeal. We do a ton of fenagling here to reduce overrun 2. Pipenets - Updates the internal gasmixes of attached pipe machinery, and reacts the gases in a pipeline - Calls `process()` on each `/datum/pipenet` in the `networks` list @@ -109,20 +109,27 @@ Each gas mixture has an associative list, gases, which maps according to a key t Each type of gas is defined by defining a new subtype of /datum/gas. These datums do not get instantiated; they merely serve as a convenient and familiar means for a coder unfamiliar with the inner workings of listmos to define a new gas. Additionally, the type paths serve a second use as the keys used to access a particular gas within the gases list. It is easiest to demonstrate the manipulation of gas, including these list accesses, with an example. +### Vectorized xgm-like system +Current system uses two associative arrays: `moles` and `moles_archived` with the key being `/datum/gas`. The gas metadata array is stored in a static variable `gas_mixture::gas_meta`. The layout of keys in the array is `gas_meta[META_INDEX][gas_path]`. This allows us to use vector functions like `values_sum` and `values_dot` (introduced in BYOND v516). This approach showed improvement of ~20% on process_cell and memory footprint stayed the same. + +While these vector functions are extremely fast, they can only work with associative arrays of number. To calculate total moles you would write `values_sum(moles)` and to calculate heat capacity, it would be `values_dot(moles, gas_meta[META_GAS_SPECIFIC_HEAT])`. This does not seem like a lot, but when a subsytem is doing 1000 updates each simulation tick every microsecond matters. + +Another benefit of using one-dimensional associative arrays is that for any arithmetic or logic operation a missing key acts like a 0. For example you could write `moles[/datum/gas/oxygen] += 10` even if `/datum/gas/oxygen` is not in the list. + ### Interfacing with a Gas Mixture ```DM var/datum/gas_mixture/air = new air.assert_gas(/datum/gas/oxygen) -air.gases[/datum/gas/oxygen][MOLES] = 100 -world << air.gases[/datum/gas/oxygen][GAS_META][META_GAS_NAME] //outputs "Oxygen" -world << air.gases.heat_capacity() //outputs 2000 (100 mol * 20 J/K/mol) -air.gases[/datum/gas/oxygen][MOLES] -= 110 +air.moles[/datum/gas/oxygen] = 100 +world << air.gas_meta[META_GAS_NAME][/datum/gas/oxygen] //outputs "Oxygen" +world << air.heat_capacity() //outputs 2000 (100 mol * 20 J/K/mol) +air.moles[/datum/gas/oxygen] -= 110 air.garbage_collect() //oxygen is now removed from the gases list, since it was empty ``` *Snippet 4.2: gas mixture usage examples* -Of particular note in this snippet are the two procs assert_gas() and garbage_collect(). These procs are very important while interfacing with gas mixtures. If you are uncertain about whether a given mixture has a particular gas, you must use assert_gas() before any reads or writes from the gas. If you fail to use assert_gas() then there will be runtime errors when you try to access the inner lists. When you remove any number of moles from a given gas, be sure to call garbage_collect(). This proc removes all gases which have mole counts less than or equal to 0. This is a memory and performance enhancement for list accesses achieved by reducing the size of the list, and also saves us from having to do sanity checks for negative moles whenever gas is removed. As a quick reference, here is a list of common procs/vars/list indices which the average coder may wish to use when interfacing with a gas mixture. +Of particular note in this snippet are the two procs assert_gas() and garbage_collect(). These procs are very important while interfacing with gas mixtures. If you are uncertain about whether a given mixture has a particular gas, you must use assert_gas() before any reads or writes from the gas. When you remove any number of moles from a given gas, be sure to call garbage_collect(). This proc removes all gases which have mole counts less than or equal to 0. This is a memory and performance enhancement for list accesses achieved by reducing the size of the list, and also saves us from having to do sanity checks for negative moles whenever gas is removed. As a quick reference, here is a list of common procs/vars/list indices which the average coder may wish to use when interfacing with a gas mixture. ##### Gas Mixture Datum * *`/datum/gas_mixture/proc/assert_gas()`* - Used before accessing a particular type of gas. @@ -141,10 +148,13 @@ It's used by `/datum/gas_mixture/immutable/space`, which implements some particu It's also implemented by `/datum/gas_mixture/immutable/planetary`, which is used for planetary turfs, and has some code that makes actually having a gasmix possible. -##### Gas List -* *`gases[path][MOLES]`* - Quantity of a particular gas within a mixture. -* *`gases[path][GAS_META][META_GAS_NAME]`* - The long name of a gas, ex. "Oxygen" or "Hyper-noblium" -* *`gases[path][GAS_META][META_GAS_ID]`* - The internal ID of a given gas, ex. "o2" or "nob" +- _`moles[path]`_ - Quantity of a particular gas within a mixture. +- _`gas_meta[META_GAS_NAME][path]`_ - The long name of a gas, ex. "Oxygen" or "Hyper-noblium" +- _`gas_meta[META_GAS_ID][path]`_ - The internal ID of a given gas, ex. "o2" or "nob" + +##### Gas Meta +As was said previously gas metadata is stored in a static variable `gas_mixture.gas_meta`. This is done so you can easily access this variable from within gas_mixture, but if you are outside of gas_mixture code and you need gas metadata you can use convenience macro `GAS_META`. There is also third name for that list and it is `GLOB.meta_gas_info`. All of those variables point to the same list and you can use either. As the rule of thumb, in the performance critical code use `GAS_META` or `gas_meta`, otherwise `GLOB.meta_gas_info`. + ### Reactions While defining a new gas on its own is very simple, there is no gas-specific behavior defined within /datum/gas. This behavior gets defined in a few places, notably breath code (to be discussed later) and in reactions. The most important and well known reaction in SS13 is fire - the combustion of plasma. Reactions are used for several things - in particular, it is conventional (though by no means enforced) that to form a gas, a reaction must occur. Creating a new reaction is fairly simple, this is the area of atmos that has received the most attention over the last few years, and the best place to start. Don't be scared of the size of reactions.dm, it's not that complex. @@ -180,7 +190,7 @@ You may notice something like this in `process_cell()`. It's not quite the same Back in the old FEA days, neighbor count was hardcoded to 4 (Likely because this is what cell sharing on an infinite grid would look like). This means that turf A -> turf B is the same as turf B -> turf A, because they're each portioning up the gas in the same way. -But when we moved to LINDA, we started using the length of our atmos_adjacent_turfs list (or an analog). +But when we moved to LINDA, we started using the length of our atmos_adjacent_turfs list (or an analog). We need this so things like multiz can work, and so tiles in a corner share in a way that makes sense. Because of this, turf A -> turf B was no longer the same as turf B -> turf A, assuming one of those turfs had a different neighbor count, from I DON'T KNOW WALLS? diff --git a/code/modules/atmospherics/environmental/LINDA_fire.dm b/code/modules/atmospherics/environmental/LINDA_fire.dm index 268b48907459..10e9bf458eaa 100644 --- a/code/modules/atmospherics/environmental/LINDA_fire.dm +++ b/code/modules/atmospherics/environmental/LINDA_fire.dm @@ -22,38 +22,34 @@ */ /turf/open/hotspot_expose(exposed_temperature, exposed_volume, soh) //If the air doesn't exist we just return false - var/list/air_gases = air?.gases - if(!air_gases) + var/cached_moles = air?.moles + if(!cached_moles) return - . = air_gases[/datum/gas/oxygen] - var/oxy = . ? .[MOLES] : 0 - if (oxy < 0.5) + if (cached_moles[/datum/gas/oxygen] < 0.5) return - . = air_gases[/datum/gas/plasma] - var/plas = . ? .[MOLES] : 0 - . = air_gases[/datum/gas/tritium] - var/trit = . ? .[MOLES] : 0 - . = air_gases[/datum/gas/hydrogen] - var/h2 = . ? .[MOLES] : 0 - . = air_gases[/datum/gas/freon] - var/freon = . ? .[MOLES] : 0 + + var/plas_trit_h2_threshold = (\ + cached_moles[/datum/gas/plasma] > 0.5\ + || cached_moles[/datum/gas/tritium] > 0.5\ + || cached_moles[/datum/gas/hydrogen] > 0.5) + var/freon_threshold = (cached_moles[/datum/gas/freon] > 0.5) if(active_hotspot) if(soh) - if(plas > 0.5 || trit > 0.5 || h2 > 0.5) + if(plas_trit_h2_threshold) if(active_hotspot.temperature < exposed_temperature) active_hotspot.temperature = exposed_temperature if(active_hotspot.volume < exposed_volume) active_hotspot.volume = exposed_volume - else if(freon > 0.5) + else if(freon_threshold) if(active_hotspot.temperature > exposed_temperature) active_hotspot.temperature = exposed_temperature if(active_hotspot.volume < exposed_volume) active_hotspot.volume = exposed_volume return - if(((exposed_temperature > PLASMA_MINIMUM_BURN_TEMPERATURE) && (plas > 0.5 || trit > 0.5 || h2 > 0.5)) || \ - ((exposed_temperature < FREON_MAXIMUM_BURN_TEMPERATURE) && (freon > 0.5))) + if (((exposed_temperature > PLASMA_MINIMUM_BURN_TEMPERATURE) && plas_trit_h2_threshold) || \ + ((exposed_temperature < FREON_MAXIMUM_BURN_TEMPERATURE) && freon_threshold)) new /obj/effect/hotspot(src, exposed_volume * 25, exposed_temperature) SSair.add_to_active(src) @@ -273,7 +269,7 @@ color = list(LERP(0.3, 1, 1-greyscale_fire) * heat_r,0.3 * heat_g * greyscale_fire,0.3 * heat_b * greyscale_fire, 0.59 * heat_r * greyscale_fire,LERP(0.59, 1, 1-greyscale_fire) * heat_g,0.59 * heat_b * greyscale_fire, 0.11 * heat_r * greyscale_fire,0.11 * heat_g * greyscale_fire,LERP(0.11, 1, 1-greyscale_fire) * heat_b, 0,0,0) alpha = heat_a -#define INSUFFICIENT(path) (!location.air.gases[path] || location.air.gases[path][MOLES] < 0.5) +#define INSUFFICIENT(path) (!location.air.moles[path] || location.air.moles[path] < 0.5) /** * Regular process proc for hotspots governed by the controller. diff --git a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm index 4bc54df2a2aa..988c228f0d8b 100644 --- a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm +++ b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm @@ -173,10 +173,9 @@ src.atmos_overlay_types = null return - var/list/gases = air.gases - + var/list/moles = air.moles var/list/new_overlay_types - GAS_OVERLAYS(gases, new_overlay_types, src) + GAS_OVERLAYS(moles, new_overlay_types, src) if (atmos_overlay_types) for(var/overlay in atmos_overlay_types-new_overlay_types) //doesn't remove overlays that would only be added @@ -300,7 +299,7 @@ our_excited_group = excited_group //update our cache if(our_excited_group && enemy_excited_group && enemy_tile.excited) //If you're both excited, no need to compare right? should_share_air = TRUE - else if(our_air.compare(enemy_air)) //Lets see if you're up for it + else if(our_air.compare(enemy_air, /*cmp_archive = */ TRUE)) //Lets see if you're up for it SSair.add_to_active(enemy_tile) //Add yourself young man var/datum/excited_group/existing_group = our_excited_group || enemy_excited_group || new if(!our_excited_group) @@ -328,7 +327,7 @@ var/datum/gas_mixture/planetary_mix = SSair.planetary[initial_gas_mix] // archive ourself again so we don't accidentally share more gas than we currently have LINDA_CYCLE_ARCHIVE(src) - if(our_air.compare(planetary_mix)) + if(our_air.compare(planetary_mix, /*cmp_archive = */ TRUE)) if(!our_excited_group) var/datum/excited_group/new_group = new new_group.add_turf(src) @@ -462,7 +461,7 @@ var/datum/gas_mixture/shared_mix = new //make local for sanic speed - var/list/shared_gases = shared_mix.gases + var/list/shared_cached_moles = shared_mix.moles var/list/turf_list = src.turf_list var/turflen = turf_list.len var/imumutable_in_group = FALSE @@ -472,26 +471,32 @@ for(var/turf/open/group_member as anything in turf_list) //Cache? var/datum/gas_mixture/turf/mix = group_member.air - if (roundstart && istype(group_member.air, /datum/gas_mixture/immutable)) - imumutable_in_group = TRUE - shared_mix.copy_from(group_member.air) //This had better be immutable young man - shared_gases = shared_mix.gases //update the cache - break + if (roundstart) + if(istype(group_member.air, /datum/gas_mixture/immutable)) + imumutable_in_group = TRUE + shared_mix.copy_from(group_member.air) //This had better be immutable young man + shared_cached_moles = shared_mix.moles //update the cache + break + // If we're planetary use THAT mix, and stop here + if(group_member.planetary_atmos) + imumutable_in_group = TRUE + var/datum/gas_mixture/planetary_mix = SSair.planetary[group_member.initial_gas_mix] + shared_mix.copy_from(planetary_mix) + shared_cached_moles = shared_mix.moles // Cache update + break //"borrowing" this code from merge(), I need to play with the temp portion. Lets expand it out //temperature = (giver.temperature * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity var/capacity = mix.heat_capacity() energy += mix.temperature * capacity heat_cap += capacity - var/list/giver_gases = mix.gases - for(var/giver_id in giver_gases) - ASSERT_GAS_IN_LIST(giver_id, shared_gases) - shared_gases[giver_id][MOLES] += giver_gases[giver_id][MOLES] + for(var/gas_id, amount in mix.moles) + shared_cached_moles[gas_id] += amount if(!imumutable_in_group) shared_mix.temperature = energy / heat_cap - for(var/id in shared_gases) - shared_gases[id][MOLES] /= turflen + for(var/gas_id in shared_cached_moles) + shared_cached_moles[gas_id] /= turflen shared_mix.garbage_collect() for(var/turf/open/group_member as anything in turf_list) diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index 86f7762ce64a..2c1d91485b54 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -5,22 +5,14 @@ This prevents race conditions that arise based on the order of tile processing. */ GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm -GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) - -/proc/init_gaslist_cache() - var/list/gases = list() - for(var/id in GLOB.meta_gas_info) - var/list/cached_gas = new(3) - - gases[id] = cached_gas - - cached_gas[MOLES] = 0 - cached_gas[ARCHIVE] = 0 - cached_gas[GAS_META] = GLOB.meta_gas_info[id] - return gases /datum/gas_mixture - var/list/gases + /// Associative list of moles for each gas. List key is /datum/gas/, value is amount in moles + var/list/moles + /// Archived version of moles + var/list/moles_archive + /// Static list of gas meta data like heat capacity (initialized globally) + var/static/list/gas_meta /// The temperature of the gas mix in kelvin. Should never be lower then TCMB var/temperature = TCMB /// Used, like all archived variables, to ensure turf sharing is consistent inside a tick, no matter @@ -39,7 +31,8 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) var/pipeline_cycle = -1 /datum/gas_mixture/New(volume) - gases = new + moles = list() + moles_archive = list() if(!isnull(volume)) src.volume = volume if(src.volume <= 0) @@ -52,75 +45,74 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) ///assert_gas(gas_id) - used to guarantee that the gas list for this id exists in gas_mixture.gases. ///Must be used before adding to a gas. May be used before reading from a gas. /datum/gas_mixture/proc/assert_gas(gas_id) - ASSERT_GAS(gas_id, src) + moles[gas_id] += 0 + moles_archive[gas_id] += 0 -///assert_gases(args) - shorthand for calling ASSERT_GAS() once for each gas type. +///assert_gases(args) - shorthand for calling assert_gas(gas_id) once for each gas type. /datum/gas_mixture/proc/assert_gases(...) - for(var/id in args) - ASSERT_GAS(id, src) + var/cached_moles = moles + var/cached_moles_archive = moles_archive + for(var/gas_id in args) + cached_moles[gas_id] += 0 + cached_moles_archive[gas_id] += 0 ///add_gas(gas_id) - similar to assert_gas(), but does not check for an existing gas list for this id. This can clobber existing gases. ///Used instead of assert_gas() when you know the gas does not exist. Faster than assert_gas(). /datum/gas_mixture/proc/add_gas(gas_id) - ADD_GAS(gas_id, gases) + moles[gas_id] = 0 + moles_archive[gas_id] = 0 ///add_gases(args) - shorthand for calling add_gas() once for each gas_type. /datum/gas_mixture/proc/add_gases(...) - var/cached_gases = gases - for(var/id in args) - ADD_GAS(id, cached_gases) + var/cached_moles = moles + var/cached_moles_archive = moles_archive + for(var/gas_id in args) + cached_moles[gas_id] = 0 + cached_moles_archive[gas_id] = 0 ///garbage_collect() - removes any gas list which is empty. ///If called with a list as an argument, only removes gas lists with IDs from that list. ///Must be used after subtracting from a gas. Must be used after assert_gas() ///if assert_gas() was called only to read from the gas. ///By removing empty gases, processing speed is increased. -/datum/gas_mixture/proc/garbage_collect(list/tocheck) - var/list/cached_gases = gases - for(var/id in (tocheck || cached_gases)) - if(QUANTIZE(cached_gases[id][MOLES]) <= 0) - cached_gases -= id +/datum/gas_mixture/proc/garbage_collect() + values_cut_under(moles, MOLAR_ACCURACY, TRUE) + values_cut_under(moles_archive, MOLAR_ACCURACY, TRUE) //PV = nRT ///joules per kelvin -/datum/gas_mixture/proc/heat_capacity(data = MOLES) - var/list/cached_gases = gases - . = 0 - for(var/id in cached_gases) - var/gas_data = cached_gases[id] - . += gas_data[data] * gas_data[GAS_META][META_GAS_SPECIFIC_HEAT] +/datum/gas_mixture/proc/heat_capacity() + return values_dot(moles, GAS_META[META_GAS_SPECIFIC_HEAT]) + +///joules per kelvin. Same as heat_capacity() for moles_archive. +// Separate function to reduce branches in a hot function +/datum/gas_mixture/proc/heat_capacity_archive() + return values_dot(moles_archive, GAS_META[META_GAS_SPECIFIC_HEAT]) /// Same as above except vacuums return HEAT_CAPACITY_VACUUM -/datum/gas_mixture/turf/heat_capacity(data = MOLES) - var/list/cached_gases = gases - . = 0 - for(var/id in cached_gases) - var/gas_data = cached_gases[id] - . += gas_data[data] * gas_data[GAS_META][META_GAS_SPECIFIC_HEAT] - if(!.) - . += HEAT_CAPACITY_VACUUM //we want vacuums in turfs to have the same heat capacity as space +/datum/gas_mixture/turf/heat_capacity() + return values_dot(moles, GAS_META[META_GAS_SPECIFIC_HEAT]) || HEAT_CAPACITY_VACUUM + +/// Same as above except vacuums return HEAT_CAPACITY_VACUUM +// Separate function to reduce branches in a hot function +/datum/gas_mixture/turf/heat_capacity_archive() + return values_dot(moles_archive, GAS_META[META_GAS_SPECIFIC_HEAT]) || HEAT_CAPACITY_VACUUM /// Calculate moles /datum/gas_mixture/proc/total_moles() - var/cached_gases = gases - TOTAL_MOLES(cached_gases, .) + return values_sum(moles) /// Checks to see if gas amount exists in mixture. /// Do NOT use this in code where performance matters! /// It's better to batch calls to garbage_collect(), especially in places where you're checking many gastypes /datum/gas_mixture/proc/has_gas(gas_id, amount=0) - ASSERT_GAS(gas_id, src) - var/is_there_gas = amount < gases[gas_id][MOLES] - garbage_collect() - return is_there_gas + return amount < moles[gas_id] /// Calculate pressure in kilopascals /datum/gas_mixture/proc/return_pressure() if(volume) // to prevent division by zero - var/cached_gases = gases - TOTAL_MOLES(cached_gases, .) - return . * R_IDEAL_GAS_EQUATION * temperature / volume + return values_sum(moles) * R_IDEAL_GAS_EQUATION * temperature / volume return 0 /// Calculate temperature in kelvins @@ -134,7 +126,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) /// Gets the gas visuals for everything in this mixture /datum/gas_mixture/proc/return_visuals(turf/z_context) var/list/output - GAS_OVERLAYS(gases, output, z_context) + GAS_OVERLAYS(moles, output, z_context) return output /// Calculate thermal energy in joules @@ -143,11 +135,12 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) ///Update archived versions of variables. Returns: 1 in all cases /datum/gas_mixture/proc/archive() - var/list/cached_gases = gases + var/list/cached_moles = moles + var/list/cached_moles_archive = moles_archive temperature_archived = temperature - for(var/id in cached_gases) - cached_gases[id][ARCHIVE] = cached_gases[id][MOLES] + for(var/gas_id in cached_moles) + cached_moles_archive[gas_id] = cached_moles[gas_id] return TRUE @@ -164,20 +157,18 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) if(combined_heat_capacity) temperature = (giver.temperature * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity - var/list/cached_gases = gases //accessing datum vars is slower than proc vars - var/list/giver_gases = giver.gases + var/list/cached_moles = moles //accessing datum vars is slower than proc vars + var/list/cached_giver_moles = giver.moles //gas transfer - for(var/giver_id in giver_gases) - ASSERT_GAS_IN_LIST(giver_id, cached_gases) - cached_gases[giver_id][MOLES] += giver_gases[giver_id][MOLES] + for(var/gas_id in cached_giver_moles) + cached_moles[gas_id] += cached_giver_moles[gas_id] SEND_SIGNAL(src, COMSIG_GASMIX_MERGED) return TRUE // Set the gas specie within the gas mix to a set amount, if there is none it will be created at the target temp /datum/gas_mixture/proc/set_gas(gas_specie, amount) - ASSERT_GAS(gas_specie, src) - gases[gas_specie][MOLES] = amount + moles[gas_specie] = amount garbage_collect() /datum/gas_mixture/proc/set_temperature(target_temp) @@ -186,46 +177,46 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) /// Add a specific amount of moles to specified gas or add a new gas to the mix /// amount is added so make it negative to remove /datum/gas_mixture/proc/adjust_gas(gas, amount) - ASSERT_GAS(gas, src) - gases[gas][MOLES] += QUANTIZE(amount) + moles[gas] += QUANTIZE(amount) garbage_collect() /// Add a specific amount of moles to all the gasses present or add a new gas to the mix ///gases_moles is an associative list of gas species to their amount to be added /datum/gas_mixture/proc/adjust_multiple_gases(list/gases_moles) - for(var/gas_specie in gases_moles) - ASSERT_GAS(gas_specie, src) - gases[gas_specie][MOLES] += gases_moles[gas_specie] + var/cached_moles = moles + for(var/gas_id in gases_moles) + cached_moles[gas_id] += gases_moles[gas_id] garbage_collect() /// Modify the gas list as to convert moles of gas species A to gas species B /// reactant and product are the gas species to convert and conversion_amount is the amount to be converted /datum/gas_mixture/proc/convert_gas(datum/gas/reactant, datum/gas/product, conversion_amount) - var/list/cached_gases = gases + var/list/cached_moles = moles assert_gases(reactant, product) - cached_gases[reactant][MOLES] -= QUANTIZE(conversion_amount) - cached_gases[product][MOLES] += QUANTIZE(conversion_amount) + cached_moles[reactant] -= QUANTIZE(conversion_amount) + cached_moles[product] += QUANTIZE(conversion_amount) garbage_collect() ///Proportionally removes amount of gas from the gas_mixture. ///Returns: gas_mixture with the gases removed /datum/gas_mixture/proc/remove(amount) - var/sum - var/list/cached_gases = gases - TOTAL_MOLES(cached_gases, sum) - amount = min(amount, sum) //Can not take more air than tile has! + + var/list/cached_moles = moles + var/total_moles = values_sum(cached_moles) + amount = min(amount, total_moles) //Can not take more air than tile has! if(amount <= 0) return null - var/ratio = amount / sum + var/ratio = amount / total_moles + var/datum/gas_mixture/removed = new type(volume) - var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars + var/list/cached_removed_moles = removed.moles //accessing datum vars is slower than proc vars removed.temperature = temperature - for(var/id in cached_gases) - ADD_GAS(id, removed.gases) - removed_gases[id][MOLES] = QUANTIZE(cached_gases[id][MOLES] * ratio) - cached_gases[id][MOLES] -= removed_gases[id][MOLES] + for(var/id in cached_moles) + cached_removed_moles[id] = QUANTIZE(cached_moles[id] * ratio) + cached_moles[id] -= cached_removed_moles[id] + garbage_collect() SEND_SIGNAL(src, COMSIG_GASMIX_REMOVED) @@ -239,15 +230,14 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) return removed ratio = min(ratio, 1) - var/list/cached_gases = gases + var/list/cached_moles = moles var/datum/gas_mixture/removed = new type(volume) - var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars + var/list/cached_removed_moles = removed.moles //accessing datum vars is slower than proc vars removed.temperature = temperature - for(var/id in cached_gases) - ADD_GAS(id, removed.gases) - removed_gases[id][MOLES] = QUANTIZE(cached_gases[id][MOLES] * ratio) - cached_gases[id][MOLES] -= removed_gases[id][MOLES] + for(var/id in cached_moles) + cached_removed_moles[id] = QUANTIZE(cached_moles[id] * ratio) + cached_moles[id] -= cached_removed_moles[id] garbage_collect() @@ -257,18 +247,17 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) ///Removes an amount of a specific gas from the gas_mixture. ///Returns: gas_mixture with the gas removed /datum/gas_mixture/proc/remove_specific(gas_id, amount) - var/list/cached_gases = gases - amount = min(amount, cached_gases[gas_id][MOLES]) + var/list/cached_moles = moles + amount = min(amount, cached_moles[gas_id]) if(amount <= 0) return null var/datum/gas_mixture/removed = new type - var/list/removed_gases = removed.gases removed.temperature = temperature - ADD_GAS(gas_id, removed.gases) - removed_gases[gas_id][MOLES] = amount - cached_gases[gas_id][MOLES] -= amount + removed.moles[gas_id] = amount + cached_moles[gas_id] -= amount - garbage_collect(list(gas_id)) + // TODO(antropod): maybe use if (cached_moles[gas_id] < MOLAR_ACCURACY) cached_moles -= gas_id? + garbage_collect() return removed /datum/gas_mixture/proc/remove_specific_ratio(gas_id, ratio) @@ -276,16 +265,15 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) return null ratio = min(ratio, 1) - var/list/cached_gases = gases + var/list/cached_moles = moles var/datum/gas_mixture/removed = new type - var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars + var/list/cached_removed_moles = removed.moles //accessing datum vars is slower than proc vars removed.temperature = temperature - ADD_GAS(gas_id, removed.gases) - removed_gases[gas_id][MOLES] = QUANTIZE(cached_gases[gas_id][MOLES] * ratio) - cached_gases[gas_id][MOLES] -= removed_gases[gas_id][MOLES] + cached_removed_moles[gas_id] = QUANTIZE(cached_moles[gas_id] * ratio) + cached_moles[gas_id] -= cached_removed_moles[gas_id] - garbage_collect(list(gas_id)) + garbage_collect() return removed @@ -303,33 +291,33 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) var/min_p_delta = 0.1 var/total_volume = volume + other.volume - var/list/gas_list = gases | other.gases + var/list/cached_moles = moles + var/list/cached_other_moles = other.moles + var/list/gas_list = cached_moles | cached_other_moles for(var/gas_id in gas_list) assert_gas(gas_id) other.assert_gas(gas_id) //math is under the assumption temperatures are equal - if(abs(gases[gas_id][MOLES] / volume - other.gases[gas_id][MOLES] / other.volume) > min_p_delta / (R_IDEAL_GAS_EQUATION * temperature)) + if(abs(cached_moles[gas_id] / volume - cached_other_moles[gas_id] / other.volume) > min_p_delta / (R_IDEAL_GAS_EQUATION * temperature)) . = TRUE - var/total_moles = gases[gas_id][MOLES] + other.gases[gas_id][MOLES] - gases[gas_id][MOLES] = total_moles * (volume/total_volume) - other.gases[gas_id][MOLES] = total_moles * (other.volume/total_volume) + var/total_moles = cached_moles[gas_id] + cached_other_moles[gas_id] + cached_moles[gas_id] = total_moles * (volume/total_volume) + cached_other_moles[gas_id] = total_moles * (other.volume/total_volume) garbage_collect() other.garbage_collect() ///Creates new, identical gas mixture ///Returns: duplicate gas mixture /datum/gas_mixture/proc/copy() - // Type as /list/list to make spacemandmm happy with the inlined access we do down there - var/list/list/cached_gases = gases + var/list/cached_moles = moles var/datum/gas_mixture/copy = new type - var/list/copy_gases = copy.gases + var/list/copy_cached_moles = copy.moles + var/list/copy_cached_moles_archive = copy.moles_archive copy.temperature = temperature - for(var/id in cached_gases) - // Sort of a sideways way of doing ADD_GAS() - // Faster tho, gotta save those cpu cycles - copy_gases[id] = cached_gases[id].Copy() - copy_gases[id][ARCHIVE] = 0 + for(var/gas_id in cached_moles) + copy_cached_moles[gas_id] = cached_moles[gas_id] + copy_cached_moles_archive[gas_id] = 0 return copy @@ -337,33 +325,33 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) ///Copies variables from sample ///Returns: TRUE if we are mutable, FALSE otherwise /datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample) - var/list/cached_gases = gases //accessing datum vars is slower than proc vars - // Type as /list/list to make spacemandmm happy with the inlined access we do down there - var/list/list/sample_gases = sample.gases + var/list/cached_moles = moles //accessing datum vars is slower than proc vars + var/list/cached_moles_archive = moles_archive + var/list/sample_cached_moles = sample.moles //remove all gases - cached_gases.Cut() + cached_moles.Cut() + cached_moles_archive.Cut() temperature = sample.temperature - for(var/id in sample_gases) - cached_gases[id] = sample_gases[id].Copy() - cached_gases[id][ARCHIVE] = 0 + for(var/gas_id in sample_cached_moles) + cached_moles[gas_id] = sample_cached_moles[gas_id] + cached_moles_archive[gas_id] = 0 return TRUE ///Copies variables from sample, moles multiplicated by partial ///Returns: TRUE if we are mutable, FALSE otherwise /datum/gas_mixture/proc/copy_from_ratio(datum/gas_mixture/sample, partial = 1) - var/list/cached_gases = gases //accessing datum vars is slower than proc vars - var/list/sample_gases = sample.gases + var/list/cached_moles = moles //accessing datum vars is slower than proc vars + var/list/sample_cached_moles = sample.moles //remove all gases not in the sample - cached_gases &= sample_gases + cached_moles &= sample_cached_moles temperature = sample.temperature - for(var/id in sample_gases) - ASSERT_GAS_IN_LIST(id, cached_gases) - cached_gases[id][MOLES] = sample_gases[id][MOLES] * partial + for(var/gas_id in sample_cached_moles) + cached_moles[gas_id] = sample_cached_moles[gas_id] * partial return TRUE @@ -372,18 +360,20 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) /// If we don't retain this, we will get negative moles. Don't do it /// Returns: amount of gas exchanged (+ if sharer received) /datum/gas_mixture/proc/share(datum/gas_mixture/sharer, our_coeff, sharer_coeff) - var/list/cached_gases = gases - var/list/sharer_gases = sharer.gases + var/list/cached_moles = moles + var/list/cached_moles_archive = moles_archive + var/list/sharer_cached_moles = sharer.moles + var/list/sharer_cached_moles_archive = sharer.moles_archive - var/list/only_in_sharer = sharer_gases - cached_gases - var/list/only_in_cached = cached_gases - sharer_gases + var/list/only_in_sharer = sharer_cached_moles - cached_moles + var/list/only_in_cached = cached_moles - sharer_cached_moles var/temperature_delta = temperature_archived - sharer.temperature_archived - var/abs_temperature_delta = abs(temperature_delta) + var/temp_delta_threshold = abs(temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER var/old_self_heat_capacity = 0 var/old_sharer_heat_capacity = 0 - if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) + if(temp_delta_threshold) old_self_heat_capacity = heat_capacity() old_sharer_heat_capacity = sharer.heat_capacity() @@ -396,15 +386,16 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) //GAS TRANSFER //Prep - for(var/id in only_in_sharer) //create gases not in our cache - ADD_GAS(id, cached_gases) - for(var/id in only_in_cached) //create gases not in the sharing mix - ADD_GAS(id, sharer_gases) + for(var/gas_id in only_in_sharer) //create gases not in our cache + cached_moles[gas_id] = 0 + cached_moles_archive[gas_id] = 0 + for(var/gas_id in only_in_cached) //create gases not in the sharing mix + sharer_cached_moles[gas_id] = 0 + sharer_cached_moles_archive[gas_id] = 0 - for(var/id in cached_gases) //transfer gases - var/gas = cached_gases[id] - var/sharergas = sharer_gases[id] - var/delta = QUANTIZE(gas[ARCHIVE] - sharergas[ARCHIVE]) //the amount of gas that gets moved between the mixtures + var/list/cached_specific_heat = GAS_META[META_GAS_SPECIFIC_HEAT] + for(var/gas_id in cached_moles) //transfer gases + var/delta = QUANTIZE(cached_moles_archive[gas_id] - sharer_cached_moles_archive[gas_id]) //the amount of gas that gets moved between the mixtures if(!delta) continue @@ -416,22 +407,22 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) else delta = delta * sharer_coeff - if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) - var/gas_heat_capacity = delta * gas[GAS_META][META_GAS_SPECIFIC_HEAT] + if(temp_delta_threshold) + var/gas_heat_capacity = delta * cached_specific_heat[gas_id] if(delta > 0) heat_capacity_self_to_sharer += gas_heat_capacity else heat_capacity_sharer_to_self -= gas_heat_capacity //subtract here instead of adding the absolute value because we know that delta is negative. - gas[MOLES] -= delta - sharergas[MOLES] += delta + cached_moles[gas_id] -= delta + sharer_cached_moles[gas_id] += delta moved_moles += delta abs_moved_moles += abs(delta) last_share = abs_moved_moles //THERMAL ENERGY TRANSFER - if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) + if(temp_delta_threshold) var/new_self_heat_capacity = old_self_heat_capacity + heat_capacity_sharer_to_self - heat_capacity_self_to_sharer var/new_sharer_heat_capacity = old_sharer_heat_capacity + heat_capacity_self_to_sharer - heat_capacity_sharer_to_self @@ -454,10 +445,8 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) sharer.garbage_collect() if(temperature_delta > MINIMUM_TEMPERATURE_TO_MOVE || abs(moved_moles) > MINIMUM_MOLES_DELTA_TO_MOVE) - var/our_moles - TOTAL_MOLES(cached_gases,our_moles) - var/their_moles - TOTAL_MOLES(sharer_gases,their_moles) + var/our_moles = values_sum(cached_moles) + var/their_moles = values_sum(sharer_cached_moles) return (temperature_archived*(our_moles + moved_moles) - sharer.temperature_archived*(their_moles - moved_moles)) * R_IDEAL_GAS_EQUATION / volume ///Performs temperature sharing calculations (via conduction) between two gas_mixtures assuming only 1 boundary length @@ -468,8 +457,8 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) sharer_temperature = sharer.temperature_archived var/temperature_delta = temperature_archived - sharer_temperature if(abs(temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) - var/self_heat_capacity = heat_capacity(ARCHIVE) - sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity(ARCHIVE) + var/self_heat_capacity = heat_capacity_archive() + sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity_archive() if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY)) // coefficient applied first because some turfs have very big heat caps. @@ -485,27 +474,26 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) //thermal energy of the system (self and sharer) is unchanged ///Compares sample to self to see if within acceptable ranges that group processing may be enabled +///Takes the bool as a second arg to read to read archived values for moles and temperature ///Returns: a string indicating what check failed, or "" if check passes -/datum/gas_mixture/proc/compare(datum/gas_mixture/sample) - var/list/sample_gases = sample.gases //accessing datum vars is slower than proc vars - var/list/cached_gases = gases - var/moles_sum = 0 - - for(var/id in cached_gases | sample_gases) // compare gases from either mixture - // Yes this is actually fast. I too hate it here - var/gas_moles = cached_gases[id]?[MOLES] || 0 - var/sample_moles = sample_gases[id]?[MOLES] || 0 - // Brief explanation. We are much more likely to not pass this first check then pass the first and fail the second - // Because of this, double calculating the delta is FASTER then inserting it into a var - if(abs(gas_moles - sample_moles) > MINIMUM_MOLES_DELTA_TO_MOVE) - if(abs(gas_moles - sample_moles) > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE) - return id - // similarly, we will rarely get cut off, so this is cheaper then doing it later - moles_sum += gas_moles - - if(moles_sum > MINIMUM_MOLES_DELTA_TO_MOVE) //Don't consider temp if there's not enough mols - if(abs(temperature - sample.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) - return "temp" +/datum/gas_mixture/proc/compare(datum/gas_mixture/sample, cmp_archive) + var/list/cached_moles = (cmp_archive) ? moles_archive : moles + var/list/sample_cached_moles = (cmp_archive) ? sample.moles_archive : sample.moles //accessing datum vars is slower than proc vars + + for(var/gas_id in cached_moles | sample_cached_moles) // compare gases from either mixture + var/gas_moles = cached_moles[gas_id] // it can be null, but everything coerce to 0 after, so we save JMP and Tst + var/sample_moles = sample_cached_moles[gas_id] + var/abs_delta = abs(gas_moles - sample_moles) + if((abs_delta > MINIMUM_MOLES_DELTA_TO_MOVE) && (abs_delta > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE)) + return gas_id + + if(values_sum(cached_moles) > MINIMUM_MOLES_DELTA_TO_MOVE) //Don't consider temp if there's not enough mols + if(cmp_archive) + if(abs(temperature_archived - sample.temperature_archived) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) + return "temp" + else + if(abs(temperature - sample.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) + return "temp" return "" @@ -513,8 +501,8 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) ///Returns: 1 if any reaction took place; 0 otherwise /datum/gas_mixture/proc/react(datum/holder) . = NO_REACTION - var/list/cached_gases = gases - if(!length(cached_gases)) + var/list/cached_moles = moles + if(!length(cached_moles)) return var/list/pre_formation = list() @@ -522,7 +510,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) var/list/post_formation = list() var/list/fires = list() var/list/gas_reactions = SSair.gas_reactions - for(var/gas_id in cached_gases) + for(var/gas_id in cached_moles) var/list/reaction_set = gas_reactions[gas_id] if(!reaction_set) continue @@ -537,7 +525,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) return //Fuck you - if(cached_gases[/datum/gas/hypernoblium] && cached_gases[/datum/gas/hypernoblium][MOLES] >= REACTION_OPPRESSION_THRESHOLD && temperature > 20) + if(cached_moles[/datum/gas/hypernoblium] >= REACTION_OPPRESSION_THRESHOLD && temperature > REACTION_OPPRESSION_MIN_TEMP) return STOP_REACTIONS reaction_results = new @@ -553,7 +541,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) for(var/id in reqs) if (id == "MIN_TEMP" || id == "MAX_TEMP") continue - if(!cached_gases[id] || cached_gases[id][MOLES] < reqs[id]) + if(cached_moles[id] < reqs[id]) continue reaction_loop //at this point, all requirements for the reaction are satisfied. we can now react() @@ -568,8 +556,8 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) /** * Returns the partial pressure of the gas in the breath based on BREATH_VOLUME * eg: - * Plas_PP = get_breath_partial_pressure(gas_mixture.gases[/datum/gas/plasma][MOLES]) - * O2_PP = get_breath_partial_pressure(gas_mixture.gases[/datum/gas/oxygen][MOLES]) + * Plas_PP = get_breath_partial_pressure(gas_mixture.moles[/datum/gas/plasma]) + * O2_PP = get_breath_partial_pressure(gas_mixture.moles[/datum/gas/oxygen]) * get_breath_partial_pressure(gas_mole_count) --> PV = nRT, P = nRT/V * * 10/20*5 = 2.5 @@ -753,3 +741,73 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) output_air.merge(removed) return TRUE + +/** + * Calls for electrolyzer_reaction reactions on the gas_mixture. + * Arguments: + * * working_power - working_power to use for the electrolyzer_reaction reactions. + * * electrolyzer_args - electrolysis arguments to use for the electrolyzer_reaction reactions. + */ +// /datum/gas_mixture/proc/electrolyze(working_power = 0, electrolyzer_args = list()) +// for(var/reaction in GLOB.electrolyzer_reactions) +// var/datum/electrolyzer_reaction/current_reaction = GLOB.electrolyzer_reactions[reaction] + +// if(!current_reaction.reaction_check(air_mixture = src, electrolyzer_args = electrolyzer_args)) +// continue + +// current_reaction.react(air_mixture = src, working_power = working_power, electrolyzer_args = electrolyzer_args) + +// garbage_collect() + +/// Convert a gas mixture to a string (ie. "o2=22;n2=82;TEMP=180") +/// Rounds all temperature and gases to 0.01 and skips any gases less than that amount +/datum/gas_mixture/proc/to_string() + var/list/cached_moles = moles + var/rounded_temp = round(temperature, 0.01) + + var/list/atmos_contents = list() + var/temperature_str = "TEMP=[num2text(rounded_temp)]" + + if(!length(cached_moles) || total_moles() < 0.01) + return temperature_str + + var/list/cached_gas_id = GAS_META[META_GAS_ID] + for(var/gas_id, gas_moles in cached_moles) + gas_moles = round(gas_moles, 0.01) + if(gas_moles >= 0.01) + atmos_contents += "[cached_gas_id[gas_id]]=[num2text(gas_moles)]" + + atmos_contents += temperature_str + return atmos_contents.Join(";") + +/** + * A simple helper proc that checks if the contents of a list of gases are within acceptable terms. + * + * Arguments: + * * acceptable_gas_bounds: An associated list of gas types and acceptable boundaries in moles. e.g. /datum/gas/oxygen = list(16, 30) + * * * if the assoc list is null, then it'll be considered a safe gas and won't return FALSE. + * * extraneous_gas_limit: If a gas not in gases is found, this is the limit above which the proc will return FALSE. + * + * Returns TRUE if the list of gases is acceptable, FALSE otherwise. + */ +/datum/gas_mixture/proc/check_gases(list/acceptable_gas_bounds, extraneous_gas_limit = 0.1) + SHOULD_BE_PURE(TRUE) + + var/list/gases_to_check = acceptable_gas_bounds.Copy() // thank you spaceman + var/list/cached_moles = moles + for(var/id in cached_moles) + var/gas_moles = cached_moles[id] + if(!(id in gases_to_check)) + if(gas_moles > extraneous_gas_limit) + return FALSE + continue + var/list/boundaries = gases_to_check[id] + if(boundaries && !ISINRANGE(gas_moles, boundaries[1], boundaries[2])) + return FALSE + gases_to_check -= id + ///Check that gases absent from the turf have a lower boundary of zero or none at all, otherwise return FALSE + for(var/id in gases_to_check) + var/list/boundaries = gases_to_check[id] + if(boundaries && boundaries[1] > 0) + return FALSE + return TRUE diff --git a/code/modules/atmospherics/gasmixtures/gas_types.dm b/code/modules/atmospherics/gasmixtures/gas_types.dm index ed655f1e59b4..0e002145b04c 100644 --- a/code/modules/atmospherics/gasmixtures/gas_types.dm +++ b/code/modules/atmospherics/gasmixtures/gas_types.dm @@ -1,21 +1,25 @@ /proc/meta_gas_list() - . = subtypesof(/datum/gas) - for(var/gas_path in .) - var/list/gas_info = new(8) - var/datum/gas/gas = gas_path - - gas_info[META_GAS_SPECIFIC_HEAT] = initial(gas.specific_heat) - gas_info[META_GAS_NAME] = initial(gas.name) + var/list/gas_info = new (META_GAS_LENGTH) + for (var/array_idx in 1 to gas_info.len) + gas_info[array_idx] = list() - gas_info[META_GAS_MOLES_VISIBLE] = initial(gas.moles_visible) - if(initial(gas.moles_visible) != null) - gas_info[META_GAS_OVERLAY] = generate_gas_overlays(0, SSmapping.max_plane_offset, gas) + var/list/gas_types = subtypesof(/datum/gas) + ASSERT(GAS_TYPE_COUNT == length(gas_types),\ + "GAS_TYPE_COUNT != length(subtypesof(gas_types)), if you added new gas please increment GAS_TYPE_COUNT") + for(var/gas_path in gas_types) + var/datum/gas/gas = gas_path + gas_info[META_GAS_SPECIFIC_HEAT][gas_path] = initial(gas.specific_heat) + gas_info[META_GAS_NAME][gas_path] = initial(gas.name) + gas_info[META_GAS_MOLES_VISIBLE][gas_path] = initial(gas.moles_visible) + if (gas_info[META_GAS_MOLES_VISIBLE][gas_path]) + gas_info[META_GAS_OVERLAY][gas_path] += generate_gas_overlays(0, SSmapping.max_plane_offset, gas) + gas_info[META_GAS_FUSION_POWER][gas_path] = initial(gas.fusion_power) + gas_info[META_GAS_DANGER][gas_path] = initial(gas.dangerous) + gas_info[META_GAS_ID][gas_path] = initial(gas.id) + gas_info[META_GAS_DESC][gas_path] = initial(gas.desc) - gas_info[META_GAS_FUSION_POWER] = initial(gas.fusion_power) - gas_info[META_GAS_DANGER] = initial(gas.dangerous) - gas_info[META_GAS_ID] = initial(gas.id) - gas_info[META_GAS_DESC] = initial(gas.desc) - .[gas_path] = gas_info + /datum/gas_mixture::gas_meta = gas_info // save the reference to the list + return gas_info /proc/generate_gas_overlays(old_offset, new_offset, datum/gas/gas_type) var/list/to_return = list() @@ -28,11 +32,11 @@ return to_return /proc/gas_id2path(id) - var/list/meta_gas = GLOB.meta_gas_info - if(id in meta_gas) + var/list/meta_gas_id = GLOB.meta_gas_info[META_GAS_ID] + if(id in meta_gas_id) return id - for(var/path in meta_gas) - if(meta_gas[path][META_GAS_ID] == id) + for(var/path in meta_gas_id) + if(meta_gas_id[path] == id) return path return "" diff --git a/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm b/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm index ab0517f9d02c..dce8ab87ed5d 100644 --- a/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm +++ b/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm @@ -10,9 +10,9 @@ garbage_collect() /datum/gas_mixture/immutable/garbage_collect() - temperature = initial_temperature - temperature_archived = initial_temperature - gases.Cut() + temperature = temperature_archived = initial_temperature + moles.Cut() + moles_archive.Cut() /datum/gas_mixture/immutable/archive() return TRUE //nothing changes, so we do nothing and the archive is successful @@ -58,13 +58,11 @@ /datum/gas_mixture/immutable/planetary var/list/initial_gas = list() +// Intentionally duplicate code to save microseconds on a call to parent /datum/gas_mixture/immutable/planetary/garbage_collect() - ..() - gases.Cut() - for(var/id in initial_gas) - ADD_GAS(id, gases) - gases[id][MOLES] = initial_gas[id][MOLES] - gases[id][ARCHIVE] = initial_gas[id][ARCHIVE] + temperature = temperature_archived = initial_temperature + moles = initial_gas.Copy() + moles_archive = initial_gas.Copy() /datum/gas_mixture/immutable/planetary/proc/parse_string_immutable(gas_string) //I know I know, I need this tho gas_string = SSair.preprocess_gas_string(gas_string) @@ -81,13 +79,11 @@ var/path = id if(!ispath(path)) path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around - ADD_GAS(path, mix) - mix[path][MOLES] = text2num(gas[id]) - mix[path][ARCHIVE] = mix[path][MOLES] - - for(var/id in mix) - ADD_GAS(id, gases) - gases[id][MOLES] = mix[id][MOLES] - gases[id][ARCHIVE] = mix[id][MOLES] + mix[path] = text2num(gas[id]) + + var/list/cached_moles = moles + var/list/cached_moles_archive = moles_archive + for(var/gas_id in mix) + cached_moles[gas_id] = cached_moles_archive[gas_id] = mix[gas_id] diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index d00fdb1edfbd..79f85379c4db 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -5,7 +5,7 @@ var/list/priority_reactions = list() //Builds a list of gas id to reaction group - for(var/gas_id in GLOB.meta_gas_info) + for(var/gas_id in GLOB.meta_gas_info[META_GAS_ID]) priority_reactions[gas_id] = list( /* PRIORITY_PRE_FORMATION = */ list(), /* PRIORITY_FORMATION = */ list(), @@ -27,7 +27,7 @@ priority_reactions[reaction_key][reaction.priority_group] += reaction //Culls empty gases - for(var/gas_id in GLOB.meta_gas_info) + for(var/gas_id in GLOB.meta_gas_info[META_GAS_ID]) var/passed = FALSE for(var/list/priority_grouping in priority_reactions[gas_id]) if(length(priority_grouping)) @@ -116,7 +116,7 @@ consumed = MOLES_GAS_VISIBLE if(consumed) - air.gases[/datum/gas/water_vapor][MOLES] -= consumed + air.moles[/datum/gas/water_vapor] -= consumed SET_REACTION_RESULTS(consumed) . = REACTING @@ -139,16 +139,17 @@ ) /datum/gas_reaction/miaster/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases + var/list/cached_moles = air.moles + var/water_vapor_moles = cached_moles[/datum/gas/water_vapor] + var/miasma_moles = cached_moles[/datum/gas/miasma] // As the name says it, it needs to be dry - if(cached_gases[/datum/gas/water_vapor] && cached_gases[/datum/gas/water_vapor][MOLES] / air.total_moles() > MIASTER_STERILIZATION_MAX_HUMIDITY) + if(water_vapor_moles && water_vapor_moles / air.total_moles() > MIASTER_STERILIZATION_MAX_HUMIDITY) return NO_REACTION //Replace miasma with oxygen - var/cleaned_air = min(cached_gases[/datum/gas/miasma][MOLES], MIASTER_STERILIZATION_RATE_BASE + (air.temperature - MIASTER_STERILIZATION_TEMP) / MIASTER_STERILIZATION_RATE_SCALE) - cached_gases[/datum/gas/miasma][MOLES] -= cleaned_air - ASSERT_GAS(/datum/gas/oxygen, air) - cached_gases[/datum/gas/oxygen][MOLES] += cleaned_air + var/cleaned_air = min(miasma_moles, MIASTER_STERILIZATION_RATE_BASE + (air.temperature - MIASTER_STERILIZATION_TEMP) / MIASTER_STERILIZATION_RATE_SCALE) + cached_moles[/datum/gas/miasma] -= cleaned_air + cached_moles[/datum/gas/oxygen] += cleaned_air //Possibly burning a bit of organic matter through maillard reaction, so a *tiny* bit more heat would be understandable air.temperature += cleaned_air * MIASTER_STERILIZATION_ENERGY @@ -194,33 +195,30 @@ var/oxygen_burn_ratio = OXYGEN_BURN_RATIO_BASE - temperature_scale var/plasma_burn_rate = 0 var/super_saturation = FALSE // Whether we should make tritium. - var/list/cached_gases = air.gases //this speeds things up because accessing datum vars is slow - var/list/plasma = cached_gases[/datum/gas/plasma] - var/list/oxygen = cached_gases[/datum/gas/oxygen] - switch(cached_gases[/datum/gas/oxygen][MOLES] / cached_gases[/datum/gas/plasma][MOLES]) + var/list/cached_moles = air.moles //this speeds things up because accessing datum vars is slow + var/oxygen_moles = cached_moles[/datum/gas/oxygen] || 0 + var/plasma_moles = cached_moles[/datum/gas/plasma] || 0 + switch(oxygen_moles / plasma_moles) if(SUPER_SATURATION_THRESHOLD to INFINITY) - plasma_burn_rate = (cached_gases[/datum/gas/plasma][MOLES] / PLASMA_BURN_RATE_DELTA) * temperature_scale + plasma_burn_rate = (plasma_moles / PLASMA_BURN_RATE_DELTA) * temperature_scale super_saturation = TRUE // Begin to form tritium if(PLASMA_OXYGEN_FULLBURN to SUPER_SATURATION_THRESHOLD) - plasma_burn_rate = (cached_gases[/datum/gas/plasma][MOLES] / PLASMA_BURN_RATE_DELTA) * temperature_scale + plasma_burn_rate = (plasma_moles / PLASMA_BURN_RATE_DELTA) * temperature_scale else - plasma_burn_rate = ((cached_gases[/datum/gas/oxygen][MOLES] / PLASMA_OXYGEN_FULLBURN) / PLASMA_BURN_RATE_DELTA) * temperature_scale + plasma_burn_rate = ((oxygen_moles / PLASMA_OXYGEN_FULLBURN) / PLASMA_BURN_RATE_DELTA) * temperature_scale if(plasma_burn_rate < MINIMUM_HEAT_CAPACITY) return NO_REACTION var/old_heat_capacity = air.heat_capacity() - plasma_burn_rate = min(plasma_burn_rate, plasma[MOLES], oxygen[MOLES] * INVERSE(oxygen_burn_ratio)) //Ensures matter is conserved properly - plasma[MOLES] = QUANTIZE(plasma[MOLES] - plasma_burn_rate) - oxygen[MOLES] = QUANTIZE(oxygen[MOLES] - (plasma_burn_rate * oxygen_burn_ratio)) + plasma_burn_rate = min(plasma_burn_rate, plasma_moles, oxygen_moles * INVERSE(oxygen_burn_ratio)) //Ensures matter is conserved properly + cached_moles[/datum/gas/plasma] = QUANTIZE(plasma_moles - plasma_burn_rate) + cached_moles[/datum/gas/oxygen] = QUANTIZE(oxygen_moles - (plasma_burn_rate * oxygen_burn_ratio)) if(super_saturation) - ASSERT_GAS(/datum/gas/tritium, air) - cached_gases[/datum/gas/tritium][MOLES] += plasma_burn_rate + cached_moles[/datum/gas/tritium] += plasma_burn_rate else - ASSERT_GAS(/datum/gas/carbon_dioxide, air) - ASSERT_GAS(/datum/gas/water_vapor, air) - cached_gases[/datum/gas/carbon_dioxide][MOLES] += plasma_burn_rate * 0.75 - cached_gases[/datum/gas/water_vapor][MOLES] += plasma_burn_rate * 0.25 + cached_moles[/datum/gas/carbon_dioxide] += plasma_burn_rate * 0.75 + cached_moles[/datum/gas/water_vapor] += plasma_burn_rate * 0.25 SET_REACTION_RESULTS((plasma_burn_rate) * (1 + oxygen_burn_ratio)) @@ -261,18 +259,20 @@ ) /datum/gas_reaction/h2fire/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases //this speeds things up because accessing datum vars is slow + . = NO_REACTION + var/list/cached_moles = air.moles //this speeds things up because accessing datum vars is slow + var/hydrogen_moles = cached_moles[/datum/gas/hydrogen] + var/oxygen_moles = cached_moles[/datum/gas/oxygen] var/old_heat_capacity = air.heat_capacity() var/temperature = air.temperature - var/burned_fuel = min(cached_gases[/datum/gas/hydrogen][MOLES] / FIRE_HYDROGEN_BURN_RATE_DELTA, cached_gases[/datum/gas/oxygen][MOLES] / (FIRE_HYDROGEN_BURN_RATE_DELTA * HYDROGEN_OXYGEN_FULLBURN), cached_gases[/datum/gas/hydrogen][MOLES], cached_gases[/datum/gas/oxygen][MOLES] * INVERSE(0.5)) - if(burned_fuel <= 0 || cached_gases[/datum/gas/hydrogen][MOLES] - burned_fuel < 0 || cached_gases[/datum/gas/oxygen][MOLES] - burned_fuel * 0.5 < 0) //Shouldn't produce gas from nothing. - return NO_REACTION + var/burned_fuel = min(hydrogen_moles / FIRE_HYDROGEN_BURN_RATE_DELTA, oxygen_moles / (FIRE_HYDROGEN_BURN_RATE_DELTA * HYDROGEN_OXYGEN_FULLBURN), hydrogen_moles, oxygen_moles * INVERSE(0.5)) + if(burned_fuel <= 0 || hydrogen_moles - burned_fuel < 0 || oxygen_moles - burned_fuel * 0.5 < 0) //Shouldn't produce gas from nothing. + return - cached_gases[/datum/gas/hydrogen][MOLES] -= burned_fuel - cached_gases[/datum/gas/oxygen][MOLES] -= burned_fuel * 0.5 - ASSERT_GAS(/datum/gas/water_vapor, air) - cached_gases[/datum/gas/water_vapor][MOLES] += burned_fuel + cached_moles[/datum/gas/hydrogen] -= burned_fuel + cached_moles[/datum/gas/oxygen] -= burned_fuel * 0.5 + cached_moles[/datum/gas/water_vapor] += burned_fuel SET_REACTION_RESULTS(burned_fuel) @@ -315,18 +315,20 @@ ) /datum/gas_reaction/tritfire/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases //this speeds things up because accessing datum vars is slow + . = NO_REACTION + var/list/cached_moles = air.moles //this speeds things up because accessing datum vars is slow + var/tritium_moles = cached_moles[/datum/gas/tritium] + var/oxygen_moles = cached_moles[/datum/gas/oxygen] var/old_heat_capacity = air.heat_capacity() var/temperature = air.temperature - var/burned_fuel = min(cached_gases[/datum/gas/tritium][MOLES] / FIRE_TRITIUM_BURN_RATE_DELTA, cached_gases[/datum/gas/oxygen][MOLES] / (FIRE_TRITIUM_BURN_RATE_DELTA * TRITIUM_OXYGEN_FULLBURN), cached_gases[/datum/gas/tritium][MOLES], cached_gases[/datum/gas/oxygen][MOLES] * INVERSE(0.5)) - if(burned_fuel <= 0 || cached_gases[/datum/gas/tritium][MOLES] - burned_fuel < 0 || cached_gases[/datum/gas/oxygen][MOLES] - burned_fuel * 0.5 < 0) //Shouldn't produce gas from nothing. - return NO_REACTION + var/burned_fuel = min(tritium_moles / FIRE_TRITIUM_BURN_RATE_DELTA, oxygen_moles / (FIRE_TRITIUM_BURN_RATE_DELTA * TRITIUM_OXYGEN_FULLBURN), tritium_moles, oxygen_moles * INVERSE(0.5)) + if(burned_fuel <= 0 || tritium_moles - burned_fuel < 0 || oxygen_moles - burned_fuel * 0.5 < 0) //Shouldn't produce gas from nothing. + return - cached_gases[/datum/gas/tritium][MOLES] -= burned_fuel - cached_gases[/datum/gas/oxygen][MOLES] -= burned_fuel * 0.5 - ASSERT_GAS(/datum/gas/water_vapor, air) - cached_gases[/datum/gas/water_vapor][MOLES] += burned_fuel + cached_moles[/datum/gas/tritium] -= burned_fuel + cached_moles[/datum/gas/oxygen] -= burned_fuel * 0.5 + cached_moles[/datum/gas/water_vapor] += burned_fuel SET_REACTION_RESULTS(burned_fuel) @@ -391,21 +393,22 @@ var/oxygen_burn_ratio = OXYGEN_BURN_RATIO_BASE - temperature_scale var/freon_burn_rate - var/list/cached_gases = air.gases - if(cached_gases[/datum/gas/oxygen][MOLES] < cached_gases[/datum/gas/freon][MOLES] * FREON_OXYGEN_FULLBURN) - freon_burn_rate = ((cached_gases[/datum/gas/oxygen][MOLES] / FREON_OXYGEN_FULLBURN) / FREON_BURN_RATE_DELTA) * temperature_scale + var/list/cached_moles = air.moles + var/freon_moles = cached_moles[/datum/gas/freon] + var/oxygen_moles = cached_moles[/datum/gas/oxygen] + if(oxygen_moles < freon_moles * FREON_OXYGEN_FULLBURN) + freon_burn_rate = ((oxygen_moles / FREON_OXYGEN_FULLBURN) / FREON_BURN_RATE_DELTA) * temperature_scale else - freon_burn_rate = (cached_gases[/datum/gas/freon][MOLES] / FREON_BURN_RATE_DELTA) * temperature_scale + freon_burn_rate = (freon_moles / FREON_BURN_RATE_DELTA) * temperature_scale if (freon_burn_rate < MINIMUM_HEAT_CAPACITY) return NO_REACTION var/old_heat_capacity = air.heat_capacity() - freon_burn_rate = min(freon_burn_rate, cached_gases[/datum/gas/freon][MOLES], cached_gases[/datum/gas/oxygen][MOLES] * INVERSE(oxygen_burn_ratio)) //Ensures matter is conserved properly - cached_gases[/datum/gas/freon][MOLES] = QUANTIZE(cached_gases[/datum/gas/freon][MOLES] - freon_burn_rate) - cached_gases[/datum/gas/oxygen][MOLES] = QUANTIZE(cached_gases[/datum/gas/oxygen][MOLES] - (freon_burn_rate * oxygen_burn_ratio)) - ASSERT_GAS(/datum/gas/carbon_dioxide, air) - cached_gases[/datum/gas/carbon_dioxide][MOLES] += freon_burn_rate + freon_burn_rate = min(freon_burn_rate, freon_moles, oxygen_moles * INVERSE(oxygen_burn_ratio)) //Ensures matter is conserved properly + cached_moles[/datum/gas/freon] = QUANTIZE(freon_moles - freon_burn_rate) + cached_moles[/datum/gas/oxygen] = QUANTIZE(oxygen_moles - (freon_burn_rate * oxygen_burn_ratio)) + cached_moles[/datum/gas/carbon_dioxide] += freon_burn_rate if(temperature < HOT_ICE_FORMATION_MAXIMUM_TEMPERATURE && temperature > HOT_ICE_FORMATION_MINIMUM_TEMPERATURE && prob(HOT_ICE_FORMATION_PROB) && isturf(holder)) new /obj/item/stack/sheet/hot_ice(holder) @@ -450,16 +453,17 @@ ) /datum/gas_reaction/nitrousformation/react(datum/gas_mixture/air) - var/list/cached_gases = air.gases - var/heat_efficency = min(cached_gases[/datum/gas/oxygen][MOLES] * INVERSE(0.5), cached_gases[/datum/gas/nitrogen][MOLES]) - if ((cached_gases[/datum/gas/oxygen][MOLES] - heat_efficency * 0.5 < 0 ) || (cached_gases[/datum/gas/nitrogen][MOLES] - heat_efficency < 0)) + var/list/cached_moles = air.moles + var/oxygen_moles = cached_moles[/datum/gas/oxygen] + var/nitrogen_moles = cached_moles[/datum/gas/nitrogen] + var/heat_efficiency = min(oxygen_moles * INVERSE(0.5), nitrogen_moles) + if ((oxygen_moles - heat_efficiency * 0.5 < 0 ) || (nitrogen_moles - heat_efficiency < 0)) return NO_REACTION // Shouldn't produce gas from nothing. var/old_heat_capacity = air.heat_capacity() - cached_gases[/datum/gas/oxygen][MOLES] -= heat_efficency * 0.5 - cached_gases[/datum/gas/nitrogen][MOLES] -= heat_efficency - ASSERT_GAS(/datum/gas/nitrous_oxide, air) - cached_gases[/datum/gas/nitrous_oxide][MOLES] += heat_efficency + cached_moles[/datum/gas/oxygen] -= heat_efficiency * 0.5 + cached_moles[/datum/gas/nitrogen] -= heat_efficiency + cached_moles[/datum/gas/nitrous_oxide] += heat_efficiency SET_REACTION_RESULTS(heat_efficency) var/energy_released = heat_efficency * N2O_FORMATION_ENERGY @@ -489,20 +493,17 @@ ) /datum/gas_reaction/nitrous_decomp/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases //this speeds things up because accessing datum vars is slow + var/list/cached_moles = air.moles //this speeds things up because accessing datum vars is slow + var/nitrous_oxide_moles = cached_moles[/datum/gas/nitrous_oxide] var/temperature = air.temperature - var/burned_fuel = (cached_gases[/datum/gas/nitrous_oxide][MOLES] / N2O_DECOMPOSITION_RATE_DIVISOR) * ((temperature - N2O_DECOMPOSITION_MIN_SCALE_TEMP) * (temperature - N2O_DECOMPOSITION_MAX_SCALE_TEMP) / (N2O_DECOMPOSITION_SCALE_DIVISOR)) - if(burned_fuel <= 0) - return NO_REACTION - if(cached_gases[/datum/gas/nitrous_oxide][MOLES] - burned_fuel < 0) + var/burned_fuel = (nitrous_oxide_moles / N2O_DECOMPOSITION_RATE_DIVISOR) * ((temperature - N2O_DECOMPOSITION_MIN_SCALE_TEMP) * (temperature - N2O_DECOMPOSITION_MAX_SCALE_TEMP) / (N2O_DECOMPOSITION_SCALE_DIVISOR)) + if(burned_fuel <= 0 || nitrous_oxide_moles - burned_fuel < 0) return NO_REACTION var/old_heat_capacity = air.heat_capacity() - cached_gases[/datum/gas/nitrous_oxide][MOLES] -= burned_fuel - ASSERT_GAS(/datum/gas/nitrogen, air) - cached_gases[/datum/gas/nitrogen][MOLES] += burned_fuel - ASSERT_GAS(/datum/gas/oxygen, air) - cached_gases[/datum/gas/oxygen][MOLES] += burned_fuel / 2 + cached_moles[/datum/gas/nitrous_oxide] -= burned_fuel + cached_moles[/datum/gas/nitrogen] += burned_fuel + cached_moles[/datum/gas/oxygen] += burned_fuel / 2 SET_REACTION_RESULTS(burned_fuel) var/energy_released = N2O_DECOMPOSITION_ENERGY * burned_fuel @@ -534,15 +535,17 @@ ) /datum/gas_reaction/bzformation/react(datum/gas_mixture/air) - var/list/cached_gases = air.gases + var/list/cached_moles = air.moles + var/nitrous_oxide_moles = cached_moles[/datum/gas/nitrous_oxide] + var/plasma_moles = cached_moles[/datum/gas/plasma] var/pressure = air.return_pressure() var/volume = air.return_volume() var/environment_effciency = volume/pressure //More volume and less pressure gives better rates - var/ratio_efficency = min(cached_gases[/datum/gas/nitrous_oxide][MOLES]/cached_gases[/datum/gas/plasma][MOLES], 1) //Less n2o than plasma give lower rates - var/nitrous_oxide_decomposed_factor = max(4 * (cached_gases[/datum/gas/plasma][MOLES] / (cached_gases[/datum/gas/nitrous_oxide][MOLES] + cached_gases[/datum/gas/plasma][MOLES]) - 0.75), 0) // Nitrous oxide decomposes when there are more than 3 parts plasma per n2o. - var/bz_formed = min(0.01 * ratio_efficency * environment_effciency, cached_gases[/datum/gas/nitrous_oxide][MOLES] * INVERSE(0.4), cached_gases[/datum/gas/plasma][MOLES] * INVERSE(0.8 * (1 - nitrous_oxide_decomposed_factor))) + var/ratio_efficency = min(nitrous_oxide_moles/plasma_moles, 1) //Less n2o than plasma give lower rates + var/nitrous_oxide_decomposed_factor = max(4 * (plasma_moles / (nitrous_oxide_moles + plasma_moles) - 0.75), 0) // Nitrous oxide decomposes when there are more than 3 parts plasma per n2o. + var/bz_formed = min(0.01 * ratio_efficency * environment_effciency, nitrous_oxide_moles * INVERSE(0.4), plasma_moles * INVERSE(0.8 * (1 - nitrous_oxide_decomposed_factor))) - if (cached_gases[/datum/gas/nitrous_oxide][MOLES] - bz_formed * 0.4 < 0 || cached_gases[/datum/gas/plasma][MOLES] - 0.8 * bz_formed * (1 - nitrous_oxide_decomposed_factor) < 0 || bz_formed <= 0) + if (nitrous_oxide_moles - bz_formed * 0.4 < 0 || plasma_moles - 0.8 * bz_formed * (1 - nitrous_oxide_decomposed_factor) < 0 || bz_formed <= 0) return NO_REACTION var/old_heat_capacity = air.heat_capacity() @@ -554,18 +557,13 @@ *N2O decomposes with its normal decomposition energy */ if (nitrous_oxide_decomposed_factor>0) - ASSERT_GAS(/datum/gas/nitrogen, air) - ASSERT_GAS(/datum/gas/oxygen, air) var/amount_decomposed = 0.4 * bz_formed * nitrous_oxide_decomposed_factor - cached_gases[/datum/gas/nitrogen] += amount_decomposed - cached_gases[/datum/gas/oxygen] += 0.5 * amount_decomposed - - ASSERT_GAS(/datum/gas/bz, air) - cached_gases[/datum/gas/bz][MOLES] += bz_formed * (1-nitrous_oxide_decomposed_factor) - cached_gases[/datum/gas/nitrous_oxide][MOLES] -= 0.4 * bz_formed - cached_gases[/datum/gas/plasma][MOLES] -= 0.8 * bz_formed * (1-nitrous_oxide_decomposed_factor) - + cached_moles[/datum/gas/nitrogen] += amount_decomposed + cached_moles[/datum/gas/oxygen] += 0.5 * amount_decomposed + cached_moles[/datum/gas/bz] += bz_formed * (1-nitrous_oxide_decomposed_factor) + cached_moles[/datum/gas/nitrous_oxide] -= 0.4 * bz_formed + cached_moles[/datum/gas/plasma] -= 0.8 * bz_formed * (1-nitrous_oxide_decomposed_factor) SET_REACTION_RESULTS(bz_formed) var/energy_released = bz_formed * (BZ_FORMATION_ENERGY + nitrous_oxide_decomposed_factor * (N2O_DECOMPOSITION_ENERGY - BZ_FORMATION_ENERGY)) @@ -599,19 +597,20 @@ ) /datum/gas_reaction/pluox_formation/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases - var/produced_amount = min(PLUOXIUM_FORMATION_MAX_RATE, cached_gases[/datum/gas/carbon_dioxide][MOLES], cached_gases[/datum/gas/oxygen][MOLES] * INVERSE(0.5), cached_gases[/datum/gas/tritium][MOLES] * INVERSE(0.01)) - if (produced_amount <= 0 || cached_gases[/datum/gas/carbon_dioxide][MOLES] - produced_amount < 0 || cached_gases[/datum/gas/oxygen][MOLES] - produced_amount * 0.5 < 0 || cached_gases[/datum/gas/tritium][MOLES] - produced_amount * 0.01 < 0) + var/list/cached_moles = air.moles + var/carbon_dioxide_moles = cached_moles[/datum/gas/carbon_dioxide] + var/oxygen_moles = cached_moles[/datum/gas/oxygen] + var/tritium_moles = cached_moles[/datum/gas/tritium] + var/produced_amount = min(PLUOXIUM_FORMATION_MAX_RATE, carbon_dioxide_moles, oxygen_moles * INVERSE(0.5), tritium_moles * INVERSE(0.01)) + if (produced_amount <= 0 || carbon_dioxide_moles - produced_amount < 0 || oxygen_moles - produced_amount * 0.5 < 0 || tritium_moles - produced_amount * 0.01 < 0) return NO_REACTION var/old_heat_capacity = air.heat_capacity() - cached_gases[/datum/gas/carbon_dioxide][MOLES] -= produced_amount - cached_gases[/datum/gas/oxygen][MOLES] -= produced_amount * 0.5 - cached_gases[/datum/gas/tritium][MOLES] -= produced_amount * 0.01 - ASSERT_GAS(/datum/gas/pluoxium, air) - cached_gases[/datum/gas/pluoxium][MOLES] += produced_amount - ASSERT_GAS(/datum/gas/hydrogen, air) - cached_gases[/datum/gas/hydrogen][MOLES] += produced_amount * 0.01 + cached_moles[/datum/gas/carbon_dioxide] -= produced_amount + cached_moles[/datum/gas/oxygen] -= produced_amount * 0.5 + cached_moles[/datum/gas/tritium] -= produced_amount * 0.01 + cached_moles[/datum/gas/pluoxium] += produced_amount + cached_moles[/datum/gas/hydrogen] += produced_amount * 0.01 SET_REACTION_RESULTS(produced_amount) var/energy_released = produced_amount * PLUOXIUM_FORMATION_ENERGY @@ -645,19 +644,21 @@ ) /datum/gas_reaction/nitrium_formation/react(datum/gas_mixture/air) - var/list/cached_gases = air.gases + var/list/cached_moles = air.moles + var/tritium_moles = cached_moles[/datum/gas/tritium] + var/nitrogen_moles = cached_moles[/datum/gas/nitrogen] + var/bz_moles = cached_moles[/datum/gas/bz] var/temperature = air.temperature - var/heat_efficency = min(temperature / NITRIUM_FORMATION_TEMP_DIVISOR, cached_gases[/datum/gas/tritium][MOLES], cached_gases[/datum/gas/nitrogen][MOLES], cached_gases[/datum/gas/bz][MOLES] * INVERSE(0.05)) + var/heat_efficiency = min(temperature / NITRIUM_FORMATION_TEMP_DIVISOR, tritium_moles, nitrogen_moles, bz_moles * INVERSE(0.05)) - if( heat_efficiency <= 0 || (cached_gases[/datum/gas/tritium][MOLES] - heat_efficiency < 0 ) || (cached_gases[/datum/gas/nitrogen][MOLES] - heat_efficiency < 0) || (cached_gases[/datum/gas/bz][MOLES] - heat_efficiency * 0.05 < 0)) //Shouldn't produce gas from nothing. + if( heat_efficiency <= 0 || (tritium_moles - heat_efficiency < 0 ) || (nitrogen_moles - heat_efficiency < 0) || (bz_moles - heat_efficiency * 0.05 < 0)) //Shouldn't produce gas from nothing. return NO_REACTION var/old_heat_capacity = air.heat_capacity() - ASSERT_GAS(/datum/gas/nitrium, air) - cached_gases[/datum/gas/tritium][MOLES] -= heat_efficiency - cached_gases[/datum/gas/nitrogen][MOLES] -= heat_efficiency - cached_gases[/datum/gas/bz][MOLES] -= heat_efficiency * 0.05 //bz gets consumed to balance the nitrium production and not make it too common and/or easy - cached_gases[/datum/gas/nitrium][MOLES] += heat_efficiency + cached_moles[/datum/gas/tritium] -= heat_efficiency + cached_moles[/datum/gas/nitrogen] -= heat_efficiency + cached_moles[/datum/gas/bz] -= heat_efficiency * 0.05 //bz gets consumed to balance the nitrium production and not make it too common and/or easy + cached_moles[/datum/gas/nitrium] += heat_efficiency SET_REACTION_RESULTS(heat_efficiency) @@ -689,20 +690,21 @@ ) /datum/gas_reaction/nitrium_decomposition/react(datum/gas_mixture/air) - var/list/cached_gases = air.gases + var/list/cached_moles = air.moles + var/nitrium_moles = cached_moles[/datum/gas/nitrium] var/temperature = air.temperature - //This reaction is agressively slow. like, a tenth of a mole per fire slow. Keep that in mind - var/heat_efficency = min(temperature / NITRIUM_DECOMPOSITION_TEMP_DIVISOR, cached_gases[/datum/gas/nitrium][MOLES]) + //This reaction is aggressively slow. like, a tenth of a mole per fire slow. Keep that in mind + var/heat_efficiency = min(temperature / NITRIUM_DECOMPOSITION_TEMP_DIVISOR, nitrium_moles) - if (heat_efficency <= 0 || (cached_gases[/datum/gas/nitrium][MOLES] - heat_efficency < 0)) //Shouldn't produce gas from nothing. + if (heat_efficiency <= 0 || (nitrium_moles - heat_efficiency < 0)) //Shouldn't produce gas from nothing. return NO_REACTION var/old_heat_capacity = air.heat_capacity() air.assert_gases(/datum/gas/nitrogen, /datum/gas/hydrogen) - cached_gases[/datum/gas/nitrium][MOLES] -= heat_efficency - cached_gases[/datum/gas/hydrogen][MOLES] += heat_efficency - cached_gases[/datum/gas/nitrogen][MOLES] += heat_efficency + cached_moles[/datum/gas/nitrium] -= heat_efficiency + cached_moles[/datum/gas/hydrogen] += heat_efficiency + cached_moles[/datum/gas/nitrogen] += heat_efficiency SET_REACTION_RESULTS(heat_efficency) var/energy_released = heat_efficency * NITRIUM_DECOMPOSITION_ENERGY @@ -733,24 +735,26 @@ ) /datum/gas_reaction/freonformation/react(datum/gas_mixture/air) - var/list/cached_gases = air.gases + var/list/cached_moles = air.moles + var/plasma_moles = cached_moles[/datum/gas/plasma] + var/carbon_dioxide_moles = cached_moles[/datum/gas/carbon_dioxide] + var/bz_moles = cached_moles[/datum/gas/bz] var/temperature = air.temperature - var/minimal_mole_factor = min(cached_gases[/datum/gas/plasma][MOLES] * INVERSE(0.6), cached_gases[/datum/gas/bz][MOLES] * INVERSE(0.1), cached_gases[/datum/gas/carbon_dioxide][MOLES] * INVERSE(0.3)) + var/minimal_mole_factor = min(plasma_moles * INVERSE(0.6), bz_moles * INVERSE(0.1), carbon_dioxide_moles * INVERSE(0.3)) var/equation_first_part = NUM_E ** (-(((temperature - 800) / 200) ** 2)) var/equation_second_part = 3 / (1 + NUM_E ** (-0.001 * (temperature - 6000))) var/heat_factor = equation_first_part + equation_second_part - var/freon_formed = min(heat_factor * minimal_mole_factor * 0.05, cached_gases[/datum/gas/plasma][MOLES] * INVERSE(0.6), cached_gases[/datum/gas/carbon_dioxide][MOLES] * INVERSE(0.3), cached_gases[/datum/gas/bz][MOLES] * INVERSE(0.1)) - if (freon_formed <= 0 || (cached_gases[/datum/gas/plasma][MOLES] - freon_formed * 0.6 < 0 ) || (cached_gases[/datum/gas/carbon_dioxide][MOLES] - freon_formed * 0.3 < 0) || (cached_gases[/datum/gas/bz][MOLES] - freon_formed * 0.1 < 0)) //Shouldn't produce gas from nothing. + var/freon_formed = min(heat_factor * minimal_mole_factor * 0.05, plasma_moles * INVERSE(0.6), carbon_dioxide_moles * INVERSE(0.3), bz_moles * INVERSE(0.1)) + if (freon_formed <= 0 || (plasma_moles - freon_formed * 0.6 < 0 ) || (carbon_dioxide_moles - freon_formed * 0.3 < 0) || (bz_moles - freon_formed * 0.1 < 0)) //Shouldn't produce gas from nothing. return NO_REACTION var/old_heat_capacity = air.heat_capacity() - ASSERT_GAS(/datum/gas/freon, air) - cached_gases[/datum/gas/plasma][MOLES] -= freon_formed * 0.6 - cached_gases[/datum/gas/carbon_dioxide][MOLES] -= freon_formed * 0.3 - cached_gases[/datum/gas/bz][MOLES] -= freon_formed * 0.1 - cached_gases[/datum/gas/freon][MOLES] += freon_formed + cached_moles[/datum/gas/plasma] -= freon_formed * 0.6 + cached_moles[/datum/gas/carbon_dioxide] -= freon_formed * 0.3 + cached_moles[/datum/gas/bz] -= freon_formed * 0.1 + cached_moles[/datum/gas/freon] += freon_formed SET_REACTION_RESULTS(freon_formed) @@ -784,24 +788,29 @@ ) /datum/gas_reaction/nobliumformation/react(datum/gas_mixture/air) - var/list/cached_gases = air.gases + . = NO_REACTION + var/list/cached_moles = air.moles + var/nitrogen_moles = cached_moles[/datum/gas/nitrogen] + var/tritium_moles = cached_moles[/datum/gas/tritium] /// List of gases we will assert, and possibly garbage collect. var/list/asserted_gases = list(/datum/gas/hypernoblium, /datum/gas/bz) air.assert_gases(arglist(asserted_gases)) - var/reduction_factor = clamp(cached_gases[/datum/gas/tritium][MOLES] / (cached_gases[/datum/gas/tritium][MOLES] + cached_gases[/datum/gas/bz][MOLES]), 0.001 , 1) //reduces trit consumption in presence of bz upward to 0.1% reduction - var/nob_formed = min((cached_gases[/datum/gas/nitrogen][MOLES] + cached_gases[/datum/gas/tritium][MOLES]) * 0.01, cached_gases[/datum/gas/tritium][MOLES] * INVERSE(5 * reduction_factor), cached_gases[/datum/gas/nitrogen][MOLES] * INVERSE(10)) + var/bz_moles = cached_moles[/datum/gas/bz] + var/reduction_factor = clamp(tritium_moles / (tritium_moles + bz_moles), 0.001 , 1) //reduces trit consumption in presence of bz upward to 0.1% reduction + var/nob_formed = min((nitrogen_moles + tritium_moles) * 0.01, tritium_moles * INVERSE(5 * reduction_factor), nitrogen_moles * INVERSE(10)) - if (nob_formed <= 0 || (cached_gases[/datum/gas/tritium][MOLES] - 5 * nob_formed * reduction_factor < 0) || (cached_gases[/datum/gas/nitrogen][MOLES] - 10 * nob_formed < 0)) - air.garbage_collect(arglist(asserted_gases)) - return NO_REACTION + //calling QUANTIZE on results to round very small floating point values. + if (QUANTIZE(nob_formed) <= 0 || (QUANTIZE(tritium_moles - 5 * nob_formed * reduction_factor) < 0) || (QUANTIZE(nitrogen_moles - 10 * nob_formed) < 0)) + air.garbage_collect() + return var/old_heat_capacity = air.heat_capacity() - cached_gases[/datum/gas/tritium][MOLES] -= 5 * nob_formed * reduction_factor - cached_gases[/datum/gas/nitrogen][MOLES] -= 10 * nob_formed - cached_gases[/datum/gas/hypernoblium][MOLES] += nob_formed // I'm not going to nitpick, but N20H10 feels like it should be an explosive more than anything. + cached_moles[/datum/gas/tritium] -= 5 * nob_formed * reduction_factor + cached_moles[/datum/gas/nitrogen] -= 10 * nob_formed + cached_moles[/datum/gas/hypernoblium] += nob_formed // I'm not going to nitpick, but N20H10 feels like it should be an explosive more than anything. SET_REACTION_RESULTS(nob_formed) - var/energy_released = nob_formed * (NOBLIUM_FORMATION_ENERGY / (max(cached_gases[/datum/gas/bz][MOLES], 1))) + var/energy_released = nob_formed * NOBLIUM_FORMATION_ENERGY / max(bz_moles, 1) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air.temperature = max(((air.temperature * old_heat_capacity + energy_released) / new_heat_capacity), TCMB) @@ -831,18 +840,20 @@ ) /datum/gas_reaction/halon_o2removal/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases + . = NO_REACTION + var/list/cached_moles = air.moles + var/halon_moles = cached_moles[/datum/gas/halon] + var/oxygen_moles = cached_moles[/datum/gas/oxygen] var/temperature = air.temperature - var/heat_efficency = min(temperature / ( FIRE_MINIMUM_TEMPERATURE_TO_EXIST * 10), cached_gases[/datum/gas/halon][MOLES], cached_gases[/datum/gas/oxygen][MOLES] * INVERSE(20)) - if (heat_efficency <= 0 || (cached_gases[/datum/gas/halon][MOLES] - heat_efficency < 0 ) || (cached_gases[/datum/gas/oxygen][MOLES] - heat_efficency * 20 < 0)) //Shouldn't produce gas from nothing. - return NO_REACTION + var/heat_efficiency = min(temperature / HALON_COMBUSTION_TEMPERATURE_SCALE, halon_moles, oxygen_moles * INVERSE(20)) + if (heat_efficiency <= 0 || (halon_moles - heat_efficiency < 0 ) || (oxygen_moles - heat_efficiency * 20 < 0)) //Shouldn't produce gas from nothing. + return var/old_heat_capacity = air.heat_capacity() - ASSERT_GAS(/datum/gas/carbon_dioxide, air) - cached_gases[/datum/gas/halon][MOLES] -= heat_efficency - cached_gases[/datum/gas/oxygen][MOLES] -= heat_efficency * 20 - cached_gases[/datum/gas/carbon_dioxide][MOLES] += heat_efficency * 5 + cached_moles[/datum/gas/halon] -= heat_efficiency + cached_moles[/datum/gas/oxygen] -= heat_efficiency * 20 + cached_moles[/datum/gas/pluoxium] += heat_efficiency * 2.5 SET_REACTION_RESULTS(heat_efficency * 5) var/energy_used = heat_efficency * HALON_COMBUSTION_ENERGY @@ -874,17 +885,18 @@ ) /datum/gas_reaction/healium_formation/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases + var/list/cached_moles = air.moles + var/bz_moles = cached_moles[/datum/gas/bz] + var/freon_moles = cached_moles[/datum/gas/freon] var/temperature = air.temperature - var/heat_efficency = min(temperature * 0.3, cached_gases[/datum/gas/freon][MOLES] * INVERSE(2.75), cached_gases[/datum/gas/bz][MOLES] * INVERSE(0.25)) - if (heat_efficency <= 0 || (cached_gases[/datum/gas/freon][MOLES] - heat_efficency * 2.75 < 0 ) || (cached_gases[/datum/gas/bz][MOLES] - heat_efficency * 0.25 < 0)) //Shouldn't produce gas from nothing. + var/heat_efficiency = min(temperature * 0.3, freon_moles * INVERSE(2.75), bz_moles * INVERSE(0.25)) + if (heat_efficiency <= 0 || (freon_moles - heat_efficiency * 2.75 < 0 ) || (bz_moles - heat_efficiency * 0.25 < 0)) //Shouldn't produce gas from nothing. return NO_REACTION var/old_heat_capacity = air.heat_capacity() - ASSERT_GAS(/datum/gas/healium, air) - cached_gases[/datum/gas/freon][MOLES] -= heat_efficency * 2.75 - cached_gases[/datum/gas/bz][MOLES] -= heat_efficency * 0.25 - cached_gases[/datum/gas/healium][MOLES] += heat_efficency * 3 + cached_moles[/datum/gas/freon] -= heat_efficiency * 2.75 + cached_moles[/datum/gas/bz] -= heat_efficiency * 0.25 + cached_moles[/datum/gas/healium] += heat_efficiency * 3 SET_REACTION_RESULTS(heat_efficency * 3) var/energy_released = heat_efficency * HEALIUM_FORMATION_ENERGY @@ -914,18 +926,19 @@ ) /datum/gas_reaction/zauker_formation/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases + var/list/cached_moles = air.moles + var/hypernoblium_moles = cached_moles[/datum/gas/hypernoblium] + var/nitrium_moles = cached_moles[/datum/gas/nitrium] var/temperature = air.temperature - var/heat_efficency = min(temperature * ZAUKER_FORMATION_TEMPERATURE_SCALE, cached_gases[/datum/gas/hypernoblium][MOLES] * INVERSE(0.01), cached_gases[/datum/gas/nitrium][MOLES] * INVERSE(0.5)) - if (heat_efficency <= 0 || (cached_gases[/datum/gas/hypernoblium][MOLES] - heat_efficency * 0.01 < 0 ) || (cached_gases[/datum/gas/nitrium][MOLES] - heat_efficency * 0.5 < 0)) //Shouldn't produce gas from nothing. + var/heat_efficiency = min(temperature * ZAUKER_FORMATION_TEMPERATURE_SCALE, hypernoblium_moles * INVERSE(0.01), nitrium_moles * INVERSE(0.5)) + if (heat_efficiency <= 0 || (hypernoblium_moles - heat_efficiency * 0.01 < 0 ) || (nitrium_moles - heat_efficiency * 0.5 < 0)) //Shouldn't produce gas from nothing. return NO_REACTION var/old_heat_capacity = air.heat_capacity() - ASSERT_GAS(/datum/gas/zauker, air) - cached_gases[/datum/gas/hypernoblium][MOLES] -= heat_efficency * 0.01 - cached_gases[/datum/gas/nitrium][MOLES] -= heat_efficency * 0.5 - cached_gases[/datum/gas/zauker][MOLES] += heat_efficency * 0.5 + cached_moles[/datum/gas/hypernoblium] -= heat_efficiency * 0.01 + cached_moles[/datum/gas/nitrium] -= heat_efficiency * 0.5 + cached_moles[/datum/gas/zauker] += heat_efficiency * 0.5 SET_REACTION_RESULTS(heat_efficency * 0.5) var/energy_used = heat_efficency * ZAUKER_FORMATION_ENERGY @@ -954,17 +967,17 @@ ) /datum/gas_reaction/zauker_decomp/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases //this speeds things up because accessing datum vars is slow - var/burned_fuel = min(ZAUKER_DECOMPOSITION_MAX_RATE, cached_gases[/datum/gas/nitrogen][MOLES], cached_gases[/datum/gas/zauker][MOLES]) - if (burned_fuel <= 0 || cached_gases[/datum/gas/zauker][MOLES] - burned_fuel < 0) + var/list/cached_moles = air.moles //this speeds things up because accessing datum vars is slow + var/nitrogen_moles = cached_moles[/datum/gas/nitrogen] + var/zauker_moles = cached_moles[/datum/gas/zauker] + var/burned_fuel = min(ZAUKER_DECOMPOSITION_MAX_RATE, nitrogen_moles, zauker_moles) + if (burned_fuel <= 0 || zauker_moles - burned_fuel < 0) return NO_REACTION var/old_heat_capacity = air.heat_capacity() - cached_gases[/datum/gas/zauker][MOLES] -= burned_fuel - ASSERT_GAS(/datum/gas/oxygen, air) - cached_gases[/datum/gas/oxygen][MOLES] += burned_fuel * 0.3 - ASSERT_GAS(/datum/gas/nitrogen, air) - cached_gases[/datum/gas/nitrogen][MOLES] += burned_fuel * 0.7 + cached_moles[/datum/gas/zauker] -= burned_fuel + cached_moles[/datum/gas/oxygen] += burned_fuel * 0.3 + cached_moles[/datum/gas/nitrogen] += burned_fuel * 0.7 SET_REACTION_RESULTS(burned_fuel) var/energy_released = ZAUKER_DECOMPOSITION_ENERGY * burned_fuel @@ -996,18 +1009,19 @@ ) /datum/gas_reaction/proto_nitrate_formation/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases + var/list/cached_moles = air.moles + var/pluoxium_moles = cached_moles[/datum/gas/pluoxium] + var/hydrogen_moles = cached_moles[/datum/gas/hydrogen] var/temperature = air.temperature - var/heat_efficency = min(temperature * 0.005, cached_gases[/datum/gas/pluoxium][MOLES] * INVERSE(0.2), cached_gases[/datum/gas/hydrogen][MOLES] * INVERSE(2)) - if (heat_efficency <= 0 || (cached_gases[/datum/gas/pluoxium][MOLES] - heat_efficency * 0.2 < 0 ) || (cached_gases[/datum/gas/hydrogen][MOLES] - heat_efficency * 2 < 0)) //Shouldn't produce gas from nothing. + var/heat_efficiency = min(temperature * 0.005, pluoxium_moles * INVERSE(0.2), hydrogen_moles * INVERSE(2)) + if (heat_efficiency <= 0 || (pluoxium_moles - heat_efficiency * 0.2 < 0 ) || (hydrogen_moles - heat_efficiency * 2 < 0)) //Shouldn't produce gas from nothing. return NO_REACTION var/old_heat_capacity = air.heat_capacity() - ASSERT_GAS(/datum/gas/proto_nitrate, air) - cached_gases[/datum/gas/hydrogen][MOLES] -= heat_efficency * 2 - cached_gases[/datum/gas/pluoxium][MOLES] -= heat_efficency * 0.2 - cached_gases[/datum/gas/proto_nitrate][MOLES] += heat_efficency * 2.2 + cached_moles[/datum/gas/hydrogen] -= heat_efficiency * 2 + cached_moles[/datum/gas/pluoxium] -= heat_efficiency * 0.2 + cached_moles[/datum/gas/proto_nitrate] += heat_efficiency * 2.2 SET_REACTION_RESULTS(heat_efficency * 2.2) var/energy_released = heat_efficency * PN_FORMATION_ENERGY @@ -1035,14 +1049,16 @@ ) /datum/gas_reaction/proto_nitrate_hydrogen_response/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases - var/produced_amount = min(PN_HYDROGEN_CONVERSION_MAX_RATE, cached_gases[/datum/gas/hydrogen][MOLES], cached_gases[/datum/gas/proto_nitrate][MOLES]) - if (produced_amount <= 0 || cached_gases[/datum/gas/hydrogen][MOLES] - produced_amount < 0) + var/list/cached_moles = air.moles + var/proto_nitrate_moles = cached_moles[/datum/gas/proto_nitrate] + var/hydrogen_moles = cached_moles[/datum/gas/hydrogen] + var/produced_amount = min(PN_HYDROGEN_CONVERSION_MAX_RATE, hydrogen_moles, proto_nitrate_moles) + if (produced_amount <= 0 || hydrogen_moles - produced_amount < 0) return NO_REACTION var/old_heat_capacity = air.heat_capacity() - cached_gases[/datum/gas/hydrogen][MOLES] -= produced_amount - cached_gases[/datum/gas/proto_nitrate][MOLES] += produced_amount * 0.5 + cached_moles[/datum/gas/hydrogen] -= produced_amount + cached_moles[/datum/gas/proto_nitrate] += produced_amount * 0.5 SET_REACTION_RESULTS(produced_amount * 0.5) var/energy_used = produced_amount * PN_HYDROGEN_CONVERSION_ENERGY @@ -1073,17 +1089,19 @@ ) /datum/gas_reaction/proto_nitrate_tritium_response/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases + . = NO_REACTION + var/list/cached_moles = air.moles + var/proto_nitrate_moles = cached_moles[/datum/gas/proto_nitrate] + var/tritium_moles = cached_moles[/datum/gas/tritium] var/temperature = air.temperature - var/produced_amount = min(air.temperature / 34 * (cached_gases[/datum/gas/tritium][MOLES] * cached_gases[/datum/gas/proto_nitrate][MOLES]) / (cached_gases[/datum/gas/tritium][MOLES] + 10 * cached_gases[/datum/gas/proto_nitrate][MOLES]), cached_gases[/datum/gas/tritium][MOLES], cached_gases[/datum/gas/proto_nitrate][MOLES] * INVERSE(0.01)) - if(cached_gases[/datum/gas/tritium][MOLES] - produced_amount < 0 || cached_gases[/datum/gas/proto_nitrate][MOLES] - produced_amount * 0.01 < 0) - return NO_REACTION + var/produced_amount = min(temperature / 34 * (tritium_moles * proto_nitrate_moles) / (tritium_moles + 10 * proto_nitrate_moles), tritium_moles, proto_nitrate_moles * INVERSE(0.01)) + if(tritium_moles - produced_amount < 0 || proto_nitrate_moles - produced_amount * 0.01 < 0) + return var/old_heat_capacity = air.heat_capacity() - cached_gases[/datum/gas/proto_nitrate][MOLES] -= produced_amount * 0.01 - cached_gases[/datum/gas/tritium][MOLES] -= produced_amount - ASSERT_GAS(/datum/gas/hydrogen, air) - cached_gases[/datum/gas/hydrogen][MOLES] += produced_amount + cached_moles[/datum/gas/proto_nitrate] -= produced_amount * 0.01 + cached_moles[/datum/gas/tritium] -= produced_amount + cached_moles[/datum/gas/hydrogen] += produced_amount SET_REACTION_RESULTS(produced_amount) var/turf/open/location @@ -1122,20 +1140,20 @@ ) /datum/gas_reaction/proto_nitrate_bz_response/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases + . = NO_REACTION + var/list/cached_moles = air.moles + var/proto_nitrate_moles = cached_moles[/datum/gas/proto_nitrate] + var/bz_moles = cached_moles[/datum/gas/bz] var/temperature = air.temperature - var/consumed_amount = min(air.temperature / 2240 * cached_gases[/datum/gas/bz][MOLES] * cached_gases[/datum/gas/proto_nitrate][MOLES] / (cached_gases[/datum/gas/bz][MOLES] + cached_gases[/datum/gas/proto_nitrate][MOLES]), cached_gases[/datum/gas/bz][MOLES], cached_gases[/datum/gas/proto_nitrate][MOLES]) - if (consumed_amount <= 0 || cached_gases[/datum/gas/bz][MOLES] - consumed_amount < 0) - return NO_REACTION + var/consumed_amount = min(temperature / 2240 * bz_moles * proto_nitrate_moles / (bz_moles + proto_nitrate_moles), bz_moles, proto_nitrate_moles) + if (consumed_amount <= 0 || bz_moles - consumed_amount < 0) + return var/old_heat_capacity = air.heat_capacity() - cached_gases[/datum/gas/bz][MOLES] -= consumed_amount - ASSERT_GAS(/datum/gas/nitrogen, air) - cached_gases[/datum/gas/nitrogen][MOLES] += consumed_amount * 0.4 - ASSERT_GAS(/datum/gas/helium, air) - cached_gases[/datum/gas/helium][MOLES] += consumed_amount * 1.6 - ASSERT_GAS(/datum/gas/plasma, air) - cached_gases[/datum/gas/plasma][MOLES] += consumed_amount * 0.8 + cached_moles[/datum/gas/bz] -= consumed_amount + cached_moles[/datum/gas/nitrogen] += consumed_amount * 0.4 + cached_moles[/datum/gas/helium] += consumed_amount * 1.6 + cached_moles[/datum/gas/plasma] += consumed_amount * 0.8 SET_REACTION_RESULTS(consumed_amount) var/turf/open/location diff --git a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm index 40883eecad6e..b8bff006ff86 100644 --- a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm +++ b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm @@ -100,13 +100,14 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) tlv_collection = list() tlv_collection["pressure"] = new /datum/tlv/pressure tlv_collection["temperature"] = new /datum/tlv/temperature - var/list/meta_info = GLOB.meta_gas_info // shorthand - for(var/gas_path in meta_info) + + var/list/cached_gas_info = GLOB.meta_gas_info + for(var/datum/gas/gas_path as anything in cached_gas_info[META_GAS_ID]) if(ispath(gas_path, /datum/gas/oxygen)) tlv_collection[gas_path] = new /datum/tlv/oxygen else if(ispath(gas_path, /datum/gas/carbon_dioxide)) tlv_collection[gas_path] = new /datum/tlv/carbon_dioxide - else if(meta_info[gas_path][META_GAS_DANGER]) + else if(cached_gas_info[META_GAS_DANGER][gas_path]) tlv_collection[gas_path] = new /datum/tlv/dangerous else tlv_collection[gas_path] = new /datum/tlv/no_checks @@ -262,11 +263,10 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) "danger" = tlv_collection["temperature"].check_value(temp), )) if(total_moles) - for(var/gas_path in environment.gases) - var/moles = environment.gases[gas_path][MOLES] + for(var/gas_path, moles in environment.moles) var/portion = moles / total_moles data["envData"] += list(list( - "name" = GLOB.meta_gas_info[gas_path][META_GAS_NAME], + "name" = GLOB.meta_gas_info[META_GAS_NAME][gas_path], "value" = "[round(moles, 0.01)] moles / [round(100 * portion, 0.01)] % / [round(portion * pressure, 0.01)] kPa", "danger" = tlv_collection[gas_path].check_value(portion * pressure), )) @@ -282,7 +282,7 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) singular_tlv["name"] = "Temperature" singular_tlv["unit"] = "K" else - singular_tlv["name"] = GLOB.meta_gas_info[threshold][META_GAS_NAME] + singular_tlv["name"] = GLOB.meta_gas_info[META_GAS_NAME][threshold] singular_tlv["unit"] = "kPa" singular_tlv["id"] = threshold singular_tlv["warning_min"] = tlv.warning_min @@ -312,9 +312,9 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) data["scrubbers"] = list() for(var/obj/machinery/atmospherics/components/unary/vent_scrubber/scrubber as anything in my_area.air_scrubbers) var/list/filter_types = list() - for (var/path in GLOB.meta_gas_info) - var/list/gas = GLOB.meta_gas_info[path] - filter_types += list(list("gas_id" = gas[META_GAS_ID], "gas_name" = gas[META_GAS_NAME], "enabled" = (path in scrubber.filter_types))) + var/cached_gas_info = GLOB.meta_gas_info + for (var/path in cached_gas_info[META_GAS_ID]) + filter_types += list(list("gas_id" = cached_gas_info[META_GAS_ID][path], "gas_name" = cached_gas_info[META_GAS_NAME][path], "enabled" = (path in scrubber.filter_types))) data["scrubbers"] += list(list( "refID" = REF(scrubber), "long_name" = sanitize(scrubber.name), @@ -561,8 +561,9 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) danger_level = max(danger_level, tlv_collection["pressure"].check_value(pressure), tlv_collection["temperature"].check_value(temp)) if(total_moles) - for(var/gas_path in GLOB.meta_gas_info) - var/moles = environment.gases[gas_path]?[MOLES] || 0 + var/list/cached_gas_info = GLOB.meta_gas_info + for(var/datum/gas/gas_path as anything in cached_gas_info[META_GAS_ID]) + var/moles = environment.moles[gas_path] || 0 danger_level = max(danger_level, tlv_collection[gas_path].check_value(pressure * moles / total_moles)) if(danger_level) @@ -674,7 +675,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/airalarm, 27) tlv_collection["temperature"] = new /datum/tlv/no_checks tlv_collection["pressure"] = new /datum/tlv/no_checks - for(var/gas_path in GLOB.meta_gas_info) + for(var/gas_path in GLOB.meta_gas_info[META_GAS_ID]) tlv_collection[gas_path] = new /datum/tlv/no_checks ///Used for air alarm link helper, which connects air alarm to a sensor with corresponding chamber_id diff --git a/code/modules/atmospherics/machinery/air_alarm/air_alarm_circuit.dm b/code/modules/atmospherics/machinery/air_alarm/air_alarm_circuit.dm index 04c5f0c4bfed..286e416b501d 100644 --- a/code/modules/atmospherics/machinery/air_alarm/air_alarm_circuit.dm +++ b/code/modules/atmospherics/machinery/air_alarm/air_alarm_circuit.dm @@ -164,8 +164,9 @@ "Temperature" = "temperature" ) - for(var/gas_id in GLOB.meta_gas_info) - component_options[GLOB.meta_gas_info[gas_id][META_GAS_NAME]] = gas_id2path(gas_id) + var/cached_gas_info = GLOB.meta_gas_info + for(var/gas_id in cached_gas_info[META_GAS_ID]) + component_options[cached_gas_info[META_GAS_NAME][gas_id]] = gas_id2path(gas_id) air_alarm_options = add_option_port("Air Alarm Options", component_options) options_map = component_options @@ -230,7 +231,7 @@ pressure.set_output(round(environment.return_pressure())) temperature.set_output(round(environment.temperature)) if(ispath(options_map[current_option])) - gas_amount.set_output(round(environment.gases[options_map[current_option]][MOLES])) + gas_amount.set_output(round(environment.moles[options_map[current_option]])) update_received.set_output(COMPONENT_SIGNAL) @@ -351,8 +352,9 @@ . = ..() var/static/list/meta_data = list() if(length(meta_data) == 0) - for(var/typepath as anything in GLOB.meta_gas_info) - meta_data += GLOB.meta_gas_info[typepath][META_GAS_ID] + var/cached_gas_info = GLOB.meta_gas_info + for(var/typepath in cached_gas_info[META_GAS_ID]) + meta_data += cached_gas_info[META_GAS_ID][typepath] . += create_table_notices(meta_data, column_name = "Gas", column_name_plural = "Gases") /obj/item/circuit_component/air_alarm_scrubbers/proc/set_gas_to_filter(datum/port/input/port) diff --git a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm index 454ad98ab86e..9b50c7b11d5a 100644 --- a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm +++ b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm @@ -24,13 +24,13 @@ GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list()) /datum/electrolyzer_reaction/proc/reaction_check(datum/gas_mixture/air_mixture) var/temp = air_mixture.temperature - var/list/cached_gases = air_mixture.gases + var/list/cached_moles = air_mixture.moles if((requirements["MIN_TEMP"] && temp < requirements["MIN_TEMP"]) || (requirements["MAX_TEMP"] && temp > requirements["MAX_TEMP"])) return FALSE for(var/id in requirements) if (id == "MIN_TEMP" || id == "MAX_TEMP") continue - if(!cached_gases[id] || cached_gases[id][MOLES] < requirements[id]) + if(cached_moles[id] < requirements[id]) return FALSE return TRUE @@ -52,7 +52,7 @@ GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list()) var/old_heat_capacity = air_mixture.heat_capacity() - var/proportion = min(air_mixture.gases[/datum/gas/water_vapor][MOLES] * INVERSE(2), (2.5 * (working_power ** 2))) + var/proportion = min(air_mixture.moles[/datum/gas/water_vapor] * INVERSE(2), (2.5 * (working_power ** 2))) air_mixture.adjust_gas(/datum/gas/water_vapor, -proportion * 2) air_mixture.adjust_gas(/datum/gas/oxygen, proportion) air_mixture.adjust_gas(/datum/gas/hydrogen, proportion * 2) diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm index c6447166d223..ca7bd39eecd5 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm @@ -82,7 +82,7 @@ negative_temperature_multiplier = selected_fuel.negative_temperature_multiplier for(var/gas_id in selected_fuel.requirements | selected_fuel.primary_products) - var/amount = internal_fusion.gases[gas_id][MOLES] + var/amount = internal_fusion.moles[gas_id] fuel_list[gas_id] = amount scaled_fuel_list[gas_id] = max((amount - FUSION_MOLE_THRESHOLD) / scale_factor, 0) @@ -90,8 +90,7 @@ var/list/moderator_list = list() /// Scaled down moles of gases, no less than 0 var/list/scaled_moderator_list = list() - for(var/gas_id in moderator_internal.gases) - var/amount = moderator_internal.gases[gas_id][MOLES] + for(var/gas_id, amount in moderator_internal.moles) moderator_list[gas_id] = amount scaled_moderator_list[gas_id] = max((amount - FUSION_MOLE_THRESHOLD) / scale_factor, 0) @@ -102,11 +101,9 @@ //The size of the phase space hypertorus var/toroidal_size = (2 * PI) + TORADIANS(arctan((volume - TOROID_VOLUME_BREAKEVEN) / TOROID_VOLUME_BREAKEVEN)) //Calculation of the gas power, only for theoretical instability calculations - var/gas_power = 0 - for (var/gas_id in internal_fusion.gases) - gas_power += (internal_fusion.gases[gas_id][GAS_META][META_GAS_FUSION_POWER] * internal_fusion.gases[gas_id][MOLES]) - for (var/gas_id in moderator_internal.gases) - gas_power += (moderator_internal.gases[gas_id][GAS_META][META_GAS_FUSION_POWER] * moderator_internal.gases[gas_id][MOLES] * 0.75) + var/list/cached_fusion_power = GAS_META[META_GAS_FUSION_POWER] + var/gas_power = values_dot(cached_fusion_power, internal_fusion.moles) + gas_power += 0.75 * values_dot(cached_fusion_power, moderator_internal.moles) instability = MODULUS((gas_power * INSTABILITY_GAS_POWER_FACTOR)**2, toroidal_size) + (current_damper * 0.01) - iron_content * 0.05 //Effective reaction instability (determines if the energy is used/released) @@ -290,31 +287,31 @@ if(1) if(moderator_list[/datum/gas/plasma] > 100) internal_output.adjust_gas(/datum/gas/nitrous_oxide, scaled_production * 0.5) - moderator_internal.adjust_gas(/datum/gas/plasma, min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 0.85)) + moderator_internal.adjust_gas(/datum/gas/plasma, min(moderator_internal.moles[/datum/gas/plasma], scaled_production * 0.85)) if(moderator_list[/datum/gas/bz] > 150) internal_output.adjust_gas(/datum/gas/halon, scaled_production * 0.55) - moderator_internal.adjust_gas(/datum/gas/bz, min(moderator_internal.gases[/datum/gas/bz][MOLES], scaled_production * 0.95)) + moderator_internal.adjust_gas(/datum/gas/bz, min(moderator_internal.moles[/datum/gas/bz], scaled_production * 0.95)) if(2) if(moderator_list[/datum/gas/plasma] > 50) internal_output.adjust_gas(/datum/gas/bz, scaled_production * 1.8) - moderator_internal.adjust_gas(/datum/gas/plasma, min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.75)) + moderator_internal.adjust_gas(/datum/gas/plasma, min(moderator_internal.moles[/datum/gas/plasma], scaled_production * 1.75)) if(moderator_list[/datum/gas/proto_nitrate] > 20) radiation *= 1.55 heat_output *= 1.025 internal_output.adjust_gas(/datum/gas/nitrium, scaled_production * 1.05) - moderator_internal.adjust_gas(/datum/gas/proto_nitrate, min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.35)) + moderator_internal.adjust_gas(/datum/gas/proto_nitrate, min(moderator_internal.moles[/datum/gas/proto_nitrate], scaled_production * 1.35)) if(3, 4) if(moderator_list[/datum/gas/plasma] > 10) var/list/new_gases = list(/datum/gas/freon = scaled_production * 0.15, /datum/gas/nitrium = scaled_production * 1.05) internal_output.adjust_multiple_gases(new_gases) - moderator_internal.adjust_gas(/datum/gas/plasma, min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 0.45)) + moderator_internal.adjust_gas(/datum/gas/plasma, min(moderator_internal.moles[/datum/gas/plasma], scaled_production * 0.45)) if(moderator_list[/datum/gas/freon] > 50) heat_output *= 0.9 radiation *= 0.8 if(moderator_list[/datum/gas/proto_nitrate]> 15) var/list/new_gases = list(/datum/gas/nitrium = scaled_production * 1.25, /datum/gas/halon = scaled_production * 1.15) internal_output.adjust_multiple_gases(new_gases) - moderator_internal.adjust_gas(/datum/gas/proto_nitrate, min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.55)) + moderator_internal.adjust_gas(/datum/gas/proto_nitrate, min(moderator_internal.moles[/datum/gas/proto_nitrate], scaled_production * 1.55)) radiation *= 1.95 heat_output *= 1.25 if(moderator_list[/datum/gas/bz] > 100) @@ -325,14 +322,14 @@ if(5) if(moderator_list[/datum/gas/plasma] > 15) internal_output.adjust_gas(/datum/gas/freon, scaled_production *0.25) - moderator_internal.adjust_gas(/datum/gas/plasma,min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.45)) + moderator_internal.adjust_gas(/datum/gas/plasma,min(moderator_internal.moles[/datum/gas/plasma], scaled_production * 1.45)) if(moderator_list[/datum/gas/freon] > 500) heat_output *= 0.5 radiation *= 0.2 if(moderator_list[/datum/gas/proto_nitrate] > 50) var/list/new_gases = list(/datum/gas/nitrium = scaled_production * 1.95, /datum/gas/pluoxium = scaled_production) internal_output.adjust_multiple_gases(new_gases) - moderator_internal.adjust_gas(/datum/gas/proto_nitrate, min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.35)) + moderator_internal.adjust_gas(/datum/gas/proto_nitrate, min(moderator_internal.moles[/datum/gas/proto_nitrate], scaled_production * 1.35)) radiation *= 1.95 heat_output *= 1.25 if(moderator_list[/datum/gas/bz] > 100) @@ -342,18 +339,18 @@ if(moderator_list[/datum/gas/healium] > 100) if(critical_threshold_proximity > 400) critical_threshold_proximity = max(critical_threshold_proximity - (moderator_list[/datum/gas/healium] / 100 * seconds_per_tick ), 0) - moderator_internal.adjust_gas(/datum/gas/healium, -min(moderator_internal.gases[/datum/gas/healium][MOLES], scaled_production * 20)) + moderator_internal.adjust_gas(/datum/gas/healium, -min(moderator_internal.moles[/datum/gas/healium], scaled_production * 20)) if(moderator_internal.temperature < 1e7 || (moderator_list[/datum/gas/plasma] > 100 && moderator_list[/datum/gas/bz] > 50)) internal_output.adjust_gas(/datum/gas/antinoblium, dirty_production_rate * 0.9 / 0.065 * seconds_per_tick) if(6) internal_output.assert_gases(/datum/gas/antinoblium) if(moderator_list[/datum/gas/plasma] > 30) internal_output.adjust_gas(/datum/gas/bz, scaled_production * 1.15) - moderator_internal.adjust_gas(/datum/gas/plasma, -min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.45)) + moderator_internal.adjust_gas(/datum/gas/plasma, -min(moderator_internal.moles[/datum/gas/plasma], scaled_production * 1.45)) if(moderator_list[/datum/gas/proto_nitrate]) var/list/new_gases = list(/datum/gas/zauker = scaled_production * 5.35, /datum/gas/nitrium = scaled_production * 2.15) internal_output.adjust_multiple_gases(new_gases) - moderator_internal.adjust_gas(/datum/gas/proto_nitrate, -min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 3.35)) + moderator_internal.adjust_gas(/datum/gas/proto_nitrate, -min(moderator_internal.moles[/datum/gas/proto_nitrate], scaled_production * 3.35)) radiation *= 2 heat_output *= 2.25 if(moderator_list[/datum/gas/bz]) @@ -362,7 +359,7 @@ if(moderator_list[/datum/gas/healium] > 100) if(critical_threshold_proximity > 400) critical_threshold_proximity = max(critical_threshold_proximity - (moderator_list[/datum/gas/healium] / 100 * seconds_per_tick ), 0) - moderator_internal.adjust_gas(/datum/gas/healium, -min(moderator_internal.gases[/datum/gas/healium][MOLES], scaled_production * 20)) + moderator_internal.adjust_gas(/datum/gas/healium, -min(moderator_internal.moles[/datum/gas/healium], scaled_production * 20)) internal_fusion.adjust_gas(/datum/gas/antinoblium, dirty_production_rate * 0.01 / 0.095 * seconds_per_tick) //Modifies the internal_fusion temperature with the amount of heat output @@ -500,7 +497,7 @@ if(!waste_remove) return var/filtering_amount = moderator_scrubbing.len - for(var/gas in moderator_internal.gases & moderator_scrubbing) + for(var/gas in moderator_internal.moles & moderator_scrubbing) var/datum/gas_mixture/removed = moderator_internal.remove_specific(gas, (moderator_filtering_rate / filtering_amount) * seconds_per_tick) if(removed) linked_output.airs[1].merge(removed) @@ -508,8 +505,8 @@ if (selected_fuel) var/datum/gas_mixture/internal_remove for(var/gas_id in selected_fuel.primary_products) - if(internal_fusion.gases[gas_id][MOLES] > 0) - internal_remove = internal_fusion.remove_specific(gas_id, internal_fusion.gases[gas_id][MOLES] * (1 - (1 - 0.25) ** seconds_per_tick)) + if(internal_fusion.moles[gas_id] > 0) + internal_remove = internal_fusion.remove_specific(gas_id, internal_fusion.moles[gas_id] * (1 - (1 - 0.25) ** seconds_per_tick)) linked_output.airs[1].merge(internal_remove) internal_fusion.garbage_collect() moderator_internal.garbage_collect() diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm index 0eee62cdbeaf..49d21a597c13 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm @@ -235,14 +235,14 @@ //Internal Fusion gases var/list/fusion_gasdata = list() if(connected_core.internal_fusion.total_moles()) - for(var/gas_type in connected_core.internal_fusion.gases) + for(var/gas_type in connected_core.internal_fusion.moles) var/datum/gas/gas = gas_type fusion_gasdata.Add(list(list( "id"= initial(gas.id), - "amount" = round(connected_core.internal_fusion.gases[gas][MOLES], 0.01), + "amount" = round(connected_core.internal_fusion.moles[gas], 0.01), ))) else - for(var/gas_type in connected_core.internal_fusion.gases) + for(var/gas_type in connected_core.internal_fusion.moles) var/datum/gas/gas = gas_type fusion_gasdata.Add(list(list( "id"= initial(gas.id), @@ -251,14 +251,14 @@ //Moderator gases var/list/moderator_gasdata = list() if(connected_core.moderator_internal.total_moles()) - for(var/gas_type in connected_core.moderator_internal.gases) + for(var/gas_type in connected_core.moderator_internal.moles) var/datum/gas/gas = gas_type moderator_gasdata.Add(list(list( "id"= initial(gas.id), - "amount" = round(connected_core.moderator_internal.gases[gas][MOLES], 0.01), + "amount" = round(connected_core.moderator_internal.moles[gas], 0.01), ))) else - for(var/gas_type in connected_core.moderator_internal.gases) + for(var/gas_type in connected_core.moderator_internal.moles) var/datum/gas/gas = gas_type moderator_gasdata.Add(list(list( "id"= initial(gas.id), @@ -304,9 +304,13 @@ data["waste_remove"] = connected_core.waste_remove data["filter_types"] = list() - for(var/path in GLOB.meta_gas_info) - var/list/gas = GLOB.meta_gas_info[path] - data["filter_types"] += list(list("gas_id" = gas[META_GAS_ID], "gas_name" = gas[META_GAS_NAME], "enabled" = (path in connected_core.moderator_scrubbing))) + var/cached_gas_info = GLOB.meta_gas_info + for(var/path in cached_gas_info[META_GAS_ID]) + data["filter_types"] += list(list( + "gas_id" = cached_gas_info[META_GAS_ID][path], + "gas_name" = cached_gas_info[META_GAS_NAME][path], + "enabled" = (path in connected_core.moderator_scrubbing) + )) data["cooling_volume"] = connected_core.airs[1].volume data["mod_filtering_rate"] = connected_core.moderator_filtering_rate diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm index 3726291e5af9..efa04323c459 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm @@ -167,7 +167,7 @@ internal_fusion.assert_gas(/datum/gas/antinoblium) - moderator_internal.assert_gases(arglist(GLOB.meta_gas_info)) + moderator_internal.assert_gases(arglist(GLOB.meta_gas_info[META_GAS_ID])) if (!selected_fuel) return @@ -241,7 +241,7 @@ return FALSE for(var/gas_type in selected_fuel.requirements) internal_fusion.assert_gas(gas_type) - if(internal_fusion.gases[gas_type][MOLES] < FUSION_MOLE_THRESHOLD) + if(internal_fusion.moles[gas_type] < FUSION_MOLE_THRESHOLD) return FALSE return TRUE @@ -261,7 +261,7 @@ /obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_gas_requirements() var/datum/gas_mixture/contents = linked_input.airs[1] for(var/gas_type in selected_fuel.requirements) - if(!contents.gases[gas_type] || !contents.gases[gas_type][MOLES]) + if(!contents.moles[gas_type]) return FALSE return TRUE diff --git a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm index 219d7c338ca4..0fdf7936cd2a 100644 --- a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm +++ b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm @@ -110,19 +110,19 @@ /obj/machinery/atmospherics/components/binary/crystallizer/proc/inject_gases() var/datum/gas_mixture/contents = airs[2] for(var/gas_type in selected_recipe.requirements) - if(!contents.gases[gas_type] || !contents.gases[gas_type][MOLES]) + if(!contents.moles[gas_type]) continue - if(internal.gases[gas_type] && internal.gases[gas_type][MOLES] >= selected_recipe.requirements[gas_type] * 2) + if(internal.moles[gas_type] >= selected_recipe.requirements[gas_type] * 2) continue - internal.merge(contents.remove_specific(gas_type, contents.gases[gas_type][MOLES] * gas_input)) + internal.merge(contents.remove_specific(gas_type, contents.moles[gas_type] * gas_input)) ///Checks if the gases required are all inside /obj/machinery/atmospherics/components/binary/crystallizer/proc/internal_check() var/gas_check = 0 for(var/gas_type in selected_recipe.requirements) - if(!internal.gases[gas_type] || !internal.gases[gas_type][MOLES]) + if(!internal.moles[gas_type]) return FALSE - if(internal.gases[gas_type][MOLES] >= selected_recipe.requirements[gas_type]) + if(internal.moles[gas_type] >= selected_recipe.requirements[gas_type]) gas_check++ if(gas_check == selected_recipe.requirements.len) return TRUE @@ -194,7 +194,7 @@ for(var/gas_type in selected_recipe.requirements) var/required_gas_moles = selected_recipe.requirements[gas_type] var/amount_consumed = required_gas_moles + (required_gas_moles * (quality_loss * 0.01)) - if(internal.gases[gas_type][MOLES] < amount_consumed) + if(internal.moles[gas_type] < amount_consumed) quality_loss = min(quality_loss + 10, 100) internal.remove_specific(gas_type, amount_consumed) @@ -261,18 +261,20 @@ data["selected"] = "" var/list/internal_gas_data = list() + var/list/cached_gas_name = GAS_META[META_GAS_NAME] + var/list/cached_gas_id = GAS_META[META_GAS_ID] if(internal.total_moles()) - for(var/gasid in internal.gases) + for(var/gasid, amount in internal.moles) internal_gas_data.Add(list(list( - "name"= internal.gases[gasid][GAS_META][META_GAS_NAME], - "id" = internal.gases[gasid][GAS_META][META_GAS_ID], - "amount" = round(internal.gases[gasid][MOLES], 0.01), + "name"= cached_gas_name[gasid], + "id" = cached_gas_id[gasid], + "amount" = round(amount, 0.01), ))) else - for(var/gasid in internal.gases) + for(var/gasid in internal.moles) internal_gas_data.Add(list(list( - "name"= internal.gases[gasid][GAS_META][META_GAS_NAME], - "id" = internal.gases[gasid][GAS_META][META_GAS_ID], + "name"= cached_gas_name[gasid], + "id" = cached_gas_id[gasid], "amount" = 0, ))) data["internal_gas_data"] = internal_gas_data diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm index 25217de538ce..cbc0c31994e5 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm @@ -100,7 +100,7 @@ if(filtering) var/datum/gas_mixture/filtered_out = new - for(var/gas in removed.gases & filter_type) + for(var/gas in removed.moles & filter_type) var/datum/gas_mixture/removing = removed.remove_specific_ratio(gas, 1) if(removing) filtered_out.merge(removing) @@ -135,9 +135,9 @@ data["max_rate"] = round(MAX_TRANSFER_RATE) data["filter_types"] = list() - for(var/path in GLOB.meta_gas_info) - var/list/gas = GLOB.meta_gas_info[path] - data["filter_types"] += list(list("gas_id" = gas[META_GAS_ID], "enabled" = (path in filter_type))) + var/cached_gas_info = GLOB.meta_gas_info + for(var/path in cached_gas_info[META_GAS_ID]) + data["filter_types"] += list(list("gas_id" = cached_gas_info[META_GAS_ID][path], "enabled" = (path in filter_type))) return data @@ -170,7 +170,7 @@ change = "added" else change = "removed" - var/gas_name = GLOB.meta_gas_info[gas_id2path(params["val"])][META_GAS_NAME] + var/gas_name = GLOB.meta_gas_info[META_GAS_NAME][gas_id2path(params["val"])] usr.investigate_log("[change] [gas_name] from the filter type.", INVESTIGATE_ATMOS) . = TRUE update_appearance() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index 28a6de3a1d8e..617be670dbb7 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -481,7 +481,7 @@ var/datum/gas_mixture/air1 = internal_connector.gas_connector.airs[1] //check for workable conditions - if(!internal_connector.gas_connector.nodes[1] || !air1 || !air1.gases.len || air1.total_moles() < CRYO_MIN_GAS_MOLES) // Turn off if the machine won't work. + if(!internal_connector.gas_connector.nodes[1] || !air1 || !air1.moles.len || air1.total_moles() < CRYO_MIN_GAS_MOLES) // Turn off if the machine won't work. set_on(FALSE) aas_config_announce(/datum/aas_config_entry/medical_cryo_announcements, list("EJECTING" = autoeject), src, list(broadcast_channel), "Insufficient Gas") if(autoeject) // Eject if configured. diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm index 117eae4e8dc5..2165337b2ff3 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm @@ -179,12 +179,10 @@ on = FALSE return FALSE - var/list/changed_gas = air.gases - - if(!changed_gas) + if(!air.moles) return FALSE - if(scrubbing == ATMOS_DIRECTION_SIPHONING || length(filter_types & changed_gas)) + if(scrubbing == ATMOS_DIRECTION_SIPHONING || length(filter_types & air.moles)) return TRUE return FALSE @@ -218,36 +216,36 @@ return FALSE var/datum/gas_mixture/environment = tile.return_air() var/datum/gas_mixture/air_contents = airs[1] - var/list/env_gases = environment.gases + var/list/env_cached_moles = environment.moles if(air_contents.return_pressure() >= 50 * ONE_ATMOSPHERE) return FALSE if(scrubbing == ATMOS_DIRECTION_SCRUBBING) - if(length(env_gases & filter_types)) + if(length(env_cached_moles & filter_types)) ///contains all of the gas we're sucking out of the tile, gets put into our parent pipenet var/datum/gas_mixture/filtered_out = new - var/list/filtered_gases = filtered_out.gases + var/list/filtered_out_cached_moles = filtered_out.moles filtered_out.temperature = environment.temperature ///maximum percentage of the turfs gas we can filter var/removal_ratio = min(1, volume_rate / environment.volume) var/total_moles_to_remove = 0 - for(var/gas in filter_types & env_gases) - total_moles_to_remove += env_gases[gas][MOLES] + for(var/gas_id in filter_types & env_cached_moles) + total_moles_to_remove += env_cached_moles[gas_id] if(total_moles_to_remove == 0)//sometimes this gets non gc'd values environment.garbage_collect() return FALSE - for(var/gas in filter_types & env_gases) - filtered_out.add_gas(gas) - //take this gases portion of removal_ratio of the turfs air, or all of that gas if less than or equal to MINIMUM_MOLES_TO_SCRUB - var/transferred_moles = max(QUANTIZE(env_gases[gas][MOLES] * removal_ratio * (env_gases[gas][MOLES] / total_moles_to_remove)), min(MINIMUM_MOLES_TO_SCRUB, env_gases[gas][MOLES])) + for(var/gas_id in filter_types & env_cached_moles) + filtered_out.add_gas(gas_id) + //take this gases portion of removal_ratio of the turfs air, or all of that gas_id if less than or equal to MINIMUM_MOLES_TO_SCRUB + var/transferred_moles = max(QUANTIZE(env_cached_moles[gas_id] * removal_ratio * (env_cached_moles[gas_id] / total_moles_to_remove)), min(MINIMUM_MOLES_TO_SCRUB, env_cached_moles[gas_id])) - filtered_gases[gas][MOLES] = transferred_moles - env_gases[gas][MOLES] -= transferred_moles + filtered_out_cached_moles[gas_id] = transferred_moles + env_cached_moles[gas_id] -= transferred_moles environment.garbage_collect() diff --git a/code/modules/atmospherics/machinery/datum_pipeline.dm b/code/modules/atmospherics/machinery/datum_pipeline.dm index 35b8fa61bbc2..b862295e8714 100644 --- a/code/modules/atmospherics/machinery/datum_pipeline.dm +++ b/code/modules/atmospherics/machinery/datum_pipeline.dm @@ -254,12 +254,13 @@ var/total_thermal_energy = 0 var/total_heat_capacity = 0 - var/list/total_gases = list() - var/volume_sum = 0 var/static/process_id = 0 - process_id = (process_id + 1) % (SHORT_REAL_LIMIT - 1) + process_id = WRAP_UID(process_id + 1) + var/datum/gas_mixture/total_gas_mixture = new + var/list/total_cached_moles = total_gas_mixture.moles + var/list/cached_specific_heat = GAS_META[META_GAS_SPECIFIC_HEAT] for(var/datum/gas_mixture/gas_mixture as anything in gas_mixture_list) // Ensure we never walk the same mix twice @@ -271,14 +272,11 @@ // This is sort of a combined merge + heat_capacity calculation - var/list/giver_gases = gas_mixture.gases - var/heat_capacity = 0 + var/list/giver_cached_moles = gas_mixture.moles + var/heat_capacity = values_dot(giver_cached_moles, cached_specific_heat) //gas transfer - for(var/giver_id in giver_gases) - var/giver_gas_data = giver_gases[giver_id] - ASSERT_GAS_IN_LIST(giver_id, total_gases) - total_gases[giver_id][MOLES] += giver_gas_data[MOLES] - heat_capacity += giver_gas_data[MOLES] * giver_gas_data[GAS_META][META_GAS_SPECIFIC_HEAT] + for(var/gas_id, amount in giver_cached_moles) + total_cached_moles[gas_id] += amount total_heat_capacity += heat_capacity total_thermal_energy += gas_mixture.temperature * heat_capacity @@ -286,9 +284,8 @@ if(volume_sum == 0) return - var/datum/gas_mixture/total_gas_mixture = new(volume_sum) + total_gas_mixture.volume = volume_sum total_gas_mixture.temperature = total_heat_capacity ? (total_thermal_energy / total_heat_capacity) : 0 - total_gas_mixture.gases = total_gases total_gas_mixture.garbage_collect() //Update individual gas_mixtures by volume ratio @@ -329,8 +326,8 @@ var/current_weight = 0 var/current_color - for(var/datum/gas/gas_path as anything in air.gases) - var/gas_weight = air.gases[gas_path][MOLES] + for(var/datum/gas/gas_path as anything in air.moles) + var/gas_weight = air.moles[gas_path] if(!gas_weight) continue var/gas_color = initial(gas_path.primary_color) diff --git a/code/modules/atmospherics/machinery/other/miner.dm b/code/modules/atmospherics/machinery/other/miner.dm index f61ebf3766be..d588bd886288 100644 --- a/code/modules/atmospherics/machinery/other/miner.dm +++ b/code/modules/atmospherics/machinery/other/miner.dm @@ -136,7 +136,7 @@ return FALSE var/datum/gas_mixture/merger = new merger.assert_gas(spawn_id) - merger.gases[spawn_id][MOLES] = spawn_mol * seconds_per_tick + merger.moles[spawn_id] = spawn_mol * seconds_per_tick merger.temperature = spawn_temp O.assume_air(merger) diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index ab4e1a524a1f..211a86e5b8a6 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -61,7 +61,7 @@ create_gas() if(ispath(gas_type, /datum/gas)) - desc = "[GLOB.meta_gas_info[gas_type][META_GAS_NAME]]. [GLOB.meta_gas_info[gas_type][META_GAS_DESC]]" + desc = "[GLOB.meta_gas_info[META_GAS_NAME][gas_type]]. [GLOB.meta_gas_info[META_GAS_DESC][gas_type]]" update_window() @@ -713,14 +713,16 @@ var/list/output = list() output += "[key_name(user)] opened a canister [wire_pulsed ? "via wire pulse" : ""] that contains the following:" var/list/admin_output = list() - admin_output += "[ADMIN_LOOKUPFLW(user)] opened a canister [wire_pulsed ? "via wire pulse" : ""] that contains the following at [ADMIN_VERBOSEJMP(src)]:" - var/list/gases = air_contents.gases + admin_output += "[ADMIN_LOOKUPFLW(user)] opened a canister[wire_pulsed ? " via wire pulse" : ""] that contains the following at [ADMIN_VERBOSEJMP(src)]:" + var/list/cached_moles = air_contents.moles + var/list/cached_gas_name = GAS_META[META_GAS_NAME] + var/list/cached_gas_danger = GAS_META[META_GAS_DANGER] + var/list/cached_gas_visible = GAS_META[META_GAS_MOLES_VISIBLE] var/danger = FALSE - for(var/gas_index in 1 to length(gases)) - var/list/gas_info = gases[gases[gas_index]] - var/list/meta = gas_info[GAS_META] - var/name = meta[META_GAS_NAME] - var/moles = gas_info[MOLES] + for(var/gas_index in 1 to length(cached_moles)) + var/gas_id = cached_moles[gas_index] + var/name = cached_gas_name[gas_id] + var/moles = cached_moles[gas_id] output += "[name]: [moles] moles." if(gas_index <= 5) //the first five gases added @@ -728,7 +730,7 @@ else if(gas_index == 6) // anddd the warning admin_output += "Too many gases to log. Check investigate log." //if moles_visible is undefined, default to default visibility - if(meta[META_GAS_DANGER] && moles > (meta[META_GAS_MOLES_VISIBLE] || MOLES_GAS_VISIBLE)) + if(cached_gas_danger[gas_id] && moles > (cached_gas_visible[gas_id] || MOLES_GAS_VISIBLE)) danger = TRUE if(danger) //sent to admin's chat if contains dangerous gases diff --git a/code/modules/atmospherics/machinery/portable/pump.dm b/code/modules/atmospherics/machinery/portable/pump.dm index e7dfc229a39d..ab6af7a22577 100644 --- a/code/modules/atmospherics/machinery/portable/pump.dm +++ b/code/modules/atmospherics/machinery/portable/pump.dm @@ -119,11 +119,11 @@ if(on) SSair.start_processing_machine(src) if(on && !holding) - var/plasma = air_contents.gases[/datum/gas/plasma] - var/n2o = air_contents.gases[/datum/gas/nitrous_oxide] - if(n2o || plasma) - message_admins("[ADMIN_LOOKUPFLW(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [ADMIN_VERBOSEJMP(src)]") - log_admin("[key_name(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [AREACOORD(src)]") + var/plasma_moles = air_contents.moles[/datum/gas/plasma] + var/n2o_moles = air_contents.moles[/datum/gas/nitrous_oxide] + if(n2o_moles || plasma_moles) + message_admins("[ADMIN_LOOKUPFLW(usr)] turned on a pump that contains [n2o_moles ? "N2O" : ""][n2o_moles && plasma_moles ? " & " : ""][plasma_moles ? "Plasma" : ""] at [ADMIN_VERBOSEJMP(src)]") + log_admin("[key_name(usr)] turned on a pump that contains [n2o_moles ? "N2O" : ""][n2o_moles && plasma_moles ? " & " : ""][plasma_moles ? "Plasma" : ""] at [AREACOORD(src)]") else if(on && direction == PUMP_OUT) usr.investigate_log("started a transfer into [holding].", INVESTIGATE_ATMOS) . = TRUE diff --git a/code/modules/cargo/bounties/atmos.dm b/code/modules/cargo/bounties/atmos.dm index 16f52c459acc..76e8dcdf087c 100644 --- a/code/modules/cargo/bounties/atmos.dm +++ b/code/modules/cargo/bounties/atmos.dm @@ -13,9 +13,14 @@ return FALSE var/obj/item/tank/applied_tank = applied_obj var/datum/gas_mixture/our_mix = applied_tank.return_air() - if(!our_mix.gases[gas_type]) + if(!our_mix.moles[gas_type]) return FALSE - return our_mix.gases[gas_type][MOLES] >= moles_required + return our_mix.moles[gas_type] >= moles_required + +// /datum/bounty/item/atmospherics/contribution_amount(obj/shipped) +// var/obj/item/tank/shipped_tank = shipped +// var/datum/gas_mixture/our_mix = shipped_tank.return_air() +// return our_mix.moles[gas_type] /datum/bounty/item/atmospherics/pluox_tank name = "Full Tank of Pluoxium" diff --git a/code/modules/cargo/exports/large_objects.dm b/code/modules/cargo/exports/large_objects.dm index 1871d69eb452..d94f9fc1bb58 100644 --- a/code/modules/cargo/exports/large_objects.dm +++ b/code/modules/cargo/exports/large_objects.dm @@ -120,32 +120,37 @@ export_types = list(/obj/machinery/portable_atmospherics/canister) k_elasticity = 0.00033 -/datum/export/large/gas_canister/get_cost(obj/O) - var/obj/machinery/portable_atmospherics/canister/C = O +/datum/export/large/gas_canister/get_cost(obj/machinery/portable_atmospherics/canister/canister) + var/datum/gas_mixture/canister_mix = canister.return_air() + if(!canister_mix.total_moles()) + return 0 + var/cached_moles = canister_mix.moles + + var/static/list/gases_to_check = list( + /datum/gas/bz, + /datum/gas/nitrium, + /datum/gas/hypernoblium, + /datum/gas/miasma, + /datum/gas/tritium, + /datum/gas/pluoxium, + /datum/gas/freon, + /datum/gas/hydrogen, + /datum/gas/healium, + /datum/gas/proto_nitrate, + /datum/gas/zauker, + /datum/gas/helium, + /datum/gas/antinoblium, + /datum/gas/halon, + ) + var/worth = cost - var/datum/gas_mixture/canister_mix = C.return_air() - var/canister_gas = canister_mix.gases - var/list/gases_to_check = list( - /datum/gas/bz, - /datum/gas/nitrium, - /datum/gas/hypernoblium, - /datum/gas/miasma, - /datum/gas/tritium, - /datum/gas/pluoxium, - /datum/gas/freon, - /datum/gas/hydrogen, - /datum/gas/healium, - /datum/gas/proto_nitrate, - /datum/gas/zauker, - /datum/gas/helium, - /datum/gas/antinoblium, - /datum/gas/halon, - ) - - for(var/gasID in gases_to_check) - canister_mix.assert_gas(gasID) - if(canister_gas[gasID][MOLES] > 0) - worth += get_gas_value(gasID, canister_gas[gasID][MOLES]) + for(var/gas_id in gases_to_check) + canister_mix.assert_gas(gas_id) + if(cached_moles[gas_id] > 0) + worth += get_gas_value(gas_id, cached_moles[gas_id]) + if(worth > MAX_GAS_CREDITS) + worth = MAX_GAS_CREDITS + break canister_mix.garbage_collect() return worth diff --git a/code/modules/cargo/packs/materials.dm b/code/modules/cargo/packs/materials.dm index ba9a162698bb..c3dfd52317d7 100644 --- a/code/modules/cargo/packs/materials.dm +++ b/code/modules/cargo/packs/materials.dm @@ -83,7 +83,7 @@ // This is the amount of moles in a default canister var/moleCount = (initial(fakeCanister.maximum_pressure) * initial(fakeCanister.filled)) * initial(fakeCanister.volume) / (R_IDEAL_GAS_EQUATION * T20C) - for(var/gasType in GLOB.meta_gas_info) + for(var/gasType in GLOB.meta_gas_info[META_GAS_ID]) var/datum/gas/gas = gasType var/name = initial(gas.name) if(!initial(gas.purchaseable)) diff --git a/code/modules/clothing/masks/gas_filter.dm b/code/modules/clothing/masks/gas_filter.dm index dadb8730ca98..70ca3cab1c6f 100644 --- a/code/modules/clothing/masks/gas_filter.dm +++ b/code/modules/clothing/masks/gas_filter.dm @@ -67,29 +67,29 @@ var/danger_points = 0 - for(var/gas_id in breath.gases) + for(var/gas_id, amount in breath.moles) if(gas_id in high_filtering_gases) - if(breath.gases[gas_id][MOLES] > HIGH_FILTERING_MOLES) - breath.set_gas(gas_id, max(breath.gases[gas_id][MOLES] - filter_strength_high * filter_efficiency * HIGH_FILTERING_RATIO, 0)) + if(amount > HIGH_FILTERING_MOLES) + breath.set_gas(gas_id, max(amount - filter_strength_high * filter_efficiency * HIGH_FILTERING_RATIO, 0)) danger_points += 1 continue - breath.set_gas(gas_id, max(breath.gases[gas_id][MOLES] - filter_strength_high * filter_efficiency * LOW_FILTERING_RATIO, 0)) + breath.set_gas(gas_id, max(amount - filter_strength_high * filter_efficiency * LOW_FILTERING_RATIO, 0)) danger_points += 0.2 continue if(gas_id in mid_filtering_gases) - if(breath.gases[gas_id][MOLES] > MID_FILTERING_MOLES) - breath.set_gas(gas_id, max(breath.gases[gas_id][MOLES] - filter_strength_mid * filter_efficiency * HIGH_FILTERING_RATIO, 0)) + if(amount > MID_FILTERING_MOLES) + breath.set_gas(gas_id, max(amount - filter_strength_mid * filter_efficiency * HIGH_FILTERING_RATIO, 0)) danger_points += 1.25 continue - breath.set_gas(gas_id, max(breath.gases[gas_id][MOLES] - filter_strength_mid * filter_efficiency * LOW_FILTERING_RATIO, 0)) + breath.set_gas(gas_id, max(amount - filter_strength_mid * filter_efficiency * LOW_FILTERING_RATIO, 0)) danger_points += 0.25 continue if(gas_id in low_filtering_gases) - if(breath.gases[gas_id][MOLES] > LOW_FILTERING_MOLES) - breath.set_gas(gas_id, max(breath.gases[gas_id][MOLES] - filter_strength_low * filter_efficiency * HIGH_FILTERING_RATIO, 0)) + if(amount > LOW_FILTERING_MOLES) + breath.set_gas(gas_id, max(amount - filter_strength_low * filter_efficiency * HIGH_FILTERING_RATIO, 0)) danger_points += 1.5 continue - breath.set_gas(gas_id, max(breath.gases[gas_id][MOLES] - filter_strength_low * filter_efficiency * LOW_FILTERING_RATIO, 0)) + breath.set_gas(gas_id, max(amount - filter_strength_low * filter_efficiency * LOW_FILTERING_RATIO, 0)) danger_points += 0.5 continue diff --git a/code/modules/events/space_vines/vine_mutations.dm b/code/modules/events/space_vines/vine_mutations.dm index 8ab80382650b..b7a625230a75 100644 --- a/code/modules/events/space_vines/vine_mutations.dm +++ b/code/modules/events/space_vines/vine_mutations.dm @@ -244,10 +244,10 @@ return var/datum/gas_mixture/gas_mix = turf.air - if(!gas_mix.gases[gas_type]) + if(!gas_mix.moles[gas_type]) return - gas_mix.set_gas(gas_type, max(gas_mix.gases[gas_type][MOLES] - GAS_MUTATION_REMOVAL_MULTIPLIER * holder.growth_stage, 0)) + gas_mix.set_gas(gas_type, max(gas_mix.moles[gas_type] - GAS_MUTATION_REMOVAL_MULTIPLIER * holder.growth_stage, 0)) gas_mix.garbage_collect() /datum/spacevine_mutation/gas_eater/oxy_eater diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm index 4513f51b414e..82cc5772d1d9 100644 --- a/code/modules/fishing/fish/_fish.dm +++ b/code/modules/fishing/fish/_fish.dm @@ -1007,7 +1007,7 @@ var/datum/gas_mixture/mixture = loc.return_air() if(!mixture) return FALSE - if(safe_air_limits && !check_gases(mixture.gases, safe_air_limits)) + if(safe_air_limits && !mixture.check_gases(safe_air_limits)) return FALSE if(!ISINRANGE(mixture.temperature, required_temperature_min, required_temperature_max)) return FALSE diff --git a/code/modules/mob/living/basic/guardian/guardian_types/gaseous.dm b/code/modules/mob/living/basic/guardian/guardian_types/gaseous.dm index f38025cf8650..5e407401ee5e 100644 --- a/code/modules/mob/living/basic/guardian/guardian_types/gaseous.dm +++ b/code/modules/mob/living/basic/guardian/guardian_types/gaseous.dm @@ -149,7 +149,7 @@ return // We shouldn't even be registered at this point but just in case var/datum/gas_mixture/mix_to_spawn = new() mix_to_spawn.add_gas(active_gas) - mix_to_spawn.gases[active_gas][MOLES] = possible_gases[active_gas] * seconds_per_tick + mix_to_spawn.moles[active_gas] = possible_gases[active_gas] * seconds_per_tick mix_to_spawn.temperature = T20C var/turf/open/our_turf = get_turf(owner) our_turf.assume_air(mix_to_spawn) diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm index 86b2ef3d9508..a7cfb6733e8c 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm @@ -108,9 +108,9 @@ /mob/living/basic/regal_rat/handle_environment(datum/gas_mixture/environment) . = ..() - if(stat == DEAD || isnull(environment) || isnull(environment.gases[/datum/gas/miasma])) + if(stat == DEAD || isnull(environment) || isnull(environment.moles[/datum/gas/miasma])) return - var/miasma_percentage = environment.gases[/datum/gas/miasma][MOLES] / environment.total_moles() + var/miasma_percentage = environment.moles[/datum/gas/miasma] / environment.total_moles() if(miasma_percentage >= 0.25) heal_bodypart_damage(1) diff --git a/code/modules/mob/living/basic/tree.dm b/code/modules/mob/living/basic/tree.dm index 295c369627ee..47794ea57eb0 100644 --- a/code/modules/mob/living/basic/tree.dm +++ b/code/modules/mob/living/basic/tree.dm @@ -66,10 +66,10 @@ if(!isopenturf(loc)) return var/turf/open/our_turf = src.loc - if(!our_turf.air || !our_turf.air.gases[/datum/gas/carbon_dioxide]) + if(!our_turf.air || !our_turf.air.moles[/datum/gas/carbon_dioxide]) return var/datum/gas_mixture/our_air = our_turf.air - var/co2 = our_air.gases[/datum/gas/carbon_dioxide][MOLES] + var/co2 = our_air.moles[/datum/gas/carbon_dioxide] if(co2 > 0 && SPT_PROB(13, seconds_per_tick)) var/amt = min(co2, 9) our_air.adjust_gas(/datum/gas/carbon_dioxide, -amt) diff --git a/code/modules/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm index 1d0cafeb8049..e89bb872dd94 100644 --- a/code/modules/mob/living/carbon/alien/life.dm +++ b/code/modules/mob/living/carbon/alien/life.dm @@ -16,25 +16,25 @@ var/plasma_used = 0 var/plas_detect_threshold = 0.02 var/breath_pressure = (breath.total_moles()*R_IDEAL_GAS_EQUATION*breath.temperature)/BREATH_VOLUME - var/list/breath_gases = breath.gases + var/list/breath_moles = breath.moles breath.assert_gases(/datum/gas/plasma, /datum/gas/oxygen) //Partial pressure of the plasma in our breath - var/Plasma_pp = (breath_gases[/datum/gas/plasma][MOLES]/breath.total_moles())*breath_pressure + var/plasma_pp = (breath_moles[/datum/gas/plasma] / breath.total_moles()) * breath_pressure - if(Plasma_pp > plas_detect_threshold) // Detect plasma in air - adjustPlasma(breath_gases[/datum/gas/plasma][MOLES]*250) + if(plasma_pp > plas_detect_threshold) // Detect plasma in air + adjustPlasma(breath_moles[/datum/gas/plasma] * 250) throw_alert(ALERT_XENO_PLASMA, /atom/movable/screen/alert/alien_plas) - plasma_used = breath_gases[/datum/gas/plasma][MOLES] + plasma_used = breath_moles[/datum/gas/plasma] else clear_alert(ALERT_XENO_PLASMA) //Breathe in plasma and out oxygen - breath_gases[/datum/gas/plasma][MOLES] -= plasma_used - breath_gases[/datum/gas/oxygen][MOLES] += plasma_used + breath_moles[/datum/gas/plasma] -= plasma_used + breath_moles[/datum/gas/oxygen] += plasma_used breath.garbage_collect() diff --git a/code/modules/mob/living/simple_animal/slime/life.dm b/code/modules/mob/living/simple_animal/slime/life.dm index 328431b0db99..9e13c1f04bed 100644 --- a/code/modules/mob/living/simple_animal/slime/life.dm +++ b/code/modules/mob/living/simple_animal/slime/life.dm @@ -48,9 +48,7 @@ REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, SLIME_COLD) if(stat != DEAD) - var/bz_percentage =0 - if(environment.gases[/datum/gas/bz]) - bz_percentage = environment.gases[/datum/gas/bz][MOLES] / environment.total_moles() + var/bz_percentage = environment.moles[/datum/gas/bz] / environment.total_moles() var/stasis = (bz_percentage >= 0.05 && body_temperature < (T0C + 100)) || force_stasis switch(stat) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 26ffa76dfdf7..20c534ca9466 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -201,10 +201,10 @@ var/t = "[span_notice("Coordinates: [x],[y] ")]\n" t += "[span_danger("Temperature: [environment.temperature] ")]\n" - for(var/id in environment.gases) - var/gas = environment.gases[id] - if(gas[MOLES]) - t+="[span_notice("[gas[GAS_META][META_GAS_NAME]]: [gas[MOLES]] ")]\n" + var/list/cached_gas_name = GAS_META[META_GAS_NAME] + for(var/gas_id, gas_moles in environment.moles) + if(gas_moles) + t += "[span_notice("[cached_gas_name[gas_id]]: [gas_moles] ")]\n" to_chat(usr, t) diff --git a/code/modules/mod/modules/modules_maint.dm b/code/modules/mod/modules/modules_maint.dm index 2d43b3d46ec2..cdf03beb188c 100644 --- a/code/modules/mod/modules/modules_maint.dm +++ b/code/modules/mod/modules/modules_maint.dm @@ -67,7 +67,7 @@ var/turf/wearer_turf = get_turf(src) var/datum/gas_mixture/air = wearer_turf.return_air() - if(!(air.gases[/datum/gas/water_vapor] && (air.gases[/datum/gas/water_vapor][MOLES]) >= 5)) + if(air.moles[/datum/gas/water_vapor] < 5) return //return if there aren't more than 5 Moles of Water Vapor in the air snap_signal() diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index ab1a6f6e2811..d2f5d043cf9f 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -293,7 +293,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) // Extra effects should always fire after the compositions are all finished // Some extra effects like [/datum/sm_gas/carbon_dioxide/extra_effects] // needs more than one gas and rely on a fully parsed gas_percentage. - for (var/gas_path in absorbed_gasmix.gases) + for (var/gas_path in absorbed_gasmix.moles) var/datum/sm_gas/sm_gas = current_gas_behavior[gas_path] sm_gas?.extra_effects(src) @@ -341,8 +341,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) merged_gasmix.temperature += device_energy * waste_multiplier / THERMAL_RELEASE_MODIFIER merged_gasmix.temperature = clamp(merged_gasmix.temperature, TCMB, 2500 * waste_multiplier) merged_gasmix.assert_gases(/datum/gas/plasma, /datum/gas/oxygen) - merged_gasmix.gases[/datum/gas/plasma][MOLES] += max(device_energy * waste_multiplier / PLASMA_RELEASE_MODIFIER, 0) - merged_gasmix.gases[/datum/gas/oxygen][MOLES] += max(((device_energy + merged_gasmix.temperature * waste_multiplier) - T0C) / OXYGEN_RELEASE_MODIFIER, 0) + merged_gasmix.moles[/datum/gas/plasma] += max(device_energy * waste_multiplier / PLASMA_RELEASE_MODIFIER, 0) + merged_gasmix.moles[/datum/gas/oxygen] += max(((device_energy + merged_gasmix.temperature * waste_multiplier) - T0C) / OXYGEN_RELEASE_MODIFIER, 0) merged_gasmix.garbage_collect() env.merge(merged_gasmix) air_update_turf(FALSE, FALSE) @@ -664,8 +664,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) var/total_moles = absorbed_gasmix.total_moles() if(total_moles < MINIMUM_MOLE_COUNT) //it's not worth processing small amounts like these, total_moles can also be 0 in vacuume return - for (var/gas_path in absorbed_gasmix.gases) - var/mole_count = absorbed_gasmix.gases[gas_path][MOLES] + for (var/gas_path, mole_count in absorbed_gasmix.moles) if(mole_count < MINIMUM_MOLE_COUNT) //save processing power from small amounts like these continue gas_percentage[gas_path] = mole_count / total_moles diff --git a/code/modules/power/supermatter/supermatter_gas.dm b/code/modules/power/supermatter/supermatter_gas.dm index 4848bda3e08e..9f26e632a3d6 100644 --- a/code/modules/power/supermatter/supermatter_gas.dm +++ b/code/modules/power/supermatter/supermatter_gas.dm @@ -105,11 +105,11 @@ GLOBAL_LIST_INIT(sm_gas_behavior, init_sm_gas()) return var/co2_pp = sm.absorbed_gasmix.return_pressure() * sm.gas_percentage[/datum/gas/carbon_dioxide] var/co2_ratio = clamp((1/2 * (co2_pp - CO2_CONSUMPTION_PP) / (co2_pp + CO2_PRESSURE_SCALING)), 0, 1) - var/consumed_co2 = sm.absorbed_gasmix.gases[/datum/gas/carbon_dioxide][MOLES] * co2_ratio + var/consumed_co2 = sm.absorbed_gasmix.moles[/datum/gas/carbon_dioxide] * co2_ratio consumed_co2 = min( consumed_co2, - sm.absorbed_gasmix.gases[/datum/gas/carbon_dioxide][MOLES], - sm.absorbed_gasmix.gases[/datum/gas/oxygen][MOLES] + sm.absorbed_gasmix.moles[/datum/gas/carbon_dioxide], + sm.absorbed_gasmix.moles[/datum/gas/oxygen] ) if(!consumed_co2) return @@ -175,7 +175,7 @@ GLOBAL_LIST_INIT(sm_gas_behavior, init_sm_gas()) return var/miasma_pp = sm.absorbed_gasmix.return_pressure() * sm.gas_percentage[/datum/gas/miasma] var/miasma_ratio = clamp(((miasma_pp - MIASMA_CONSUMPTION_PP) / (miasma_pp + MIASMA_PRESSURE_SCALING)) * (1 + (sm.gas_heat_power_generation * MIASMA_GASMIX_SCALING)), 0, 1) - var/consumed_miasma = sm.absorbed_gasmix.gases[/datum/gas/miasma][MOLES] * miasma_ratio + var/consumed_miasma = sm.absorbed_gasmix.moles[/datum/gas/miasma] * miasma_ratio if(!consumed_miasma) return sm.absorbed_gasmix.adjust_gas(/datum/gas/miasma, -consumed_miasma) diff --git a/code/modules/research/ordnance/tank_compressor.dm b/code/modules/research/ordnance/tank_compressor.dm index 1461a535404f..359efc9c8e23 100644 --- a/code/modules/research/ordnance/tank_compressor.dm +++ b/code/modules/research/ordnance/tank_compressor.dm @@ -170,8 +170,8 @@ new_record.name = "Log Recording #[record_number]" new_record.experiment_source = inserted_tank.name new_record.timestamp = station_time_timestamp() - for(var/gas_path in leaked_gas_buffer.gases) - new_record.gas_data[gas_path] = leaked_gas_buffer.gases[gas_path][MOLES] + for(var/gas_path, amount in leaked_gas_buffer.moles) + new_record.gas_data[gas_path] = amount compressor_record += new_record record_number += 1 diff --git a/code/modules/research/xenobiology/crossbreeding/chilling.dm b/code/modules/research/xenobiology/crossbreeding/chilling.dm index fa40392135d3..dc1ed32d8541 100644 --- a/code/modules/research/xenobiology/crossbreeding/chilling.dm +++ b/code/modules/research/xenobiology/crossbreeding/chilling.dm @@ -113,7 +113,7 @@ Chilling extracts: var/datum/gas_mixture/G = T.air if(istype(G)) G.assert_gas(/datum/gas/plasma) - G.gases[/datum/gas/plasma][MOLES] = 0 + G.moles[/datum/gas/plasma] = 0 filtered = TRUE G.garbage_collect() T.air_update_turf(FALSE, FALSE) diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm index da37d5b9cdab..06c41294c428 100644 --- a/code/modules/surgery/organs/internal/lungs/_lungs.dm +++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm @@ -267,7 +267,7 @@ // Not safe to check the old pp because of can_breath_vacuum breather.throw_alert(ALERT_NOT_ENOUGH_OXYGEN, /atom/movable/screen/alert/not_enough_oxy) - var/gas_breathed = handle_suffocation(breather, o2_pp, safe_oxygen_min, breath.gases[/datum/gas/oxygen][MOLES]) + var/gas_breathed = handle_suffocation(breather, o2_pp, safe_oxygen_min, breath.moles[/datum/gas/oxygen]) if(o2_pp) breathe_gas_volume(breath, /datum/gas/oxygen, /datum/gas/carbon_dioxide, volume = gas_breathed) return @@ -289,7 +289,7 @@ return BREATH_LOST return - var/ratio = (breath.gases[/datum/gas/oxygen][MOLES] / safe_oxygen_max) * 10 + var/ratio = (breath.moles[/datum/gas/oxygen] / safe_oxygen_max) * 10 breather.apply_damage(clamp(ratio, oxy_breath_dam_min, oxy_breath_dam_max), oxy_damage_type, spread_damage = TRUE) if(!HAS_TRAIT(breather, TRAIT_ANOSMIA)) breather.throw_alert(ALERT_TOO_MUCH_OXYGEN, /atom/movable/screen/alert/too_much_oxy) @@ -313,7 +313,7 @@ // Not safe to check the old pp because of can_breath_vacuum if(!HAS_TRAIT(breather, TRAIT_ANOSMIA)) breather.throw_alert(ALERT_NOT_ENOUGH_NITRO, /atom/movable/screen/alert/not_enough_nitro) - var/gas_breathed = handle_suffocation(breather, nitro_pp, safe_nitro_min, breath.gases[/datum/gas/nitrogen][MOLES]) + var/gas_breathed = handle_suffocation(breather, nitro_pp, safe_nitro_min, breath.moles[/datum/gas/nitrogen]) if(nitro_pp) breathe_gas_volume(breath, /datum/gas/nitrogen, /datum/gas/carbon_dioxide, volume = gas_breathed) return @@ -366,7 +366,7 @@ if(!HAS_TRAIT(breather, TRAIT_ANOSMIA)) breather.throw_alert(ALERT_NOT_ENOUGH_PLASMA, /atom/movable/screen/alert/not_enough_plas) // Breathe insufficient amount of Plasma, exhale CO2. - var/gas_breathed = handle_suffocation(breather, plasma_pp, safe_plasma_min, breath.gases[/datum/gas/plasma][MOLES]) + var/gas_breathed = handle_suffocation(breather, plasma_pp, safe_plasma_min, breath.moles[/datum/gas/plasma]) if(plasma_pp) breathe_gas_volume(breath, /datum/gas/plasma, /datum/gas/carbon_dioxide, volume = gas_breathed) return @@ -390,7 +390,7 @@ if(!HAS_TRAIT(breather, TRAIT_ANOSMIA)) breather.throw_alert(ALERT_TOO_MUCH_PLASMA, /atom/movable/screen/alert/too_much_plas) - var/ratio = (breath.gases[/datum/gas/plasma][MOLES] / safe_plasma_max) * 10 + var/ratio = (breath.moles[/datum/gas/plasma] / safe_plasma_max) * 10 breather.apply_damage(clamp(ratio, plas_breath_dam_min, plas_breath_dam_max), plas_damage_type, spread_damage = TRUE) /// Resets plasma side effects @@ -553,7 +553,7 @@ /// Radioactive, green gas. Toxin damage, and a radiation chance /obj/item/organ/lungs/proc/too_much_tritium(mob/living/carbon/breather, datum/gas_mixture/breath, trit_pp, old_trit_pp) var/gas_breathed = breathe_gas_volume(breath, /datum/gas/tritium) - var/moles_visible = GLOB.meta_gas_info[/datum/gas/tritium][META_GAS_MOLES_VISIBLE] * BREATH_PERCENTAGE + var/moles_visible = GLOB.meta_gas_info[META_GAS_MOLES_VISIBLE][/datum/gas/tritium] * BREATH_PERCENTAGE // Tritium side-effects. if(gas_breathed > moles_visible) var/ratio = gas_breathed * 15 @@ -642,7 +642,7 @@ apply_organ_damage(maxHealth * 0.075) // The list of gases in the breath. - var/list/breath_gases = breath.gases + var/list/breath_moles = breath.moles // Copy the breath's temperature into breath_out to avoid cooling the output breath down unfairly breath_out.temperature = breath.temperature @@ -654,8 +654,8 @@ // Build out our partial pressures, for use as we go var/list/partial_pressures = list() - for(var/gas_id in breath_gases) - partial_pressures[gas_id] = breath.get_breath_partial_pressure(breath_gases[gas_id][MOLES] * received_pressure_mult) + for(var/gas_id, amount in breath_moles) + partial_pressures[gas_id] = breath.get_breath_partial_pressure(amount * received_pressure_mult) // Treat gas as other types of gas for(var/list/conversion_packet in treat_as) @@ -673,32 +673,31 @@ var/partial_pressure = partial_pressures[breath_id] || 0 var/old_partial_pressure = last_partial_pressures[breath_id] || 0 // Ensures the gas will always be instanciated, so people can interact with it safely - ASSERT_GAS(breath_id, breath) var/inhale = breathe_always[breath_id] call(src, inhale)(breather, breath, partial_pressure, old_partial_pressure) // Now we'll handle the callbacks that want to be run conditionally off our current breath - for(var/breath_id in breath_gases) - var/when_present = breath_present[breath_id] + for(var/gas_id in breath_moles) + var/when_present = breath_present[gas_id] if(!when_present) continue - var/reaction = call(src, when_present)(breather, breath, partial_pressures[breath_id], last_partial_pressures[breath_id]) + var/reaction = call(src, when_present)(breather, breath, partial_pressures[gas_id], last_partial_pressures[gas_id]) if(reaction == BREATH_LOST) - var/on_lose = breath_lost[breath_id] + var/on_lose = breath_lost[gas_id] if(on_lose) - call(src, on_lose)(breather, breath, partial_pressures[breath_id], last_partial_pressures[breath_id]) + call(src, on_lose)(breather, breath, partial_pressures[gas_id], last_partial_pressures[gas_id]) // Finally, we'll run the callbacks that aren't in breath_gases, but WERE in our last breath - for(var/gas_lost in last_partial_pressures) + for(var/gas_id in last_partial_pressures) // If we still have it, go away - if(breath_gases[gas_lost]) + if(breath_moles[gas_id]) continue - var/on_loss = breath_lost[gas_lost] + var/on_loss = breath_lost[gas_id] if(!on_loss) continue - call(src, on_loss)(breather, breath, last_partial_pressures[gas_lost]) + call(src, on_loss)(breather, breath, last_partial_pressures[gas_id]) src.last_partial_pressures = partial_pressures @@ -717,12 +716,11 @@ /// Removes 100% of the given gas type unless given a volume argument. /// Returns the amount of gas theoretically removed. /obj/item/organ/lungs/proc/breathe_gas_volume(datum/gas_mixture/breath, remove_id, exchange_id = null, volume = INFINITY) - var/list/breath_gases = breath.gases - volume = min(volume, breath_gases[remove_id][MOLES]) - breath_gases[remove_id][MOLES] -= volume + var/list/breath_moles = breath.moles + volume = min(volume, breath_moles[remove_id]) + breath_moles[remove_id] -= volume if(exchange_id) - ASSERT_GAS(exchange_id, breath_out) - breath_out.gases[exchange_id][MOLES] += volume + breath_out.moles[exchange_id] += volume return volume /// Handles what happens when we breathe in successfully, before we parse through the gases to determine specific side effects @@ -970,8 +968,8 @@ /obj/item/organ/lungs/slime/check_breath(datum/gas_mixture/breath, mob/living/carbon/human/breather_slime, skip_breath) . = ..() - if (breath?.gases[/datum/gas/plasma] && !skip_breath) - var/plasma_pp = breath.get_breath_partial_pressure(breath.gases[/datum/gas/plasma][MOLES]) + if (breath?.moles[/datum/gas/plasma]) + var/plasma_pp = breath.get_breath_partial_pressure(breath.moles[/datum/gas/plasma]) breather_slime.blood_volume += (0.2 * plasma_pp) // 10/s when breathing literally nothing but plasma, which will suffocate you. /obj/item/organ/lungs/smoker_lungs @@ -1054,7 +1052,7 @@ // Take a "breath" of the air var/datum/gas_mixture/breath = mix.remove(mix.total_moles() * BREATH_PERCENTAGE) - var/list/breath_gases = breath.gases + var/list/breath_moles = breath.moles breath.assert_gases( /datum/gas/oxygen, @@ -1065,12 +1063,12 @@ /datum/gas/miasma, ) - var/oxygen_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/oxygen][MOLES]) - var/nitrogen_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrogen][MOLES]) - var/plasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/plasma][MOLES]) - var/carbon_dioxide_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/carbon_dioxide][MOLES]) - var/bz_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/bz][MOLES]) - var/miasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/miasma][MOLES]) + var/oxygen_pp = breath.get_breath_partial_pressure(breath_moles[/datum/gas/oxygen]) + var/nitrogen_pp = breath.get_breath_partial_pressure(breath_moles[/datum/gas/nitrogen]) + var/plasma_pp = breath.get_breath_partial_pressure(breath_moles[/datum/gas/plasma]) + var/carbon_dioxide_pp = breath.get_breath_partial_pressure(breath_moles[/datum/gas/carbon_dioxide]) + var/bz_pp = breath.get_breath_partial_pressure(breath_moles[/datum/gas/bz]) + var/miasma_pp = breath.get_breath_partial_pressure(breath_moles[/datum/gas/miasma]) safe_oxygen_min = max(0, oxygen_pp - GAS_TOLERANCE) safe_nitro_min = max(0, nitrogen_pp - GAS_TOLERANCE) @@ -1150,7 +1148,7 @@ /// H2O electrolysis /obj/item/organ/lungs/ethereal/proc/consume_water(mob/living/carbon/breather, datum/gas_mixture/breath, h2o_pp, old_h2o_pp) - var/gas_breathed = breath.gases[/datum/gas/water_vapor][MOLES] + var/gas_breathed = breath.moles[/datum/gas/water_vapor] breath.adjust_gas(/datum/gas/water_vapor, -gas_breathed) var/list/new_gases = list(/datum/gas/oxygen = gas_breathed, /datum/gas/hydrogen = gas_breathed * 2) breath_out.adjust_multiple_gases(new_gases) diff --git a/code/modules/unit_tests/breath.dm b/code/modules/unit_tests/breath.dm index faba1a08e22e..9270f9bbfe28 100644 --- a/code/modules/unit_tests/breath.dm +++ b/code/modules/unit_tests/breath.dm @@ -43,7 +43,7 @@ lab_rat = allocate(/mob/living/carbon/human/consistent) source = equip_labrat_internals(lab_rat, /obj/item/tank/internals/emergency_oxygen/empty) source.air_contents.assert_gas(/datum/gas/nitrogen) - source.air_contents.gases[/datum/gas/nitrogen][MOLES] = (10 * ONE_ATMOSPHERE) * source.volume / (R_IDEAL_GAS_EQUATION * T20C) + source.air_contents.moles[/datum/gas/nitrogen] = (10 * ONE_ATMOSPHERE) * source.volume / (R_IDEAL_GAS_EQUATION * T20C) TEST_ASSERT(source.toggle_internals(lab_rat) && !isnull(lab_rat.internal), "Plasmaman toggle_internals() failed to toggle internals") lab_rat.breathe() TEST_ASSERT(lab_rat.failed_last_breath && lab_rat.has_alert(ALERT_NOT_ENOUGH_OXYGEN), "Humans should suffocate from pure n2 tanks") @@ -77,7 +77,7 @@ lab_rat = allocate(/mob/living/carbon/human/species/plasma) source = equip_labrat_internals(lab_rat, /obj/item/tank/internals/emergency_oxygen/empty) source.air_contents.assert_gas(/datum/gas/nitrogen) - source.air_contents.gases[/datum/gas/nitrogen][MOLES] = (10 * ONE_ATMOSPHERE) * source.volume / (R_IDEAL_GAS_EQUATION * T20C) + source.air_contents.moles[/datum/gas/nitrogen] = (10 * ONE_ATMOSPHERE) * source.volume / (R_IDEAL_GAS_EQUATION * T20C) TEST_ASSERT(source.toggle_internals(lab_rat) && !isnull(lab_rat.internal), "Plasmaman toggle_internals() failed to toggle internals") lab_rat.breathe() TEST_ASSERT(lab_rat.failed_last_breath && lab_rat.has_alert(ALERT_NOT_ENOUGH_PLASMA), "Humans should suffocate from pure n2 tanks") diff --git a/code/modules/unit_tests/gas_transfer.dm b/code/modules/unit_tests/gas_transfer.dm index 2b174ad8c625..06f4afd587ca 100644 --- a/code/modules/unit_tests/gas_transfer.dm +++ b/code/modules/unit_tests/gas_transfer.dm @@ -25,13 +25,10 @@ first_mix.volume = 200 second_mix.volume = 200 - ASSERT_GAS(/datum/gas/hypernoblium, first_mix) - ASSERT_GAS(/datum/gas/tritium, second_mix) - - first_mix.gases[/datum/gas/hypernoblium][MOLES] = nob_moles + first_mix.moles[/datum/gas/hypernoblium] = nob_moles first_mix.temperature = nob_temp - second_mix.gases[/datum/gas/tritium][MOLES] = trit_moles + second_mix.moles[/datum/gas/tritium] = trit_moles second_mix.temperature = trit_temp var/initial_pressure = second_mix.return_pressure() diff --git a/code/modules/unit_tests/lungs.dm b/code/modules/unit_tests/lungs.dm index c8338b6dec88..d7f9bf48f2ae 100644 --- a/code/modules/unit_tests/lungs.dm +++ b/code/modules/unit_tests/lungs.dm @@ -1,7 +1,7 @@ #define TEST_CHECK_BREATH_MESSAGE(lungs_organ, message) "[lungs_organ.type]/check_breath() [message]" #define TEST_ALERT_THROW_MESSAGE(lungs_organ, alert_name) TEST_CHECK_BREATH_MESSAGE(lungs_organ, "failed to throw alert [alert_name] when expected.") #define TEST_ALERT_INHIBIT_MESSAGE(lungs_organ, alert_name) TEST_CHECK_BREATH_MESSAGE(lungs_organ, "threw alert [alert_name] when it wasn't expected.") -#define GET_MOLES(gas_mixture, gas_type) (gas_mixture.gases[gas_type] ? gas_mixture.gases[gas_type][MOLES] : 0) +#define GET_MOLES(gas_mixture, gas_type) (gas_mixture.moles[gas_type] || 0) /// Tests the standard, plasmaman, and lavaland lungs organ to ensure breathing and suffocation behave as expected. /// Performs a check on each main (can be life-sustaining) gas, and ensures gas alerts are only thrown when expected. @@ -174,7 +174,7 @@ test_mix.temperature = T20C for(var/datum/gas/gas_type as anything in gas_to_percent) test_mix.add_gas(gas_type) - test_mix.gases[gas_type][MOLES] = (ONE_ATMOSPHERE * 2500 / (R_IDEAL_GAS_EQUATION * T20C) * gas_to_percent[gas_type]) + test_mix.moles[gas_type] = (ONE_ATMOSPHERE * 2500 / (R_IDEAL_GAS_EQUATION * T20C) * gas_to_percent[gas_type]) return test_mix /// Set up an O2/N2 gas mix which is "ideal" for organic life. diff --git a/maplestation_modules/code/game/machinery/cloning_pod.dm b/maplestation_modules/code/game/machinery/cloning_pod.dm index 08b6f3367587..fbed61e60461 100644 --- a/maplestation_modules/code/game/machinery/cloning_pod.dm +++ b/maplestation_modules/code/game/machinery/cloning_pod.dm @@ -284,8 +284,8 @@ /datum/gas_mixture/immutable/cloner/garbage_collect() . = ..() - ASSERT_GAS(/datum/gas/nitrogen, src) - gases[/datum/gas/nitrogen][MOLES] = MOLES_O2STANDARD + MOLES_N2STANDARD + gases.assert_gas(/datum/gas/nitrogen) + gases.set_gas(/datum/gas/nitrogen, MOLES_O2STANDARD + MOLES_N2STANDARD) /datum/gas_mixture/immutable/cloner/heat_capacity() return (MOLES_O2STANDARD + MOLES_N2STANDARD) * 20 //specific heat of nitrogen is 20 From 426bf6a2a1cd0470de26e9ae1fb0e56de3958c1e Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Mon, 7 Sep 2026 03:00:52 -0500 Subject: [PATCH 09/15] More --- code/__HELPERS/global_lists.dm | 1 - code/datums/components/self_ignition.dm | 4 +- .../advance/floor_diseases/gastritium.dm | 15 +- .../machines/machine_circuitboards.dm | 9 - code/game/objects/items/flamethrower.dm | 3 +- .../crates_lockers/closets/bodybag.dm | 4 +- .../atmospherics/gasmixtures/reactions.dm | 24 +- .../machinery/bluespace_vendor.dm | 285 ------------------ .../electrolyzer/electrolyzer_reactions.dm | 5 +- .../unary_devices/bluespace_sender.dm | 231 -------------- code/modules/cargo/exports/large_objects.dm | 5 + code/modules/mob/living/death.dm | 5 +- .../mob/living/simple_animal/simple_animal.dm | 10 +- .../atmospherics/bluespace_gas_selling.dmi | Bin 12655 -> 0 bytes maplestation.dme | 2 - .../code/game/machinery/cloning_pod.dm | 4 +- .../code/modules/smells/smell_helpers.dm | 5 +- 17 files changed, 45 insertions(+), 567 deletions(-) delete mode 100644 code/modules/atmospherics/machinery/bluespace_vendor.dm delete mode 100644 code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm delete mode 100644 icons/obj/machines/atmospherics/bluespace_gas_selling.dmi diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index 3f2cacaf1045..cccad01091eb 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -164,7 +164,6 @@ GLOBAL_LIST_INIT(WALLITEMS_INTERIOR, typecacheof(list( /obj/item/radio/intercom, /obj/structure/secure_safe, /obj/machinery/airalarm, - /obj/machinery/bluespace_vendor, /obj/machinery/button, /obj/machinery/computer/security/telescreen, /obj/machinery/computer/security/telescreen/entertainment, diff --git a/code/datums/components/self_ignition.dm b/code/datums/components/self_ignition.dm index eda5f69f2f27..92ac74920bef 100644 --- a/code/datums/components/self_ignition.dm +++ b/code/datums/components/self_ignition.dm @@ -45,7 +45,7 @@ if (!environment?.total_moles()) return - if(environment.gases[/datum/gas/hypernoblium] && environment.gases[/datum/gas/hypernoblium][MOLES] >= 5) + if(environment.moles[/datum/gas/hypernoblium] >= 5) if(owner.on_fire && owner.fire_stacks > 0) owner.adjust_fire_stacks(-fire_stacks_loss * seconds_per_tick) return @@ -55,7 +55,7 @@ active_burning = TRUE - if(!environment.gases[/datum/gas/oxygen] || environment.gases[/datum/gas/oxygen][MOLES] < 1) //Same threshhold that extinguishes fire + if(environment.moles[/datum/gas/oxygen] < 1) //Same threshhold that extinguishes fire return owner.adjust_fire_stacks(fire_stacks_per_second * seconds_per_tick) diff --git a/code/datums/diseases/advance/floor_diseases/gastritium.dm b/code/datums/diseases/advance/floor_diseases/gastritium.dm index f012f4515f2c..65e589527948 100644 --- a/code/datums/diseases/advance/floor_diseases/gastritium.dm +++ b/code/datums/diseases/advance/floor_diseases/gastritium.dm @@ -25,7 +25,7 @@ /datum/disease/advance/gastritium/GetDiseaseID() return "[type]" -/datum/disease/advance/gastritium/stage_act(seconds_per_tick, times_fired) +/datum/disease/gastritium/stage_act(seconds_per_tick) . = ..() if(!.) return @@ -49,15 +49,16 @@ to_chat(affected_mob, span_warning("You feel like you're about to delam...")) else if(SPT_PROB(1, seconds_per_tick)) tritium_burp(hot_chance = TRUE) + var/change_limit = max(affected_mob.get_body_temp_heat_damage_limit() - 5 - affected_mob.get_body_temp_normal(apply_change=FALSE), 0) + affected_mob.add_body_temperature_change("gastritium", min(15 * stage, change_limit)) -/datum/disease/advance/gastritium/proc/tritium_burp(hot_chance = FALSE) +/datum/disease/gastritium/proc/tritium_burp(hot_chance = FALSE) var/datum/gas_mixture/burp = new - ADD_GAS(/datum/gas/tritium, burp.gases) - burp.gases[/datum/gas/tritium][MOLES] = MOLES_GAS_VISIBLE - burp.temperature = affected_mob.body_temperature + burp.set_gas(/datum/gas/tritium, MOLES_GAS_VISIBLE) + burp.temperature = affected_mob.bodytemperature if(hot_chance && prob(tritium_burp_hot_chance)) - burp.temperature = TRITIUM_MINIMUM_BURN_TEMPERATURE - if(affected_mob.stat == CONSCIOUS) + burp.set_temperature(TRITIUM_MINIMUM_BURN_TEMPERATURE) + if(!IS_UNCONSCIOUS_OR_CRIT(affected_mob)) to_chat(affected_mob, span_warning("Your throat feels hot!")) affected_mob.visible_message("burps out green gas.", visible_message_flags = EMOTE_MESSAGE) affected_mob.loc.assume_air(burp) diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index 1d6e75eb6097..de24082a31ba 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -447,15 +447,6 @@ /obj/item/stack/sheet/glass = 10, /obj/item/stack/sheet/plasteel = 5) -/obj/item/circuitboard/machine/bluespace_sender - name = "Bluespace Sender" - greyscale_colors = CIRCUIT_COLOR_ENGINEERING - build_path = /obj/machinery/atmospherics/components/unary/bluespace_sender - req_components = list( - /obj/item/stack/cable_coil = 10, - /obj/item/stack/sheet/glass = 10, - /obj/item/stack/sheet/plasteel = 5) - //Generic /obj/item/circuitboard/machine/component_printer name = "\improper Component Printer (Machine Board)" diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm index a35e023316b6..e97f61c9771e 100644 --- a/code/game/objects/items/flamethrower.dm +++ b/code/game/objects/items/flamethrower.dm @@ -279,8 +279,7 @@ SIGNAL_HANDLER if(ptank) var/datum/gas_mixture/tank_mix = ptank.return_air() - tank_mix.assert_gas(/datum/gas/plasma) - tank_mix.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*ptank.volume/(R_IDEAL_GAS_EQUATION*T20C) + tank_mix.set_gas(/datum/gas/plasma, (10*ONE_ATMOSPHERE)*ptank.volume/(R_IDEAL_GAS_EQUATION*T20C)) else ptank = new /obj/item/tank/internals/plasma/full(src) update_appearance() diff --git a/code/game/objects/structures/crates_lockers/closets/bodybag.dm b/code/game/objects/structures/crates_lockers/closets/bodybag.dm index a87f5654963e..b2ebc605de8d 100644 --- a/code/game/objects/structures/crates_lockers/closets/bodybag.dm +++ b/code/game/objects/structures/crates_lockers/closets/bodybag.dm @@ -434,11 +434,11 @@ if(opened) // lose a majority of all n2o when we start leaking gas, to stop this being a free (obnoxious) way to make n2o internal_air.assert_gases(/datum/gas/nitrous_oxide) - internal_air.adjust_gas(/datum/gas/nitrous_oxide, internal_air.gases[/datum/gas/nitrous_oxide][MOLES] * 0.15) + internal_air.adjust_gas(/datum/gas/nitrous_oxide, internal_air.moles[/datum/gas/nitrous_oxide] * 0.15) return ..() internal_air.assert_gases(/datum/gas/nitrogen, /datum/gas/nitrous_oxide) - var/conversion_amount = min(internal_air.gases[/datum/gas/nitrogen][MOLES], 0.2 * internal_air.total_moles() * seconds_per_tick) + var/conversion_amount = min(internal_air.moles[/datum/gas/nitrogen], 0.2 * internal_air.total_moles() * seconds_per_tick) if(conversion_amount > 0) // 20% of the nitrogen in the bag is converted to nitrous oxide every second while closed internal_air.convert_gas(/datum/gas/nitrogen, /datum/gas/nitrous_oxide, conversion_amount) diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index 79f85379c4db..0118f96307e7 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -465,8 +465,8 @@ cached_moles[/datum/gas/nitrogen] -= heat_efficiency cached_moles[/datum/gas/nitrous_oxide] += heat_efficiency - SET_REACTION_RESULTS(heat_efficency) - var/energy_released = heat_efficency * N2O_FORMATION_ENERGY + SET_REACTION_RESULTS(heat_efficiency) + var/energy_released = heat_efficiency * N2O_FORMATION_ENERGY var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air.temperature = max(((air.temperature * old_heat_capacity + energy_released) / new_heat_capacity), TCMB) // The air cools down when reacting. @@ -706,8 +706,8 @@ cached_moles[/datum/gas/hydrogen] += heat_efficiency cached_moles[/datum/gas/nitrogen] += heat_efficiency - SET_REACTION_RESULTS(heat_efficency) - var/energy_released = heat_efficency * NITRIUM_DECOMPOSITION_ENERGY + SET_REACTION_RESULTS(heat_efficiency) + var/energy_released = heat_efficiency * NITRIUM_DECOMPOSITION_ENERGY var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air.temperature = max(((temperature * old_heat_capacity + energy_released) / new_heat_capacity), TCMB) //the air heats up when reacting @@ -855,8 +855,8 @@ cached_moles[/datum/gas/oxygen] -= heat_efficiency * 20 cached_moles[/datum/gas/pluoxium] += heat_efficiency * 2.5 - SET_REACTION_RESULTS(heat_efficency * 5) - var/energy_used = heat_efficency * HALON_COMBUSTION_ENERGY + SET_REACTION_RESULTS(heat_efficiency * 5) + var/energy_used = heat_efficiency * HALON_COMBUSTION_ENERGY var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air.temperature = max(((temperature * old_heat_capacity - energy_used) / new_heat_capacity), TCMB) @@ -898,8 +898,8 @@ cached_moles[/datum/gas/bz] -= heat_efficiency * 0.25 cached_moles[/datum/gas/healium] += heat_efficiency * 3 - SET_REACTION_RESULTS(heat_efficency * 3) - var/energy_released = heat_efficency * HEALIUM_FORMATION_ENERGY + SET_REACTION_RESULTS(heat_efficiency * 3) + var/energy_released = heat_efficiency * HEALIUM_FORMATION_ENERGY var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air.temperature = max(((temperature * old_heat_capacity + energy_released) / new_heat_capacity), TCMB) @@ -940,8 +940,8 @@ cached_moles[/datum/gas/nitrium] -= heat_efficiency * 0.5 cached_moles[/datum/gas/zauker] += heat_efficiency * 0.5 - SET_REACTION_RESULTS(heat_efficency * 0.5) - var/energy_used = heat_efficency * ZAUKER_FORMATION_ENERGY + SET_REACTION_RESULTS(heat_efficiency * 0.5) + var/energy_used = heat_efficiency * ZAUKER_FORMATION_ENERGY var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air.temperature = max(((temperature * old_heat_capacity - energy_used) / new_heat_capacity), TCMB) @@ -1023,8 +1023,8 @@ cached_moles[/datum/gas/pluoxium] -= heat_efficiency * 0.2 cached_moles[/datum/gas/proto_nitrate] += heat_efficiency * 2.2 - SET_REACTION_RESULTS(heat_efficency * 2.2) - var/energy_released = heat_efficency * PN_FORMATION_ENERGY + SET_REACTION_RESULTS(heat_efficiency * 2.2) + var/energy_released = heat_efficiency * PN_FORMATION_ENERGY var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air.temperature = max(((temperature * old_heat_capacity + energy_released) / new_heat_capacity), TCMB) diff --git a/code/modules/atmospherics/machinery/bluespace_vendor.dm b/code/modules/atmospherics/machinery/bluespace_vendor.dm deleted file mode 100644 index b301eb891293..000000000000 --- a/code/modules/atmospherics/machinery/bluespace_vendor.dm +++ /dev/null @@ -1,285 +0,0 @@ -/obj/item/wallframe/bluespace_vendor_mount - name = "bluespace vendor wall mount" - desc = "Used for placing bluespace vendors." - icon = 'icons/obj/machines/atmospherics/bluespace_gas_selling.dmi' - icon_state = "bluespace_vendor_open" - result_path = /obj/machinery/bluespace_vendor/built - pixel_shift = 30 - -///Defines for the mode of the vendor -#define BS_MODE_OFF 1 -#define BS_MODE_IDLE 2 -#define BS_MODE_PUMPING 3 -#define BS_MODE_OPEN 4 - -/obj/machinery/bluespace_vendor - icon = 'icons/obj/machines/atmospherics/bluespace_gas_selling.dmi' - icon_state = "bluespace_vendor_off" - base_icon_state = "bluespace_vendor" - name = "Bluespace Gas Vendor" - desc = "Sells gas tanks with custom mixes for all the family!" - - max_integrity = 300 - armor_type = /datum/armor/machinery_bluespace_vendor - layer = OBJ_LAYER - - ///The bluespace sender that this vendor is connected to - var/obj/machinery/atmospherics/components/unary/bluespace_sender/connected_machine - ///Amount of usable tanks inside the machine - var/empty_tanks = 10 - ///Reference to the current in use tank to be filled - var/obj/item/tank/internals/generic/internal_tank - ///Path of the gas selected from the UI to be pumped inside the tanks - var/selected_gas - ///Is the vendor trying to move gases from the network to the tanks? - var/pumping = FALSE - ///Has the user prepared a tank to be filled with gases? - var/inserted_tank = FALSE - ///Amount of the tank already filled with gas (from 0 to 100) - var/tank_filling_amount = 0 - ///Base price of the tank - var/tank_cost = 10 - ///Stores the current price of the gases inside the tank - var/gas_price = 0 - ///Helper for mappers, will automatically connect to the sender (ensure to only place one sender per map) - var/map_spawned = TRUE - ///Current operating mode of the vendor - var/mode = BS_MODE_OFF - -//The one that the players make -/obj/machinery/bluespace_vendor/built - map_spawned = FALSE - mode = BS_MODE_OPEN - -MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/bluespace_vendor, 30) - -/datum/armor/machinery_bluespace_vendor - energy = 100 - fire = 80 - acid = 30 - -/obj/machinery/bluespace_vendor/Initialize(mapload, ndir, nbuild) - . = ..() - - if(nbuild) - set_panel_open(TRUE) - - update_appearance() - - AddComponent(/datum/component/payment, tank_cost, SSeconomy.get_dep_account(ACCOUNT_ENG), PAYMENT_ANGRY) - find_and_hang_on_wall( FALSE) - -/obj/machinery/bluespace_vendor/post_machine_initialize() - . = ..() - if(!map_spawned) - return - for(var/obj/machinery/atmospherics/components/unary/bluespace_sender/sender as anything in GLOB.bluespace_senders) - register_machine(sender) - -/obj/machinery/bluespace_vendor/Destroy() - unregister_machine() - return ..() - -/obj/machinery/bluespace_vendor/update_icon_state() - switch(mode) - if(BS_MODE_OFF) - icon_state = "[base_icon_state]_off" - if(BS_MODE_IDLE) - icon_state = "[base_icon_state]_idle" - if(BS_MODE_PUMPING) - icon_state = "[base_icon_state]_pumping" - if(BS_MODE_OPEN) - icon_state = "[base_icon_state]_open" - return ..() - -/obj/machinery/bluespace_vendor/Exited(atom/movable/gone, direction) - if(gone == internal_tank) - internal_tank = null - return ..() - -/obj/machinery/bluespace_vendor/process() - if(mode == BS_MODE_OPEN) - return - if(!selected_gas) - return - var/gas_path = gas_id2path(selected_gas) - - if(!connected_machine.bluespace_network.gases[gas_path]) - pumping = FALSE - selected_gas = null - mode = BS_MODE_IDLE - update_appearance() - return - - connected_machine.bluespace_network.pump_gas_to(internal_tank.return_air(), (tank_filling_amount * 0.01) * 10 * ONE_ATMOSPHERE, gas_path) - -/obj/machinery/bluespace_vendor/multitool_act(mob/living/user, obj/item/multitool/multitool) - if(!istype(multitool)) - return - if(!istype(multitool.buffer, /obj/machinery/atmospherics/components/unary/bluespace_sender)) - to_chat(user, span_notice("Wrong machine type in [multitool] buffer...")) - return - if(connected_machine) - to_chat(user, span_notice("Changing [src] bluespace network...")) - if(!do_after(user, 0.2 SECONDS, src)) - return - playsound(get_turf(user), 'sound/machines/click.ogg', 10, TRUE) - register_machine(multitool.buffer) - to_chat(user, span_notice("You link [src] to the console in [multitool]'s buffer.")) - return TRUE - -/obj/machinery/bluespace_vendor/attackby(obj/item/item, mob/living/user) - if(!pumping && default_deconstruction_screwdriver(user, "[base_icon_state]_open", "[base_icon_state]_off", item)) - check_mode() - return - if(default_deconstruction_crowbar(item, FALSE, custom_deconstruct = TRUE)) - new/obj/item/wallframe/bluespace_vendor_mount(user.loc) - qdel(src) - return - - if(istype(item, /obj/item/stack/sheet/iron)) - var/obj/item/stack/sheet/iron/iron = item - if (iron.use(1)) - empty_tanks++ - return TRUE - return ..() - -/obj/machinery/bluespace_vendor/examine(mob/user) - . = ..() - if(empty_tanks > 1) - . += span_notice("There are currently [empty_tanks] empty tanks available, more can be made by inserting iron sheets in the machine.") - else if(empty_tanks == 1) - . += span_notice("There is only one empty tank available, please refill the machine by using iron sheets.") - else - . += span_notice("There is no available tank, please refill the machine by using iron sheets.") - -///Check what is the current operating mode -/obj/machinery/bluespace_vendor/proc/check_mode() - if(panel_open) - mode = BS_MODE_OPEN - else if(connected_machine) - mode = BS_MODE_IDLE - else - mode = BS_MODE_OFF - update_appearance() - -///Register the sender as the connected_machine -/obj/machinery/bluespace_vendor/proc/register_machine(machine) - connected_machine = machine - LAZYADD(connected_machine.vendors, src) - RegisterSignal(connected_machine, COMSIG_QDELETING, PROC_REF(unregister_machine)) - mode = BS_MODE_IDLE - update_appearance() - -///Unregister the connected_machine (either when qdel this or the sender) -/obj/machinery/bluespace_vendor/proc/unregister_machine() - SIGNAL_HANDLER - if(connected_machine) - UnregisterSignal(connected_machine, COMSIG_QDELETING) - LAZYREMOVE(connected_machine.vendors, src) - connected_machine = null - mode = BS_MODE_OFF - update_appearance() - -///Check the price of the current tank, if the user doesn't have the money the gas will be merged back into the network -/obj/machinery/bluespace_vendor/proc/check_price(mob/user) - var/temp_price = 0 - var/datum/gas_mixture/working_mix = internal_tank.return_air() - var/list/gases = working_mix.gases - for(var/gas_id in gases) - temp_price += gases[gas_id][MOLES] * connected_machine.base_prices[gas_id] - gas_price = temp_price - - if(attempt_charge(src, user, gas_price) & COMPONENT_OBJ_CANCEL_CHARGE) - var/datum/gas_mixture/remove = working_mix.remove_ratio(1) - connected_machine.bluespace_network.merge(remove) - return - connected_machine.credits_gained += gas_price + tank_cost - - if(internal_tank && Adjacent(user)) //proper capitalysm take money before goods - inserted_tank = FALSE - user.put_in_hands(internal_tank) - -/obj/machinery/bluespace_vendor/ui_interact(mob/user, datum/tgui/ui) - if(!connected_machine || mode == BS_MODE_OPEN) - return - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "BluespaceVendor", name) - ui.open() - -/obj/machinery/bluespace_vendor/ui_data(mob/user) - var/list/data = list() - var/list/bluespace_gasdata = list() - if(connected_machine.bluespace_network.total_moles()) - for(var/gas_id in connected_machine.bluespace_network.gases) - bluespace_gasdata.Add(list(list( - "name" = connected_machine.bluespace_network.gases[gas_id][GAS_META][META_GAS_NAME], - "id" = connected_machine.bluespace_network.gases[gas_id][GAS_META][META_GAS_ID], - "amount" = round(connected_machine.bluespace_network.gases[gas_id][MOLES], 0.01), - "price" = connected_machine.base_prices[gas_id], - ))) - else - for(var/gas_id in connected_machine.bluespace_network.gases) - bluespace_gasdata.Add(list(list( - "name" = connected_machine.bluespace_network.gases[gas_id][GAS_META][META_GAS_NAME], - "id" = "", - "amount" = 0, - "price" = 0, - ))) - data["bluespace_network_gases"] = bluespace_gasdata - data["pumping"] = pumping - data["tank_filling_amount"] = tank_filling_amount - data["selected_gas"] = selected_gas - data["tank_amount"] = empty_tanks - data["inserted_tank"] = inserted_tank - var/total_tank_pressure - if(internal_tank) - var/datum/gas_mixture/working_mix = internal_tank.return_air() - total_tank_pressure = working_mix.return_pressure() - else - total_tank_pressure = 0 - data["tank_full"] = total_tank_pressure - return data - -/obj/machinery/bluespace_vendor/ui_act(action, params) - . = ..() - if(.) - return - - if(mode == BS_MODE_OPEN) - return - - switch(action) - if("start_pumping") - if(inserted_tank && !pumping) - pumping = TRUE - selected_gas = params["gas_id"] - mode = BS_MODE_PUMPING - update_appearance() - . = TRUE - if("stop_pumping") - if(inserted_tank && pumping) - pumping = FALSE - selected_gas = null - mode = BS_MODE_IDLE - update_appearance() - . = TRUE - if("pumping_rate") - tank_filling_amount = clamp(params["rate"], 0, 100) - . = TRUE - if("tank_prepare") - if(empty_tanks && !inserted_tank) - inserted_tank = TRUE - internal_tank = new(src) - empty_tanks = max(empty_tanks - 1, 0) - . = TRUE - if("tank_expel") - if(inserted_tank && !pumping) - check_price(usr) - . = TRUE - -#undef BS_MODE_OFF -#undef BS_MODE_IDLE -#undef BS_MODE_PUMPING -#undef BS_MODE_OPEN diff --git a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm index 9b50c7b11d5a..4e31eae5f90f 100644 --- a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm +++ b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm @@ -79,10 +79,9 @@ GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list()) var/old_heat_capacity = air_mixture.heat_capacity() air_mixture.assert_gases(/datum/gas/hypernoblium, /datum/gas/antinoblium) - var/proportion = min(air_mixture.gases[/datum/gas/hypernoblium][MOLES], (1.5 * (working_power ** 2))) + var/proportion = min(air_mixture.moles[/datum/gas/hypernoblium], (1.5 * (working_power ** 2))) - air_mixture.adjust_gas(/datum/gas/hypernoblium, -proportion) - air_mixture.adjust_gas(/datum/gas/antinoblium, proportion) + air_mixture.convert_gas(/datum/gas/hypernoblium, /datum/gas/antinoblium, proportion) var/new_heat_capacity = air_mixture.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm b/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm deleted file mode 100644 index ebe053663f9e..000000000000 --- a/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm +++ /dev/null @@ -1,231 +0,0 @@ -/obj/machinery/atmospherics/components/unary/bluespace_sender - icon = 'icons/obj/machines/atmospherics/bluespace_gas_selling.dmi' - icon_state = "bluespace_sender_off" - base_icon_state = "bluespace_sender" - name = "Bluespace Gas Sender" - desc = "Sends gases to the bluespace network to be shared with the connected vendors, who knows what's beyond!" - interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT - - density = TRUE - max_integrity = 300 - armor_type = /datum/armor/unary_bluespace_sender - layer = OBJ_LAYER - circuit = /obj/item/circuitboard/machine/bluespace_sender - move_resist = MOVE_RESIST_DEFAULT - set_dir_on_move = FALSE - pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY - - ///Gas mixture containing the inserted gases and that is connected to the vendors - var/datum/gas_mixture/bluespace_network - ///Rate of gas transfer inside the network (from 0 to 1) - var/gas_transfer_rate = 0.5 - ///A base price for each and every gases, in case you don't want to change them - var/list/base_prices = list() - ///List storing all the vendors connected to the machine - var/list/vendors - ///Amount of credits gained from each vendor - var/credits_gained = 0 - -/// All bluespace gas senders -GLOBAL_LIST_EMPTY_TYPED(bluespace_senders, /obj/machinery/atmospherics/components/unary/bluespace_sender) - -/datum/armor/unary_bluespace_sender - energy = 100 - fire = 80 - acid = 30 - -/obj/machinery/atmospherics/components/unary/bluespace_sender/Initialize(mapload) - . = ..() - initialize_directions = dir - bluespace_network = new - for(var/gas_id in GLOB.meta_gas_info) - bluespace_network.assert_gas(gas_id) - for(var/gas_id in GLOB.meta_gas_info) - var/datum/gas/gas = gas_id - base_prices[gas_id] = initial(gas.base_value) - - GLOB.bluespace_senders += src - - update_appearance() - register_context() - -/obj/machinery/atmospherics/components/unary/bluespace_sender/Destroy() - GLOB.bluespace_senders -= src - - return ..() - -/obj/machinery/atmospherics/components/unary/bluespace_sender/examine(mob/user) - . = ..() - . += span_notice("With the panel open:") - . += span_notice(" -Use a wrench with left-click to rotate [src] and right-click to unanchor it.") - -/obj/machinery/atmospherics/components/unary/bluespace_sender/add_context(atom/source, list/context, obj/item/held_item, mob/user) - . = ..() - if(anchored && !panel_open && is_operational) - context[SCREENTIP_CONTEXT_CTRL_LMB] = "Turn [on ? "off" : "on"]" - if(!held_item) - return CONTEXTUAL_SCREENTIP_SET - switch(held_item.tool_behaviour) - if(TOOL_SCREWDRIVER) - context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel" - if(TOOL_WRENCH) - context[SCREENTIP_CONTEXT_LMB] = "Rotate" - context[SCREENTIP_CONTEXT_RMB] = "[anchored ? "Unan" : "An"]chor" - return CONTEXTUAL_SCREENTIP_SET - -/obj/machinery/atmospherics/components/unary/bluespace_sender/is_connectable() - if(!anchored) - return FALSE - . = ..() - -/obj/machinery/atmospherics/components/unary/bluespace_sender/update_icon_state() - if(panel_open) - icon_state = "[base_icon_state]_open" - return ..() - if(on && is_operational) - icon_state = "[base_icon_state]_on" - return ..() - icon_state = "[base_icon_state]_off" - return ..() - -/obj/machinery/atmospherics/components/unary/bluespace_sender/update_overlays() - . = ..() - . += get_pipe_image(icon, "pipe", dir, , piping_layer) - if(showpipe) - . += get_pipe_image(icon, "pipe", initialize_directions) - -/obj/machinery/atmospherics/components/unary/bluespace_sender/process_atmos() - if(!is_operational || !on || !nodes[1]) //if it has no power or its switched off, dont process atmos - return - - var/datum/gas_mixture/content = airs[1] - var/datum/gas_mixture/remove = content.remove_ratio(gas_transfer_rate) - bluespace_network.merge(remove) - bluespace_network.temperature = T20C - update_parents() - -/obj/machinery/atmospherics/components/unary/bluespace_sender/relocate_airs() - if(bluespace_network.total_moles() > 0) - airs[1].merge(bluespace_network) - airs[1].garbage_collect() - return ..() - -/obj/machinery/atmospherics/components/unary/bluespace_sender/screwdriver_act(mob/living/user, obj/item/tool) - if(on) - balloon_alert(user, "turn off!") - return ITEM_INTERACT_SUCCESS - if(!anchored) - balloon_alert(user, "anchor!") - return ITEM_INTERACT_SUCCESS - if(default_deconstruction_screwdriver(user, "[base_icon_state]_open", "[base_icon_state]_off", tool)) - return ITEM_INTERACT_SUCCESS - -/obj/machinery/atmospherics/components/unary/bluespace_sender/crowbar_act(mob/living/user, obj/item/tool) - if(panel_open && bluespace_network.total_moles() > 0 && !nodes[1]) - say("WARNING - Bluespace network can contain hazardous gases, deconstruct with caution!") - return crowbar_deconstruction_act(user, tool) - -/obj/machinery/atmospherics/components/unary/bluespace_sender/multitool_act(mob/living/user, obj/item/item) - var/obj/item/multitool/multitool = item - multitool.set_buffer(src) - balloon_alert(user, "saved to multitool buffer") - return TRUE - -/obj/machinery/atmospherics/components/unary/bluespace_sender/wrench_act(mob/living/user, obj/item/tool) - return default_change_direction_wrench(user, tool) - -/obj/machinery/atmospherics/components/unary/bluespace_sender/wrench_act_secondary(mob/living/user, obj/item/tool) - if(!panel_open) - balloon_alert(user, "open panel!") - return - if(default_unfasten_wrench(user, tool)) - change_pipe_connection(!anchored) - return ITEM_INTERACT_SUCCESS - return - -/obj/machinery/atmospherics/components/unary/bluespace_sender/default_change_direction_wrench(mob/user, obj/item/item) - if(!..()) - return FALSE - set_init_directions() - update_appearance() - return TRUE - -/obj/machinery/atmospherics/components/unary/bluespace_sender/click_ctrl(mob/user) - if(!panel_open && is_operational) - on = !on - balloon_alert(user, "turned [on ? "on" : "off"]") - investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) - update_appearance() - return CLICK_ACTION_SUCCESS - return NONE - -/obj/machinery/atmospherics/components/unary/bluespace_sender/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "BluespaceSender", name) - ui.open() - -/obj/machinery/atmospherics/components/unary/bluespace_sender/ui_data(mob/user) - var/list/data = list() - data["on"] = on - data["gas_transfer_rate"] = gas_transfer_rate - var/list/bluespace_gasdata = list() - if(bluespace_network.total_moles()) - for(var/gas_id in bluespace_network.gases) - bluespace_gasdata.Add(list(list( - "name" = bluespace_network.gases[gas_id][GAS_META][META_GAS_NAME], - "id" = bluespace_network.gases[gas_id][GAS_META][META_GAS_ID], - "amount" = round(bluespace_network.gases[gas_id][MOLES], 0.01), - "price" = base_prices[gas_id], - ))) - else - for(var/gas_id in bluespace_network.gases) - bluespace_gasdata.Add(list(list( - "name" = bluespace_network.gases[gas_id][GAS_META][META_GAS_NAME], - "id" = "", - "amount" = 0, - "price" = 0, - ))) - data["bluespace_network_gases"] = bluespace_gasdata - var/list/vendors_list = list() - if(vendors) - for(var/obj/machinery/bluespace_vendor/vendor in vendors) - vendors_list.Add(list(list( - "name" = vendor.name, - "area" = get_area(vendor), - ))) - data["vendors_list"] = vendors_list - data["credits"] = credits_gained - return data - -/obj/machinery/atmospherics/components/unary/bluespace_sender/ui_act(action, params) - . = ..() - if(.) - return - - switch(action) - if("power") - on = !on - investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS) - update_appearance() - . = TRUE - - if("rate") - gas_transfer_rate = clamp(params["rate"], 0, 1) - . = TRUE - - if("price") - var/gas_type = gas_id2path(params["gas_type"]) - base_prices[gas_type] = clamp(params["gas_price"], 0, 100) - . = TRUE - - if("retrieve") - if(bluespace_network.total_moles() > 0) - var/datum/gas_mixture/remove = bluespace_network.remove(bluespace_network.total_moles()) - airs[1].merge(remove) - update_parents() - bluespace_network.garbage_collect() - . = TRUE - -/obj/machinery/atmospherics/components/unary/bluespace_sender/update_layer() - return diff --git a/code/modules/cargo/exports/large_objects.dm b/code/modules/cargo/exports/large_objects.dm index d94f9fc1bb58..c3b773abc085 100644 --- a/code/modules/cargo/exports/large_objects.dm +++ b/code/modules/cargo/exports/large_objects.dm @@ -101,6 +101,9 @@ unit_name = "security barrier" export_types = list(/obj/item/grenade/barrier, /obj/structure/barricade/security) +///Maximum number of credits you can earn from selling your gas canister cause its theoritically infinite +#define MAX_GAS_CREDITS 15000 + /** * Gas canister exports. * I'm going to put a quick aside here as this has been a pain to balance for several years now, and I'd like to at least break how to keep gas exports tame. @@ -158,3 +161,5 @@ /datum/export/large/gas_canister/proc/get_gas_value(datum/gas/gasType, moles) var/baseValue = initial(gasType.base_value) return round((baseValue/k_elasticity) * (1 - NUM_E**(-1 * k_elasticity * moles))) + +#undef MAX_GAS_CREDITS diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 2b5330ce2d71..9d5a303053cd 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -82,13 +82,14 @@ * * drop_items - Should the mob drop their items before dusting? * * force - Should this mob be FORCABLY dusted? */ -/mob/living/proc/dust(just_ash, drop_items, force) +/mob/living/proc/dust(just_ash, drop_items, give_moodlet = TRUE, force) if(body_position == STANDING_UP) // keep us upright so the animation fits. ADD_TRAIT(src, TRAIT_FORCED_STANDING, TRAIT_GENERIC) death(TRUE, "being vaporized") - send_death_moodlets(dusted = TRUE) + if(give_moodlet) + send_death_moodlets(dusted = TRUE) if(drop_items) unequip_everything() diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index ca23f14f25da..033b378b47a9 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -301,13 +301,13 @@ if(isturf(loc) && isopenturf(loc)) var/turf/open/ST = loc if(ST.air) - var/ST_gases = ST.air.gases + var/ST_moles = ST.air.moles ST.air.assert_gases(/datum/gas/oxygen, /datum/gas/pluoxium, /datum/gas/nitrogen, /datum/gas/carbon_dioxide, /datum/gas/plasma) - var/plas = ST_gases[/datum/gas/plasma][MOLES] - var/oxy = ST_gases[/datum/gas/oxygen][MOLES] + (ST_gases[/datum/gas/pluoxium][MOLES] * PLUOXIUM_PROPORTION) - var/n2 = ST_gases[/datum/gas/nitrogen][MOLES] - var/co2 = ST_gases[/datum/gas/carbon_dioxide][MOLES] + var/plas = ST_moles[/datum/gas/plasma] + var/oxy = ST_moles[/datum/gas/oxygen] + (ST_moles[/datum/gas/pluoxium] * PLUOXIUM_PROPORTION) + var/n2 = ST_moles[/datum/gas/nitrogen] + var/co2 = ST_moles[/datum/gas/carbon_dioxide] ST.air.garbage_collect() diff --git a/icons/obj/machines/atmospherics/bluespace_gas_selling.dmi b/icons/obj/machines/atmospherics/bluespace_gas_selling.dmi deleted file mode 100644 index c397dd2dc84131e403d7367a26353f575e4119ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12655 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 Date: Mon, 7 Sep 2026 18:05:15 -0500 Subject: [PATCH 10/15] Fire fix --- code/modules/mob/status_procs.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/modules/mob/status_procs.dm b/code/modules/mob/status_procs.dm index a313a02cbcde..951346515ffe 100644 --- a/code/modules/mob/status_procs.dm +++ b/code/modules/mob/status_procs.dm @@ -125,7 +125,8 @@ . = CELCIUS_TO_KELVIN(skin_temp) // and if we're on fire just add a flat amount of heat if(on_fire) - . += fire_stacks ** 2 KELVIN + var/fire_heat = (fire_stacks ** 2) KELVIN + . += fire_heat * (1 - get_insulation(area_temperature + fire_heat)) return . From 6bf9ba9c6f8f46ec37a0f611e47f3ba5e4ba93d7 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Mon, 7 Sep 2026 21:55:27 -0500 Subject: [PATCH 11/15] Tackle clear tables --- code/datums/components/tackle.dm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm index cd69e05d64db..016fbfd78f15 100644 --- a/code/datums/components/tackle.dm +++ b/code/datums/components/tackle.dm @@ -609,6 +609,9 @@ if(!kevved) return + if(prob(2 * owner.get_skill_modifier(/datum/skill/athletics, SKILL_RANDS_MODIFIER))) + return + var/list/messes = list() // we split the mess-making into two parts (check what we're gonna send flying, intermission for dealing with the tackler, then actually send stuff flying) for the benefit of making sure the face-slam text From 6af26d02399324c7b38826b0254da464c6b3161e Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:11:30 -0500 Subject: [PATCH 12/15] Great big atmos grammar and qol pas (#97542) --- code/__DEFINES/atmospherics/atmos_core.dm | 8 + code/__HELPERS/atmospherics.dm | 42 +- code/datums/materials/basemats.dm | 10 +- .../atmos_computers/_atmos_control.dm | 8 + .../computer/atmos_computers/air_sensors.dm | 4 +- .../atmos_computers/atmos_controls.dm | 48 +- .../embedded_controller/airlock_controller.dm | 6 +- .../items/devices/scanners/gas_analyzer.dm | 53 ++- .../objects/items/grenades/atmos_grenades.dm | 19 +- .../objects/items/stacks/ammonia_crystals.dm | 1 + .../game/turfs/open/floor/reinforced_floor.dm | 8 +- .../atmospherics/environmental/LINDA_fire.dm | 2 +- .../atmospherics/gasmixtures/gas_mixture.dm | 4 +- .../atmospherics/gasmixtures/gas_types.dm | 136 +++--- .../gasmixtures/reaction_factors.dm | 339 ++++++++------ .../atmospherics/gasmixtures/reactions.dm | 222 ++++----- .../components/electrolyzer/electrolyzer.dm | 8 +- .../electrolyzer/electrolyzer_reactions.dm | 72 +-- .../machinery/components/fusion/hfr_core.dm | 2 +- .../components/fusion/hfr_fuel_datums.dm | 6 +- .../machinery/components/fusion/hfr_parts.dm | 26 +- .../atmos_machines_recipes.dm | 30 +- .../gas_recipe_machines/crystallizer_items.dm | 16 +- .../components/trinary_devices/filter.dm | 46 +- .../machinery/portable/canister.dm | 8 +- code/modules/cargo/exports/large_objects.dm | 27 +- code/modules/cargo/packs/materials.dm | 17 +- .../experisci/experiment/experiments.dm | 4 +- .../file_system/programs/atmosscan.dm | 1 - code/modules/power/cell.dm | 2 +- .../research/ordnance/doppler_array.dm | 2 +- maplestation.dme | 1 - .../tgui/interfaces/AtmosControlConsole.tsx | 297 ++++++------ tgui/packages/tgui/interfaces/GasAnalyzer.tsx | 42 +- .../tgui/interfaces/common/AtmosHandbook.tsx | 441 +++++++++++++----- .../tgui/interfaces/common/GasmixParser.tsx | 229 +++++---- 36 files changed, 1309 insertions(+), 878 deletions(-) diff --git a/code/__DEFINES/atmospherics/atmos_core.dm b/code/__DEFINES/atmospherics/atmos_core.dm index 021dfe2ea4aa..da63ab6b1edd 100644 --- a/code/__DEFINES/atmospherics/atmos_core.dm +++ b/code/__DEFINES/atmospherics/atmos_core.dm @@ -179,3 +179,11 @@ #define ATMOS_PRESSURE_ERROR_TOLERANCE 0.01 /// Helper function for retrieving gas meta info for use in performace critical places #define GAS_META /datum/gas_mixture::gas_meta + +// Flags for gas-cargo handling +/// Gas can be sold in canisters +#define GAS_EXPORTABLE (1 << 0) +/// Gas can be purchased in canisters +#define GAS_PURCHASABLE (1 << 1) +/// Gas is dangerous and may need additional access +#define GAS_DANGEROUS (1 << 2) diff --git a/code/__HELPERS/atmospherics.dm b/code/__HELPERS/atmospherics.dm index bcbdf5e7dc8c..7ca179f75459 100644 --- a/code/__HELPERS/atmospherics.dm +++ b/code/__HELPERS/atmospherics.dm @@ -49,7 +49,7 @@ cached_gas_name[gas_path], amount, )) - for(var/datum/gas_reaction/reaction_result as anything in gasmix.reaction_results) + for(var/datum/gas_reaction/standard/reaction_result as anything in gasmix.reaction_results) .["reactions"] += list(list( initial(reaction_result.id), initial(reaction_result.name), @@ -84,10 +84,11 @@ GLOBAL_LIST_EMPTY(gas_handbook) gas_info["name"] = meta_information[META_GAS_NAME][gas_path] gas_info["description"] = meta_information[META_GAS_DESC][gas_path] gas_info["specific_heat"] = meta_information[META_GAS_SPECIFIC_HEAT][gas_path] + gas_info["export_value"] = (gas_path::cargo_flags & GAS_EXPORTABLE) ? gas_path::base_value : 0 gas_info["reactions"] = list() momentary_gas_list[gas_path] = gas_info - for (var/datum/gas_reaction/reaction_path as anything in subtypesof(/datum/gas_reaction)) + for (var/datum/gas_reaction/standard/reaction_path as anything in valid_subtypesof(/datum/gas_reaction)) var/datum/gas_reaction/reaction = new reaction_path var/list/reaction_info = list() reaction_info["id"] = reaction.id @@ -109,9 +110,9 @@ GLOBAL_LIST_EMPTY(gas_handbook) if(factor == "Temperature" || factor == "Pressure") factor_info["tooltip"] = "Reaction is influenced by the [LOWER_TEXT(factor)] of the place where the reaction is occuring." else if(factor == "Energy") - factor_info["tooltip"] = "Energy released by the reaction, may or may not result in linear temperature change depending on a slew of other factors." + factor_info["tooltip"] = "Energy released by the reaction. May or may not result in linear temperature change depending on a slew of other factors." else if(factor == "Radiation") - factor_info["tooltip"] = "This reaction emits dangerous radiation! Take precautions." + factor_info["tooltip"] = "This reaction emits hazardous radiation - take precautions." else if (factor == "Location") factor_info["tooltip"] = "This reaction has special behaviour when occuring in specific locations." else if(factor == "Hot Ice") @@ -120,44 +121,13 @@ GLOBAL_LIST_EMPTY(gas_handbook) GLOB.reaction_handbook += list(reaction_info) qdel(reaction) - for (var/datum/electrolyzer_reaction/reaction_path as anything in subtypesof(/datum/electrolyzer_reaction)) - var/datum/electrolyzer_reaction/reaction = new reaction_path - var/list/reaction_info = list() - reaction_info["id"] = reaction.id - reaction_info["name"] = reaction.name - reaction_info["description"] = reaction.desc - reaction_info["factors"] = list() - for (var/factor in reaction.factor) - var/list/factor_info = list() - factor_info["desc"] = reaction.factor[factor] - - if(factor in momentary_gas_list) - momentary_gas_list[factor]["reactions"] += list(reaction.id = reaction.name) - factor_info["factor_id"] = momentary_gas_list[factor]["id"] //Gas id - factor_info["factor_type"] = "gas" - factor_info["factor_name"] = momentary_gas_list[factor]["name"] //Common name - else - factor_info["factor_name"] = factor - factor_info["factor_type"] = "misc" - if(factor == "Temperature" || factor == "Pressure") - factor_info["tooltip"] = "Reaction is influenced by the [LOWER_TEXT(factor)] of the place where the reaction is occuring." - else if(factor == "Energy") - factor_info["tooltip"] = "Energy released by the reaction, may or may not result in linear temperature change depending on a slew of other factors." - else if(factor == "Radiation") - factor_info["tooltip"] = "This reaction emits dangerous radiation! Take precautions." - else if (factor == "Location") - factor_info["tooltip"] = "This reaction has special behaviour when occuring in specific locations." - reaction_info["factors"] += list(factor_info) - GLOB.reaction_handbook += list(reaction_info) - qdel(reaction) - for (var/gas_info_index in momentary_gas_list) GLOB.gas_handbook += list(momentary_gas_list[gas_info_index]) /// Returns an assoc list of the gas handbook and the reaction handbook. /// For UIs, simply do data += return_atmos_handbooks() to use. /proc/return_atmos_handbooks() - return list("gasInfo" = GLOB.gas_handbook, "reactionInfo" = GLOB.reaction_handbook) + return list("gasInfo" = GLOB.gas_handbook, "reactionInfo" = GLOB.reaction_handbook, "moneySymbol" = MONEY_SYMBOL, "moneyName" = MONEY_NAME) /proc/extract_id_tags(list/objects) var/list/tags = list() diff --git a/code/datums/materials/basemats.dm b/code/datums/materials/basemats.dm index b34b79bc5bf0..2ddf78439cd5 100644 --- a/code/datums/materials/basemats.dm +++ b/code/datums/materials/basemats.dm @@ -336,7 +336,7 @@ Unless you know what you're doing, only use the first three numbers. They're in ///RPG Magic. /datum/material/mythril name = "mythril" - desc = "How this even exists is byond me" + desc = "How this even exists is byond me." color = "#f2d5d7" greyscale_colors = "#f2d5d7" categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) @@ -365,7 +365,7 @@ Unless you know what you're doing, only use the first three numbers. They're in //formed when freon react with o2, emits a lot of plasma when heated /datum/material/hot_ice name = "hot ice" - desc = "A weird kind of ice, feels warm to the touch" + desc = "A crystalline solid formed when Freon reacts with Oxygen. Extremely flammable, and will easily combust when exposed to heat." color = "#88cdf1" greyscale_colors = "#88cdf196" alpha = 150 @@ -389,8 +389,8 @@ Unless you know what you're doing, only use the first three numbers. They're in return TRUE /datum/material/metalhydrogen - name = "Metal Hydrogen" - desc = "Solid metallic hydrogen. Some say it should be impossible" + name = "metal hydrogen" + desc = "Hydrogen in a metallic state, formed under extreme pressure. Some say achieving this state is impossible." color = "#f2d5d7" greyscale_colors = "#f2d5d796" alpha = 150 @@ -561,7 +561,7 @@ Unless you know what you're doing, only use the first three numbers. They're in /datum/material/zaukerite name = "zaukerite" - desc = "A light absorbing crystal" + desc = "A light absorbing crystal formed out of Zauker. Vaguely toxic, like the gas itself." color = COLOR_ALMOST_BLACK greyscale_colors = COLOR_ALMOST_BLACK categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) diff --git a/code/game/machinery/computer/atmos_computers/_atmos_control.dm b/code/game/machinery/computer/atmos_computers/_atmos_control.dm index 094f12e36e36..49cdb7462a65 100644 --- a/code/game/machinery/computer/atmos_computers/_atmos_control.dm +++ b/code/game/machinery/computer/atmos_computers/_atmos_control.dm @@ -116,6 +116,7 @@ data["maxOutput"] = MAX_OUTPUT_PRESSURE data["control"] = control data["reconnecting"] = reconnecting + data["defaultGas"] = get_default_gas() data += return_atmos_handbooks() return data @@ -229,6 +230,13 @@ return TRUE +/obj/machinery/computer/atmos_control/proc/get_default_gas() + for(var/gas_path, gas_id in GLOB.meta_gas_info[META_GAS_ID]) + if(gas_id == atmos_chambers[1]) + return gas_id + + return null + /obj/machinery/computer/atmos_control/nocontrol control = FALSE circuit = /obj/item/circuitboard/computer/atmos_control/nocontrol diff --git a/code/game/machinery/computer/atmos_computers/air_sensors.dm b/code/game/machinery/computer/atmos_computers/air_sensors.dm index 723ffacb2eb7..374459906a4a 100644 --- a/code/game/machinery/computer/atmos_computers/air_sensors.dm +++ b/code/game/machinery/computer/atmos_computers/air_sensors.dm @@ -47,7 +47,7 @@ chamber_id = ATMOS_GAS_MONITOR_H2 /obj/machinery/air_sensor/hypernoblium_tank - name = "hypernoblium tank gas sensor" + name = "hyper-noblium tank gas sensor" chamber_id = ATMOS_GAS_MONITOR_HYPERNOBLIUM /obj/machinery/air_sensor/miasma_tank @@ -83,7 +83,7 @@ chamber_id = ATMOS_GAS_MONITOR_HELIUM /obj/machinery/air_sensor/antinoblium_tank - name = "antinoblium tank gas sensor" + name = "anti-noblium tank gas sensor" chamber_id = ATMOS_GAS_MONITOR_ANTINOBLIUM /obj/machinery/air_sensor/incinerator_tank diff --git a/code/game/machinery/computer/atmos_computers/atmos_controls.dm b/code/game/machinery/computer/atmos_computers/atmos_controls.dm index bf386c83a1c3..76ccc228dfa6 100644 --- a/code/game/machinery/computer/atmos_computers/atmos_controls.dm +++ b/code/game/machinery/computer/atmos_computers/atmos_controls.dm @@ -4,122 +4,122 @@ atmos_chambers = list(ATMOS_GAS_MONITOR_DISTRO = "Distribution Loop", ATMOS_GAS_MONITOR_WASTE = "Waste Loop") /obj/machinery/computer/atmos_control/oxygen_tank - name = "Oxygen Supply Control" + name = "\improper Oxygen supply control" circuit = /obj/item/circuitboard/computer/atmos_control/oxygen_tank atmos_chambers = list(ATMOS_GAS_MONITOR_O2 = "Oxygen Supply") /obj/machinery/computer/atmos_control/plasma_tank - name = "Plasma Supply Control" + name = "\improper Plasma supply control" circuit = /obj/item/circuitboard/computer/atmos_control/plasma_tank atmos_chambers = list(ATMOS_GAS_MONITOR_PLAS = "Plasma Supply") /obj/machinery/computer/atmos_control/air_tank - name = "Mixed Air Supply Control" + name = "\improper mixed air supply control" circuit = /obj/item/circuitboard/computer/atmos_control/air_tank atmos_chambers = list(ATMOS_GAS_MONITOR_AIR = "Mixed Air Supply") /obj/machinery/computer/atmos_control/nitrous_tank - name = "Nitrous Oxide Supply Control" + name = "\improper Nitrous Oxide supply control" circuit = /obj/item/circuitboard/computer/atmos_control/nitrous_tank atmos_chambers = list(ATMOS_GAS_MONITOR_N2O = "Nitrous Oxide Supply") /obj/machinery/computer/atmos_control/nitrogen_tank - name = "Nitrogen Supply Control" + name = "\improper Nitrogen supply control" circuit = /obj/item/circuitboard/computer/atmos_control/nitrogen_tank atmos_chambers = list(ATMOS_GAS_MONITOR_N2 = "Nitrogen Supply") /obj/machinery/computer/atmos_control/carbon_tank - name = "Carbon Dioxide Supply Control" + name = "\improper Carbon Dioxide supply control" circuit = /obj/item/circuitboard/computer/atmos_control/carbon_tank atmos_chambers = list(ATMOS_GAS_MONITOR_CO2 = "Carbon Dioxide Supply") /obj/machinery/computer/atmos_control/bz_tank - name = "BZ Supply Control" + name = "\improper BZ supply control" circuit = /obj/item/circuitboard/computer/atmos_control/bz_tank atmos_chambers = list(ATMOS_GAS_MONITOR_BZ = "BZ Supply") /obj/machinery/computer/atmos_control/freon_tank - name = "Freon Supply Control" + name = "\improper Freon supply control" circuit = /obj/item/circuitboard/computer/atmos_control/freon_tank atmos_chambers = list(ATMOS_GAS_MONITOR_FREON = "Freon Supply") /obj/machinery/computer/atmos_control/halon_tank - name = "Halon Supply Control" + name = "\improper Halon supply control" circuit = /obj/item/circuitboard/computer/atmos_control/halon_tank atmos_chambers = list(ATMOS_GAS_MONITOR_HALON = "Halon Supply") /obj/machinery/computer/atmos_control/healium_tank - name = "Healium Supply Control" + name = "\improper Healium supply control" circuit = /obj/item/circuitboard/computer/atmos_control/healium_tank atmos_chambers = list(ATMOS_GAS_MONITOR_HEALIUM = "Healium Supply") /obj/machinery/computer/atmos_control/hydrogen_tank - name = "Hydrogen Supply Control" + name = "\improper Hydrogen supply control" circuit = /obj/item/circuitboard/computer/atmos_control/hydrogen_tank atmos_chambers = list(ATMOS_GAS_MONITOR_H2 = "Hydrogen Supply") /obj/machinery/computer/atmos_control/hypernoblium_tank - name = "Hypernoblium Supply Control" + name = "\improper Hyper-Noblium supply control" circuit = /obj/item/circuitboard/computer/atmos_control/hypernoblium_tank atmos_chambers = list(ATMOS_GAS_MONITOR_HYPERNOBLIUM = "Hypernoblium Supply") /obj/machinery/computer/atmos_control/miasma_tank - name = "Miasma Supply Control" + name = "\improper Miasma supply control" circuit = /obj/item/circuitboard/computer/atmos_control/miasma_tank atmos_chambers = list(ATMOS_GAS_MONITOR_MIASMA = "Miasma Supply") /obj/machinery/computer/atmos_control/nitrium_tank - name = "Nitrium Supply Control" + name = "\improper Nitrium supply control" circuit = /obj/item/circuitboard/computer/atmos_control/nitrium_tank atmos_chambers = list(ATMOS_GAS_MONITOR_NITRIUM = "Nitrium Supply") /obj/machinery/computer/atmos_control/pluoxium_tank - name = "Pluoxium Supply Control" + name = "\improper Pluoxium supply control" circuit = /obj/item/circuitboard/computer/atmos_control/pluoxium_tank atmos_chambers = list(ATMOS_GAS_MONITOR_PLUOXIUM = "Pluoxium Supply") /obj/machinery/computer/atmos_control/proto_nitrate_tank - name = "Proto-Nitrate Supply Control" + name = "\improper Proto-Nitrate supply control" circuit = /obj/item/circuitboard/computer/atmos_control/proto_nitrate_tank atmos_chambers = list(ATMOS_GAS_MONITOR_PROTO_NITRATE = "Proto-Nitrate Supply") /obj/machinery/computer/atmos_control/tritium_tank - name = "Tritium Supply Control" + name = "\improper Tritium supply control" circuit = /obj/item/circuitboard/computer/atmos_control/tritium_tank atmos_chambers = list(ATMOS_GAS_MONITOR_TRITIUM = "Tritium Supply") /obj/machinery/computer/atmos_control/water_vapor - name = "Water Vapor Supply Control" + name = "\improper Water Vapor supply control" circuit = /obj/item/circuitboard/computer/atmos_control/water_vapor atmos_chambers = list(ATMOS_GAS_MONITOR_H2O = "Water Vapor Supply") /obj/machinery/computer/atmos_control/zauker_tank - name = "Zauker Supply Control" + name = "\improper Zauker supply control" circuit = /obj/item/circuitboard/computer/atmos_control/zauker_tank atmos_chambers = list(ATMOS_GAS_MONITOR_ZAUKER = "Zauker Supply") /obj/machinery/computer/atmos_control/helium_tank - name = "Helium Supply Control" + name = "\improper Helium supply control" circuit = /obj/item/circuitboard/computer/atmos_control/helium_tank atmos_chambers = list(ATMOS_GAS_MONITOR_HELIUM = "Helium Supply") /obj/machinery/computer/atmos_control/antinoblium_tank - name = "Antinoblium Supply Control" + name = "\improper Anti-Noblium supply control" circuit = /obj/item/circuitboard/computer/atmos_control/antinoblium_tank atmos_chambers = list(ATMOS_GAS_MONITOR_ANTINOBLIUM = "Antinoblium Supply") /obj/machinery/computer/atmos_control/mix_tank - name = "Mix Chamber Control" + name = "mix chamber control" circuit = /obj/item/circuitboard/computer/atmos_control/mix_tank atmos_chambers = list(ATMOS_GAS_MONITOR_MIX = "Mix Chamber") /obj/machinery/computer/atmos_control/nocontrol/incinerator - name = "Incinerator Chamber Monitor" + name = "incinerator chamber monitor" circuit = /obj/item/circuitboard/computer/atmos_control/nocontrol/incinerator atmos_chambers = list(ATMOS_GAS_MONITOR_INCINERATOR = "Incinerator Chamber") /obj/machinery/computer/atmos_control/ordnancemix - name = "Ordnance Chamber Control" + name = "ordnance chamber control" circuit = /obj/item/circuitboard/computer/atmos_control/ordnancemix atmos_chambers = list( ATMOS_GAS_MONITOR_ORDNANCE_BURN = "Ordnance Burn Chamber", diff --git a/code/game/machinery/embedded_controller/airlock_controller.dm b/code/game/machinery/embedded_controller/airlock_controller.dm index e82c102db219..6e30fad1a328 100644 --- a/code/game/machinery/embedded_controller/airlock_controller.dm +++ b/code/game/machinery/embedded_controller/airlock_controller.dm @@ -348,7 +348,7 @@ return ..() /obj/machinery/airlock_controller/incinerator_ordmix - name = "Incinerator Access Console" + name = "incinerator access console" airpump_tag = INCINERATOR_ORDMIX_DP_VENTPUMP exterior_door_tag = INCINERATOR_ORDMIX_AIRLOCK_EXTERIOR id_tag = INCINERATOR_ORDMIX_AIRLOCK_CONTROLLER @@ -359,7 +359,7 @@ exterior_target_pressure = 0 /obj/machinery/airlock_controller/incinerator_atmos - name = "Incinerator Access Console" + name = "incinerator access console" airpump_tag = INCINERATOR_ATMOS_DP_VENTPUMP exterior_door_tag = INCINERATOR_ATMOS_AIRLOCK_EXTERIOR id_tag = INCINERATOR_ATMOS_AIRLOCK_CONTROLLER @@ -370,7 +370,7 @@ exterior_target_pressure = 0 /obj/machinery/airlock_controller/incinerator_syndicatelava - name = "Incinerator Access Console" + name = "incinerator access console" airpump_tag = INCINERATOR_SYNDICATELAVA_DP_VENTPUMP exterior_door_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_EXTERIOR id_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_CONTROLLER diff --git a/code/game/objects/items/devices/scanners/gas_analyzer.dm b/code/game/objects/items/devices/scanners/gas_analyzer.dm index 2accf4e2a094..d7d53b87153a 100644 --- a/code/game/objects/items/devices/scanners/gas_analyzer.dm +++ b/code/game/objects/items/devices/scanners/gas_analyzer.dm @@ -136,39 +136,57 @@ /obj/item/analyzer/ui_data(mob/user) var/obj/item/last_scanned_real = last_scanned?.resolve() if(!QDELETED(last_scanned_real) && can_see(user, last_scanned_real, ranged_scan_distance)) - on_analyze(src, last_scanned_real) // updates last_gasmix_data as long as we're in range + collect_scan_info(last_scanned_real) // updates last_gasmix_data as long as we're in range return list( - "gasmixes" = last_gasmix_data || list(), + "gasmixes" = last_gasmix_data, ) +/// Checks if we can use the analyzer at all +/obj/item/analyzer/proc/can_use(mob/user) + if(!user.can_read(src)) + return FALSE + // Logical, but it contains "tutorial information", so we should allow it. + // if(user.is_blind()) + // return FALSE + return TRUE + +/obj/item/analyzer/ui_status(mob/user) + return can_use(user) ? ..() : UI_CLOSE + /obj/item/analyzer/attack_self(mob/user, modifiers) - if(user.stat != CONSCIOUS || !user.can_read(src) || user.is_blind()) - return - var/lowest_obj = ismob(loc) ? loc.loc : loc - atmos_scan(user = user, target = lowest_obj, silent = FALSE) - on_analyze(src, lowest_obj) + scan_atom(get_turf(src), user) + return TRUE /obj/item/analyzer/attack_self_secondary(mob/user, modifiers) - if(user.stat != CONSCIOUS || !user.can_read(src) || user.is_blind()) - return - ui_interact(user) + return TRUE /obj/item/analyzer/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + // if(istype(interacting_with, /obj/effect/anomaly) && can_see(user, interacting_with, ranged_scan_distance)) + // var/obj/effect/anomaly/ranged_anomaly = interacting_with + // ranged_anomaly.analyzer_act(user, src) + // return ITEM_INTERACT_SUCCESS + return interact_with_atom(interacting_with, user, modifiers) /obj/item/analyzer/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!HAS_TRAIT(interacting_with, TRAIT_COMBAT_MODE_SKIP_INTERACTION) && can_see(user, interacting_with, ranged_scan_distance)) - atmos_scan(user, (interacting_with.return_analyzable_air() ? interacting_with : get_turf(interacting_with))) + scan_atom(interacting_with.return_analyzable_air() ? interacting_with : get_turf(interacting_with), user) return NONE // Non-blocking -/// Called when our analyzer is used on something -/obj/item/analyzer/proc/on_analyze(datum/source, atom/target) - SIGNAL_HANDLER +/obj/item/analyzer/proc/scan_atom(atom/target, mob/living/user) + if(!can_use(user)) + return + + atmos_scan(user, target, silent = FALSE) + collect_scan_info(target) + +/obj/item/analyzer/proc/collect_scan_info(atom/target) var/mixture = target.return_analyzable_air() if(!mixture) - return FALSE + return + var/list/airs = islist(mixture) ? mixture : list(mixture) var/list/new_gasmix_data = list() for(var/datum/gas_mixture/air as anything in airs) @@ -179,6 +197,11 @@ last_gasmix_data = new_gasmix_data last_scanned = WEAKREF(target) +/// Called when our analyzer is used on something +/obj/item/analyzer/proc/on_analyze(datum/source, atom/target) + SIGNAL_HANDLER + collect_scan_info(target) + /** * Outputs a message to the user describing the target's gasmixes. * diff --git a/code/game/objects/items/grenades/atmos_grenades.dm b/code/game/objects/items/grenades/atmos_grenades.dm index 9f35ac6824c0..3c6556f227f8 100644 --- a/code/game/objects/items/grenades/atmos_grenades.dm +++ b/code/game/objects/items/grenades/atmos_grenades.dm @@ -1,6 +1,7 @@ /obj/item/grenade/gas_crystal - desc = "Some kind of crystal, this shouldn't spawn" - name = "Gas Crystal" + name = "gas crystal" + desc = "Some kind of crystal." + abstract_type = /obj/item/grenade/gas_crystal icon = 'icons/obj/weapons/grenade.dmi' icon_state = "bluefrag" inhand_icon_state = "flashbang" @@ -24,8 +25,8 @@ addtimer(CALLBACK(src, PROC_REF(detonate)), isnull(delayoverride)? det_time : delayoverride) /obj/item/grenade/gas_crystal/healium_crystal - name = "Healium crystal" - desc = "A crystal made from the Healium gas, it's cold to the touch." + name = "\improper Healium crystal" + desc = "A crystal made from the Healium gas. It's cold to the touch." icon_state = "healium_crystal" ///Range of the grenade that will cool down and affect mobs var/fix_range = 7 @@ -46,8 +47,8 @@ qdel(src) /obj/item/grenade/gas_crystal/proto_nitrate_crystal - name = "Proto Nitrate crystal" - desc = "A crystal made from the Proto Nitrate gas, you can see the liquid gases inside." + name = "\improper Proto-Nitrate crystal" + desc = "A crystal made from the Proto-Nitrate gas. It feels lighter than you'd expect." icon_state = "proto_nitrate_crystal" ///Range of the grenade air refilling var/refill_range = 5 @@ -72,8 +73,8 @@ qdel(src) /obj/item/grenade/gas_crystal/nitrous_oxide_crystal - name = "N2O crystal" - desc = "A crystal made from the N2O gas, you can see the liquid gases inside." + name = "\improper N2O crystal" + desc = "A crystal made from Nitrous Oxide gas. Looking at it makes you feel sleepy." icon_state = "n2o_crystal" ///Range of the grenade air refilling var/fill_range = 1 @@ -97,7 +98,7 @@ /obj/item/grenade/gas_crystal/crystal_foam name = "crystal foam" - desc = "A crystal with a foggy inside" + desc = "A crystal with a foggy inside." icon_state = "crystal_foam" var/breach_range = 7 diff --git a/code/game/objects/items/stacks/ammonia_crystals.dm b/code/game/objects/items/stacks/ammonia_crystals.dm index 367864269e9a..446204d18cc4 100644 --- a/code/game/objects/items/stacks/ammonia_crystals.dm +++ b/code/game/objects/items/stacks/ammonia_crystals.dm @@ -1,5 +1,6 @@ /obj/item/stack/ammonia_crystals name = "ammonia crystals" + desc = "Crystallized ammonia. Valuable for someone, but probably not you." singular_name = "ammonia crystal" icon = 'icons/obj/stack_objects.dmi' icon_state = "ammonia_crystal" diff --git a/code/game/turfs/open/floor/reinforced_floor.dm b/code/game/turfs/open/floor/reinforced_floor.dm index 3fe427f1aced..e8c3a5420b97 100644 --- a/code/game/turfs/open/floor/reinforced_floor.dm +++ b/code/game/turfs/open/floor/reinforced_floor.dm @@ -113,7 +113,7 @@ initial_gas_mix = ATMOS_TANK_CO2 /turf/open/floor/engine/plasma - name = "plasma floor" + name = "\improper Plasma floor" initial_gas_mix = ATMOS_TANK_PLASMA /turf/open/floor/engine/o2 @@ -147,7 +147,7 @@ initial_gas_mix = ATMOS_TANK_H2 /turf/open/floor/engine/hypernoblium - name = "\improper Hypernoblium floor" + name = "\improper Hyper-Noblium floor" initial_gas_mix = ATMOS_TANK_HYPERNOBLIUM /turf/open/floor/engine/miasma @@ -155,7 +155,7 @@ initial_gas_mix = ATMOS_TANK_MIASMA /turf/open/floor/engine/nitrium - name = "\improper nitrium floor" + name = "\improper Nitrium floor" initial_gas_mix = ATMOS_TANK_NITRIUM /turf/open/floor/engine/pluoxium @@ -184,7 +184,7 @@ initial_gas_mix = ATMOS_TANK_HELIUM /turf/open/floor/engine/antinoblium - name = "\improper Antinoblium floor" + name = "\improper Anti-Noblium floor" initial_gas_mix = ATMOS_TANK_ANTINOBLIUM /turf/open/floor/engine/air diff --git a/code/modules/atmospherics/environmental/LINDA_fire.dm b/code/modules/atmospherics/environmental/LINDA_fire.dm index 10e9bf458eaa..ae001a646179 100644 --- a/code/modules/atmospherics/environmental/LINDA_fire.dm +++ b/code/modules/atmospherics/environmental/LINDA_fire.dm @@ -1,7 +1,7 @@ /// Returns reactions which will contribute to a hotspot's size. /proc/init_hotspot_reactions() var/list/fire_reactions = list() - for (var/datum/gas_reaction/reaction as anything in subtypesof(/datum/gas_reaction)) + for (var/datum/gas_reaction/standard/reaction as anything in subtypesof(/datum/gas_reaction/standard)) if(initial(reaction.expands_hotspot)) fire_reactions += reaction diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index 2c1d91485b54..9f02b092aab2 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -532,7 +532,7 @@ GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm //It might be worth looking into updating these after each reaction, but that makes us care more about order of operations, so be careful var/temp = temperature reaction_loop: - for(var/datum/gas_reaction/reaction as anything in reactions) + for(var/datum/gas_reaction/standard/reaction as anything in reactions) var/list/reqs = reaction.requirements if((reqs["MIN_TEMP"] && temp < reqs["MIN_TEMP"]) || (reqs["MAX_TEMP"] && temp > reqs["MAX_TEMP"])) @@ -750,7 +750,7 @@ GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm */ // /datum/gas_mixture/proc/electrolyze(working_power = 0, electrolyzer_args = list()) // for(var/reaction in GLOB.electrolyzer_reactions) -// var/datum/electrolyzer_reaction/current_reaction = GLOB.electrolyzer_reactions[reaction] +// var/datum/gas_reaction/electrolyzer/current_reaction = GLOB.electrolyzer_reactions[reaction] // if(!current_reaction.reaction_check(air_mixture = src, electrolyzer_args = electrolyzer_args)) // continue diff --git a/code/modules/atmospherics/gasmixtures/gas_types.dm b/code/modules/atmospherics/gasmixtures/gas_types.dm index 0e002145b04c..ae50f80047e4 100644 --- a/code/modules/atmospherics/gasmixtures/gas_types.dm +++ b/code/modules/atmospherics/gasmixtures/gas_types.dm @@ -6,17 +6,16 @@ var/list/gas_types = subtypesof(/datum/gas) ASSERT(GAS_TYPE_COUNT == length(gas_types),\ "GAS_TYPE_COUNT != length(subtypesof(gas_types)), if you added new gas please increment GAS_TYPE_COUNT") - for(var/gas_path in gas_types) - var/datum/gas/gas = gas_path - gas_info[META_GAS_SPECIFIC_HEAT][gas_path] = initial(gas.specific_heat) - gas_info[META_GAS_NAME][gas_path] = initial(gas.name) - gas_info[META_GAS_MOLES_VISIBLE][gas_path] = initial(gas.moles_visible) + for(var/datum/gas/gas_path as anything in gas_types) + gas_info[META_GAS_SPECIFIC_HEAT][gas_path] = initial(gas_path.specific_heat) + gas_info[META_GAS_NAME][gas_path] = initial(gas_path.name) + gas_info[META_GAS_MOLES_VISIBLE][gas_path] = initial(gas_path.moles_visible) if (gas_info[META_GAS_MOLES_VISIBLE][gas_path]) - gas_info[META_GAS_OVERLAY][gas_path] += generate_gas_overlays(0, SSmapping.max_plane_offset, gas) - gas_info[META_GAS_FUSION_POWER][gas_path] = initial(gas.fusion_power) - gas_info[META_GAS_DANGER][gas_path] = initial(gas.dangerous) - gas_info[META_GAS_ID][gas_path] = initial(gas.id) - gas_info[META_GAS_DESC][gas_path] = initial(gas.desc) + gas_info[META_GAS_OVERLAY][gas_path] += generate_gas_overlays(0, SSmapping.max_plane_offset, gas_path) + gas_info[META_GAS_FUSION_POWER][gas_path] = initial(gas_path.fusion_power) + gas_info[META_GAS_DANGER][gas_path] = initial(gas_path.cargo_flags) & GAS_DANGEROUS + gas_info[META_GAS_ID][gas_path] = initial(gas_path.id) + gas_info[META_GAS_DESC][gas_path] = initial(gas_path.desc) /datum/gas_mixture::gas_meta = gas_info // save the reference to the list return gas_info @@ -40,35 +39,36 @@ return path return "" -/*||||||||||||||/----------\||||||||||||||*\ -||||||||||||||||[GAS DATUMS]|||||||||||||||| -||||||||||||||||\__________/|||||||||||||||| -|||| These should never be instantiated. |||| -|||| They exist only to make it easier |||| -|||| to add a new gas. They are accessed |||| -|||| only by meta_gas_list(). |||| -\*||||||||||||||||||||||||||||||||||||||||*/ - -//This is a plot created using the values for gas exports. Each gas has a value that works as it's kind of soft-cap, which limits you from making billions of credits per sale, based on the base_value variable on the gasses themselves. Most of these gasses as a result have a rather low value when sold, like nitrogen and oxygen at 1500 and 600 respectively at their maximum value. The +/** + * # Gas datums + * + * These are never and should never be instantiated, + * they just exist to hold data which is passed into the gas metadata list. + * + * They are templates for how gases should generally act. + */ /datum/gas var/id = "" var/specific_heat = 0 var/name = "" + var/desc ///icon_state in icons/effects/atmospherics.dmi var/gas_overlay = "" var/moles_visible = null - ///currently used by canisters - var/dangerous = FALSE ///How much the gas accelerates a fusion reaction var/fusion_power = 0 /// relative rarity compared to other gases, used when setting up the reactions list. var/rarity = 0 - ///Can gas of this type can purchased through cargo? - var/purchaseable = FALSE - ///How does a single mole of this gas sell for? Formula to calculate maximum value is in code\modules\cargo\exports\large_objects.dm. Doesn't matter for roundstart gasses. + /// Flags that relate to how the gas is handled in cargo + /// GAS_PURCHASABLE - Can be purchased in cargo + /// GAS_EXPORTABLE - Can be sold in cargo + /// GAS_DANGEROUS - Is considered dangerous, requires elevated access to purchase + var/cargo_flags = NONE + /// How does a single mole of this gas buy/sell for? + /// Formula to calculate maximum value is in [code\modules\cargo\exports\large_objects.dm]. + /// Only necessary for exportable or purchasable gases, otherwise meaningless. var/base_value = 0 - var/desc - ///RGB code for use when a generic color representing the gas is needed. Colors taken from contants.ts + /// RGB code for use when a generic color representing the gas is needed. Colors taken from contants.ts var/primary_color /// Smell string or typepath for this gas var/smell @@ -78,7 +78,7 @@ specific_heat = 20 name = "Oxygen" rarity = 900 - purchaseable = TRUE + cargo_flags = GAS_PURCHASABLE base_value = 0.2 desc = "The gas most life forms need to be able to survive. Also an oxidizer." primary_color = "#0000ff" @@ -88,7 +88,7 @@ specific_heat = 20 name = "Nitrogen" rarity = 1000 - purchaseable = TRUE + cargo_flags = GAS_PURCHASABLE base_value = 0.1 desc = "A very common gas that used to pad artifical atmospheres to habitable pressure." primary_color = "#ffff00" @@ -97,11 +97,11 @@ id = GAS_CO2 specific_heat = 30 name = "Carbon Dioxide" - dangerous = TRUE rarity = 700 - purchaseable = TRUE + cargo_flags = GAS_PURCHASABLE | GAS_DANGEROUS base_value = 0.2 - desc = "What the fuck is carbon dioxide?" + desc = "What the fuck is Carbon Dioxide?" + // desc = "A colorless, odorless gas commonly produced by respiration and combustion. Potentially dangerous when inhaled in high concentrations." primary_color = COLOR_GRAY /datum/gas/plasma @@ -110,10 +110,10 @@ name = "Plasma" gas_overlay = "plasma" moles_visible = MOLES_GAS_VISIBLE - dangerous = TRUE + cargo_flags = GAS_DANGEROUS rarity = 800 base_value = 1.5 - desc = "A flammable gas with many other curious properties. It's research is one of NT's primary objective." + desc = "A flammable gas with many curious properties. Its research is one of Nanotrasen's primary objectives." primary_color = "#ffc0cb" smell = /datum/smell/plasma @@ -125,7 +125,7 @@ moles_visible = MOLES_GAS_VISIBLE fusion_power = 8 rarity = 500 - purchaseable = TRUE + cargo_flags = GAS_PURCHASABLE base_value = 0.5 desc = "Water, in gas form. Makes things slippery." primary_color = "#b0c4de" @@ -133,13 +133,14 @@ /datum/gas/hypernoblium id = GAS_HYPER_NOBLIUM specific_heat = 2000 - name = "Hyper-noblium" + name = "Hyper-Noblium" gas_overlay = "freon" moles_visible = MOLES_GAS_VISIBLE fusion_power = 10 rarity = 50 + cargo_flags = GAS_EXPORTABLE base_value = 2.5 - desc = "The most noble gas of them all. High quantities of hyper-noblium actively prevents reactions from occuring." + desc = "The most noble gas of them all. High quantities actively prevents reactions from occurring." primary_color = COLOR_TEAL /datum/gas/nitrous_oxide @@ -149,11 +150,11 @@ gas_overlay = "nitrous_oxide" moles_visible = MOLES_GAS_VISIBLE * 2 fusion_power = 10 - dangerous = TRUE rarity = 600 - purchaseable = TRUE + cargo_flags = GAS_PURCHASABLE | GAS_DANGEROUS base_value = 1.5 - desc = "Causes drowsiness, euphoria, and eventually unconsciousness." + desc = "A gas known to causes drowsiness, euphoria, and eventually unconsciousness. \ + Commonly used as an anesthetic for surgical procedures, and occasionally, as a recreational drug." primary_color = "#ffe4c4" smell = "sweet" @@ -164,10 +165,10 @@ fusion_power = 7 gas_overlay = "nitrium" moles_visible = MOLES_GAS_VISIBLE - dangerous = TRUE + cargo_flags = GAS_PURCHASABLE | GAS_DANGEROUS rarity = 1 base_value = 6 - desc = "An experimental performance enhancing gas. Nitrium can have amplified effects as more of it gets into your bloodstream." + desc = "An experimental (and slightly toxic) performance enhancing gas that increases speed and alertness when inhaled." primary_color = "#a52a2a" smell = "coffee" @@ -177,7 +178,7 @@ name = "Tritium" gas_overlay = "tritium" moles_visible = MOLES_GAS_VISIBLE - dangerous = TRUE + cargo_flags = GAS_DANGEROUS | GAS_EXPORTABLE fusion_power = 5 rarity = 300 base_value = 2.5 @@ -188,10 +189,9 @@ id = GAS_BZ specific_heat = 20 name = "BZ" - dangerous = TRUE fusion_power = 8 rarity = 400 - purchaseable = TRUE + cargo_flags = GAS_PURCHASABLE | GAS_EXPORTABLE | GAS_DANGEROUS base_value = 1.5 desc = "A powerful hallucinogenic nerve agent able to induce cognitive damage." primary_color = "#9370db" @@ -203,8 +203,10 @@ name = "Pluoxium" fusion_power = -10 rarity = 200 + cargo_flags = GAS_EXPORTABLE base_value = 2.5 - desc = "A gas that could supply even more oxygen to the bloodstream when inhaled, without being an oxidizer." + desc = "An alternative to oxygen that is eight times more efficient at lung diffusion \ + and even has minor healing properties - while itself not being an oxidizer." primary_color = "#7b68ee" smell = "disinfectant" @@ -212,12 +214,13 @@ id = GAS_MIASMA specific_heat = 20 name = "Miasma" - dangerous = TRUE + cargo_flags = GAS_DANGEROUS | GAS_EXPORTABLE gas_overlay = "miasma" moles_visible = MOLES_GAS_VISIBLE * 60 rarity = 250 base_value = 1 - desc = "Not necessarily a gas, miasma refers to biological pollutants found in the atmosphere." + desc = "Miasma is not necessarily a gas, but more broadly refers to biological pollutants \ + found in the atmosphere known to cause disease and nausea." primary_color = COLOR_OLIVE smell = /datum/smell/miasma @@ -225,20 +228,20 @@ id = GAS_FREON specific_heat = 600 name = "Freon" - dangerous = TRUE + cargo_flags = GAS_DANGEROUS | GAS_EXPORTABLE gas_overlay = "freon" moles_visible = MOLES_GAS_VISIBLE *30 fusion_power = -5 rarity = 10 base_value = 5 - desc = "A coolant gas. Mainly used for it's endothermic reaction with oxygen." + desc = "A coolant gas. Primarily used for its endothermic reaction with oxygen." primary_color = "#afeeee" /datum/gas/hydrogen id = GAS_HYDROGEN specific_heat = 15 name = "Hydrogen" - dangerous = TRUE + cargo_flags = GAS_DANGEROUS | GAS_EXPORTABLE fusion_power = 2 rarity = 600 base_value = 1 @@ -249,25 +252,26 @@ id = GAS_HEALIUM specific_heat = 10 name = "Healium" - dangerous = TRUE + cargo_flags = GAS_DANGEROUS | GAS_EXPORTABLE gas_overlay = "healium" moles_visible = MOLES_GAS_VISIBLE rarity = 300 base_value = 5.5 - desc = "Causes deep, regenerative sleep." + desc = "An experimental alternative to anesthetic that induces a state of unconsciousness \ + that accelerates healing and regeneration when inhaled." primary_color = "#fa8072" smell = "sweet" /datum/gas/proto_nitrate id = GAS_PROTO_NITRATE specific_heat = 30 - name = "Proto Nitrate" - dangerous = TRUE + name = "Proto-Nitrate" + cargo_flags = GAS_DANGEROUS | GAS_EXPORTABLE gas_overlay = "proto_nitrate" moles_visible = MOLES_GAS_VISIBLE rarity = 200 base_value = 2.5 - desc = "A very volatile gas that reacts differently with various gases." + desc = "A volatile gas that has wildly different reactions with other gases." primary_color = "#adff2f" smell = "ozone" @@ -275,12 +279,13 @@ id = GAS_ZAUKER specific_heat = 350 name = "Zauker" - dangerous = TRUE + cargo_flags = GAS_DANGEROUS | GAS_EXPORTABLE gas_overlay = "zauker" moles_visible = MOLES_GAS_VISIBLE rarity = 1 base_value = 7 - desc = "A highly toxic gas, it's production is highly regulated on top of being difficult. It also breaks down when in contact with nitrogen." + desc = "A highly toxic gas and difficult to produce gas. \ + It breaks down when in contact with Nitrogen, making it relatively safe in Earth-like atmospheres." primary_color = "#006400" smell = "death" @@ -288,12 +293,12 @@ id = GAS_HALON specific_heat = 175 name = "Halon" - dangerous = TRUE + cargo_flags = GAS_DANGEROUS | GAS_EXPORTABLE gas_overlay = "halon" moles_visible = MOLES_GAS_VISIBLE rarity = 300 base_value = 4 - desc = "A potent fire supressant. Removes oxygen from high temperature fires and cools down the area" + desc = "A potent fire suppressant. It removes Oxygen from high temperature fires and cools down the area." primary_color = COLOR_PURPLE /datum/gas/helium @@ -302,21 +307,24 @@ name = "Helium" fusion_power = 7 rarity = 50 + cargo_flags = GAS_EXPORTABLE base_value = 3.5 - desc = "A very inert gas produced by the fusion of hydrogen and it's derivatives." + desc = "An inert noble gas produced by the fusion of Hydrogen and its derivatives. \ + Commonly known for being lighter than air and its ability to raise voice pitch when inhaled." primary_color = "#f0f8ff" /datum/gas/antinoblium id = GAS_ANTINOBLIUM specific_heat = 1 - name = "Antinoblium" - dangerous = TRUE + name = "Anti-Noblium" + cargo_flags = GAS_DANGEROUS | GAS_EXPORTABLE gas_overlay = "antinoblium" moles_visible = MOLES_GAS_VISIBLE fusion_power = 20 rarity = 1 base_value = 10 - desc = "We still don't know what it does, but it sells for a lot." + desc = "A mysterious and highly reactive gas known to replicate itself. \ + It is highly sought after due to its rarity, and will export for a high price." primary_color = COLOR_MAROON smell = "yourself" diff --git a/code/modules/atmospherics/gasmixtures/reaction_factors.dm b/code/modules/atmospherics/gasmixtures/reaction_factors.dm index 1b96a8976837..88eb79e3940e 100644 --- a/code/modules/atmospherics/gasmixtures/reaction_factors.dm +++ b/code/modules/atmospherics/gasmixtures/reaction_factors.dm @@ -1,208 +1,269 @@ -/datum/gas_reaction/water_vapor/init_factors() +/datum/gas_reaction/standard/water_vapor/init_factors() factor = list( - /datum/gas/water_vapor = "Condensation will consume [MOLES_GAS_VISIBLE] moles, freezing will not consume any. Both needs a minimum of [MOLES_GAS_VISIBLE] moles to occur.", - "Temperature" = "Freezes a turf at [WATER_VAPOR_DEPOSITION_POINT] Kelvins or below, wets it at [WATER_VAPOR_CONDENSATION_POINT] Kelvins or below.", - "Location" = "Can only happen on turfs.", + /datum/gas/water_vapor = "Condensation will consume [MOLES_GAS_VISIBLE] moles. \ + Freezing will not consume any. Both requires a minimum of [MOLES_GAS_VISIBLE] moles to occur.", + "Temperature" = "Freezes a tile at [WATER_VAPOR_DEPOSITION_POINT]K or below, \ + wets it at [WATER_VAPOR_CONDENSATION_POINT]K or below.", + "Location" = "Can only happen on tiles.", ) -/datum/gas_reaction/miaster/init_factors() +/datum/gas_reaction/standard/miaster/init_factors() factor = list( - /datum/gas/miasma = "Miasma is sterilized at a rate that scales with the difference between the temperature and [MIASTER_STERILIZATION_TEMP]K.", - /datum/gas/oxygen = "One mole of oxygen is released per mole of miasma consumed.", - "Temperature" = "Higher temperature increases the speed of miasma sterilization.", - "Energy" = "[MIASTER_STERILIZATION_ENERGY] joules of energy is released per mole of miasma sterilized.", + /datum/gas/miasma = "[/datum/gas/miasma::name] is sterilized at a rate that scales \ + with the difference between the temperature and [MIASTER_STERILIZATION_TEMP]K.", + /datum/gas/oxygen = "One mole of [/datum/gas/oxygen::name] is released per mole of [/datum/gas/miasma::name] consumed.", + "Temperature" = "Higher temperature increases the speed of [/datum/gas/miasma::name] sterilization.", + "Energy" = "[MIASTER_STERILIZATION_ENERGY] joules of energy is released per mole of [/datum/gas/miasma::name] sterilized.", ) -/datum/gas_reaction/plasmafire/init_factors() +/datum/gas_reaction/standard/plasmafire/init_factors() factor = list( - /datum/gas/oxygen = "Oxygen consumption is determined by the temperature, ranging from [OXYGEN_BURN_RATIO_BASE] moles per mole of plasma consumed at [PLASMA_MINIMUM_BURN_TEMPERATURE] Kelvins to [OXYGEN_BURN_RATIO_BASE-1] moles per mole of plasma consumed at [PLASMA_UPPER_TEMPERATURE] Kelvins. Higher oxygen concentration up to [PLASMA_OXYGEN_FULLBURN] times the plasma increases the speed of plasma consumption.", - /datum/gas/plasma = "Plasma is consumed at a rate that scales with the difference between the temperature and [PLASMA_MINIMUM_BURN_TEMPERATURE]K, with maximum scaling at [PLASMA_UPPER_TEMPERATURE]K.", - /datum/gas/tritium = "Tritium is formed at 1 mole per mole of plasma consumed if there are at least 97 times more oxygen than plasma.", - /datum/gas/water_vapor = "Water vapor is formed at 0.25 moles per mole of plasma consumed if tritium isn't being formed.", - /datum/gas/carbon_dioxide = "Carbon Dioxide is formed at 0.75 moles per mole of plasma consumed if tritium isn't being formed.", - "Temperature" = "Minimum temperature of [PLASMA_MINIMUM_BURN_TEMPERATURE] kelvin to occur. Higher temperature up to [PLASMA_UPPER_TEMPERATURE]K increases the oxygen efficiency and also the plasma consumption rate.", - "Energy" = "[FIRE_PLASMA_ENERGY_RELEASED] joules of energy is released per mole of plasma consumed.", + /datum/gas/oxygen = "[/datum/gas/oxygen::name] consumption is determined by the temperature, \ + ranging from [OXYGEN_BURN_RATIO_BASE] moles per mole of [/datum/gas/plasma::name] consumed at [PLASMA_MINIMUM_BURN_TEMPERATURE]K \ + to [OXYGEN_BURN_RATIO_BASE-1] moles per mole of [/datum/gas/plasma::name] consumed at [PLASMA_UPPER_TEMPERATURE]K. \ + Higher [/datum/gas/oxygen::name] concentration up to [PLASMA_OXYGEN_FULLBURN] times the [/datum/gas/plasma::name] \ + increases the speed of [/datum/gas/plasma::name] consumption.", + /datum/gas/plasma = "[/datum/gas/plasma::name] is consumed at a rate that scales with the difference between the temperature \ + and [PLASMA_MINIMUM_BURN_TEMPERATURE]K, with maximum scaling at [PLASMA_UPPER_TEMPERATURE]K.", + /datum/gas/tritium = "[/datum/gas/tritium::name] is formed at 1 mole per mole of [/datum/gas/plasma::name] consumed \ + if there are at least 97 times more [/datum/gas/oxygen::name] than [/datum/gas/plasma::name].", + /datum/gas/water_vapor = "[/datum/gas/water_vapor::name] is formed at 0.25 moles per mole of [/datum/gas/plasma::name] consumed \ + if [/datum/gas/tritium::name] isn't being formed.", + /datum/gas/carbon_dioxide = "[/datum/gas/carbon_dioxide::name] is formed at 0.75 moles per mole of [/datum/gas/plasma::name] consumed \ + if [/datum/gas/tritium::name] isn't being formed.", + "Temperature" = "Minimum temperature of [PLASMA_MINIMUM_BURN_TEMPERATURE]K to occur. \ + Higher temperature up to [PLASMA_UPPER_TEMPERATURE]K increases the [/datum/gas/oxygen::name] efficiency \ + and also the [/datum/gas/plasma::name] consumption rate.", + "Energy" = "[FIRE_PLASMA_ENERGY_RELEASED] joules of energy is released per mole of [/datum/gas/plasma::name] consumed.", ) -/datum/gas_reaction/h2fire/init_factors() +/datum/gas_reaction/standard/h2fire/init_factors() factor = list( - /datum/gas/oxygen = "Oxygen is consumed at 0.5 moles per mole of hydrogen consumed. Higher oxygen concentration up to [HYDROGEN_OXYGEN_FULLBURN] times the hydrogen increases the hydrogen consumption rate.", - /datum/gas/hydrogen = "Hydrogen is consumed rapidly fast as long as there's enough oxygen to allow combustion.", - /datum/gas/water_vapor = "Water vapor is produced at 1 mole per mole of hydrogen combusted.", - "Temperature" = "Minimum temperature of [FIRE_MINIMUM_TEMPERATURE_TO_EXIST] kelvin to occur", - "Energy" = "[FIRE_HYDROGEN_ENERGY_RELEASED] joules of energy is released per mol of hydrogen consumed.", + /datum/gas/oxygen = "[/datum/gas/oxygen::name] is consumed at 0.5 moles per mole of [/datum/gas/hydrogen::name] consumed. \ + Higher [/datum/gas/oxygen::name] concentration up to [HYDROGEN_OXYGEN_FULLBURN] times \ + the [/datum/gas/hydrogen::name] increases the [/datum/gas/hydrogen::name] consumption rate.", + /datum/gas/hydrogen = "[/datum/gas/hydrogen::name] is consumed rapidly fast as long as there's enough [/datum/gas/oxygen::name] to allow combustion.", + /datum/gas/water_vapor = "[/datum/gas/water_vapor::name] is produced at 1 mole per mole of [/datum/gas/hydrogen::name] combusted.", + "Temperature" = "Minimum temperature of [FIRE_MINIMUM_TEMPERATURE_TO_EXIST]K to occur", + "Energy" = "[FIRE_HYDROGEN_ENERGY_RELEASED] joules of energy is released per mol of [/datum/gas/hydrogen::name] consumed.", ) -/datum/gas_reaction/tritfire/init_factors() +/datum/gas_reaction/standard/tritfire/init_factors() factor = list( - /datum/gas/oxygen = "Oxygen is consumed at 0.5 moles per mole of tritium consumed. Higher oxygen concentration up to [TRITIUM_OXYGEN_FULLBURN] times the tritium increases the tritium consumption rate.", - /datum/gas/tritium = "Tritium is consumed at rapidly fast as long as there's enough oxygen to allow combustion.", - /datum/gas/water_vapor = "Water vapor is produced at 1 mole per mole of tritium combusted.", - "Temperature" = "Minimum temperature of [FIRE_MINIMUM_TEMPERATURE_TO_EXIST] kelvin to occur", - "Energy" = "[FIRE_TRITIUM_ENERGY_RELEASED] joules of energy is released per mol of tritium consumed.", + /datum/gas/oxygen = "[/datum/gas/oxygen::name] is consumed at 0.5 moles per mole of [/datum/gas/tritium::name] consumed. \ + Higher [/datum/gas/oxygen::name] concentration up to [TRITIUM_OXYGEN_FULLBURN] times \ + the [/datum/gas/tritium::name] increases the [/datum/gas/tritium::name] consumption rate.", + /datum/gas/tritium = "[/datum/gas/tritium::name] is consumed at rapidly fast as long as there's enough [/datum/gas/oxygen::name] to allow combustion.", + /datum/gas/water_vapor = "[/datum/gas/water_vapor::name] is produced at 1 mole per mole of [/datum/gas/tritium::name] combusted.", + "Temperature" = "Minimum temperature of [FIRE_MINIMUM_TEMPERATURE_TO_EXIST]K to occur", + "Energy" = "[FIRE_TRITIUM_ENERGY_RELEASED] joules of energy is released per mol of [/datum/gas/tritium::name] consumed.", "Radiation" = "This reaction emits radiation proportional to the amount of energy released.", ) -/datum/gas_reaction/freonfire/init_factors() +/datum/gas_reaction/standard/freonfire/init_factors() factor = list( - /datum/gas/oxygen = "Oxygen consumption is determined by the temperature, ranging from [OXYGEN_BURN_RATIO_BASE] moles per mole of freon consumed at [FREON_LOWER_TEMPERATURE] Kelvins to [OXYGEN_BURN_RATIO_BASE-1] moles per mole of freon consumed at [FREON_MAXIMUM_BURN_TEMPERATURE] Kelvins. Higher oxygen concentration up to [FREON_OXYGEN_FULLBURN] times the freon increases freon consumption rate.", - /datum/gas/freon = "Freon is consumed at a rate that scales with the distance of the temperature from [FREON_MAXIMUM_BURN_TEMPERATURE]K. Its relationship with oxygen also determines consumption rate.", - /datum/gas/carbon_dioxide = "Carbon Dioxide is formed at 1 mole per mole of freon consumed.", - "Temperature" = "Can only occur between [FREON_LOWER_TEMPERATURE] - [FREON_MAXIMUM_BURN_TEMPERATURE] Kelvin", - "Energy" = "[FIRE_FREON_ENERGY_CONSUMED] joules of energy is absorbed per mole of freon consumed.", - "Hot Ice" = "This reaction produces hot ice when occuring between [HOT_ICE_FORMATION_MINIMUM_TEMPERATURE]-[HOT_ICE_FORMATION_MAXIMUM_TEMPERATURE] kelvins", + /datum/gas/oxygen = "[/datum/gas/oxygen::name] consumption is determined by the temperature, \ + ranging from [OXYGEN_BURN_RATIO_BASE] moles per mole of [/datum/gas/freon::name] consumed at [FREON_LOWER_TEMPERATURE]K \ + to [OXYGEN_BURN_RATIO_BASE - 1] moles per mole of [/datum/gas/freon::name] consumed at [FREON_MAXIMUM_BURN_TEMPERATURE]K. \ + Higher [/datum/gas/oxygen::name] concentration up to [FREON_OXYGEN_FULLBURN] times \ + the [/datum/gas/freon::name] increases [/datum/gas/freon::name] consumption rate.", + /datum/gas/freon = "[/datum/gas/freon::name] is consumed at a rate that scales with the distance of the temperature \ + from [FREON_MAXIMUM_BURN_TEMPERATURE]K. Its relationship with [/datum/gas/oxygen::name] also determines consumption rate.", + /datum/gas/carbon_dioxide = "[/datum/gas/carbon_dioxide::name] is formed at 1 mole per mole of [/datum/gas/freon::name] consumed.", + "Temperature" = "Can only occur between [FREON_LOWER_TEMPERATURE] - [FREON_MAXIMUM_BURN_TEMPERATURE]K.", + "Energy" = "[FIRE_FREON_ENERGY_CONSUMED] joules of energy is absorbed per mole of [/datum/gas/freon::name] consumed.", + "Hot Ice" = "This reaction produces \"hot ice\" when occurring between [HOT_ICE_FORMATION_MINIMUM_TEMPERATURE]-[HOT_ICE_FORMATION_MAXIMUM_TEMPERATURE]K.", ) -/datum/gas_reaction/nitrousformation/init_factors() +/datum/gas_reaction/standard/nitrousformation/init_factors() factor = list( - /datum/gas/oxygen = "10 moles of Oxygen needs to be present for the reaction to occur. Oxygen is consumed at 0.5 moles per mole of nitrous oxide formed.", - /datum/gas/nitrogen = " 20 moles of Nitrogen needs to be present for the reaction to occur. Nitrogen is consumed at 1 mole per mole of nitrous oxife formed.", - /datum/gas/bz = "5 moles of BZ needs to be present for the reaction to occur. Not consumed.", - /datum/gas/nitrous_oxide = "Nitrous oxide gets produced rapidly.", - "Temperature" = "Can only occur between [N2O_FORMATION_MIN_TEMPERATURE] - [N2O_FORMATION_MAX_TEMPERATURE] Kelvin", - "Energy" = "[N2O_FORMATION_ENERGY] joules of energy is released per mole of nitrous oxide formed.", + /datum/gas/oxygen = "10 moles of [/datum/gas/oxygen::name] needs to be present for the reaction to occur. \ + [/datum/gas/oxygen::name] is consumed at 0.5 moles per mole of [/datum/gas/nitrous_oxide::name] formed.", + /datum/gas/nitrogen = " 20 moles of [/datum/gas/nitrogen::name] needs to be present for the reaction to occur. \ + [/datum/gas/nitrogen::name] is consumed at 1 mole per mole of [/datum/gas/nitrous_oxide::name] formed.", + /datum/gas/bz = "5 moles of [/datum/gas/bz::name] needs to be present for the reaction to occur. Not consumed.", + /datum/gas/nitrous_oxide = "[/datum/gas/nitrous_oxide::name] gets produced rapidly.", + "Temperature" = "Can only occur between [N2O_FORMATION_MIN_TEMPERATURE] - [N2O_FORMATION_MAX_TEMPERATURE]K", + "Energy" = "[N2O_FORMATION_ENERGY] joules of energy is released per mole of [/datum/gas/nitrous_oxide::name] formed.", ) -/datum/gas_reaction/nitrous_decomp/init_factors() +/datum/gas_reaction/standard/nitrous_decomp/init_factors() factor = list( - /datum/gas/nitrous_oxide = "Nitrous Oxide is decomposed at a rate that scales negatively with the distance between the temperature and average of the minimum and maximum temperature of the reaction. Minimum of [MINIMUM_MOLE_COUNT * 2] to occur.", //okay this one isn't made into a define yet. - /datum/gas/oxygen = "Oxygen is formed at 0.5 moles per mole of nitrous oxide decomposed.", - /datum/gas/nitrogen = "Nitrogen is formed at 1 mole per mole of nitrous oxide decomposed.", - "Temperature" = "The decomposition rate scales with the product of the distances between temperature and minimum and maximum temperature. Can only happen between [N2O_DECOMPOSITION_MIN_TEMPERATURE] - [N2O_DECOMPOSITION_MAX_TEMPERATURE] kelvin.", - "Energy" = "[N2O_DECOMPOSITION_ENERGY] joules of energy is released per mole of nitrous oxide decomposed.", + /datum/gas/nitrous_oxide = "[/datum/gas/nitrous_oxide::name] is decomposed at a rate that scales negatively with the distance between \ + the temperature and average of the minimum and maximum temperature of the reaction. \ + Minimum of [MINIMUM_MOLE_COUNT * 2] to occur.", //okay this one isn't made into a define yet. + /datum/gas/oxygen = "[/datum/gas/oxygen::name] is formed at 0.5 moles per mole of [/datum/gas/nitrous_oxide::name] decomposed.", + /datum/gas/nitrogen = "[/datum/gas/nitrogen::name] is formed at 1 mole per mole of [/datum/gas/nitrous_oxide::name] decomposed.", + "Temperature" = "The decomposition rate scales with the product of the distances between temperature and minimum and maximum temperature. Can only happen between [N2O_DECOMPOSITION_MIN_TEMPERATURE] - [N2O_DECOMPOSITION_MAX_TEMPERATURE]K.", + "Energy" = "[N2O_DECOMPOSITION_ENERGY] joules of energy is released per mole of [/datum/gas/nitrous_oxide::name] decomposed.", ) -/datum/gas_reaction/bzformation/init_factors() +/datum/gas_reaction/standard/bzformation/init_factors() factor = list( - /datum/gas/plasma = "Each mole of BZ made consumes 0.8 moles of plasma. If there is more plasma than nitrous oxide, bz formation rate gets slowed down.", - /datum/gas/nitrous_oxide = "Each mole of bz made consumes 0.4 moles of Nitrous oxide. If there is less nitrous oxide than plasma the reaction rate is slowed down. At three times the amount of plasma to Nitrous oxide it will start breaking down into Nitrogen and Oxygen, the lower the ratio the more Nitrous oxide decomposes.", - /datum/gas/bz = "The lower the pressure and larger the volume the more bz gets made. Less nitrous oxide than plasma will slow down the reaction.", - /datum/gas/nitrogen = "Each mole Nitrous oxide decomposed makes 1 mol Nitrogen. Lower ratio of Nitrous oxide to Plasma means a higher ratio of decomposition to BZ production.", - /datum/gas/oxygen = "Each mole Nitrous oxide decomposed makes 0.5 moles Oxygen. Lower ratio of Nitrous oxide to Plasma means a higher ratio of decomposition to BZ production.", - "Energy" = "[BZ_FORMATION_ENERGY] joules of energy is released per mol of BZ made. Nitrous oxide decomposition releases [N2O_DECOMPOSITION_ENERGY] per mol decomposed", + /datum/gas/plasma = "Each mole of [/datum/gas/bz::name] made consumes 0.8 moles of [/datum/gas/plasma::name]. \ + If there is more [/datum/gas/plasma::name] than [/datum/gas/nitrous_oxide::name], [/datum/gas/bz::name] formation rate gets slowed down.", + /datum/gas/nitrous_oxide = "Each mole of [/datum/gas/bz::name] made consumes 0.4 moles of [/datum/gas/nitrous_oxide::name]. \ + If there is less [/datum/gas/nitrous_oxide::name] than [/datum/gas/plasma::name] the reaction rate is slowed down. \ + At three times the amount of [/datum/gas/plasma::name] to [/datum/gas/nitrous_oxide::name], \ + it will start breaking down into [/datum/gas/nitrogen::name] and [/datum/gas/oxygen::name] - \ + the lower the ratio, the more [/datum/gas/nitrous_oxide::name] decomposes.", + /datum/gas/bz = "The lower the pressure and larger the volume the more [/datum/gas/bz::name] gets made. \ + Less [/datum/gas/nitrous_oxide::name] than [/datum/gas/plasma::name] will slow down the reaction.", + /datum/gas/nitrogen = "Each mole [/datum/gas/nitrous_oxide::name] decomposed makes 1 mol [/datum/gas/nitrogen::name]. \ + Lower ratio of [/datum/gas/nitrous_oxide::name] to [/datum/gas/plasma::name] means a higher ratio of decomposition to [/datum/gas/bz::name] production.", + /datum/gas/oxygen = "Each mole [/datum/gas/nitrous_oxide::name] decomposed makes 0.5 moles [/datum/gas/oxygen::name]. \ + Lower ratio of [/datum/gas/nitrous_oxide::name] to [/datum/gas/plasma::name] means a higher ratio of decomposition to [/datum/gas/bz::name] production.", + "Energy" = "[BZ_FORMATION_ENERGY] joules of energy is released per mol of [/datum/gas/bz::name] made. \ + [/datum/gas/nitrous_oxide::name] decomposition releases [N2O_DECOMPOSITION_ENERGY] per mol decomposed.", ) -/datum/gas_reaction/pluox_formation/init_factors() +/datum/gas_reaction/standard/pluox_formation/init_factors() factor = list( - /datum/gas/carbon_dioxide = "1 mole of carbon dioxide gets consumed per mole of pluoxium formed.", - /datum/gas/oxygen = "Oxygen is consumed at 0.5 moles per mole of pluoxium formed.", - /datum/gas/tritium = "Tritium is converted into hydrogen at 0.01 moles per mole of pluoxium formed.", - /datum/gas/pluoxium = "Pluoxium is produced at a constant rate in any given mixture.", - /datum/gas/hydrogen = "Hydrogen is formed from the tritium losing their neutrons.", - "Energy" = "[PLUOXIUM_FORMATION_ENERGY] joules of energy is released per mole of pluoxium formed.", - "Temperature" = "Can only occur between [PLUOXIUM_FORMATION_MIN_TEMP] - [PLUOXIUM_FORMATION_MAX_TEMP] Kelvin", + /datum/gas/carbon_dioxide = "1 mole of [/datum/gas/carbon_dioxide::name] gets consumed per mole of [/datum/gas/pluoxium::name] formed.", + /datum/gas/oxygen = "[/datum/gas/oxygen::name] is consumed at 0.5 moles per mole of [/datum/gas/pluoxium::name] formed.", + /datum/gas/tritium = "[/datum/gas/tritium::name] is converted into [/datum/gas/hydrogen::name] at 0.01 moles per mole of [/datum/gas/pluoxium::name] formed.", + /datum/gas/pluoxium = "[/datum/gas/pluoxium::name] is produced at a constant rate in any given mixture.", + /datum/gas/hydrogen = "[/datum/gas/hydrogen::name] is formed from the [/datum/gas/tritium::name] losing their neutrons.", + "Energy" = "[PLUOXIUM_FORMATION_ENERGY] joules of energy is released per mole of [/datum/gas/pluoxium::name] formed.", + "Temperature" = "Can only occur between [PLUOXIUM_FORMATION_MIN_TEMP] - [PLUOXIUM_FORMATION_MAX_TEMP]K", ) -/datum/gas_reaction/nitrium_formation/init_factors() +/datum/gas_reaction/standard/nitrium_formation/init_factors() factor = list( - /datum/gas/bz = "5 moles of BZ needs to be present for the reaction to occur. BZ is consumed at 0.05 moles per mole of nitrium formed.", - /datum/gas/tritium = "20 moles of tritium needs to be present for the reaction to occur. Tritium is consumed at 1 mole per mole of nitroum formed.", - /datum/gas/nitrogen = "10 moles of nitrogen needs to be present for the reaction to occur. Nitrogen is consumed at 1 mole per mole of nitrium formed.", - /datum/gas/nitrium = "Nitrium is produced at a rate that scales with the temperature.", - "Temperature" = "Can only occur above [NITRIUM_FORMATION_MIN_TEMP] kelvins", - "Energy" = "[NITRIUM_FORMATION_ENERGY] joules of energy is absorbed per mole of nitrium formed.", + /datum/gas/bz = "5 moles of [/datum/gas/bz::name] needs to be present for the reaction to occur. \ + [/datum/gas/bz::name] is consumed at 0.05 moles per mole of [/datum/gas/nitrium::name] formed.", + /datum/gas/tritium = "20 moles of [/datum/gas/tritium::name] needs to be present for the reaction to occur. \ + [/datum/gas/tritium::name] is consumed at 1 mole per mole of [/datum/gas/nitrium::name] formed.", + /datum/gas/nitrogen = "10 moles of [/datum/gas/nitrogen::name] needs to be present for the reaction to occur. \ + [/datum/gas/nitrogen::name] is consumed at 1 mole per mole of [/datum/gas/nitrium::name] formed.", + /datum/gas/nitrium = "[/datum/gas/nitrium::name] is produced at a rate that scales with the temperature.", + "Temperature" = "Can only occur above [NITRIUM_FORMATION_MIN_TEMP]K", + "Energy" = "[NITRIUM_FORMATION_ENERGY] joules of energy is absorbed per mole of [/datum/gas/nitrium::name] formed.", ) -/datum/gas_reaction/nitrium_decomposition/init_factors() +/datum/gas_reaction/standard/nitrium_decomposition/init_factors() factor = list( - /datum/gas/oxygen = "[MINIMUM_MOLE_COUNT] moles of oxygen need to be present for the reaction to occur. Not consumed.", - /datum/gas/nitrium = "Nitrium is consumed at a rate that scales with the temperature.", - /datum/gas/hydrogen = "Hydrogen is produced at 1 mole per mole of nitrium decomposed.", - /datum/gas/nitrogen = "Nitrogen is produced at 1 mole per mole of nitrium decomposed.", - "Temperature" = "Can only occur below [NITRIUM_DECOMPOSITION_MAX_TEMP]. Higher temperature increases the nitrium decomposition rate.", - "Energy" = "[NITRIUM_DECOMPOSITION_ENERGY] joules of energy is released per mole of nitrium decomposed.", + /datum/gas/oxygen = "[MINIMUM_MOLE_COUNT] moles of [/datum/gas/oxygen::name] need to be present for the reaction to occur. Not consumed.", + /datum/gas/nitrium = "[/datum/gas/nitrium::name] is consumed at a rate that scales with the temperature.", + /datum/gas/hydrogen = "[/datum/gas/hydrogen::name] is produced at 1 mole per mole of [/datum/gas/nitrium::name] decomposed.", + /datum/gas/nitrogen = "[/datum/gas/nitrogen::name] is produced at 1 mole per mole of [/datum/gas/nitrium::name] decomposed.", + "Temperature" = "Can only occur below [NITRIUM_DECOMPOSITION_MAX_TEMP]. Higher temperature increases the [/datum/gas/nitrium::name] decomposition rate.", + "Energy" = "[NITRIUM_DECOMPOSITION_ENERGY] joules of energy is released per mole of [/datum/gas/nitrium::name] decomposed.", ) -/datum/gas_reaction/freonformation/init_factors() +/datum/gas_reaction/standard/freonformation/init_factors() factor = list( - /datum/gas/plasma = "At least 0.06 moles of plasma needs to be present. Plasma is consumed at 0.6 moles per mole of freon formed.", - /datum/gas/carbon_dioxide = "At least 0.03 moles of CO2 needs to be present. CO2 is consumed at 0.3 moles per mole of freon formed.", - /datum/gas/bz = "At least 0.01 moles of BZ needs to be present. BZ is consumed at 0.1 moles per mole of freon formed.", - /datum/gas/freon = "Freon is produced at a rate that scales with the sum of a quadratic exponential and sigmoidal function, with the quadratic exponential peaking at 800 Kelvin, but the sigmoidal function takes dominance at over 5,500K being up to 3 times more efficient.", - "Energy" = "Between 100 and 800 joules of energy is absorbed per mole of freon produced", // I don't know why the energy release is also a sigmoidal function, but it should really just be constant to be honest. - "Temperature" = "Minimum temperature of [FIRE_MINIMUM_TEMPERATURE_TO_EXIST + 100] Kelvin to occur, with production peak at 800 K. However at temperatures above 5500 K higher rates are possible maxing out at three times the low temperature rate at over 8500 K.", + /datum/gas/plasma = "At least 0.06 moles of [/datum/gas/plasma::name] needs to be present. \ + [/datum/gas/plasma::name] is consumed at 0.6 moles per mole of [/datum/gas/freon::name] formed.", + /datum/gas/carbon_dioxide = "At least 0.03 moles of [/datum/gas/carbon_dioxide::name] needs to be present. \ + [/datum/gas/carbon_dioxide::name] is consumed at 0.3 moles per mole of [/datum/gas/freon::name] formed.", + /datum/gas/bz = "At least 0.01 moles of [/datum/gas/bz::name] needs to be present. \ + [/datum/gas/bz::name] is consumed at 0.1 moles per mole of [/datum/gas/freon::name] formed.", + /datum/gas/freon = "[/datum/gas/freon::name] is produced at a rate that scales with temperature. \ + See temperature factor for more information.", + "Energy" = "Between 100 and 800 joules of energy is absorbed per mole of [/datum/gas/freon::name] produced", // I don't know why the energy release is also a sigmoidal function, but it should really just be constant to be honest. + "Temperature" = "Minimum temperature of [FIRE_MINIMUM_TEMPERATURE_TO_EXIST + 100]K to occur. \ + Production speed peaks at 800K - However, at temperatures above 5500K, \ + production speed can exceed the low temperature peak (reaching up to three times fast at 8500K).", ) -/datum/gas_reaction/nobliumformation/init_factors() +/datum/gas_reaction/standard/nobliumformation/init_factors() factor = list( - /datum/gas/nitrogen = "10 moles of nitrogen needs to be present for the reaction to occur. Nitrogen is consumed at 10 moles per mole of hypernoblium formed.", - /datum/gas/tritium = "5 moles of tritium needs to be present for the reaction to occur. Tritium is consumed at 5 moles per mole of hypernoblium formed. The relative consumption rate of tritium decreases in the exposure of BZ.", - /datum/gas/hypernoblium = "Hyper-Noblium production scales based on the sum of the nitrogen and tritium moles.", - "Energy" = "[NOBLIUM_FORMATION_ENERGY] joules of energy is released per mole of hypernoblium produced.", - /datum/gas/bz = "BZ is not consumed in the reaction but will lower the amount of energy released. It also reduces amount of tritium consumed by a ratio between tritium and bz, greater bz than tritium will reduce more.", - "Temperature" = "Can only occur between [NOBLIUM_FORMATION_MIN_TEMP] - [NOBLIUM_FORMATION_MAX_TEMP] kelvin", + /datum/gas/nitrogen = "10 moles of [/datum/gas/nitrogen::name] needs to be present for the reaction to occur. \ + [/datum/gas/nitrogen::name] is consumed at 10 moles per mole of [/datum/gas/hypernoblium::name] formed.", + /datum/gas/tritium = "5 moles of [/datum/gas/tritium::name] needs to be present for the reaction to occur. \ + [/datum/gas/tritium::name] is consumed at 5 moles per mole of [/datum/gas/hypernoblium::name] formed. \ + The relative consumption rate of [/datum/gas/tritium::name] decreases in the exposure of [/datum/gas/bz::name].", + /datum/gas/hypernoblium = "[/datum/gas/hypernoblium::name] production scales based on the sum \ + of the [/datum/gas/nitrogen::name] and [/datum/gas/tritium::name] moles.", + "Energy" = "[NOBLIUM_FORMATION_ENERGY] joules of energy is released per mole of [/datum/gas/hypernoblium::name] produced.", + /datum/gas/bz = "[/datum/gas/bz::name] is not consumed in the reaction but will lower the amount of energy released. \ + It also reduces amount of [/datum/gas/tritium::name] consumed by a ratio \ + between [/datum/gas/tritium::name] and [/datum/gas/bz::name], greater [/datum/gas/bz::name] than [/datum/gas/tritium::name] will reduce more.", + "Temperature" = "Can only occur between [NOBLIUM_FORMATION_MIN_TEMP] - [NOBLIUM_FORMATION_MAX_TEMP]K", ) -/datum/gas_reaction/halon_o2removal/init_factors() +/datum/gas_reaction/standard/halon_o2removal/init_factors() factor = list( - /datum/gas/halon = "Halon is consumed at a rate that scales with temperature.", - /datum/gas/oxygen = "20 moles of oxygen is consumed per mole of halon combusted.", - /datum/gas/carbon_dioxide = "Carbon dioxide is produced at 5 moles per mole of halon consumed.", - "Energy" = "[HALON_COMBUSTION_ENERGY] joules of energy is absorbed per mole of halon consumed.", - "Temperature" = "Can only occur above [FIRE_MINIMUM_TEMPERATURE_TO_EXIST] kelvin. Higher temperature increases halon consumption rate.", + /datum/gas/halon = "[/datum/gas/halon::name] is consumed at a rate that scales with temperature.", + /datum/gas/oxygen = "20 moles of [/datum/gas/oxygen::name] is consumed per mole of [/datum/gas/halon::name] combusted.", + /datum/gas/carbon_dioxide = "[/datum/gas/carbon_dioxide::name] is produced at 5 moles per mole of [/datum/gas/halon::name] consumed.", + "Energy" = "[HALON_COMBUSTION_ENERGY] joules of energy is absorbed per mole of [/datum/gas/halon::name] consumed.", + "Temperature" = "Can only occur above [FIRE_MINIMUM_TEMPERATURE_TO_EXIST]K. Higher temperature increases [/datum/gas/halon::name] consumption rate.", ) -/datum/gas_reaction/healium_formation/init_factors() +/datum/gas_reaction/standard/healium_formation/init_factors() factor = list( - /datum/gas/bz = "BZ is consumed at 1/12th of a mole per mole of healium formed.", - /datum/gas/freon = "Freon is consumed at 11/12th of a mole per mole of healium formed.", - /datum/gas/healium = "Healium is formed at a rate that scales with the temperature.", - "Temperature" = "Can only occur between [HEALIUM_FORMATION_MIN_TEMP] - [HEALIUM_FORMATION_MAX_TEMP]. Higher temperature increases healium formation rate.", - "Energy" = "[HEALIUM_FORMATION_ENERGY/3] joules of energy is released per mole of healium formed.", + /datum/gas/bz = "[/datum/gas/bz::name] is consumed at 1/12th of a mole per mole of [/datum/gas/healium::name] formed.", + /datum/gas/freon = "[/datum/gas/freon::name] is consumed at 11/12th of a mole per mole of [/datum/gas/healium::name] formed.", + /datum/gas/healium = "[/datum/gas/healium::name] is formed at a rate that scales with the temperature.", + "Temperature" = "Can only occur between [HEALIUM_FORMATION_MIN_TEMP] - [HEALIUM_FORMATION_MAX_TEMP]. \ + Higher temperature increases [/datum/gas/healium::name] formation rate.", + "Energy" = "[HEALIUM_FORMATION_ENERGY/3] joules of energy is released per mole of [/datum/gas/healium::name] formed.", ) -/datum/gas_reaction/zauker_formation/init_factors() +/datum/gas_reaction/standard/zauker_formation/init_factors() factor = list( - /datum/gas/hypernoblium = "Hyper-Noblium is consumed at 0.02 moles per mole of zauker formed.", - /datum/gas/nitrium = "Nitrium is consumed at 1 mole per mole of zauker formed.", - /datum/gas/zauker = "Zauker is produced at a rate that scales with the temperature.", - "Temperature" = "Can only occur between [ZAUKER_FORMATION_MIN_TEMPERATURE] - [ZAUKER_FORMATION_MAX_TEMPERATURE] kelvin. Zauker formation rate is proportional to the temperature.", - "Energy" = "[2 * ZAUKER_FORMATION_ENERGY] joules of energy is absorbed per mole of zauker formed.", + /datum/gas/hypernoblium = "[/datum/gas/hypernoblium::name] is consumed at 0.02 moles per mole of [/datum/gas/zauker::name] formed.", + /datum/gas/nitrium = "[/datum/gas/nitrium::name] is consumed at 1 mole per mole of [/datum/gas/zauker::name] formed.", + /datum/gas/zauker = "[/datum/gas/zauker::name] is produced at a rate that scales with the temperature.", + "Temperature" = "Can only occur between [ZAUKER_FORMATION_MIN_TEMPERATURE] - [ZAUKER_FORMATION_MAX_TEMPERATURE]K. [/datum/gas/zauker::name] formation rate is proportional to the temperature.", + "Energy" = "[2 * ZAUKER_FORMATION_ENERGY] joules of energy is absorbed per mole of [/datum/gas/zauker::name] formed.", ) -/datum/gas_reaction/zauker_decomp/init_factors() //Fixed reaction rate +/datum/gas_reaction/standard/zauker_decomp/init_factors() //Fixed reaction rate factor = list( - /datum/gas/zauker = "Zauker is consumed at [ZAUKER_DECOMPOSITION_MAX_RATE SECONDS / SSair.wait] moles per second in any unique gas mixture.", - /datum/gas/nitrogen = "At least [MINIMUM_MOLE_COUNT] moles of Nitrogen needs to be present for this reaction to occur. Nitrogen is produced at 0.7 moles per mole of Zauker decomposed.", - /datum/gas/oxygen = "Oxygen is produced at 0.3 moles per mole of zauker decomposed.", - "Energy" = "[ZAUKER_DECOMPOSITION_ENERGY] joules of energy is released per mole of zauker decomposed.", + /datum/gas/zauker = "[/datum/gas/zauker::name] is consumed at [ZAUKER_DECOMPOSITION_MAX_RATE SECONDS / SSair.wait] moles per second in any unique gas mixture.", + /datum/gas/nitrogen = "At least [MINIMUM_MOLE_COUNT] moles of [/datum/gas/nitrogen::name] needs to be present for this reaction to occur. \ + [/datum/gas/nitrogen::name] is produced at 0.7 moles per mole of [/datum/gas/zauker::name] decomposed.", + /datum/gas/oxygen = "[/datum/gas/oxygen::name] is produced at 0.3 moles per mole of [/datum/gas/zauker::name] decomposed.", + "Energy" = "[ZAUKER_DECOMPOSITION_ENERGY] joules of energy is released per mole of [/datum/gas/zauker::name] decomposed.", ) -/datum/gas_reaction/proto_nitrate_formation/init_factors() +/datum/gas_reaction/standard/proto_nitrate_formation/init_factors() factor = list( - /datum/gas/pluoxium = "Pluoxium is consumed at 1/11th of a mole per mole of proto-nitrate formed.", - /datum/gas/hydrogen = "Hydrogen is consumed at 10/11th of a mole per mole of proto-nitrate formed.", - /datum/gas/proto_nitrate = "Proto-Nitrate is produced at a rate that scales with the temperature.", - "Energy" = "[PN_FORMATION_ENERGY / 2.2] joules of energy is released per mole of proto-nitrate formed.", - "Temperature" = "Can only occur between [PN_FORMATION_MIN_TEMPERATURE] - [PN_FORMATION_MAX_TEMPERATURE] kelvin. Higher temperature increases proto-nitrate formation rate.", + /datum/gas/pluoxium = "[/datum/gas/pluoxium::name] is consumed at 1/11th of a mole per mole of [/datum/gas/proto_nitrate::name] formed.", + /datum/gas/hydrogen = "[/datum/gas/hydrogen::name] is consumed at 10/11th of a mole per mole of [/datum/gas/proto_nitrate::name] formed.", + /datum/gas/proto_nitrate = "[/datum/gas/proto_nitrate::name] is produced at a rate that scales with the temperature.", + "Energy" = "[PN_FORMATION_ENERGY / 2.2] joules of energy is released per mole of [/datum/gas/proto_nitrate::name] formed.", + "Temperature" = "Can only occur between [PN_FORMATION_MIN_TEMPERATURE] - [PN_FORMATION_MAX_TEMPERATURE]K. \ + Higher temperature increases [/datum/gas/proto_nitrate::name] formation rate.", ) -/datum/gas_reaction/proto_nitrate_hydrogen_response/init_factors() // Fixed reaction rate +/datum/gas_reaction/standard/proto_nitrate_hydrogen_response/init_factors() // Fixed reaction rate factor = list( - /datum/gas/hydrogen = "[PN_HYDROGEN_CONVERSION_THRESHOLD] moles of hydrogen needs to be present for the reaction to occur. Hydrogen is consumed at 2 moles per mole of proto-nitrate formed.", - /datum/gas/proto_nitrate = "[MINIMUM_MOLE_COUNT] moles of proto-nitrate needs to be present for the reaction to occur. Proto nitrate is produced a rate that scales with its mole count, up to a max of [PN_HYDROGEN_CONVERSION_MAX_RATE * 0.5 SECONDS / SSair.wait] moles per second.", - "Energy" = "[PN_HYDROGEN_CONVERSION_ENERGY * 2] joules of energy is absorbed per mole of proto-nitrate formed.", + /datum/gas/hydrogen = "[PN_HYDROGEN_CONVERSION_THRESHOLD] moles of [/datum/gas/hydrogen::name] needs to be present for the reaction to occur. \ + [/datum/gas/hydrogen::name] is consumed at 2 moles per mole of [/datum/gas/proto_nitrate::name] formed.", + /datum/gas/proto_nitrate = "[MINIMUM_MOLE_COUNT] moles of [/datum/gas/proto_nitrate::name] needs to be present for the reaction to occur. \ + [/datum/gas/proto_nitrate::name] is produced a rate that scales with its mole count, \ + up to a max of [PN_HYDROGEN_CONVERSION_MAX_RATE * 0.5 SECONDS / SSair.wait] moles per second.", + "Energy" = "[PN_HYDROGEN_CONVERSION_ENERGY * 2] joules of energy is absorbed per mole of [/datum/gas/proto_nitrate::name] formed.", ) -/datum/gas_reaction/proto_nitrate_tritium_response/init_factors() +/datum/gas_reaction/standard/proto_nitrate_tritium_response/init_factors() factor = list( - /datum/gas/tritium = "Tritium radiates its neutrons at a rate that scales with the temperature and proto-nitrate mole count.", - /datum/gas/proto_nitrate = "Proto nitrate is consumed at 0.005 moles per mole of neutrons released.", - /datum/gas/hydrogen = "Hydrogen remains after the neutrons escape.", + /datum/gas/tritium = "[/datum/gas/tritium::name] radiates its neutrons at a rate that scales with the temperature and [/datum/gas/proto_nitrate::name] mole count.", + /datum/gas/proto_nitrate = "[/datum/gas/proto_nitrate::name] is consumed at 0.005 moles per mole of neutrons released.", + /datum/gas/hydrogen = "[/datum/gas/hydrogen::name] remains after the neutrons escape.", "Energy" = "[PN_TRITIUM_CONVERSION_ENERGY / 2] joules of energy is released per mole of neutron released.", - "Radiation" = "Neutrons get released as ionising radiation.", + "Radiation" = "Neutrons are released as ionising radiation.", ) -/datum/gas_reaction/proto_nitrate_bz_response/init_factors() +/datum/gas_reaction/standard/proto_nitrate_bz_response/init_factors() factor = list( - /datum/gas/proto_nitrate = "[MINIMUM_MOLE_COUNT] moles of proto-nitrate needs to be present for the reaction to occur. Proto-nitrate accelerates the BZ decomposition.", - /datum/gas/bz = "BZ gets decomposed into plasma and nitrous oxide. The nitrous oxide then decomposes into nitrogen and oxygen, with the oxygen then decaying into helium.", - /datum/gas/nitrogen = "Nitrogen is produced at 0.4 moles per mole of BZ decomposed.", - /datum/gas/helium = "Helium is produced at 1.6 moles per mole of BZ decomposed.", - /datum/gas/plasma = "Plasma is produced at 0.8 moles per mole of BZ decomposed.", - "Energy" = "[PN_BZASE_ENERGY] joules of energy is released per mole of BZ decomposed.", - "Radiation" = "Radiation gets released during this decomposition process.", + /datum/gas/proto_nitrate = "[MINIMUM_MOLE_COUNT] moles of [/datum/gas/proto_nitrate::name] needs to be present for the reaction to occur. \ + [/datum/gas/proto_nitrate::name] accelerates the [/datum/gas/bz::name] decomposition.", + /datum/gas/bz = "[/datum/gas/bz::name] gets decomposed into [/datum/gas/plasma::name] and [/datum/gas/nitrous_oxide::name]. \ + The [/datum/gas/nitrous_oxide::name] then decomposes into [/datum/gas/nitrogen::name] and [/datum/gas/oxygen::name], \ + with the [/datum/gas/oxygen::name] then decaying into [/datum/gas/helium::name].", + /datum/gas/nitrogen = "[/datum/gas/nitrogen::name] is produced at 0.4 moles per mole of [/datum/gas/bz::name] decomposed.", + /datum/gas/helium = "[/datum/gas/helium::name] is produced at 1.6 moles per mole of [/datum/gas/bz::name] decomposed.", + /datum/gas/plasma = "[/datum/gas/plasma::name] is produced at 0.8 moles per mole of [/datum/gas/bz::name] decomposed.", + "Energy" = "[PN_BZASE_ENERGY] joules of energy is released per mole of [/datum/gas/bz::name] decomposed.", + "Radiation" = "Radiation is released during this decomposition process.", "Hallucinations" = "This reaction can cause various carbon based lifeforms in the vicinity to hallucinate.", - "Nuclear Particles" = "This reaction emits extremely high energy nuclear particles, up to [2 * PN_BZASE_NUCLEAR_PARTICLE_MAXIMUM] per second per unique gas mixture.", + "Nuclear Particles" = "This reaction emits extremely high energy nuclear particles, \ + up to [2 * PN_BZASE_NUCLEAR_PARTICLE_MAXIMUM] per second per unique gas mixture.", + "Temperature" = "Can only occur between [PN_BZASE_MIN_TEMP] - [PN_BZASE_MAX_TEMP]K.", ) diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index 0118f96307e7..051c9b48f96e 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -13,7 +13,7 @@ /* PRIORITY_FIRE = */ list() ) - for(var/datum/gas_reaction/reaction as anything in subtypesof(/datum/gas_reaction)) + for(var/datum/gas_reaction/standard/reaction as anything in subtypesof(/datum/gas_reaction/standard)) if(initial(reaction.exclude)) continue reaction = new reaction @@ -40,6 +40,13 @@ return priority_reactions /datum/gas_reaction + abstract_type = /datum/gas_reaction + /// Name of the reaction + var/name = "reaction" + /// A short string describing this reaction. + var/desc + /// ID of the reaction + var/id = "r" /** * Regarding the requirements list: the minimum or maximum requirements must be non-zero. * When in doubt, use MINIMUM_MOLE_COUNT. @@ -47,16 +54,6 @@ * More complex implementations will require modifications to gas_mixture.react() */ var/list/requirements - var/major_gas //the highest rarity gas used in the reaction. - var/exclude = FALSE //do it this way to allow for addition/removal of reactions midmatch in the future - ///The priority group this reaction is a part of. You can think of these as processing in batches, put your reaction into the one that's most fitting - var/priority_group - var/name = "reaction" - var/id = "r" - /// Whether the presence of our reaction should make fires bigger or not. - var/expands_hotspot = FALSE - /// A short string describing this reaction. - var/desc /** REACTION FACTORS * * Describe (to a human) factors influencing this reaction in an assoc list format. @@ -69,16 +66,26 @@ */ var/list/factor -/datum/gas_reaction/New() +/datum/gas_reaction/standard + abstract_type = /datum/gas_reaction/standard + var/major_gas //the highest rarity gas used in the reaction. + var/exclude = FALSE //do it this way to allow for addition/removal of reactions midmatch in the future + ///The priority group this reaction is a part of. You can think of these as processing in batches, put your reaction into the one that's most fitting + var/priority_group + /// Whether the presence of our reaction should make fires bigger or not. + var/expands_hotspot = FALSE + +/datum/gas_reaction/standard/New() + . = ..() init_reqs() init_factors() -/datum/gas_reaction/proc/init_reqs() // Override this +/datum/gas_reaction/standard/proc/init_reqs() // Override this CRASH("Reaction [type] made without specifying requirements.") -/datum/gas_reaction/proc/init_factors() +/datum/gas_reaction/standard/proc/init_factors() -/datum/gas_reaction/proc/react(datum/gas_mixture/air, atom/location) +/datum/gas_reaction/standard/proc/react(datum/gas_mixture/air, atom/location) return NO_REACTION @@ -88,19 +95,19 @@ * Makes turfs slippery. * Can frost things if the gas is cold enough. */ -/datum/gas_reaction/water_vapor +/datum/gas_reaction/standard/water_vapor priority_group = PRIORITY_POST_FORMATION name = "Water Vapor Condensation" id = "vapor" - desc = "Water vapor condensation that can make things slippery." + desc = "Water Vapor condensation that may make things slippery." -/datum/gas_reaction/water_vapor/init_reqs() +/datum/gas_reaction/standard/water_vapor/init_reqs() requirements = list( /datum/gas/water_vapor = MOLES_GAS_VISIBLE, "MAX_TEMP" = WATER_VAPOR_CONDENSATION_POINT, ) -/datum/gas_reaction/water_vapor/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/water_vapor/react(datum/gas_mixture/air, datum/holder) . = NO_REACTION if(!isturf(holder)) return @@ -126,19 +133,19 @@ * * Clears out pathogens in the air. */ -/datum/gas_reaction/miaster +/datum/gas_reaction/standard/miaster priority_group = PRIORITY_POST_FORMATION name = "Dry Heat Sterilization" id = "sterilization" desc = "Pathogens cannot survive in a hot environment. Miasma decomposes on high temperature." -/datum/gas_reaction/miaster/init_reqs() +/datum/gas_reaction/standard/miaster/init_reqs() requirements = list( /datum/gas/miasma = MINIMUM_MOLE_COUNT, "MIN_TEMP" = MIASTER_STERILIZATION_TEMP, ) -/datum/gas_reaction/miaster/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/miaster/react(datum/gas_mixture/air, datum/holder) var/list/cached_moles = air.moles var/water_vapor_moles = cached_moles[/datum/gas/water_vapor] var/miasma_moles = cached_moles[/datum/gas/miasma] @@ -167,21 +174,23 @@ * The reaction rate is dependent on the temperature of the gasmix. * May produce either tritium or carbon dioxide and water vapor depending on the fuel/oxydizer ratio of the gasmix. */ -/datum/gas_reaction/plasmafire +/datum/gas_reaction/standard/plasmafire priority_group = PRIORITY_FIRE name = "Plasma Combustion" id = "plasmafire" expands_hotspot = TRUE - desc = "Combustion of oxygen and plasma. Able to produce tritium or carbon dioxade and water vapor." + desc = "Combustion of Oxygen and Plasma. Produces Carbon Dioxide and Water Vapor, \ + and potentially even Tritium if the mixture is rich enough in Plasma." -/datum/gas_reaction/plasmafire/init_reqs() +/datum/gas_reaction/standard/plasmafire/init_reqs() requirements = list( /datum/gas/plasma = MINIMUM_MOLE_COUNT, /datum/gas/oxygen = MINIMUM_MOLE_COUNT, "MIN_TEMP" = PLASMA_MINIMUM_BURN_TEMPERATURE, ) -/datum/gas_reaction/plasmafire/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/plasmafire/react(datum/gas_mixture/air, datum/holder) + . = NO_REACTION // This reaction should proceed faster at higher temperatures. var/temperature = air.temperature var/temperature_scale = 0 @@ -244,21 +253,21 @@ * Highly exothermic. * Creates hotspots. */ -/datum/gas_reaction/h2fire +/datum/gas_reaction/standard/h2fire priority_group = PRIORITY_FIRE name = "Hydrogen Combustion" id = "h2fire" expands_hotspot = TRUE - desc = "Combustion of hydrogen with oxygen. Can be extremely fast and energetic if a few conditions are fulfilled." + desc = "Combustion of Hydrogen with Oxygen. May be extremely fast and energetic, if a few conditions are fulfilled." -/datum/gas_reaction/h2fire/init_reqs() +/datum/gas_reaction/standard/h2fire/init_reqs() requirements = list( /datum/gas/hydrogen = MINIMUM_MOLE_COUNT, /datum/gas/oxygen = MINIMUM_MOLE_COUNT, "MIN_TEMP" = HYDROGEN_MINIMUM_BURN_TEMPERATURE, ) -/datum/gas_reaction/h2fire/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/h2fire/react(datum/gas_mixture/air, datum/holder) . = NO_REACTION var/list/cached_moles = air.moles //this speeds things up because accessing datum vars is slow var/hydrogen_moles = cached_moles[/datum/gas/hydrogen] @@ -300,21 +309,21 @@ * Creates hotspots. * Creates radiation. */ -/datum/gas_reaction/tritfire +/datum/gas_reaction/standard/tritfire priority_group = PRIORITY_FIRE name = "Tritium Combustion" id = "tritfire" expands_hotspot = TRUE - desc = "Combustion of tritium with oxygen. Can be extremely fast and energetic if a few conditions are fulfilled." + desc = "Combustion of Tritium with Oxygen. May be extremely fast and energetic, if a few conditions are fulfilled." -/datum/gas_reaction/tritfire/init_reqs() +/datum/gas_reaction/standard/tritfire/init_reqs() requirements = list( /datum/gas/tritium = MINIMUM_MOLE_COUNT, /datum/gas/oxygen = MINIMUM_MOLE_COUNT, "MIN_TEMP" = TRITIUM_MINIMUM_BURN_TEMPERATURE, ) -/datum/gas_reaction/tritfire/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/tritfire/react(datum/gas_mixture/air, datum/holder) . = NO_REACTION var/list/cached_moles = air.moles //this speeds things up because accessing datum vars is slow var/tritium_moles = cached_moles[/datum/gas/tritium] @@ -364,14 +373,14 @@ * Combustion of oxygen and freon. * Endothermic. */ -/datum/gas_reaction/freonfire +/datum/gas_reaction/standard/freonfire priority_group = PRIORITY_FIRE name = "Freon Combustion" id = "freonfire" expands_hotspot = TRUE - desc = "Reaction between oxygen and freon that consumes a huge amount of energy and can cool things significantly. Also able to produce hot ice." + desc = "Reaction between Oxygen and Freon that consumes a huge amount of energy, cooling the atmosphere significantly. May produce \"hot ice\"." -/datum/gas_reaction/freonfire/init_reqs() +/datum/gas_reaction/standard/freonfire/init_reqs() requirements = list( /datum/gas/oxygen = MINIMUM_MOLE_COUNT, /datum/gas/freon = MINIMUM_MOLE_COUNT, @@ -379,7 +388,8 @@ "MAX_TEMP" = FREON_MAXIMUM_BURN_TEMPERATURE, ) -/datum/gas_reaction/freonfire/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/freonfire/react(datum/gas_mixture/air, datum/holder) + . = NO_REACTION var/temperature = air.temperature var/temperature_scale if(temperature < FREON_TERMINAL_TEMPERATURE) //stop the reaction when too cold @@ -437,13 +447,13 @@ * Endothermic. * Requires BZ as a catalyst. */ -/datum/gas_reaction/nitrousformation //formation of n2o, exothermic, requires bz as catalyst +/datum/gas_reaction/standard/nitrousformation //formation of n2o, exothermic, requires bz as catalyst priority_group = PRIORITY_FORMATION name = "Nitrous Oxide Formation" id = "nitrousformation" - desc = "Production of nitrous oxide with BZ as a catalyst." + desc = "Production of Nitrous Oxide with BZ as a catalyst." -/datum/gas_reaction/nitrousformation/init_reqs() +/datum/gas_reaction/standard/nitrousformation/init_reqs() requirements = list( /datum/gas/oxygen = 10, /datum/gas/nitrogen = 20, @@ -452,7 +462,7 @@ "MAX_TEMP" = N2O_FORMATION_MAX_TEMPERATURE, ) -/datum/gas_reaction/nitrousformation/react(datum/gas_mixture/air) +/datum/gas_reaction/standard/nitrousformation/react(datum/gas_mixture/air) var/list/cached_moles = air.moles var/oxygen_moles = cached_moles[/datum/gas/oxygen] var/nitrogen_moles = cached_moles[/datum/gas/nitrogen] @@ -479,20 +489,20 @@ * Decomposition of N2O. * Exothermic. */ -/datum/gas_reaction/nitrous_decomp +/datum/gas_reaction/standard/nitrous_decomp priority_group = PRIORITY_POST_FORMATION name = "Nitrous Oxide Decomposition" id = "nitrous_decomp" - desc = "Decomposition of nitrous oxide under high temperature." + desc = "Decomposition of Nitrous Oxide under high temperature." -/datum/gas_reaction/nitrous_decomp/init_reqs() +/datum/gas_reaction/standard/nitrous_decomp/init_reqs() requirements = list( /datum/gas/nitrous_oxide = MINIMUM_MOLE_COUNT * 2, "MIN_TEMP" = N2O_DECOMPOSITION_MIN_TEMPERATURE, "MAX_TEMP" = N2O_DECOMPOSITION_MAX_TEMPERATURE, ) -/datum/gas_reaction/nitrous_decomp/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/nitrous_decomp/react(datum/gas_mixture/air, datum/holder) var/list/cached_moles = air.moles //this speeds things up because accessing datum vars is slow var/nitrous_oxide_moles = cached_moles[/datum/gas/nitrous_oxide] var/temperature = air.temperature @@ -521,20 +531,20 @@ * Formation of BZ by combining plasma and nitrous oxide at low pressures. * Exothermic. */ -/datum/gas_reaction/bzformation +/datum/gas_reaction/standard/bzformation priority_group = PRIORITY_FORMATION name = "BZ Gas Formation" id = "bzformation" - desc = "Production of BZ using plasma and nitrous oxide." + desc = "Production of BZ using Plasma and Nitrous Oxide." -/datum/gas_reaction/bzformation/init_reqs() +/datum/gas_reaction/standard/bzformation/init_reqs() requirements = list( /datum/gas/nitrous_oxide = 10, /datum/gas/plasma = 10, "MAX_TEMP" = BZ_FORMATION_MAX_TEMPERATURE, ) -/datum/gas_reaction/bzformation/react(datum/gas_mixture/air) +/datum/gas_reaction/standard/bzformation/react(datum/gas_mixture/air) var/list/cached_moles = air.moles var/nitrous_oxide_moles = cached_moles[/datum/gas/nitrous_oxide] var/plasma_moles = cached_moles[/datum/gas/plasma] @@ -581,13 +591,13 @@ * Consumes a tiny amount of tritium to convert CO2 and oxygen to pluoxium. * Exothermic. */ -/datum/gas_reaction/pluox_formation +/datum/gas_reaction/standard/pluox_formation priority_group = PRIORITY_FORMATION name = "Pluoxium Formation" id = "pluox_formation" - desc = "Alternate production for pluoxium which uses tritium." + desc = "Alternate Production method for Pluoxium which uses Tritium." -/datum/gas_reaction/pluox_formation/init_reqs() +/datum/gas_reaction/standard/pluox_formation/init_reqs() requirements = list( /datum/gas/carbon_dioxide = MINIMUM_MOLE_COUNT, /datum/gas/oxygen = MINIMUM_MOLE_COUNT, @@ -596,7 +606,7 @@ "MAX_TEMP" = PLUOXIUM_FORMATION_MAX_TEMP, ) -/datum/gas_reaction/pluox_formation/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/pluox_formation/react(datum/gas_mixture/air, datum/holder) var/list/cached_moles = air.moles var/carbon_dioxide_moles = cached_moles[/datum/gas/carbon_dioxide] var/oxygen_moles = cached_moles[/datum/gas/oxygen] @@ -629,13 +639,13 @@ * Endothermic. * Requires BZ. */ -/datum/gas_reaction/nitrium_formation +/datum/gas_reaction/standard/nitrium_formation priority_group = PRIORITY_FORMATION name = "Nitrium Formation" id = "nitrium_formation" - desc = "Production of nitrium from BZ, tritium, and nitrogen." + desc = "Production of Nitrium from BZ, Tritium, and Nitrogen." -/datum/gas_reaction/nitrium_formation/init_reqs() +/datum/gas_reaction/standard/nitrium_formation/init_reqs() requirements = list( /datum/gas/tritium = 20, /datum/gas/nitrogen = 10, @@ -643,7 +653,7 @@ "MIN_TEMP" = NITRIUM_FORMATION_MIN_TEMP, ) -/datum/gas_reaction/nitrium_formation/react(datum/gas_mixture/air) +/datum/gas_reaction/standard/nitrium_formation/react(datum/gas_mixture/air) var/list/cached_moles = air.moles var/tritium_moles = cached_moles[/datum/gas/tritium] var/nitrogen_moles = cached_moles[/datum/gas/nitrogen] @@ -676,20 +686,20 @@ * Exothermic. * Requires oxygen as catalyst. */ -/datum/gas_reaction/nitrium_decomposition +/datum/gas_reaction/standard/nitrium_decomposition priority_group = PRIORITY_PRE_FORMATION name = "Nitrium Decomposition" id = "nitrium_decomp" - desc = "Decomposition of nitrium when exposed to oxygen under normal temperatures." + desc = "Decomposition of Nitrium when exposed to Oxygen under normal temperatures." -/datum/gas_reaction/nitrium_decomposition/init_reqs() +/datum/gas_reaction/standard/nitrium_decomposition/init_reqs() requirements = list( /datum/gas/oxygen = MINIMUM_MOLE_COUNT, /datum/gas/nitrium = MINIMUM_MOLE_COUNT, "MAX_TEMP" = NITRIUM_DECOMPOSITION_MAX_TEMP, ) -/datum/gas_reaction/nitrium_decomposition/react(datum/gas_mixture/air) +/datum/gas_reaction/standard/nitrium_decomposition/react(datum/gas_mixture/air) var/list/cached_moles = air.moles var/nitrium_moles = cached_moles[/datum/gas/nitrium] var/temperature = air.temperature @@ -720,13 +730,13 @@ * The formation of freon. * Endothermic. */ -/datum/gas_reaction/freonformation +/datum/gas_reaction/standard/freonformation priority_group = PRIORITY_FORMATION name = "Freon Formation" id = "freonformation" - desc = "Production of freon using plasma, carbon dioxide, and BZ under high temperature." + desc = "Production of Freon using Plasma, Carbon Dioxide, and BZ under high temperature." -/datum/gas_reaction/freonformation/init_reqs() //minimum requirements for freon formation +/datum/gas_reaction/standard/freonformation/init_reqs() //minimum requirements for freon formation requirements = list( /datum/gas/plasma = MINIMUM_MOLE_COUNT * 6, /datum/gas/carbon_dioxide = MINIMUM_MOLE_COUNT * 3, @@ -734,7 +744,7 @@ "MIN_TEMP" = FREON_FORMATION_MIN_TEMPERATURE, ) -/datum/gas_reaction/freonformation/react(datum/gas_mixture/air) +/datum/gas_reaction/standard/freonformation/react(datum/gas_mixture/air) var/list/cached_moles = air.moles var/plasma_moles = cached_moles[/datum/gas/plasma] var/carbon_dioxide_moles = cached_moles[/datum/gas/carbon_dioxide] @@ -773,13 +783,13 @@ * Due to its high mass, hyper-noblium uses large amounts of nitrogen and tritium. * BZ can be used as a catalyst to make it less exothermic. */ -/datum/gas_reaction/nobliumformation +/datum/gas_reaction/standard/nobliumformation priority_group = PRIORITY_FORMATION name = "Hyper-Noblium Condensation" id = "nobformation" - desc = "Production of hyper-noblium from nitrogen and tritium under very low temperatures. Extremely energetic." + desc = "Production of Hyper-Noblium from Nitrogen and Tritium under very low temperatures. Extremely energetic." -/datum/gas_reaction/nobliumformation/init_reqs() +/datum/gas_reaction/standard/nobliumformation/init_reqs() requirements = list( /datum/gas/nitrogen = 10, /datum/gas/tritium = 5, @@ -787,7 +797,7 @@ "MAX_TEMP" = NOBLIUM_FORMATION_MAX_TEMP, ) -/datum/gas_reaction/nobliumformation/react(datum/gas_mixture/air) +/datum/gas_reaction/standard/nobliumformation/react(datum/gas_mixture/air) . = NO_REACTION var/list/cached_moles = air.moles var/nitrogen_moles = cached_moles[/datum/gas/nitrogen] @@ -826,20 +836,20 @@ * Produces carbon dioxide. * Endothermic. */ -/datum/gas_reaction/halon_o2removal +/datum/gas_reaction/standard/halon_o2removal priority_group = PRIORITY_PRE_FORMATION name = "Halon Oxygen Absorption" id = "halon_o2removal" - desc = "Halon interaction with oxygen that can be used to snuff fires out." + desc = "Halon interaction with Oxygen that can be used to snuff fires out." -/datum/gas_reaction/halon_o2removal/init_reqs() +/datum/gas_reaction/standard/halon_o2removal/init_reqs() requirements = list( /datum/gas/halon = MINIMUM_MOLE_COUNT, /datum/gas/oxygen = MINIMUM_MOLE_COUNT, "MIN_TEMP" = FIRE_MINIMUM_TEMPERATURE_TO_EXIST, ) -/datum/gas_reaction/halon_o2removal/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/halon_o2removal/react(datum/gas_mixture/air, datum/holder) . = NO_REACTION var/list/cached_moles = air.moles var/halon_moles = cached_moles[/datum/gas/halon] @@ -870,13 +880,13 @@ * * Exothermic */ -/datum/gas_reaction/healium_formation +/datum/gas_reaction/standard/healium_formation priority_group = PRIORITY_FORMATION name = "Healium Formation" id = "healium_formation" - desc = "Production of healium using BZ and freon." + desc = "Production of Healium using BZ and Freon." -/datum/gas_reaction/healium_formation/init_reqs() +/datum/gas_reaction/standard/healium_formation/init_reqs() requirements = list( /datum/gas/bz = MINIMUM_MOLE_COUNT, /datum/gas/freon = MINIMUM_MOLE_COUNT, @@ -884,7 +894,7 @@ "MAX_TEMP" = HEALIUM_FORMATION_MAX_TEMP, ) -/datum/gas_reaction/healium_formation/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/healium_formation/react(datum/gas_mixture/air, datum/holder) var/list/cached_moles = air.moles var/bz_moles = cached_moles[/datum/gas/bz] var/freon_moles = cached_moles[/datum/gas/freon] @@ -911,13 +921,13 @@ * Exothermic. * Requires Hypernoblium. */ -/datum/gas_reaction/zauker_formation +/datum/gas_reaction/standard/zauker_formation priority_group = PRIORITY_FORMATION name = "Zauker Formation" id = "zauker_formation" - desc = "Production of zauker using hyper-noblium and nitrium under very high temperatures." + desc = "Production of Zauker using Hyper-Noblium and Nitrium under very high temperatures." -/datum/gas_reaction/zauker_formation/init_reqs() +/datum/gas_reaction/standard/zauker_formation/init_reqs() requirements = list( /datum/gas/hypernoblium = MINIMUM_MOLE_COUNT, /datum/gas/nitrium = MINIMUM_MOLE_COUNT, @@ -925,7 +935,7 @@ "MAX_TEMP" = ZAUKER_FORMATION_MAX_TEMPERATURE, ) -/datum/gas_reaction/zauker_formation/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/zauker_formation/react(datum/gas_mixture/air, datum/holder) var/list/cached_moles = air.moles var/hypernoblium_moles = cached_moles[/datum/gas/hypernoblium] var/nitrium_moles = cached_moles[/datum/gas/nitrium] @@ -954,19 +964,19 @@ * Occurs in the presence of nitrogen to prevent zauker floods. * Exothermic. */ -/datum/gas_reaction/zauker_decomp +/datum/gas_reaction/standard/zauker_decomp priority_group = PRIORITY_POST_FORMATION name = "Zauker Decomposition" id = "zauker_decomp" - desc = "Decomposition of zauker when exposed to nitrogen." + desc = "Decomposition of Zauker when exposed to Nitrogen." -/datum/gas_reaction/zauker_decomp/init_reqs() +/datum/gas_reaction/standard/zauker_decomp/init_reqs() requirements = list( /datum/gas/nitrogen = MINIMUM_MOLE_COUNT, /datum/gas/zauker = MINIMUM_MOLE_COUNT, ) -/datum/gas_reaction/zauker_decomp/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/zauker_decomp/react(datum/gas_mixture/air, datum/holder) var/list/cached_moles = air.moles //this speeds things up because accessing datum vars is slow var/nitrogen_moles = cached_moles[/datum/gas/nitrogen] var/zauker_moles = cached_moles[/datum/gas/zauker] @@ -994,13 +1004,13 @@ * * Exothermic. */ -/datum/gas_reaction/proto_nitrate_formation +/datum/gas_reaction/standard/proto_nitrate_formation priority_group = PRIORITY_FORMATION - name = "Proto Nitrate Formation" + name = "Proto-Nitrate Formation" id = "proto_nitrate_formation" - desc = "Production of proto-nitrate from pluoxium and hydrogen under high temperatures." + desc = "Production of Proto-Nitrate from Pluoxium and Hydrogen under high temperatures." -/datum/gas_reaction/proto_nitrate_formation/init_reqs() +/datum/gas_reaction/standard/proto_nitrate_formation/init_reqs() requirements = list( /datum/gas/pluoxium = MINIMUM_MOLE_COUNT, /datum/gas/hydrogen = MINIMUM_MOLE_COUNT, @@ -1008,7 +1018,7 @@ "MAX_TEMP" = PN_FORMATION_MAX_TEMPERATURE, ) -/datum/gas_reaction/proto_nitrate_formation/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/proto_nitrate_formation/react(datum/gas_mixture/air, datum/holder) var/list/cached_moles = air.moles var/pluoxium_moles = cached_moles[/datum/gas/pluoxium] var/hydrogen_moles = cached_moles[/datum/gas/hydrogen] @@ -1036,19 +1046,19 @@ * Converts hydrogen into proto-nitrate. * Endothermic. */ -/datum/gas_reaction/proto_nitrate_hydrogen_response +/datum/gas_reaction/standard/proto_nitrate_hydrogen_response priority_group = PRIORITY_PRE_FORMATION - name = "Proto Nitrate Hydrogen Response" + name = "Proto-Nitrate Hydrogen Response" id = "proto_nitrate_hydrogen_response" - desc = "Conversion of hydrogen into proto nitrate." + desc = "Conversion of Hydrogen into Proto-Nitrate." -/datum/gas_reaction/proto_nitrate_hydrogen_response/init_reqs() +/datum/gas_reaction/standard/proto_nitrate_hydrogen_response/init_reqs() requirements = list( /datum/gas/proto_nitrate = MINIMUM_MOLE_COUNT, /datum/gas/hydrogen = PN_HYDROGEN_CONVERSION_THRESHOLD, ) -/datum/gas_reaction/proto_nitrate_hydrogen_response/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/proto_nitrate_hydrogen_response/react(datum/gas_mixture/air, datum/holder) var/list/cached_moles = air.moles var/proto_nitrate_moles = cached_moles[/datum/gas/proto_nitrate] var/hydrogen_moles = cached_moles[/datum/gas/hydrogen] @@ -1074,13 +1084,13 @@ * Releases radiation. * Exothermic. */ -/datum/gas_reaction/proto_nitrate_tritium_response +/datum/gas_reaction/standard/proto_nitrate_tritium_response priority_group = PRIORITY_PRE_FORMATION - name = "Proto Nitrate Tritium Response" + name = "Proto-Nitrate Tritium Response" id = "proto_nitrate_tritium_response" - desc = "Conversion of tritium into hydrogen that consumes a small amount of proto-nitrate." + desc = "Conversion of Tritium into Hydrogen that consumes a small amount of Proto-Nitrate." -/datum/gas_reaction/proto_nitrate_tritium_response/init_reqs() +/datum/gas_reaction/standard/proto_nitrate_tritium_response/init_reqs() requirements = list( /datum/gas/proto_nitrate = MINIMUM_MOLE_COUNT, /datum/gas/tritium = MINIMUM_MOLE_COUNT, @@ -1088,7 +1098,7 @@ "MAX_TEMP" = PN_TRITIUM_CONVERSION_MAX_TEMP, ) -/datum/gas_reaction/proto_nitrate_tritium_response/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/proto_nitrate_tritium_response/react(datum/gas_mixture/air, datum/holder) . = NO_REACTION var/list/cached_moles = air.moles var/proto_nitrate_moles = cached_moles[/datum/gas/proto_nitrate] @@ -1125,13 +1135,13 @@ * * Breaks BZ down into nitrogen, helium, and plasma in the presence of proto-nitrate. */ -/datum/gas_reaction/proto_nitrate_bz_response +/datum/gas_reaction/standard/proto_nitrate_bz_response priority_group = PRIORITY_PRE_FORMATION - name = "Proto Nitrate BZ Response" + name = "Proto-Nitrate BZ Response" id = "proto_nitrate_bz_response" - desc = "Breakdown of BZ into nitrogen, helium, and plasma by proto-nitrate under low temperatures." + desc = "Breakdown of BZ into Nitrogen, Helium, and Plasma by Proto-Nitrate under low temperatures." -/datum/gas_reaction/proto_nitrate_bz_response/init_reqs() +/datum/gas_reaction/standard/proto_nitrate_bz_response/init_reqs() requirements = list( /datum/gas/proto_nitrate = MINIMUM_MOLE_COUNT, /datum/gas/bz = MINIMUM_MOLE_COUNT, @@ -1139,7 +1149,7 @@ "MAX_TEMP" = PN_BZASE_MAX_TEMP, ) -/datum/gas_reaction/proto_nitrate_bz_response/react(datum/gas_mixture/air, datum/holder) +/datum/gas_reaction/standard/proto_nitrate_bz_response/react(datum/gas_mixture/air, datum/holder) . = NO_REACTION var/list/cached_moles = air.moles var/proto_nitrate_moles = cached_moles[/datum/gas/proto_nitrate] diff --git a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm index c4bcfcdcc88c..20a547c1fcbf 100644 --- a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm +++ b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm @@ -7,8 +7,8 @@ interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN icon = 'icons/obj/pipes_n_cables/atmos.dmi' icon_state = "electrolyzer-off" - name = "space electrolyzer" - desc = "Thanks to the fast and dynamic response of our electrolyzers, on-site hydrogen production is guaranteed. Warranty void if used by clowns" + name = "electrolyzer" + desc = "A portable electrolyzer, allowing for on-site production of Hydrogen. Warranty void if used by clowns." max_integrity = 250 armor_type = /datum/armor/machinery_electrolyzer circuit = /obj/item/circuitboard/machine/electrolyzer @@ -71,9 +71,9 @@ . += "The charge meter reads [cell ? round(cell.percent(), 1) : 0]%." else . += "There is no power cell installed." - if(in_range(user, src) || isobserver(user)) + if(in_range(user, src) && !isobserver(user)) . += span_notice("Alt-click to toggle [on ? "off" : "on"].") - . += span_notice("Anchor to drain power from APC instead of cell") + . += span_notice("Anchor it to drain power from the area's APC instead its internal power cell.") . += span_notice("It will drain power from the [anchored ? "area's APC" : "internal power cell"].") diff --git a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm index 4e31eae5f90f..70d57dffbec0 100644 --- a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm +++ b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm @@ -5,24 +5,38 @@ GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list()) */ /proc/electrolyzer_reactions_list() var/list/built_reaction_list = list() - for(var/reaction_path in subtypesof(/datum/electrolyzer_reaction)) - var/datum/electrolyzer_reaction/reaction = new reaction_path() + for(var/reaction_path in subtypesof(/datum/gas_reaction/electrolyzer)) + var/datum/gas_reaction/electrolyzer/reaction = new reaction_path() built_reaction_list[reaction.id] = reaction return built_reaction_list -/datum/electrolyzer_reaction - var/list/requirements - var/name = "reaction" - var/id = "r" - var/desc = "" - var/list/factor +/datum/gas_reaction/electrolyzer + abstract_type = /datum/gas_reaction/electrolyzer -/datum/electrolyzer_reaction/proc/react(turf/location, datum/gas_mixture/air_mixture, working_power) +/datum/gas_reaction/electrolyzer/New() + . = ..() + factor ||= list() + factor["Location"] ||= "Can only happen on tiles with an active Electrolyzer." + +/** + * Electrolyzer reaction. + * Args: + * * air_mixture: The gas_mixture receiving the electrolysis. + * * working_power: How much energy to put into the electrolysis, in electrolyzer units. A value of 1 is what a tier 1 electrolyzer would put in. + * * electrolyzer_args: Additional arguments for alternative methods of electrolysis. + */ +/datum/gas_reaction/electrolyzer/proc/react(turf/location, datum/gas_mixture/air_mixture, working_power) return -/datum/electrolyzer_reaction/proc/reaction_check(datum/gas_mixture/air_mixture) +/** + * Checks whether the requirements are met for a reaction. + * Args: + * * air_mixture: The air mixture to check the requirements for. + * * electrolyzer_args: Additional arguments for alternative methods of electrolysis. + */ +/datum/gas_reaction/electrolyzer/proc/reaction_check(datum/gas_mixture/air_mixture) var/temp = air_mixture.temperature var/list/cached_moles = air_mixture.moles if((requirements["MIN_TEMP"] && temp < requirements["MIN_TEMP"]) || (requirements["MAX_TEMP"] && temp > requirements["MAX_TEMP"])) @@ -34,21 +48,20 @@ GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list()) return FALSE return TRUE -/datum/electrolyzer_reaction/h2o_conversion +/datum/gas_reaction/electrolyzer/h2o_conversion name = "H2O Conversion" id = "h2o_conversion" - desc = "Conversion of H2o into O2 and H2" + desc = "Conversion of H2O into H2 and O2." requirements = list( /datum/gas/water_vapor = MINIMUM_MOLE_COUNT ) factor = list( - /datum/gas/water_vapor = "2 moles of H2O get consumed", - /datum/gas/oxygen = "1 mole of O2 gets produced", - /datum/gas/hydrogen = "2 moles of H2 get produced", - "Location" = "Can only happen on turfs with an active Electrolyzer.", + /datum/gas/water_vapor = "2 moles of H2O is consumed.", + /datum/gas/oxygen = "1 mole of O2 is produced.", + /datum/gas/hydrogen = "2 moles of H2 is produced.", ) -/datum/electrolyzer_reaction/h2o_conversion/react(turf/location, datum/gas_mixture/air_mixture, working_power) +/datum/gas_reaction/electrolyzer/h2o_conversion/react(turf/location, datum/gas_mixture/air_mixture, working_power) var/old_heat_capacity = air_mixture.heat_capacity() @@ -60,22 +73,22 @@ GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list()) if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air_mixture.temperature = max(air_mixture.temperature * old_heat_capacity / new_heat_capacity, TCMB) -/datum/electrolyzer_reaction/nob_conversion - name = "Hypernob conversion" +/datum/gas_reaction/electrolyzer/nob_conversion + name = "Hyper-Noblium Conversion" id = "nob_conversion" - desc = "Conversion of Hypernoblium into Antinoblium" + desc = "Conversion of Hyper-Noblium into Anti-Noblium." requirements = list( /datum/gas/hypernoblium = MINIMUM_MOLE_COUNT, "MAX_TEMP" = 150 ) factor = list( - /datum/gas/hypernoblium = "1 mole of Hypernoblium gets consumed", - /datum/gas/antinoblium = "0.5 moles of Antinoblium get produced", + /datum/gas/hypernoblium = "1 mole of Hyper-Noblium is consumed.", + /datum/gas/antinoblium = "1 mole of Anti-Noblium is produced.", "Temperature" = "Can only occur under 150 kelvin.", "Location" = "Can only happen on turfs with an active Electrolyzer.", ) -/datum/electrolyzer_reaction/nob_conversion/react(turf/location, datum/gas_mixture/air_mixture, working_power) +/datum/gas_reaction/electrolyzer/nob_conversion/react(turf/location, datum/gas_mixture/air_mixture, working_power) var/old_heat_capacity = air_mixture.heat_capacity() air_mixture.assert_gases(/datum/gas/hypernoblium, /datum/gas/antinoblium) @@ -87,23 +100,22 @@ GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list()) if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air_mixture.temperature = max(air_mixture.temperature * old_heat_capacity / new_heat_capacity, TCMB) -/datum/electrolyzer_reaction/halon_generation - name = "Halon generation" +/datum/gas_reaction/electrolyzer/halon_generation + name = "Halon Generation" id = "halon_generation" desc = "Production of halon from the electrolysis of BZ." requirements = list( /datum/gas/bz = MINIMUM_MOLE_COUNT, ) factor = list( - /datum/gas/bz = "Consumed during reaction.", - /datum/gas/oxygen = "0.2 moles of oxygen gets produced per mole of BZ consumed.", - /datum/gas/halon = "2 moles of Halon gets produced per mole of BZ consumed.", + /datum/gas/bz = "All moles of BZ are consumed.", + /datum/gas/oxygen = "0.2 moles of oxygen is produced per mole of BZ consumed.", + /datum/gas/halon = "2 moles of Halon is produced per mole of BZ consumed.", "Energy" = "91.2321 kJ of thermal energy is released per mole of BZ consumed.", "Temperature" = "Reaction efficiency is proportional to temperature.", - "Location" = "Can only happen on turfs with an active Electrolyzer.", ) -/datum/electrolyzer_reaction/halon_generation/react(datum/gas_mixture/air_mixture, working_power, list/electrolyzer_args = list()) +/datum/gas_reaction/electrolyzer/halon_generation/react(datum/gas_mixture/air_mixture, working_power, list/electrolyzer_args = list()) var/old_heat_capacity = air_mixture.heat_capacity() air_mixture.assert_gases(/datum/gas/bz, /datum/gas/oxygen, /datum/gas/halon) var/bz_moles = air_mixture.moles[/datum/gas/bz] diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_core.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_core.dm index 55111e731fc2..3c6a3e8bd4c8 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_core.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_core.dm @@ -2,7 +2,7 @@ * This section contain the hfr core with all the variables and the Initialize() and Destroy() procs */ /obj/machinery/atmospherics/components/unary/hypertorus/core - name = "HFR core" + name = "\improper HFR core" desc = "This is the Hypertorus Fusion Reactor core, an advanced piece of technology to finely tune the reaction inside of the machine. It has I/O for cooling gases." icon = 'icons/obj/machines/atmospherics/hypertorus.dmi' icon_state = "core_off" diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_fuel_datums.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_fuel_datums.dm index c7a9545a7d25..39d8ce76cd27 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_fuel_datums.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_fuel_datums.dm @@ -99,7 +99,7 @@ GLOBAL_LIST_INIT(hfr_fuels_list, hfr_fuels_create_list()) /datum/hfr_fuel/hypernob_hydrogen_fuel id = "hypernob_hydrogen_fuel" - name = "Hypernoblium + Hydrogen fuel" + name = "Hyper-Noblium + Hydrogen fuel" negative_temperature_multiplier = 0.2 positive_temperature_multiplier = 2.2 energy_concentration_multiplier = 0.2 @@ -113,7 +113,7 @@ GLOBAL_LIST_INIT(hfr_fuels_list, hfr_fuels_create_list()) /datum/hfr_fuel/hypernob_trit_fuel id = "hypernob_trit_fuel" - name = "Hypernoblium + Tritium fuel" + name = "Hyper-Noblium + Tritium fuel" negative_temperature_multiplier = 0.1 positive_temperature_multiplier = 2.5 energy_concentration_multiplier = 0.1 @@ -127,7 +127,7 @@ GLOBAL_LIST_INIT(hfr_fuels_list, hfr_fuels_create_list()) /datum/hfr_fuel/hypernob_antinob_fuel id = "hypernob_antinob_fuel" - name = "Hypernoblium + Antinoblium fuel" + name = "Hyper-Noblium + Anti-Noblium fuel" negative_temperature_multiplier = 0.01 positive_temperature_multiplier = 3.5 energy_concentration_multiplier = 2 diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm index 49d21a597c13..2af79e0b6a33 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm @@ -78,7 +78,7 @@ return /obj/machinery/atmospherics/components/unary/hypertorus/fuel_input - name = "HFR fuel input port" + name = "\improper HFR fuel input port" desc = "Input port for the Hypertorus Fusion Reactor, designed to take in fuels with the optimal fuel mix being a 50/50 split." icon_state = "fuel_input_off" icon_state_open = "fuel_input_open" @@ -87,7 +87,7 @@ circuit = /obj/item/circuitboard/machine/HFR_fuel_input /obj/machinery/atmospherics/components/unary/hypertorus/waste_output - name = "HFR waste output port" + name = "\improper HFR waste output port" desc = "Waste port for the Hypertorus Fusion Reactor, designed to output the hot waste gases coming from the core of the machine." icon_state = "waste_output_off" icon_state_open = "waste_output_open" @@ -96,7 +96,7 @@ circuit = /obj/item/circuitboard/machine/HFR_waste_output /obj/machinery/atmospherics/components/unary/hypertorus/moderator_input - name = "HFR moderator input port" + name = "\improper HFR moderator input port" desc = "Moderator port for the Hypertorus Fusion Reactor, designed to move gases inside the machine to cool and control the flow of the reaction." icon_state = "moderator_input_off" icon_state_open = "moderator_input_open" @@ -149,7 +149,7 @@ return ..() /obj/machinery/hypertorus/interface - name = "HFR interface" + name = "\improper HFR interface" desc = "Interface for the HFR to control the flow of the reaction." icon_state = "interface_off" circuit = /obj/item/circuitboard/machine/HFR_interface @@ -395,7 +395,7 @@ . = TRUE /obj/machinery/hypertorus/corner - name = "HFR corner" + name = "\improper HFR corner" desc = "Structural piece of the machine." icon_state = "corner_off" circuit = /obj/item/circuitboard/machine/HFR_corner @@ -429,7 +429,7 @@ use more advanced guides to understando how the various gases will act as moderators." /obj/item/hfr_box - name = "HFR box" + name = "\improper HFR box" desc = "If you see this, call the police." icon = 'icons/obj/machines/atmospherics/hypertorus.dmi' icon_state = "error" @@ -439,39 +439,39 @@ var/part_path /obj/item/hfr_box/corner - name = "HFR box corner" + name = "\improper HFR box corner" desc = "Place this as the corner of your 3x3 multiblock fusion reactor" icon_state = "box_corner" box_type = "corner" part_path = /obj/machinery/hypertorus/corner /obj/item/hfr_box/body - name = "HFR box body" + name = "\improper HFR box body" desc = "Place this on the sides of the core box of your 3x3 multiblock fusion reactor" box_type = "body" icon_state = "box_body" /obj/item/hfr_box/body/fuel_input - name = "HFR box fuel input" + name = "\improper HFR box fuel input" icon_state = "box_fuel" part_path = /obj/machinery/atmospherics/components/unary/hypertorus/fuel_input /obj/item/hfr_box/body/moderator_input - name = "HFR box moderator input" + name = "\improper HFR box moderator input" icon_state = "box_moderator" part_path = /obj/machinery/atmospherics/components/unary/hypertorus/moderator_input /obj/item/hfr_box/body/waste_output - name = "HFR box waste output" + name = "\improper HFR box waste output" icon_state = "box_waste" part_path = /obj/machinery/atmospherics/components/unary/hypertorus/waste_output /obj/item/hfr_box/body/interface - name = "HFR box interface" + name = "\improper HFR box interface" part_path = /obj/machinery/hypertorus/interface /obj/item/hfr_box/core - name = "HFR box core" + name = "\improper HFR box core" desc = "Activate this with a multitool to deploy the full machine after setting up the other boxes" icon_state = "box_core" box_type = "core" diff --git a/code/modules/atmospherics/machinery/components/gas_recipe_machines/atmos_machines_recipes.dm b/code/modules/atmospherics/machinery/components/gas_recipe_machines/atmos_machines_recipes.dm index 13c872199dc0..8e94efa1a943 100644 --- a/code/modules/atmospherics/machinery/components/gas_recipe_machines/atmos_machines_recipes.dm +++ b/code/modules/atmospherics/machinery/components/gas_recipe_machines/atmos_machines_recipes.dm @@ -38,7 +38,7 @@ GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list()) /datum/gas_recipe/crystallizer/hypern_crystalium id = "hyper_crystalium" - name = "Hypernoblium Crystal" + name = "Hyper-Noblium Crystal" min_temp = 3 max_temp = 250 energy_release = -250000 @@ -47,7 +47,7 @@ GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list()) /datum/gas_recipe/crystallizer/metallic_hydrogen id = "metal_h" - name = "Metallic hydrogen" + name = "Metallic Hydrogen" min_temp = 50000 max_temp = 150000 energy_release = -2500000 @@ -56,7 +56,7 @@ GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list()) /datum/gas_recipe/crystallizer/healium_grenade id = "healium_g" - name = "Healium crystal" + name = "Healium Crystal" min_temp = 200 max_temp = 400 energy_release = -2000000 @@ -65,7 +65,7 @@ GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list()) /datum/gas_recipe/crystallizer/proto_nitrate_grenade id = "proto_nitrate_g" - name = "Proto nitrate crystal" + name = "Proto-Nitrate Crystal" min_temp = 200 max_temp = 400 energy_release = 1500000 @@ -74,7 +74,7 @@ GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list()) /datum/gas_recipe/crystallizer/hot_ice id = "hot_ice" - name = "Hot ice" + name = "Hot Ice" min_temp = 15 max_temp = 35 energy_release = -3000000 @@ -83,7 +83,7 @@ GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list()) /datum/gas_recipe/crystallizer/ammonia_crystal id = "ammonia_crystal" - name = "Ammonia crystal" + name = "Ammonia Crystal" min_temp = 200 max_temp = 240 energy_release = 950000 @@ -92,7 +92,7 @@ GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list()) /datum/gas_recipe/crystallizer/shard id = "crystal_shard" - name = "Supermatter crystal shard" + name = "Supermatter Crystal Shard" min_temp = 10 max_temp = 20 energy_release = 3500000 @@ -102,7 +102,7 @@ GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list()) /datum/gas_recipe/crystallizer/n2o_crystal id = "n2o_crystal" - name = "Nitrous oxide crystal" + name = "Nitrous Oxide Crystal" min_temp = 50 max_temp = 350 energy_release = 3500000 @@ -120,7 +120,7 @@ GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list()) /datum/gas_recipe/crystallizer/plasma_sheet id = "plasma_sheet" - name = "Plasma sheet" + name = "Solidified Plasma" min_temp = 10 max_temp = 20 energy_release = 3500000 @@ -138,7 +138,7 @@ GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list()) /datum/gas_recipe/crystallizer/zaukerite id = "zaukerite" - name = "Zaukerite sheet" + name = "Solidified Zauker" min_temp = 5 max_temp = 20 energy_release = 2900000 @@ -147,35 +147,35 @@ GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list()) /datum/gas_recipe/crystallizer/fuel_pellet id = "fuel_basic" - name = "standard fuel pellet" + name = "Standard Fuel Pellet" energy_release = -6000000 requirements = list(/datum/gas/oxygen = 50, /datum/gas/plasma = 100) products = list(/obj/item/fuel_pellet = 1) /datum/gas_recipe/crystallizer/fuel_pellet_advanced id = "fuel_advanced" - name = "advanced fuel pellet" + name = "Advanced Fuel Pellet" energy_release = -6000000 requirements = list(/datum/gas/tritium = 100, /datum/gas/hydrogen = 100) products = list(/obj/item/fuel_pellet/advanced = 1) /datum/gas_recipe/crystallizer/fuel_pellet_exotic id = "fuel_exotic" - name = "exotic fuel pellet" + name = "Exotic Fuel Pellet" energy_release = -6000000 requirements = list(/datum/gas/hypernoblium = 100, /datum/gas/nitrium = 100) products = list(/obj/item/fuel_pellet/exotic = 1) /datum/gas_recipe/crystallizer/crystal_foam id = "crystal_foam" - name = "Crystal foam grenade" + name = "Foam Crystal" energy_release = 140000 requirements = list(/datum/gas/carbon_dioxide = 150, /datum/gas/nitrous_oxide = 100, /datum/gas/water_vapor = 25) products = list(/obj/item/grenade/gas_crystal/crystal_foam = 1) /datum/gas_recipe/crystallizer/crystallized_nitrium id = "crystallized_nitrium" - name = "Nitrium crystal" + name = "Nitrium Crystal" min_temp = 10 max_temp = 25 energy_release = -45000 diff --git a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm index 7dfbae06e19f..389355e4890b 100644 --- a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm +++ b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm @@ -1,6 +1,6 @@ /obj/item/hypernoblium_crystal - name = "Hypernoblium Crystal" - desc = "Crystalized oxygen and hypernoblium stored in a bottle to pressureproof your clothes or stop reactions occuring in portable atmospheric devices." + name = "\improper Hyper-Noblium crystal" + desc = "Crystallized Oxygen and Hyper-Noblium stored in a bottle. Pressure-proofs clothing or stop reactions occurring in portable atmospheric devices." icon = 'icons/obj/pipes_n_cables/atmos.dmi' icon_state = "hypernoblium_crystal" var/uses = 1 @@ -37,3 +37,15 @@ if(uses <= 0) qdel(src) return ITEM_INTERACT_SUCCESS + +/obj/item/nitrium_crystal + name = "\improper Nitrium crystal" + desc = "A strange brown crystal that emits a foul smoke when chipped." + icon = 'icons/obj/pipes_n_cables/atmos.dmi' + icon_state = "nitrium_crystal" + var/cloud_size = 1 + +/obj/item/nitrium_crystal/attack_self(mob/user) + . = ..() + do_chem_smoke(cloud_size, src, get_turf(src), list(/datum/reagent/nitrium_low_metabolization = 3, /datum/reagent/nitrium_high_metabolization = 2)) + qdel(src) diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm index cbc0c31994e5..c1d282c4c6da 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm @@ -226,57 +226,75 @@ /obj/machinery/atmospherics/components/trinary/filter/atmos //Used for atmos waste loops on = TRUE icon_state = "filter_on-0" + /obj/machinery/atmospherics/components/trinary/filter/atmos/n2 name = "nitrogen filter" filter_type = list(/datum/gas/nitrogen) + /obj/machinery/atmospherics/components/trinary/filter/atmos/o2 name = "oxygen filter" filter_type = list(/datum/gas/oxygen) + /obj/machinery/atmospherics/components/trinary/filter/atmos/co2 name = "carbon dioxide filter" filter_type = list(/datum/gas/carbon_dioxide) + /obj/machinery/atmospherics/components/trinary/filter/atmos/n2o name = "nitrous oxide filter" filter_type = list(/datum/gas/nitrous_oxide) + /obj/machinery/atmospherics/components/trinary/filter/atmos/plasma name = "plasma filter" filter_type = list(/datum/gas/plasma) + /obj/machinery/atmospherics/components/trinary/filter/atmos/bz name = "bz filter" filter_type = list(/datum/gas/bz) + /obj/machinery/atmospherics/components/trinary/filter/atmos/freon name = "freon filter" filter_type = list(/datum/gas/freon) + /obj/machinery/atmospherics/components/trinary/filter/atmos/halon name = "halon filter" filter_type = list(/datum/gas/halon) + /obj/machinery/atmospherics/components/trinary/filter/atmos/healium name = "healium filter" filter_type = list(/datum/gas/healium) + /obj/machinery/atmospherics/components/trinary/filter/atmos/h2 name = "hydrogen filter" filter_type = list(/datum/gas/hydrogen) + /obj/machinery/atmospherics/components/trinary/filter/atmos/hypernoblium - name = "hypernoblium filter" + name = "hyper-noblium filter" filter_type = list(/datum/gas/hypernoblium) + /obj/machinery/atmospherics/components/trinary/filter/atmos/miasma name = "miasma filter" filter_type = list(/datum/gas/miasma) + /obj/machinery/atmospherics/components/trinary/filter/atmos/no2 name = "nitrium filter" filter_type = list(/datum/gas/nitrium) + /obj/machinery/atmospherics/components/trinary/filter/atmos/pluoxium name = "pluoxium filter" filter_type = list(/datum/gas/pluoxium) + /obj/machinery/atmospherics/components/trinary/filter/atmos/proto_nitrate name = "proto-nitrate filter" filter_type = list(/datum/gas/proto_nitrate) + /obj/machinery/atmospherics/components/trinary/filter/atmos/tritium name = "tritium filter" filter_type = list(/datum/gas/tritium) + /obj/machinery/atmospherics/components/trinary/filter/atmos/h2o name = "water vapor filter" filter_type = list(/datum/gas/water_vapor) + /obj/machinery/atmospherics/components/trinary/filter/atmos/zauker name = "zauker filter" filter_type = list(/datum/gas/zauker) @@ -286,71 +304,91 @@ filter_type = list(/datum/gas/helium) /obj/machinery/atmospherics/components/trinary/filter/atmos/antinoblium - name = "antinoblium filter" + name = "anti-noblium filter" filter_type = list(/datum/gas/antinoblium) /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped //This feels wrong, I know icon_state = "filter_on-0_f" flipped = TRUE + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/n2 name = "nitrogen filter" filter_type = list(/datum/gas/nitrogen) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/o2 name = "oxygen filter" filter_type = list(/datum/gas/oxygen) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/co2 name = "carbon dioxide filter" filter_type = list(/datum/gas/carbon_dioxide) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/n2o name = "nitrous oxide filter" filter_type = list(/datum/gas/nitrous_oxide) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/plasma name = "plasma filter" filter_type = list(/datum/gas/plasma) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/bz name = "bz filter" filter_type = list(/datum/gas/bz) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/freon name = "freon filter" filter_type = list(/datum/gas/freon) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/halon name = "halon filter" filter_type = list(/datum/gas/halon) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/healium name = "healium filter" filter_type = list(/datum/gas/healium) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/h2 name = "hydrogen filter" filter_type = list(/datum/gas/hydrogen) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/hypernoblium - name = "hypernoblium filter" + name = "hyper-noblium filter" filter_type = list(/datum/gas/hypernoblium) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/miasma name = "miasma filter" filter_type = list(/datum/gas/miasma) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/no2 name = "nitrium filter" filter_type = list(/datum/gas/nitrium) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/pluoxium name = "pluoxium filter" filter_type = list(/datum/gas/pluoxium) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/proto_nitrate name = "proto-nitrate filter" filter_type = list(/datum/gas/proto_nitrate) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/tritium name = "tritium filter" filter_type = list(/datum/gas/tritium) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/h2o name = "water vapor filter" filter_type = list(/datum/gas/water_vapor) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/zauker name = "zauker filter" filter_type = list(/datum/gas/zauker) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/helium name = "helium filter" filter_type = list(/datum/gas/helium) + /obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/antinoblium - name = "antinoblium filter" + name = "anti-noblium filter" filter_type = list(/datum/gas/antinoblium) // These two filter types have critical_machine flagged to on and thus causes the area they are in to be exempt from the Grid Check event. diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 211a86e5b8a6..b60ad71a9e7e 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -126,7 +126,7 @@ greyscale_colors = "#c6c0b5" /obj/machinery/portable_atmospherics/canister/antinoblium - name = "\improper Antinoblium canister" + name = "\improper Anti-Noblium canister" gas_type = /datum/gas/antinoblium filled = 1 icon_state = "/obj/machinery/portable_atmospherics/canister/antinoblium" @@ -213,7 +213,7 @@ greyscale_colors = "#e9ff5c#f4fce8" /obj/machinery/portable_atmospherics/canister/nitrous_oxide - name = "\improper Nitrous oxide canister" + name = "\improper Nitrous Oxide canister" gas_type = /datum/gas/nitrous_oxide icon_state = "/obj/machinery/portable_atmospherics/canister/nitrous_oxide" post_init_icon_state = "" @@ -229,7 +229,7 @@ greyscale_colors = "#7b4732" /obj/machinery/portable_atmospherics/canister/nob - name = "\improper Hyper-noblium canister" + name = "\improper Hyper-Noblium canister" gas_type = /datum/gas/hypernoblium icon_state = "/obj/machinery/portable_atmospherics/canister/nob" post_init_icon_state = "" @@ -253,7 +253,7 @@ greyscale_colors = "#2786e5" /obj/machinery/portable_atmospherics/canister/proto_nitrate - name = "\improper Proto Nitrate canister" + name = "\improper Proto-Nitrate canister" gas_type = /datum/gas/proto_nitrate filled = 1 icon_state = "/obj/machinery/portable_atmospherics/canister/proto_nitrate" diff --git a/code/modules/cargo/exports/large_objects.dm b/code/modules/cargo/exports/large_objects.dm index c3b773abc085..c196ba01327d 100644 --- a/code/modules/cargo/exports/large_objects.dm +++ b/code/modules/cargo/exports/large_objects.dm @@ -129,28 +129,13 @@ return 0 var/cached_moles = canister_mix.moles - var/static/list/gases_to_check = list( - /datum/gas/bz, - /datum/gas/nitrium, - /datum/gas/hypernoblium, - /datum/gas/miasma, - /datum/gas/tritium, - /datum/gas/pluoxium, - /datum/gas/freon, - /datum/gas/hydrogen, - /datum/gas/healium, - /datum/gas/proto_nitrate, - /datum/gas/zauker, - /datum/gas/helium, - /datum/gas/antinoblium, - /datum/gas/halon, - ) - var/worth = cost - for(var/gas_id in gases_to_check) - canister_mix.assert_gas(gas_id) - if(cached_moles[gas_id] > 0) - worth += get_gas_value(gas_id, cached_moles[gas_id]) + for(var/datum/gas/gas as anything in GLOB.meta_gas_info[META_GAS_ID]) + if(!(initial(gas.cargo_flags) & GAS_EXPORTABLE)) + continue + canister_mix.assert_gas(gas) + if(cached_moles[gas] > 0) + worth += get_gas_value(gas, cached_moles[gas]) if(worth > MAX_GAS_CREDITS) worth = MAX_GAS_CREDITS break diff --git a/code/modules/cargo/packs/materials.dm b/code/modules/cargo/packs/materials.dm index c3dfd52317d7..0ece1a647303 100644 --- a/code/modules/cargo/packs/materials.dm +++ b/code/modules/cargo/packs/materials.dm @@ -83,19 +83,18 @@ // This is the amount of moles in a default canister var/moleCount = (initial(fakeCanister.maximum_pressure) * initial(fakeCanister.filled)) * initial(fakeCanister.volume) / (R_IDEAL_GAS_EQUATION * T20C) - for(var/gasType in GLOB.meta_gas_info[META_GAS_ID]) - var/datum/gas/gas = gasType - var/name = initial(gas.name) - if(!initial(gas.purchaseable)) + for(var/datum/gas/gas as anything in GLOB.meta_gas_info[META_GAS_ID]) + if(!(initial(gas.cargo_flags) & GAS_PURCHASABLE)) continue var/datum/supply_pack/materials/pack = new - pack.name = "[name] Canister" - pack.desc = "Contains a canister of [name]." - if(initial(gas.dangerous)) + var/canname = initial(gas.name) + pack.name = "[canname] Canister" + pack.desc = "Contains a canister of [canname]." + if(initial(gas.cargo_flags) & GAS_DANGEROUS) pack.access = ACCESS_ATMOSPHERICS pack.access_view = ACCESS_ATMOSPHERICS - pack.crate_name = "[name] canister crate" - pack.id = "[type]([name])" + pack.crate_name = "[canname] canister crate" + pack.id = "[type]([canname])" pack.cost = cost + moleCount * initial(gas.base_value) * 1.6 pack.cost = CEILING(pack.cost, 10) diff --git a/code/modules/experisci/experiment/experiments.dm b/code/modules/experisci/experiment/experiments.dm index 89dbfe0abf68..4841ee16ccba 100644 --- a/code/modules/experisci/experiment/experiments.dm +++ b/code/modules/experisci/experiment/experiments.dm @@ -89,7 +89,7 @@ sanitized_misc = TRUE sanitized_reactions = TRUE require_all = FALSE - required_reactions = list(/datum/gas_reaction/h2fire, /datum/gas_reaction/tritfire) + required_reactions = list(/datum/gas_reaction/standard/h2fire, /datum/gas_reaction/standard/tritfire) /datum/experiment/ordnance/explosive/nobliumbomb name = "Noblium Explosives" @@ -99,7 +99,7 @@ experiment_proper = TRUE sanitized_misc = TRUE sanitized_reactions = TRUE - required_reactions = list(/datum/gas_reaction/nobliumformation) + required_reactions = list(/datum/gas_reaction/standard/nobliumformation) /datum/experiment/ordnance/explosive/pressurebomb name = "Reactionless Explosives" diff --git a/code/modules/modular_computers/file_system/programs/atmosscan.dm b/code/modules/modular_computers/file_system/programs/atmosscan.dm index 7e2608728597..f726a079620c 100644 --- a/code/modules/modular_computers/file_system/programs/atmosscan.dm +++ b/code/modules/modular_computers/file_system/programs/atmosscan.dm @@ -63,7 +63,6 @@ var/datum/gas_mixture/air = turf?.return_air() data["gasmixes"] = list(gas_mixture_parser(air, "Location Reading")) if(ATMOZPHERE_SCAN_CLICK) - LAZYINITLIST(last_gasmix_data) data["gasmixes"] = last_gasmix_data return data diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index efb3aef85613..1da2a474651b 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -236,7 +236,7 @@ /obj/item/stock_parts/power_store/cell/crystal_cell name = "crystal power cell" - desc = "A very high power cell made from crystallized plasma" + desc = "A high power cell made from crystallized plasma." icon_state = "crystal_cell" maxcharge = STANDARD_CELL_CHARGE * 50 chargerate = 0 diff --git a/code/modules/research/ordnance/doppler_array.dm b/code/modules/research/ordnance/doppler_array.dm index 1c59c654c70d..3bdda576c05d 100644 --- a/code/modules/research/ordnance/doppler_array.dm +++ b/code/modules/research/ordnance/doppler_array.dm @@ -283,7 +283,7 @@ // Make sure the list is indexed first. if(reaction_data.len) for (var/path in reaction_data[TANK_RESULTS_REACTION]) - var/datum/gas_reaction/reaction_path = path + var/datum/gas_reaction/standard/reaction_path = path record_data["reaction_results"] += initial(reaction_path.name) if(TANK_MERGE_OVERPRESSURE in reaction_data[TANK_RESULTS_MISC]) record_data["reaction_results"] += "Tank overpressurized before reaction" diff --git a/maplestation.dme b/maplestation.dme index 7cdb4f3c8a20..5ada061749eb 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -2381,7 +2381,6 @@ #include "code\game\objects\items\maintenance_loot.dm" #include "code\game\objects\items\manuals.dm" #include "code\game\objects\items\mop.dm" -#include "code\game\objects\items\nitrium_crystals.dm" #include "code\game\objects\items\paint.dm" #include "code\game\objects\items\paiwire.dm" #include "code\game\objects\items\pet_carrier.dm" diff --git a/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx b/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx index 749c9c87e957..14794037c6cf 100644 --- a/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx +++ b/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx @@ -25,154 +25,177 @@ type Chamber = { output_info?: { active: boolean; amount: number }; }; +type Data = { + chambers: Chamber[]; + maxInput: number; + maxOutput: number; + reconnecting: boolean; + control: boolean; + defaultGas: string | null; +}; + export const AtmosControlConsole = (props) => { - const { act, data } = useBackend<{ - chambers: Chamber[]; - maxInput: number; - maxOutput: number; - reconnecting: boolean; - control: boolean; - }>(); - const chambers = data.chambers || []; + const { act, data } = useBackend(); + const { + chambers = [], + maxInput, + maxOutput, + reconnecting, + control, + defaultGas, + } = data; const [chamberId, setChamberId] = useState(chambers[0]?.id); const selectedChamber = chambers.length === 1 ? chambers[0] : chambers.find((chamber) => chamber.id === chamberId); const [setActiveGasId, setActiveReactionId] = atmosHandbookHooks(); + + if (defaultGas) { + setActiveGasId(defaultGas); + } + return ( - + - {chambers.length > 1 && ( -
- chamber.name)} - selected={selectedChamber?.name} - onSelected={(value) => - setChamberId( - chambers.find((chamber) => chamber.name === value)?.id || - chambers[0].id, + + + {chambers.length > 1 && ( +
+ chamber.name)} + selected={selectedChamber?.name} + onSelected={(value) => + setChamberId( + chambers.find((chamber) => chamber.name === value)?.id || + chambers[0].id, + ) + } + /> +
+ )} +
act('reconnect')} + /> ) } - /> -
- )} -
act('reconnect')} - /> - ) - } - > - {!!selectedChamber && !!selectedChamber.gasmix ? ( - - ) : ( - {'No Sensors Detected!'} - )} -
- {!!selectedChamber && !!data.control && ( -
- - - {selectedChamber.input_info ? ( - - -
- )} - + > + {selectedChamber?.gasmix ? ( + + ) : ( + {'No Sensors Detected!'} + )} +
+ {!!selectedChamber && !!control && ( +
+ + + {selectedChamber.input_info ? ( + + +
+ )} + + + + +
); diff --git a/tgui/packages/tgui/interfaces/GasAnalyzer.tsx b/tgui/packages/tgui/interfaces/GasAnalyzer.tsx index 1528d295e724..aca317eb9e32 100644 --- a/tgui/packages/tgui/interfaces/GasAnalyzer.tsx +++ b/tgui/packages/tgui/interfaces/GasAnalyzer.tsx @@ -1,4 +1,4 @@ -import { Section } from 'tgui-core/components'; +import { Section, Stack } from 'tgui-core/components'; import { useBackend } from '../backend'; import { Window } from '../layouts'; @@ -10,33 +10,37 @@ import type { Gasmix } from './common/GasmixParser'; import { GasmixParser } from './common/GasmixParser'; export type GasAnalyzerData = { - gasmixes: Gasmix[]; + gasmixes: Gasmix[] | null; }; -export const GasAnalyzerContent = (props) => { - const { act, data } = useBackend(); +export const GasAnalyzerContent = () => { + const { data } = useBackend(); const { gasmixes } = data; const [setActiveGasId, setActiveReactionId] = atmosHandbookHooks(); return ( - <> - {gasmixes.map((gasmix) => ( -
- -
- ))} - - + + + {gasmixes?.map((gasmix) => ( +
+ +
+ ))} +
+ + + +
); }; -export const GasAnalyzer = (props) => { +export const GasAnalyzer = () => { return ( - - + + diff --git a/tgui/packages/tgui/interfaces/common/AtmosHandbook.tsx b/tgui/packages/tgui/interfaces/common/AtmosHandbook.tsx index 8991f40389c5..caa29552fe2e 100644 --- a/tgui/packages/tgui/interfaces/common/AtmosHandbook.tsx +++ b/tgui/packages/tgui/interfaces/common/AtmosHandbook.tsx @@ -1,16 +1,16 @@ import { atom, useAtom } from 'jotai'; import { type ReactNode, useState } from 'react'; import { - Box, Button, Flex, Input, LabeledList, Section, Stack, + Tabs, Tooltip, } from 'tgui-core/components'; - +import { useFuzzySearch } from 'tgui-core/fuzzysearch'; import { useBackend } from '../../backend'; /** @@ -44,41 +44,61 @@ type Gas = { name: string; description: string; specific_heat: number; + export_value: number; reactions: Record | []; }; type GasSearchProps = { title: ReactNode; + placeholder?: string; + inputText?: string; + onEnter: (inputValue: string) => void; onChange: (inputValue: string) => void; activeInput: boolean; setActiveInput: (toggle: boolean) => void; }; +enum HandbookTab { + Gases = 1, + Reactions = 2, +} + type Data = { gasInfo: Gas[]; reactionInfo: Reaction[]; + moneySymbol: string; + moneyName: string; }; const activeGasAtom = atom(''); const activeReactionAtom = atom(''); function GasSearchBar(props: GasSearchProps) { - const { title, onChange, activeInput, setActiveInput } = props; + const { + title, + placeholder, + inputText, + onChange, + onEnter, + activeInput, + setActiveInput, + } = props; return ( - {activeInput ? ( - { - setActiveInput(false); - onChange(value); - }} - /> - ) : ( - title - )} + { + setActiveInput(false); + onEnter(value); + }} + onChange={(value) => { + onChange(value); + }} + /> + + ))} + + + + )} + + ) : ( + + No Gas Selected + + )} + + + +
+ + {(query ? results : gasInfo) + .sort((a, b) => (a.name > b.name ? 1 : -1)) + .map((gas) => ( + + + + ))} + +
+
+ ); } -function ReactionHandbook(props) { +type ReactionHandbookProps = { + setTab?: (tab: number) => void; +}; + +function ReactionHandbook(props: ReactionHandbookProps) { const { data } = useBackend(); const { reactionInfo } = data; + const { setTab } = props; const [activeGasId, setActiveGasId] = useAtom(activeGasAtom); const [activeReactionId, setActiveReactionId] = useAtom(activeReactionAtom); const [reactionActiveInput, setReactionActiveInput] = useState(false); @@ -145,84 +313,149 @@ function ReactionHandbook(props) { (reaction) => reaction.id === activeReactionId, ); + const { query, setQuery, results } = useFuzzySearch({ + searchArray: reactionInfo, + matchStrategy: 'smart', + getSearchString: (item) => item.name, + }); + return (
- setActiveReactionId( - reactionInfo.find((reaction) => - reaction.name.toLowerCase().startsWith(keyword.toLowerCase()), - )?.id || '', - ) - } + title="Reaction Lookup" + onChange={(keyword) => setQuery(keyword)} + onEnter={(keyword) => { + const foundReaction = reactionInfo.find((reaction) => + reaction.name.toLowerCase().startsWith(keyword.toLowerCase()), + ); + if (foundReaction) { + setActiveReactionId(foundReaction.id); + } + }} + inputText={query} activeInput={reactionActiveInput} setActiveInput={setReactionActiveInput} /> } > - {relevantReaction && ( - <> - {relevantReaction.description} - {'Relevant Factors:'} - - {relevantReaction.factors.map((factor) => ( - setActiveGasId(String(factor.factor_id))} - content={factor.factor_name} - /> - ) : factor.tooltip ? ( - - - + + + {relevantReaction ? ( + <> + + > {relevantReaction.name} + + + {relevantReaction.description} + + +
+ + {relevantReaction.factors.map((factor) => ( + { + setActiveGasId(String(factor.factor_id)); + setTab?.(HandbookTab.Gases); + }} + fluid + > + {factor.factor_name} + + ) : factor.tooltip ? ( + + + + {`${factor.factor_name}:`} + + + + ) : ( + factor.factor_name + ) + } > - {`${factor.factor_name}:`} - - - - ) : ( - factor.factor_name - ) - } + {factor.desc} + + ))} + +
+
+ + ) : ( + - {factor.desc} -
- ))} -
- - )} + No reaction selected + + )} + + + +
+ + {(query ? results : reactionInfo) + .sort((a, b) => (a.name > b.name ? 1 : -1)) + .map((reaction) => ( + + + + ))} + +
+
+
); } -type HandbookContentProps = { - vertical?: boolean; -}; +export function AtmosHandbookContent() { + const [tab, setTab] = useState(HandbookTab.Gases); -export function AtmosHandbookContent(props: HandbookContentProps) { - return props.vertical ? ( - <> - - - - ) : ( - - - + return ( + + + + setTab(HandbookTab.Gases)} + > + Gases + + setTab(HandbookTab.Reactions)} + > + Reactions + + - + {tab === HandbookTab.Reactions ? ( + + ) : ( + + )} ); diff --git a/tgui/packages/tgui/interfaces/common/GasmixParser.tsx b/tgui/packages/tgui/interfaces/common/GasmixParser.tsx index 2987c64e8890..892022eb97e3 100644 --- a/tgui/packages/tgui/interfaces/common/GasmixParser.tsx +++ b/tgui/packages/tgui/interfaces/common/GasmixParser.tsx @@ -1,13 +1,17 @@ -import { Box, Button, LabeledList } from 'tgui-core/components'; +import { Box, Button, LabeledList, Stack } from 'tgui-core/components'; + +type GasEntry = [string, string, number]; // ID, name, and amount. + +type ReactionEntry = [string, string, number]; // ID, name, and amount. export type Gasmix = { name?: string; - gases: [string, string, number][]; // ID, name, and amount. + gases: GasEntry[]; // ID, name, and amount. temperature: number; volume: number; pressure: number; total_moles: number; - reactions: [string, string, number][]; // ID, name, and amount. + reactions: ReactionEntry[]; // ID, name, and amount. reference: string; }; @@ -37,101 +41,134 @@ export const GasmixParser = (props: GasmixParserProps) => { const { gases, temperature, volume, pressure, total_moles, reactions } = gasmix; - return !total_moles ? ( - - No Gas Detected! - - ) : ( - - {gases.map((gas) => ( - gasesOnClick(gas[0])} /> - ) : ( - gas[1] - ) - } - key={gas[1]} - > - {gas[2].toFixed(2) + - ' mol (' + - ((gas[2] / total_moles) * 100).toFixed(2) + - ' %)'} - - ))} - temperatureOnClick()} - /> - ) : ( - 'Temperature' - ) - } - > - {`${total_moles ? temperature.toFixed(2) : '-'} K`} - - volumeOnClick()} /> - ) : ( - 'Volume' - ) - } - > - {`${total_moles ? volume.toFixed(2) : '-'} L`} - - pressureOnClick()} /> - ) : ( - 'Pressure' - ) - } - > - {`${total_moles ? pressure.toFixed(2) : '-'} kPa`} - - {detailedReactions ? ( - reactions.map((reaction) => ( - reactionOnClick(reaction[0])} - /> - ) : ( - reaction[1] - ) - } - > - {reaction[2]} - - )) - ) : ( - - {reactions.length - ? reactions.map((reaction, index) => - reactionOnClick ? ( - + if (total_moles <= 0) { + return ( + + No Gas Detected! + + ); + } + + return ( + + + + + {gases.map((gas) => ( + gasesOnClick(gas[0])}> + {gas[1]} + + ) : ( + gas[1] + ) + } + key={gas[1]} + > + {gas[2].toFixed(2) + + ' mol (' + + ((gas[2] / total_moles) * 100).toFixed(2) + + ' %)'} + + ))} + + + + reactionOnClick(reaction[0])} + content={'Temperature'} + onClick={() => temperatureOnClick()} /> - - ) : ( -
{reaction[1]}
- ), - ) - : 'No reactions detected'} -
+ ) : ( + 'Temperature' + ) + } + > + {`${total_moles ? temperature.toFixed(2) : '-'} K`} + + volumeOnClick()} + /> + ) : ( + 'Volume' + ) + } + > + {`${total_moles ? volume.toFixed(2) : '-'} L`} + + pressureOnClick()} + /> + ) : ( + 'Pressure' + ) + } + > + {`${total_moles ? pressure.toFixed(2) : '-'} kPa`} + +
+
+
+ + {!!reactions.length && ( + + + + Active Reactions + + {detailedReactions ? ( + + {reactions.map((reaction) => ( + + {reactionOnClick ? ( + + ) : ( + reaction[1] + )} + - {reaction[2]} + + ))} + + ) : ( + + + {reactions.map((reaction) => ( + + {reactionOnClick ? ( + + ) : ( + reaction[1] + )} + + ))} + + + )} + + )} - + ); }; From 55464fda553bb63374932fb2b62cb6d7741c8e94 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Mon, 7 Sep 2026 22:43:23 -0500 Subject: [PATCH 13/15] Fixes --- code/__DEFINES/antagonists.dm | 2 + code/__DEFINES/economy.dm | 15 +++ code/__DEFINES/maths.dm | 3 + code/__DEFINES/reactions.dm | 18 ++- code/__DEFINES/religion.dm | 7 + code/datums/brain_damage/imaginary_friend.dm | 18 +-- .../datums/components/crafting/atmospheric.dm | 11 -- code/datums/components/food/germ_sensitive.dm | 4 +- .../advance/floor_diseases/gastritium.dm | 32 ++--- code/datums/elements/atmos_requirements.dm | 1 - code/game/objects/structures/toiletbong.dm | 2 +- .../components/electrolyzer/electrolyzer.dm | 2 +- .../religion/dreams/banish_nightmare.dm | 2 +- .../religion/dreams/dream_projection.dm | 17 +-- code/modules/religion/dreams/slumber_party.dm | 16 +-- code/modules/religion/religion_sects.dm | 121 ++++++++++-------- .../surgery/organs/internal/heart/_heart.dm | 63 +++++++++ maplestation.dme | 1 + 18 files changed, 217 insertions(+), 118 deletions(-) diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm index de4123eaff0c..a50c637263b2 100644 --- a/code/__DEFINES/antagonists.dm +++ b/code/__DEFINES/antagonists.dm @@ -212,6 +212,8 @@ GLOBAL_LIST_INIT(ai_employers, list( /// Checks if the given mob is a blood cultist #define IS_CULTIST(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/cult) || mob?.mind?.has_antag_datum(/datum/antagonist/advanced_cult)) // NON-MODULE CHANGE +/// Gets the antagonist datum for a blood cultist, or null if they are not one. +#define GET_CULTIST(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/cult)) // NON-MODULE CHANGE /// Checks if the given mob is a changeling #define IS_CHANGELING(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/changeling)) diff --git a/code/__DEFINES/economy.dm b/code/__DEFINES/economy.dm index 344b575aa41d..1415dd34d697 100644 --- a/code/__DEFINES/economy.dm +++ b/code/__DEFINES/economy.dm @@ -75,6 +75,21 @@ #define MARKET_PROFIT_MODIFIER 0.8 //We don't make every sale a 1-1 of the actual buy price value, like with real life taxes and to encourage more smart trades +// Fair warning that these defines at present are not used in all tgui, static descriptions, or any varible names or comments +/// The symbol for the default type of money used in the code. +#define MONEY_SYMBOL "cr" +/// The name for the default type of money used in the code. +#define MONEY_NAME "credits" +#define MONEY_NAME_SINGULAR "credit" +#define MONEY_NAME_CAPITALIZED "Credits" +// Due to the ways macros work, I cant just directly use credit\s. +// You will need to verify there is no loose use cases of credit\s. +// As of present there is none left floating around. +#define MONEY_NAME_AUTOPURAL(amount) "credit[##amount == 1 ? "" : "s"]" + +#define MONEY_MINING_SYMBOL "mp" +#define MONEY_BITRUNNING_SYMBOL "np" + /// Create quantity subtypes for stock market datums. #define MARKET_QUANTITY_HELPERS(path) ##path/one {\ amount = 1; \ diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm index aca45241f1cc..19598ad72178 100644 --- a/code/__DEFINES/maths.dm +++ b/code/__DEFINES/maths.dm @@ -55,6 +55,9 @@ /// Increments a value and wraps it if it exceeds some value. Can be used to circularly iterate through a list through `idx = WRAP_UP(idx, length_of_list)`. #define WRAP_UP(val, max) (((val) % (max)) + 1) +/// Helper that increments and wraps the passed in number when it hits the integer limit +#define WRAP_UID(val) WRAP_UP(val, SHORT_REAL_LIMIT - 1) + // Real modulus that handles decimals #define MODULUS(x, y) ( (x) - FLOOR(x, y)) diff --git a/code/__DEFINES/reactions.dm b/code/__DEFINES/reactions.dm index f51271d0e22b..80f70a5001be 100644 --- a/code/__DEFINES/reactions.dm +++ b/code/__DEFINES/reactions.dm @@ -174,17 +174,23 @@ /// The number of moles of hyper-noblium required to prevent reactions. #define REACTION_OPPRESSION_THRESHOLD 5 +/// Minimum temperature required for hypernoblium to prevent reactions. +#define REACTION_OPPRESSION_MIN_TEMP 20 // Halon: -/// The minimum temperature required for halon to form from tritium and BZ. -#define HALON_FORMATION_MIN_TEMPERATURE 30 -/// The maximum temperature required for halon to form from tritium and BZ. -#define HALON_FORMATION_MAX_TEMPERATURE 55 -/// The amount of energy 4.25 moles of halon forming from tritium and BZ releases. -#define HALON_FORMATION_ENERGY 300 +/// Energy released per mole of BZ consumed during halon formation. +#define HALON_FORMATION_ENERGY 91232.1 /// How much energy a mole of halon combusting consumes. #define HALON_COMBUSTION_ENERGY 2500 +/// The minimum temperature required for halon to combust. +#define HALON_COMBUSTION_MIN_TEMPERATURE (T0C + 70) +/// The temperature scale for halon combustion reaction rate. +#define HALON_COMBUSTION_TEMPERATURE_SCALE (FIRE_MINIMUM_TEMPERATURE_TO_EXIST * 10) +/// Amount of halon required to be consumed in order to release resin. This is always possible as long as there's enough gas. +#define HALON_COMBUSTION_MINIMUM_RESIN_MOLES (0.99 * HALON_COMBUSTION_MIN_TEMPERATURE / HALON_COMBUSTION_TEMPERATURE_SCALE) +/// The volume of the resin foam fluid when halon combusts, in turfs. +#define HALON_COMBUSTION_RESIN_VOLUME 1 // Healium: /// The minimum temperature healium can form from BZ and freon at. diff --git a/code/__DEFINES/religion.dm b/code/__DEFINES/religion.dm index 5186f5c48e29..d3fb3d36e410 100644 --- a/code/__DEFINES/religion.dm +++ b/code/__DEFINES/religion.dm @@ -55,6 +55,13 @@ ///brands the sinner #define PUNISHMENT_BRAND "brand" +/// Failed to bless the target, beat them over the head +#define BLESSING_FAILED "failed" +/// Blessed unsuccessfully, no limbs to heal, robotic limbs, etc +#define BLESSING_IGNORED "ignored" +/// Blessed successfully by healing or whatever +#define BLESSING_SUCCESS "success" + ///The rite will automatically delete itself by the religious tool calling it after it's invoked. #define RITE_AUTO_DELETE (1<<0) ///The rite can be performed multiple times with a religious tool, so don't delete/null it. diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index ea07db402fc1..1841673b07ac 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -76,7 +76,7 @@ see_invisible = SEE_INVISIBLE_LIVING invisibility = INVISIBILITY_MAXIMUM has_emotes = TRUE - var/icon/human_image + var/icon/human_icon var/image/current_image var/hidden = FALSE var/move_delay = 0 @@ -129,7 +129,7 @@ gender = pick(MALE, FEMALE) real_name = generate_random_name_species_based(gender, FALSE, /datum/species/human) name = real_name - human_image = get_flat_human_icon(null, pick(SSjob.joinable_occupations)) + human_icon = get_flat_human_icon(null, pick(SSjob.joinable_occupations)) Show() /** @@ -160,11 +160,11 @@ appearance_job = SSjob.get_job(JOB_ASSISTANT) if(istype(appearance_job, /datum/job/ai)) - human_image = icon('icons/mob/silicon/ai.dmi', icon_state = resolve_ai_icon(appearance_from_prefs.read_preference(/datum/preference/choiced/ai_core_display)), dir = SOUTH) + human_icon = icon('icons/mob/silicon/ai.dmi', icon_state = resolve_ai_icon(appearance_from_prefs.read_preference(/datum/preference/choiced/ai_core_display)), dir = SOUTH) else if(istype(appearance_job, /datum/job/cyborg)) - human_image = icon('icons/mob/silicon/robots.dmi', icon_state = "robot") + human_icon = icon('icons/mob/silicon/robots.dmi', icon_state = "robot") else - human_image = get_flat_human_icon(null, appearance_job, appearance_from_prefs) + human_icon = get_flat_human_icon(null, appearance_job, appearance_from_prefs) Show() /// Returns all member clients of the imaginary_group @@ -184,7 +184,7 @@ remove_image_from_clients(current_image, friend_clients) //Generate image from the static icon and the current dir - current_image = image(human_image, src, , MOB_LAYER, dir=src.dir) + current_image = image(human_icon, src, , MOB_LAYER, dir=src.dir) current_image.override = TRUE current_image.name = name if(hidden) @@ -198,9 +198,9 @@ /mob/camera/imaginary_friend/Destroy() if(owner?.client) - owner.client.images.Remove(human_image) + owner.client.images.Remove(human_icon) if(client) - client.images.Remove(human_image) + client.images.Remove(human_icon) owner.imaginary_group -= src return ..() @@ -529,4 +529,4 @@ /mob/camera/imaginary_friend/trapped/setup_friend() real_name = "[owner.real_name]?" name = real_name - human_image = icon('icons/mob/simple/lavaland/lavaland_monsters.dmi', icon_state = "curseblob") + human_icon = icon('icons/mob/simple/lavaland/lavaland_monsters.dmi', icon_state = "curseblob") diff --git a/code/datums/components/crafting/atmospheric.dm b/code/datums/components/crafting/atmospheric.dm index 1d1e82e11c2e..12bdd0e663ee 100644 --- a/code/datums/components/crafting/atmospheric.dm +++ b/code/datums/components/crafting/atmospheric.dm @@ -1,14 +1,3 @@ -/datum/crafting_recipe/bluespace_vendor_mount - name = "Bluespace Vendor Wall Mount" - result = /obj/item/wallframe/bluespace_vendor_mount - time = 6 SECONDS - reqs = list( - /obj/item/stack/sheet/iron = 15, - /obj/item/stack/sheet/glass = 10, - /obj/item/stack/cable_coil = 10, - ) - category = CAT_ATMOSPHERIC - /datum/crafting_recipe/pipe name = "Smart pipe fitting" tool_behaviors = list(TOOL_WRENCH) diff --git a/code/datums/components/food/germ_sensitive.dm b/code/datums/components/food/germ_sensitive.dm index 36992b9e1169..cd8eadc01d43 100644 --- a/code/datums/components/food/germ_sensitive.dm +++ b/code/datums/components/food/germ_sensitive.dm @@ -5,9 +5,9 @@ /// Possible diseases GLOBAL_LIST_INIT(floor_diseases, list( - /datum/disease/advance/nebula_nausea = 2, - /datum/disease/advance/gastritium = 2, /datum/disease/advance/carpellosis = 1, + /datum/disease/advance/nebula_nausea = 2, + /datum/disease/gastritium = 2, )) /// Makes items infective if left on floor, also sending corresponding signals to parent diff --git a/code/datums/diseases/advance/floor_diseases/gastritium.dm b/code/datums/diseases/advance/floor_diseases/gastritium.dm index 65e589527948..be7573e9c87e 100644 --- a/code/datums/diseases/advance/floor_diseases/gastritium.dm +++ b/code/datums/diseases/advance/floor_diseases/gastritium.dm @@ -1,10 +1,12 @@ /// Caused by dirty food. Makes you burp out Tritium, sometimes burning hot! -/datum/disease/advance/gastritium +/datum/disease/gastritium name = "Gastritium" desc = "If left untreated, may manifest in severe Tritium heartburn." - form = "Infection" + form = "Bacteria" agent = "Atmobacter Polyri" - cures = list(/datum/reagent/firefighting_foam) + cure_text = /datum/reagent/consumable/milk::name + spread_text = "None" + cures = list(/datum/reagent/consumable/milk) viable_mobtypes = list(/mob/living/carbon/human) spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS severity = DISEASE_SEVERITY_HARMFUL @@ -13,18 +15,6 @@ /// The chance of burped out tritium to be hot during max stage var/tritium_burp_hot_chance = 10 -/datum/disease/advance/gastritium/New() - symptoms = list(new/datum/symptom/fever) - ..() - -/datum/disease/advance/gastritium/generate_cure() - cures = list(pick(cures)) - var/datum/reagent/cure = GLOB.chemical_reagents_list[cures[1]] - cure_text = cure.name - -/datum/disease/advance/gastritium/GetDiseaseID() - return "[type]" - /datum/disease/gastritium/stage_act(seconds_per_tick) . = ..() if(!.) @@ -49,16 +39,20 @@ to_chat(affected_mob, span_warning("You feel like you're about to delam...")) else if(SPT_PROB(1, seconds_per_tick)) tritium_burp(hot_chance = TRUE) - var/change_limit = max(affected_mob.get_body_temp_heat_damage_limit() - 5 - affected_mob.get_body_temp_normal(apply_change=FALSE), 0) - affected_mob.add_body_temperature_change("gastritium", min(15 * stage, change_limit)) + + affected_mob.add_homeostasis_level(type, affected_mob.bodytemp_heat_damage_limit - 5 KELVIN, 0.5) + +/datum/disease/gastritium/remove_disease() + affected_mob.remove_homeostasis_level(type) + return ..() /datum/disease/gastritium/proc/tritium_burp(hot_chance = FALSE) var/datum/gas_mixture/burp = new burp.set_gas(/datum/gas/tritium, MOLES_GAS_VISIBLE) - burp.temperature = affected_mob.bodytemperature + burp.temperature = affected_mob.body_temperature if(hot_chance && prob(tritium_burp_hot_chance)) burp.set_temperature(TRITIUM_MINIMUM_BURN_TEMPERATURE) - if(!IS_UNCONSCIOUS_OR_CRIT(affected_mob)) + if(affected_mob.stat == CONSCIOUS) to_chat(affected_mob, span_warning("Your throat feels hot!")) affected_mob.visible_message("burps out green gas.", visible_message_flags = EMOTE_MESSAGE) affected_mob.loc.assume_air(burp) diff --git a/code/datums/elements/atmos_requirements.dm b/code/datums/elements/atmos_requirements.dm index 6e11ac5004cf..378d12567678 100644 --- a/code/datums/elements/atmos_requirements.dm +++ b/code/datums/elements/atmos_requirements.dm @@ -48,7 +48,6 @@ var/open_turf_moles = open_turf.air.moles open_turf.air.assert_gases(/datum/gas/oxygen, /datum/gas/pluoxium, /datum/gas/nitrogen, /datum/gas/carbon_dioxide, /datum/gas/plasma) - var/list/return_gases = list() var/plas = open_turf_moles[/datum/gas/plasma] var/oxy = open_turf_moles[/datum/gas/oxygen] + (open_turf_moles[/datum/gas/pluoxium] * PLUOXIUM_PROPORTION) var/n2 = open_turf_moles[/datum/gas/nitrogen] diff --git a/code/game/objects/structures/toiletbong.dm b/code/game/objects/structures/toiletbong.dm index 610773f9a75f..8091da40b203 100644 --- a/code/game/objects/structures/toiletbong.dm +++ b/code/game/objects/structures/toiletbong.dm @@ -86,7 +86,7 @@ new /obj/item/stack/sheet/iron(get_turf(src)) var/obj/item/tank/internals/plasma/ptank = new /obj/item/tank/internals/plasma(get_turf(src)) ptank.air_contents.set_gas(/datum/gas/plasma, 0) - drop_custom_materials() + // drop_custom_materials() qdel(src) return TRUE diff --git a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm index 20a547c1fcbf..c8ae16b971e0 100644 --- a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm +++ b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm @@ -131,7 +131,7 @@ /obj/machinery/electrolyzer/proc/call_reactions(datum/gas_mixture/env) for(var/reaction in GLOB.electrolyzer_reactions) - var/datum/electrolyzer_reaction/current_reaction = GLOB.electrolyzer_reactions[reaction] + var/datum/gas_reaction/electrolyzer/current_reaction = GLOB.electrolyzer_reactions[reaction] if(!current_reaction.reaction_check(env)) continue diff --git a/code/modules/religion/dreams/banish_nightmare.dm b/code/modules/religion/dreams/banish_nightmare.dm index 9883c8253150..889ad2d5a824 100644 --- a/code/modules/religion/dreams/banish_nightmare.dm +++ b/code/modules/religion/dreams/banish_nightmare.dm @@ -91,7 +91,7 @@ /obj/item/organ/heart/evolved/sacred/dreamer/on_blocked() charges -= 1 addtimer(CALLBACK(src, PROC_REF(recharge)), 1 MINUTES) - playsound(owner, 'sound/effects/health/slowbeat.ogg', 80) + playsound(owner, 'sound/health/slowbeat.ogg', 80) /obj/item/organ/heart/evolved/sacred/dreamer/check_block() return charges > 0 diff --git a/code/modules/religion/dreams/dream_projection.dm b/code/modules/religion/dreams/dream_projection.dm index fe23726e3f7f..847673b8d53c 100644 --- a/code/modules/religion/dreams/dream_projection.dm +++ b/code/modules/religion/dreams/dream_projection.dm @@ -57,7 +57,7 @@ /// Target of the projection VAR_PRIVATE/mob/living/carbon/human/target /// Projection mob that the owner is put into - VAR_PRIVATE/mob/eye/imaginary_friend/dream_projection/projection + VAR_PRIVATE/mob/camera/imaginary_friend/dream_projection/projection /datum/status_effect/dream_projection/on_creation(mob/living/new_owner, mob/living/carbon/human/target) if(isnull(target)) @@ -90,7 +90,8 @@ projection.real_name = owner.real_name projection.gender = owner.gender projection.human_icon = getFlatIcon(owner) - projection.PossessByPlayer(owner.ckey) + // projection.PossessByPlayer(owner.ckey) + projection.ckey = owner.ckey projection.attach_to_owner(target) RegisterSignal(projection, COMSIG_QDELETING, PROC_REF(stop_projection)) @@ -150,30 +151,30 @@ owner.SetSleeping(10 SECONDS) qdel(src) -/mob/eye/imaginary_friend/dream_projection +/mob/camera/imaginary_friend/dream_projection name = "dream projection" -/mob/eye/imaginary_friend/dream_projection/Initialize(mapload) +/mob/camera/imaginary_friend/dream_projection/Initialize(mapload) . = ..() var/datum/action/innate/stop_projection/exit_action = new(src) exit_action.Grant(src) overlay_fullscreen("curse", /atom/movable/screen/fullscreen/curse, 1) // todo something more fitting? -/mob/eye/imaginary_friend/dream_projection/Login() +/mob/camera/imaginary_friend/dream_projection/Login() . = ..() client.eye = owner || src -/mob/eye/imaginary_friend/dream_projection/greet() +/mob/camera/imaginary_friend/dream_projection/greet() return -/mob/eye/imaginary_friend/dream_projection/verb/stop_projection() +/mob/camera/imaginary_friend/dream_projection/verb/stop_projection() set category = "IC" set name = "Stop Projection" set desc = "Stop astrally projecting and return to your body." qdel(src) -/mob/eye/imaginary_friend/dream_projection/attach_to_owner(mob/living/imaginary_friend_owner) +/mob/camera/imaginary_friend/dream_projection/attach_to_owner(mob/living/imaginary_friend_owner) . = ..() client?.eye = owner diff --git a/code/modules/religion/dreams/slumber_party.dm b/code/modules/religion/dreams/slumber_party.dm index a3f258d692ae..5c7acad5f30a 100644 --- a/code/modules/religion/dreams/slumber_party.dm +++ b/code/modules/religion/dreams/slumber_party.dm @@ -36,15 +36,13 @@ return ..() /datum/status_effect/slumber_party/on_apply() - if(IS_CULTIST(owner)) - var/datum/antagonist/cult/cultist = GET_CULTIST(owner) - if(cultist.cult_team?.cult_ascendent) - return FALSE - - if(IS_HERETIC(owner)) - var/datum/antagonist/heretic/heretic = GET_HERETIC(owner) - if(heretic.ascended) - return FALSE + var/datum/antagonist/cult/cultist = GET_CULTIST(owner) + if(cultist?.cult_team?.cult_ascendent) + return FALSE + + var/datum/antagonist/heretic/heretic = GET_HERETIC(owner) + if(heretic?.ascended) + return FALSE if(!(owner.mob_biotypes & MOB_ORGANIC)) return FALSE diff --git a/code/modules/religion/religion_sects.dm b/code/modules/religion/religion_sects.dm index 88d17cbc4bf9..335ec7d3370f 100644 --- a/code/modules/religion/religion_sects.dm +++ b/code/modules/religion/religion_sects.dm @@ -108,12 +108,13 @@ /// Replaces the bible's bless mechanic. Return TRUE if you want to not do the brain hit. /datum/religion_sect/proc/sect_bless(mob/living/target, mob/living/chap) if(!ishuman(target)) - return FALSE + return BLESSING_FAILED + var/mob/living/carbon/human/blessed = target for(var/obj/item/bodypart/bodypart as anything in blessed.get_bodyparts()) if(IS_ROBOTIC_LIMB(bodypart)) to_chat(chap, span_warning("[GLOB.deity] refuses to heal this metallic taint!")) - return TRUE + return BLESSING_SUCCESS return standard_bless_healing(blessed, chap) @@ -126,20 +127,22 @@ var/heal_amt = 10 var/list/hurt_limbs = blessed.get_damaged_bodyparts(1, 1, BODYTYPE_ORGANIC) - if(hurt_limbs.len) - for(var/X in hurt_limbs) - var/obj/item/bodypart/affecting = X - if(affecting.heal_damage(heal_amt, heal_amt, required_bodytype = BODYTYPE_ORGANIC)) - blessed.update_damage_overlays() - blessed.visible_message(span_notice("[chap] heals [blessed] with the power of [GLOB.deity]!")) - to_chat(blessed, span_boldnotice("May the power of [GLOB.deity] compel you to be healed!")) - playsound(chap, SFX_PUNCH, 25, TRUE, -1) - blessed.add_mood_event("blessing", /datum/mood_event/blessing) - return TRUE + if(!length(hurt_limbs)) + return BLESSING_IGNORED + + for(var/obj/item/bodypart/affecting as anything in hurt_limbs) + if(affecting.heal_damage(heal_amt, heal_amt, required_bodytype = BODYTYPE_ORGANIC)) + blessed.update_damage_overlays() + + blessed.visible_message(span_notice("[chap] heals [blessed] with the power of [GLOB.deity]!")) + to_chat(blessed, span_boldnotice("May the power of [GLOB.deity] compel you to be healed!")) + playsound(chap, SFX_PUNCH, 25, TRUE, -1) + blessed.add_mood_event("blessing", /datum/mood_event/blessing) + return BLESSING_SUCCESS /// What happens if we bless a corpse? By default just do the default smack behavior /datum/religion_sect/proc/sect_dead_bless(mob/living/target, mob/living/chap) - return FALSE + return BLESSING_FAILED /**** Nanotrasen Approved God ****/ @@ -174,9 +177,11 @@ to_chat(R, span_boldnotice("You are charged by the power of [GLOB.deity]!")) R.add_mood_event("blessing", /datum/mood_event/blessing) playsound(chap, 'sound/effects/bang.ogg', 25, TRUE, -1) - return TRUE + return BLESSING_SUCCESS + if(!ishuman(target)) - return + return BLESSING_FAILED + var/mob/living/carbon/human/blessed = target //first we determine if we can charge them @@ -191,22 +196,23 @@ if(IS_ORGANIC_LIMB(bodypart)) if(!did_we_charge) to_chat(chap, span_warning("[GLOB.deity] scoffs at the idea of healing such fleshy matter!")) - else - blessed.visible_message(span_notice("[chap] charges [blessed] with the power of [GLOB.deity]!")) - to_chat(blessed, span_boldnotice("You feel charged by the power of [GLOB.deity]!")) - blessed.add_mood_event("blessing", /datum/mood_event/blessing) - playsound(chap, 'sound/machines/synth_yes.ogg', 25, TRUE, -1) - return TRUE + return BLESSING_IGNORED + + blessed.visible_message(span_notice("[chap] charges [blessed] with the power of [GLOB.deity]!")) + to_chat(blessed, span_boldnotice("You feel charged by the power of [GLOB.deity]!")) + blessed.add_mood_event("blessing", /datum/mood_event/blessing) + playsound(chap, 'sound/machines/synth_yes.ogg', 25, TRUE, -1) + return BLESSING_SUCCESS //charge(?) and go - if(bodypart.heal_damage(5,5,BODYTYPE_ROBOTIC)) + if(bodypart.heal_damage(5, 5 ,BODYTYPE_ROBOTIC)) blessed.update_damage_overlays() blessed.visible_message(span_notice("[chap] [did_we_charge ? "repairs and charges" : "repairs"] [blessed] with the power of [GLOB.deity]!")) to_chat(blessed, span_boldnotice("The inner machinations of [GLOB.deity] [did_we_charge ? "repairs and charges" : "repairs"] you!")) playsound(chap, 'sound/effects/bang.ogg', 25, TRUE, -1) blessed.add_mood_event("blessing", /datum/mood_event/blessing) - return TRUE + return BLESSING_SUCCESS /datum/religion_sect/mechanical/on_sacrifice(obj/item/stock_parts/power_store/cell/power_cell, mob/living/chap) if(!istype(power_cell)) @@ -270,33 +276,39 @@ return "In the eyes of [GLOB.deity], your wealth is your favor." /datum/religion_sect/greed/sect_bless(mob/living/blessed_living, mob/living/chap) + if(!ishuman(blessed_living)) + return BLESSING_FAILED + var/datum/bank_account/account = chap.get_bank_account() if(!account) to_chat(chap, span_warning("You need a way to pay for the heal!")) - return TRUE + return BLESSING_IGNORED + if(account.account_balance < GREEDY_HEAL_COST) - to_chat(chap, span_warning("Healing from [GLOB.deity] costs [GREEDY_HEAL_COST] credits for 30 health!")) - return TRUE - if(!ishuman(blessed_living)) - return FALSE + to_chat(chap, span_warning("Healing from [GLOB.deity] costs [GREEDY_HEAL_COST] [MONEY_NAME] for 30 health!")) + return BLESSING_IGNORED + var/mob/living/carbon/human/blessed = blessed_living for(var/obj/item/bodypart/robolimb as anything in blessed.get_bodyparts()) if(IS_ROBOTIC_LIMB(robolimb)) to_chat(chap, span_warning("[GLOB.deity] refuses to heal this metallic taint!")) - return TRUE + return BLESSING_IGNORED account.adjust_money(-GREEDY_HEAL_COST, "Church Donation: Treatment") var/heal_amt = 30 var/list/hurt_limbs = blessed.get_damaged_bodyparts(1, 1, BODYTYPE_ORGANIC) - if(hurt_limbs.len) - for(var/obj/item/bodypart/affecting as anything in hurt_limbs) - if(affecting.heal_damage(heal_amt, heal_amt, required_bodytype = BODYTYPE_ORGANIC)) - blessed.update_damage_overlays() - blessed.visible_message(span_notice("[chap] barters a heal for [blessed] from [GLOB.deity]!")) - to_chat(blessed, span_boldnotice("May the power of [GLOB.deity] compel you to be healed! Thank you for choosing [GLOB.deity]!")) - playsound(chap, 'sound/effects/cashregister.ogg', 60, TRUE) - blessed.add_mood_event("blessing", /datum/mood_event/blessing) - return TRUE + if(!length(hurt_limbs)) + return BLESSING_IGNORED + + for(var/obj/item/bodypart/affecting as anything in hurt_limbs) + if(affecting.heal_damage(heal_amt, heal_amt, required_bodytype = BODYTYPE_ORGANIC)) + blessed.update_damage_overlays() + + blessed.visible_message(span_notice("[chap] barters a heal for [blessed] from [GLOB.deity]!")) + to_chat(blessed, span_boldnotice("May the power of [GLOB.deity] compel you to be healed! Thank you for choosing [GLOB.deity]!")) + playsound(chap, 'sound/effects/cashregister.ogg', 60, TRUE) + blessed.add_mood_event("blessing", /datum/mood_event/blessing) + return BLESSING_SUCCESS #undef GREEDY_HEAL_COST @@ -333,10 +345,12 @@ /datum/religion_sect/burden/sect_bless(mob/living/carbon/target, mob/living/carbon/chaplain) if(!istype(target) || !istype(chaplain)) - return FALSE + return BLESSING_FAILED + var/datum/brain_trauma/special/burdened/burden = chaplain.has_trauma_type(/datum/brain_trauma/special/burdened) if(!burden) - return FALSE + return BLESSING_FAILED + var/burden_modifier = max(1 - 0.07 * burden.burden_level, 0.01) var/transferred = FALSE var/list/hurt_limbs = target.get_damaged_bodyparts(1, 1, BODYTYPE_ORGANIC) + target.get_wounded_bodyparts(BODYTYPE_ORGANIC) @@ -344,6 +358,7 @@ for(var/obj/item/bodypart/possible_limb in chaplain.get_bodyparts()) if(IS_ORGANIC_LIMB(possible_limb)) chaplains_limbs += possible_limb + if(length(chaplains_limbs)) for(var/obj/item/bodypart/affected_limb as anything in hurt_limbs) var/obj/item/bodypart/chaplains_limb = chaplain.get_bodypart(affected_limb.body_zone) @@ -359,15 +374,18 @@ transferred = TRUE iter_wound.remove_wound() iter_wound.apply_wound(chaplains_limb) + if(HAS_TRAIT_FROM(target, TRAIT_HUSK, BURN)) transferred = TRUE target.cure_husk(BURN) chaplain.become_husk(BURN) + var/toxin_damage = target.getToxLoss() if(toxin_damage && !HAS_TRAIT(chaplain, TRAIT_TOXIMMUNE)) transferred = TRUE target.adjustToxLoss(-toxin_damage) chaplain.adjustToxLoss(toxin_damage * burden_modifier, forced = TRUE) + var/suffocation_damage = target.getOxyLoss() if(suffocation_damage && !HAS_TRAIT(chaplain, TRAIT_NOBREATH)) transferred = TRUE @@ -394,14 +412,15 @@ target.update_damage_overlays() chaplain.update_damage_overlays() - if(transferred) - target.visible_message(span_notice("[chaplain] takes on [target]'s burden!")) - to_chat(target, span_boldnotice("May the power of [GLOB.deity] compel you to be healed!")) - playsound(chaplain, SFX_PUNCH, 25, vary = TRUE, extrarange = -1) - target.add_mood_event("blessing", /datum/mood_event/blessing) - else + if(!transferred) to_chat(chaplain, span_warning("They hold no burden!")) - return TRUE + return BLESSING_IGNORED + + target.visible_message(span_notice("[chaplain] takes on [target]'s burden!")) + to_chat(target, span_boldnotice("May the power of [GLOB.deity] compel you to be healed!")) + playsound(chaplain, SFX_PUNCH, 25, vary = TRUE, extrarange = -1) + target.add_mood_event("blessing", /datum/mood_event/blessing) + return BLESSING_SUCCESS /datum/religion_sect/burden/sect_dead_bless(mob/living/target, mob/living/chaplain) return sect_bless(target, chaplain) @@ -442,17 +461,19 @@ /datum/religion_sect/maintenance/sect_bless(mob/living/blessed_living, mob/living/chap) if(!ishuman(blessed_living)) - return TRUE + return BLESSING_FAILED + var/mob/living/carbon/human/blessed = blessed_living if(blessed.reagents.has_reagent(/datum/reagent/drug/maint/sludge)) to_chat(blessed, span_warning("[GLOB.deity] has already empowered them.")) - return TRUE + return BLESSING_IGNORED + blessed.reagents.add_reagent(/datum/reagent/drug/maint/sludge, 5) blessed.visible_message(span_notice("[chap] empowers [blessed] with the power of [GLOB.deity]!")) to_chat(blessed, span_boldnotice("The power of [GLOB.deity] has made you harder to wound for a while!")) playsound(chap, SFX_PUNCH, 25, TRUE, -1) blessed.add_mood_event("blessing", /datum/mood_event/blessing) - return TRUE //trust me, you'll be feeling the pain from the maint drugs all well enough + return BLESSING_SUCCESS //trust me, you'll be feeling the pain from the maint drugs all well enough /datum/religion_sect/maintenance/on_sacrifice(obj/item/reagent_containers/offering, mob/living/user) if(!istype(offering)) diff --git a/code/modules/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm index 876eacfd3ad2..5e3db3ca148b 100644 --- a/code/modules/surgery/organs/internal/heart/_heart.dm +++ b/code/modules/surgery/organs/internal/heart/_heart.dm @@ -557,3 +557,66 @@ owner.heal_overall_damage(brute = 15, burn = 15, required_bodytype = BODYTYPE_ORGANIC) if(owner.reagents.get_reagent_amount(/datum/reagent/medicine/ephedrine) < 20) owner.reagents.add_reagent(/datum/reagent/medicine/ephedrine, 10) + +/// An improved version of the organic heart, with more health and more "keeping you alive" potential +/obj/item/organ/heart/evolved + name = "evolved heart" + desc = "It beats ever strong." + icon_state = "heart-evolved-on" + base_icon_state = "heart-evolved" + maxHealth = STANDARD_ORGAN_THRESHOLD * 1.2 + /// Chance to heal per on_life + var/healing_probability = 10 + /// Base healing we receive per tick at 0 damage and for standard versions + var/base_healing = 1 + +/obj/item/organ/heart/evolved/on_life(seconds_per_tick) + . = ..() + + if(prob(healing_probability * seconds_per_tick)) + var/damage_to_heal = base_healing * ((maxHealth - damage) / initial(maxHealth)) * seconds_per_tick + owner.heal_overall_damage(damage_to_heal, damage_to_heal, required_bodytype = BODYTYPE_ORGANIC) + + if(owner.stat == HARD_CRIT && !owner.has_reagent(/datum/reagent/medicine/atropine, 5)) + owner.reagents.add_reagent(/datum/reagent/medicine/atropine, 1 * seconds_per_tick) + +/// A weaker evolved heart, but can block magic in exchange for our organs health! +/obj/item/organ/heart/evolved/sacred + name = "sacred heart" + desc = "Your foul magics stand no chance against the power of LOVE!!!" + + icon_state = "heart-sacred-on" + base_icon_state = "heart-sacred" + + healing_probability = 5 + base_healing = 0.5 + // How much damage each magic block deals to us + var/damage_per_block = 50 + +/obj/item/organ/heart/evolved/sacred/on_life(seconds_per_tick) + . = ..() + + if(IS_CULTIST(owner)) + owner.reagents.add_reagent(/datum/reagent/water/holywater, 5 * seconds_per_tick) + +/obj/item/organ/heart/evolved/sacred/on_mob_insert(mob/living/carbon/receiver, special, movement_flags) + . = ..() + + receiver.AddComponent(/datum/component/anti_magic, block_magic = CALLBACK(src, PROC_REF(on_blocked)), check_blocking = CALLBACK(src, PROC_REF(check_block))) + +/obj/item/organ/heart/evolved/sacred/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags) + . = ..() + + qdel(organ_owner.GetComponent(/datum/component/anti_magic)) + +/// When we blocked damage, do PAIN on us +/obj/item/organ/heart/evolved/sacred/proc/on_blocked() + apply_organ_damage(damage_per_block) + owner.vomit(VOMIT_CATEGORY_BLOOD) + playsound(owner, 'sound/health/slowbeat.ogg', 80) + +/// We don't block magic if it would kill our heart +/obj/item/organ/heart/evolved/sacred/proc/check_block() + if(maxHealth - damage <= damage_per_block) + return FALSE + return TRUE diff --git a/maplestation.dme b/maplestation.dme index 5ada061749eb..bc67efc11ab2 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -5623,6 +5623,7 @@ #include "code\modules\recycling\disposal\outlet.dm" #include "code\modules\recycling\disposal\pipe.dm" #include "code\modules\recycling\disposal\pipe_sorting.dm" +#include "code\modules\religion\deaconize.dm" #include "code\modules\religion\religion_sects.dm" #include "code\modules\religion\religion_structures.dm" #include "code\modules\religion\rites.dm" From fdedbc5acb6ee7c289fed68d021681b2174592e1 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Mon, 7 Sep 2026 23:01:51 -0500 Subject: [PATCH 14/15] Output --- code/game/machinery/computer/atmos_computers/outlets.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/game/machinery/computer/atmos_computers/outlets.dm b/code/game/machinery/computer/atmos_computers/outlets.dm index 529a83d72192..8a66516100d9 100644 --- a/code/game/machinery/computer/atmos_computers/outlets.dm +++ b/code/game/machinery/computer/atmos_computers/outlets.dm @@ -23,6 +23,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/siphon/monitored/mix_output name = "mix tank output inlet" chamber_id = ATMOS_GAS_MONITOR_MIX + on = FALSE /obj/machinery/atmospherics/components/unary/vent_pump/siphon/monitored/nitrous_output name = "nitrous oxide tank output inlet" From 8fffaa235ba137560bceb746526093dfad66619a Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Mon, 7 Sep 2026 23:07:46 -0500 Subject: [PATCH 15/15] Fix HMS --- code/datums/quirks/negative_quirks/chronic_illness.dm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/datums/quirks/negative_quirks/chronic_illness.dm b/code/datums/quirks/negative_quirks/chronic_illness.dm index 573e5babde5d..d8c3bc375374 100644 --- a/code/datums/quirks/negative_quirks/chronic_illness.dm +++ b/code/datums/quirks/negative_quirks/chronic_illness.dm @@ -9,9 +9,9 @@ hardcore_value = 12 mail_goodies = list(/obj/item/storage/pill_bottle/sansufentanyl) -/datum/quirk/item_quirk/chronic_illness/add_unique(client/client_source) - var/datum/disease/chronic_illness/hms = new /datum/disease/chronic_illness() - quirk_holder.ForceContractDisease(hms) +/datum/quirk/item_quirk/chronic_illness/add(client/client_source) + var/datum/disease/chronic_illness/hms = new() + quirk_holder.ForceContractDisease(hms, make_copy = FALSE, del_on_fail = TRUE) /datum/quirk/item_quirk/chronic_illness/add_unique(client/client_source) give_item_to_holder(/obj/item/storage/pill_bottle/sansufentanyl, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK), flavour_text = "You've been provided with medication to help manage your condition. Take it regularly to avoid complications.", notify_player = TRUE)