From 0efc694378e997ea9d22aad8259f9151db9d9215 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 1 Apr 2022 02:48:28 +0200 Subject: [PATCH] [MIRROR] Ingame Atmos Reaction Guide [MDB IGNORE] (#12428) * Ingame Atmos Reaction Guide * Update health_analyzer.dm Co-authored-by: vincentiusvin <54709710+vincentiusvin@users.noreply.github.com> Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com> --- code/__DEFINES/atmospherics/atmos_core.dm | 2 + code/__HELPERS/atmospherics.dm | 75 ++- code/_onclick/observer.dm | 2 +- code/controllers/subsystem/air.dm | 4 + code/datums/components/atmospheric_scanner.dm | 58 --- .../atmos_computers/_atmos_control.dm | 11 +- .../items/devices/scanners/gas_analyzer.dm | 177 +++++++ .../health_analyzer.dm} | 442 ------------------ .../items/devices/scanners/scanner_wand.dm | 39 ++ .../devices/scanners/sequence_scanner.dm | 103 ++++ .../items/devices/scanners/slime_scanner.dm | 54 +++ .../items/devices/scanners/t_scanner.dm | 58 +++ code/game/objects/objs.dm | 4 +- code/modules/admin/verbs/debug.dm | 2 +- code/modules/admin/verbs/diagnostics.dm | 2 +- .../atmospherics/gasmixtures/gas_mixture.dm | 2 - .../atmospherics/gasmixtures/gas_types.dm | 24 +- .../gasmixtures/reaction_factors.dm | 216 +++++++++ .../atmospherics/gasmixtures/reactions.dm | 115 +++-- .../machinery/portable/canister.dm | 23 +- .../file_system/programs/atmosscan.dm | 35 +- tgstation.dme | 9 +- .../tgui/interfaces/AtmosControlConsole.tsx | 14 +- tgui/packages/tgui/interfaces/GasAnalyzer.tsx | 40 ++ tgui/packages/tgui/interfaces/NtosAtmos.js | 56 --- .../tgui/interfaces/NtosGasAnalyzer.tsx | 12 + .../tgui/interfaces/common/AtmosHandbook.tsx | 244 ++++++++++ 27 files changed, 1168 insertions(+), 655 deletions(-) delete mode 100644 code/datums/components/atmospheric_scanner.dm create mode 100644 code/game/objects/items/devices/scanners/gas_analyzer.dm rename code/game/objects/items/devices/{scanners.dm => scanners/health_analyzer.dm} (61%) create mode 100644 code/game/objects/items/devices/scanners/scanner_wand.dm create mode 100644 code/game/objects/items/devices/scanners/sequence_scanner.dm create mode 100644 code/game/objects/items/devices/scanners/slime_scanner.dm create mode 100644 code/game/objects/items/devices/scanners/t_scanner.dm create mode 100644 code/modules/atmospherics/gasmixtures/reaction_factors.dm create mode 100644 tgui/packages/tgui/interfaces/GasAnalyzer.tsx delete mode 100644 tgui/packages/tgui/interfaces/NtosAtmos.js create mode 100644 tgui/packages/tgui/interfaces/NtosGasAnalyzer.tsx create mode 100644 tgui/packages/tgui/interfaces/common/AtmosHandbook.tsx diff --git a/code/__DEFINES/atmospherics/atmos_core.dm b/code/__DEFINES/atmospherics/atmos_core.dm index 10513a47d89..c0ca6bb78c8 100644 --- a/code/__DEFINES/atmospherics/atmos_core.dm +++ b/code/__DEFINES/atmospherics/atmos_core.dm @@ -20,6 +20,8 @@ #define META_GAS_ID 6 ///Power of the gas when used in the current iteration of fusion #define META_GAS_FUSION_POWER 7 +///Short description of the gas. +#define META_GAS_DESC 8 //ATMOS //stuff you should probably leave well alone! /// kPa*L/(K*mol) diff --git a/code/__HELPERS/atmospherics.dm b/code/__HELPERS/atmospherics.dm index 331cc437113..e5e6865d368 100644 --- a/code/__HELPERS/atmospherics.dm +++ b/code/__HELPERS/atmospherics.dm @@ -8,11 +8,11 @@ return (((a + epsilon) > b) && ((a - epsilon) < b)) /** A simple rudimentary gasmix to information list converter. Can be used for UIs. - * Args: + * Args: * - gasmix: [/datum/gas_mixture] * - name: String used to name the list, optional. * Returns: A list parsed_gasmixes with the following structure: - * - parsed_gasmixes Value: Assoc List Desc: The thing we return + * - parsed_gasmixes Value: Assoc List Desc: The thing we return * -- Key: name Value: String Desc: Gasmix Name * -- Key: temperature Value: Number Desc: Temperature in kelvins * -- Key: volume Value: Number Desc: Volume in liters @@ -43,8 +43,8 @@ return for(var/gas_path in gasmix.gases) .["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][GAS_META][META_GAS_ID], + gasmix.gases[gas_path][GAS_META][META_GAS_NAME], gasmix.gases[gas_path][MOLES], )) for(var/datum/gas_reaction/reaction_result as anything in gasmix.reaction_results) @@ -58,3 +58,70 @@ .["volume"] = gasmix.volume .["pressure"] = gasmix.return_pressure() .["reference"] = REF(gasmix) + +GLOBAL_LIST_EMPTY(reaction_handbook) +GLOBAL_LIST_EMPTY(gas_handbook) + +/// Automatically populates gas_handbook and reaction_handbook. They are formatted lists containing information regarding gases and reactions they participate in. +/// Structure can be found in TS form at AtmosHandbook.tsx +/proc/atmos_handbooks_init() + if(length(GLOB.reaction_handbook)) + GLOB.reaction_handbook = list() + if(length(GLOB.gas_handbook)) + GLOB.gas_handbook = list() + + /// Final product is a numbered list, this one is assoc just so we can generate the "reactions" entry easily. + var/list/momentary_gas_list = list() + + 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) + 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["reactions"] = list() + momentary_gas_list[gas_path] = gas_info + + for (var/datum/gas_reaction/reaction_path as anything in subtypesof(/datum/gas_reaction)) + var/datum/gas_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 [lowertext(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." + else if(factor == "Hot Ice") + factor_info["tooltip"] = "Hot ice are solidified stacks of plasma. Ignition of one will result in a raging fire." + 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) diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm index 005e869e32e..b1f8e627375 100644 --- a/code/_onclick/observer.dm +++ b/code/_onclick/observer.dm @@ -54,7 +54,7 @@ if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_GHOST, user) & COMPONENT_CANCEL_ATTACK_CHAIN) return TRUE if(user.client) - if(user.gas_scan && atmosanalyzer_scan(user, src)) + if(user.gas_scan && atmos_scan(user=user, target=src, tool=null, silent=TRUE)) return TRUE else if(isAdminGhostAI(user)) attack_ai(user) diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index f3da1e9f954..bbfae0d18e5 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -52,6 +52,9 @@ SUBSYSTEM_DEF(air) var/list/queued_for_activation var/display_all_groups = FALSE + var/list/reaction_handbook + var/list/gas_handbook + /datum/controller/subsystem/air/stat_entry(msg) msg += "C:{" @@ -91,6 +94,7 @@ SUBSYSTEM_DEF(air) setup_pipenets() setup_turf_visuals() process_adjacent_rebuild() + atmos_handbooks_init() return ..() diff --git a/code/datums/components/atmospheric_scanner.dm b/code/datums/components/atmospheric_scanner.dm deleted file mode 100644 index f33a941e8bf..00000000000 --- a/code/datums/components/atmospheric_scanner.dm +++ /dev/null @@ -1,58 +0,0 @@ -///Items with this component can scan the surrounding atmospherics. -/datum/component/atmospheric_scanner - /// Controls if the analyzer requires being able to see it in order to obtain the results. The value is set in AddComponent. - var/requires_sight = TRUE - -/datum/component/atmospheric_scanner/Initialize(requires_sight) - if(!isitem(parent)) - return COMPONENT_INCOMPATIBLE - if(!isnull(requires_sight)) - src.requires_sight = requires_sight - -/datum/component/atmospheric_scanner/RegisterWithParent() - RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/analyzer_scan) - -/datum/component/atmospheric_scanner/UnregisterFromParent() - UnregisterSignal(parent, COMSIG_ITEM_ATTACK_SELF) - -/datum/component/atmospheric_scanner/proc/analyzer_scan(datum/source, mob/user) - SIGNAL_HANDLER - if (requires_sight && (user.stat != CONSCIOUS || user.is_blind())) //check if it requires visibility and if the user is you know, blind. - to_chat(user, span_warning("You're unable to see [parent]'s results!")) - return - - var/turf/location = user.loc - if(!istype(location)) - return - - var/render_list = list() - var/datum/gas_mixture/environment = location.return_air() - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - - render_list += "[span_info("Results:")]\ - \nPressure: [round(pressure, 0.01)] kPa\n" - if(total_moles) - var/list/env_gases = environment.gases - - environment.assert_gases(arglist(GLOB.hardcoded_gases)) - var/o2_concentration = env_gases[/datum/gas/oxygen][MOLES]/total_moles - var/n2_concentration = env_gases[/datum/gas/nitrogen][MOLES]/total_moles - var/co2_concentration = env_gases[/datum/gas/carbon_dioxide][MOLES]/total_moles - var/plasma_concentration = env_gases[/datum/gas/plasma][MOLES]/total_moles - - render_list += "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen][MOLES], 0.01)] mol)\ - \nOxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen][MOLES], 0.01)] mol)\ - \nCO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide][MOLES], 0.01)] mol)\ - \nPlasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma][MOLES], 0.01)] mol)\n" - - environment.garbage_collect() - - for(var/id in env_gases) - if(id in GLOB.hardcoded_gases) - continue - var/gas_concentration = env_gases[id][MOLES]/total_moles - render_list += "[span_alert("[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(env_gases[id][MOLES], 0.01)] mol)")]\n" - render_list += "[span_info("Temperature: [round(environment.temperature-T0C, 0.01)] °C ([round(environment.temperature, 0.01)] K)")]\n" - // we handled the last
so we don't need handholding - to_chat(user, jointext(render_list, ""), trailing_newline = FALSE, type = MESSAGE_TYPE_INFO) diff --git a/code/game/machinery/computer/atmos_computers/_atmos_control.dm b/code/game/machinery/computer/atmos_computers/_atmos_control.dm index 1ed034872b0..7651109971f 100644 --- a/code/game/machinery/computer/atmos_computers/_atmos_control.dm +++ b/code/game/machinery/computer/atmos_computers/_atmos_control.dm @@ -17,8 +17,8 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) /// Which sensors/input/outlets do we want to listen to. /// Assoc of list[chamber_id] = readable_chamber_name var/list/atmos_chambers - - // The list where received signals about devices are written into. + + // The list where received signals about devices are written into. // Assoc of list[atmos_chambers_string] var/list/sensor_info var/list/input_info @@ -57,7 +57,7 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) if(!(tag_data[1] in atmos_chambers)) return - + var/list/info_list switch(tag_data[2]) if("sensor") @@ -102,7 +102,7 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) if(isnull(new_id)) return FALSE - + atmos_chambers = list() atmos_chambers[new_id] = new_name sensor_info = list() @@ -133,6 +133,7 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) data["maxOutput"] = MAX_OUTPUT_PRESSURE data["control"] = control data["reconnecting"] = reconnecting + data += return_atmos_handbooks() return data /obj/machinery/computer/atmos_control/ui_data(mob/user) @@ -189,7 +190,7 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) return FALSE target = clamp(target, 0, MAX_OUTPUT_PRESSURE) signal.data += list("tag" = params["chamber"] + "_out", "set_internal_pressure" = target) - + radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) return TRUE diff --git a/code/game/objects/items/devices/scanners/gas_analyzer.dm b/code/game/objects/items/devices/scanners/gas_analyzer.dm new file mode 100644 index 00000000000..eae1141663c --- /dev/null +++ b/code/game/objects/items/devices/scanners/gas_analyzer.dm @@ -0,0 +1,177 @@ +/obj/item/analyzer + desc = "A hand-held environmental scanner which reports current gas levels." + name = "gas analyzer" + custom_price = PAYCHECK_ASSISTANT * 0.9 + icon = 'icons/obj/device.dmi' + icon_state = "analyzer" + inhand_icon_state = "analyzer" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + throwforce = 0 + throw_speed = 3 + throw_range = 7 + tool_behaviour = TOOL_ANALYZER + custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) + grind_results = list(/datum/reagent/mercury = 5, /datum/reagent/iron = 5, /datum/reagent/silicon = 5) + var/cooldown = FALSE + var/cooldown_time = 250 + var/barometer_accuracy // 0 is the best accuracy. + var/list/last_gasmix_data + +/obj/item/analyzer/examine(mob/user) + . = ..() + . += span_notice("Right-click [src] to open the gas reference.") + . += span_notice("Alt-click [src] to activate the barometer function.") + +/obj/item/analyzer/suicide_act(mob/living/carbon/user) + user.visible_message(span_suicide("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!")) + return BRUTELOSS + +/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens + ..() + + if(user.canUseTopic(src, BE_CLOSE)) + if(cooldown) + to_chat(user, span_warning("[src]'s barometer function is preparing itself.")) + return + + var/turf/T = get_turf(user) + if(!T) + return + + playsound(src, 'sound/effects/pop.ogg', 100) + var/area/user_area = T.loc + var/datum/weather/ongoing_weather = null + + if(!user_area.outdoors) + to_chat(user, span_warning("[src]'s barometer function won't work indoors!")) + return + + for(var/V in SSweather.processing) + var/datum/weather/W = V + if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE)) + ongoing_weather = W + break + + if(ongoing_weather) + if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE)) + to_chat(user, span_warning("[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]")) + return + + to_chat(user, span_notice("The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].")) + if(ongoing_weather.aesthetic) + to_chat(user, span_warning("[src]'s barometer function says that the next storm will breeze on by.")) + else + var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"] + var/fixed = next_hit ? timeleft(next_hit) : -1 + if(fixed < 0) + to_chat(user, span_warning("[src]'s barometer function was unable to trace any weather patterns.")) + else + to_chat(user, span_warning("[src]'s barometer function says a storm will land in approximately [butchertime(fixed)].")) + cooldown = TRUE + addtimer(CALLBACK(src,/obj/item/analyzer/proc/ping), cooldown_time) + +/obj/item/analyzer/proc/ping() + if(isliving(loc)) + var/mob/living/L = loc + to_chat(L, span_notice("[src]'s barometer function is ready!")) + playsound(src, 'sound/machines/click.ogg', 100) + cooldown = FALSE + +/// Applies the barometer inaccuracy to the gas reading. +/obj/item/analyzer/proc/butchertime(amount) + if(!amount) + return + if(barometer_accuracy) + var/inaccurate = round(barometer_accuracy*(1/3)) + if(prob(50)) + amount -= inaccurate + if(prob(50)) + amount += inaccurate + return DisplayTimeText(max(1,amount)) + +/obj/item/analyzer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "GasAnalyzer", "Gas Analyzer") + ui.open() + +/obj/item/analyzer/ui_static_data(mob/user) + return return_atmos_handbooks() + +/obj/item/analyzer/ui_data(mob/user) + LAZYINITLIST(last_gasmix_data) + return list("gasmixes" = last_gasmix_data) + +/obj/item/analyzer/attack_self(mob/user, modifiers) + // Check if it requires visibility and if the user is you know, blind. + if (user.stat != CONSCIOUS || user.is_blind()) + to_chat(user, span_warning("You're unable to see [src]'s results!")) + return + + atmos_scan(user=user, target=get_turf(src), tool=src, silent=FALSE) + +/obj/item/analyzer/attack_self_secondary(mob/user, modifiers) + // Check if it requires visibility and if the user is you know, blind. + if (user.stat != CONSCIOUS || user.is_blind()) + to_chat(user, span_warning("You're unable to see [src]'s results!")) + return + + ui_interact(user) + +/** + * Outputs a message to the user describing the target's gasmixes. + * + * Gets called by analyzer_act, which in turn is called by tool_act. + * Also used in other chat-based gas scans. + */ +/proc/atmos_scan(mob/user, atom/target, obj/item/analyzer/tool, silent=FALSE) + var/mixture = target.return_analyzable_air() + if(!mixture) + return FALSE + + var/icon = target + var/message = list() + if(!silent && isliving(user)) + user.visible_message(span_notice("[user] uses the analyzer on [icon2html(icon, viewers(user))] [target]."), span_notice("You use the analyzer on [icon2html(icon, user)] [target].")) + message += span_boldnotice("Results of analysis of [icon2html(icon, user)] [target].") + + if(tool) + tool.last_gasmix_data = list() + + var/list/airs = islist(mixture) ? mixture : list(mixture) + for(var/datum/gas_mixture/air as anything in airs) + var/mix_name = capitalize(lowertext(target.name)) + if(airs.len > 1) //not a unary gas mixture + var/mix_number = airs.Find(air) + message += span_boldnotice("Node [mix_number]") + mix_name += " - Node [mix_number]" + + var/total_moles = air.total_moles() + var/pressure = air.return_pressure() + var/volume = air.return_volume() //could just do mixture.volume... but safety, I guess? + var/temperature = air.return_temperature() + + 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)] %)") + 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") + else + message += airs.len > 1 ? span_notice("This node is empty!") : span_notice("[target] is empty!") + + if(tool) + tool.last_gasmix_data += list(gas_mixture_parser(air, mix_name)) + + // we let the join apply newlines so we do need handholding + to_chat(user, jointext(message, "\n"), type = MESSAGE_TYPE_INFO) + return TRUE diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm similarity index 61% rename from code/game/objects/items/devices/scanners.dm rename to code/game/objects/items/devices/scanners/health_analyzer.dm index 4f331dc4f49..bb788292e4e 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -1,15 +1,3 @@ - -/* - -CONTAINS: -T-RAY -HEALTH ANALYZER -GAS ANALYZER -SLIME SCANNER -GENE SCANNER - -*/ - // Describes the three modes of scanning available for health analyzers #define SCANMODE_HEALTH 0 #define SCANMODE_WOUND 1 @@ -17,65 +5,6 @@ GENE SCANNER #define SCANNER_CONDENSED 0 #define SCANNER_VERBOSE 1 -/obj/item/t_scanner//SKYRAT EDIT - ICON OVERRIDEN BY AESTHETICS - SEE MODULE - name = "\improper T-ray scanner" - desc = "A terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes." - custom_price = PAYCHECK_ASSISTANT * 0.7 - icon = 'icons/obj/device.dmi' - icon_state = "t-ray0" - var/on = FALSE - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - inhand_icon_state = "electronic" - worn_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - custom_materials = list(/datum/material/iron=150) - -/obj/item/t_scanner/suicide_act(mob/living/carbon/user) - user.visible_message(span_suicide("[user] begins to emit terahertz-rays into [user.p_their()] brain with [src]! It looks like [user.p_theyre()] trying to commit suicide!")) - return TOXLOSS - -/obj/item/t_scanner/proc/toggle_on() - on = !on - icon_state = copytext_char(icon_state, 1, -1) + "[on]" - if(on) - START_PROCESSING(SSobj, src) - else - STOP_PROCESSING(SSobj, src) - -/obj/item/t_scanner/attack_self(mob/user) - toggle_on() - -/obj/item/t_scanner/cyborg_unequip(mob/user) - if(!on) - return - toggle_on() - -/obj/item/t_scanner/process() - if(!on) - STOP_PROCESSING(SSobj, src) - return null - scan() - -/obj/item/t_scanner/proc/scan() - t_ray_scan(loc) - -/proc/t_ray_scan(mob/viewer, flick_time = 8, distance = 3) - if(!ismob(viewer) || !viewer.client) - return - var/list/t_ray_images = list() - for(var/obj/O in orange(distance, viewer) ) - if(HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE)) - var/image/I = new(loc = get_turf(O)) - var/mutable_appearance/MA = new(O) - MA.alpha = 128 - MA.dir = O.dir - I.appearance = MA - t_ray_images += I - if(t_ray_images.len) - flick_overlay(t_ray_images, list(viewer.client), flick_time) - /obj/item/healthanalyzer name = "health analyzer" icon = 'icons/obj/device.dmi' @@ -601,377 +530,6 @@ GENE SCANNER woundscan(user, patient, src) -/obj/item/analyzer//SKYRAT EDIT - ICON OVERRIDEN BY AESTHETICS - SEE MODULE - desc = "A hand-held environmental scanner which reports current gas levels. Alt-Click to use the built in barometer function." - name = "gas analyzer" - custom_price = PAYCHECK_ASSISTANT * 0.9 - icon = 'icons/obj/device.dmi' - icon_state = "analyzer" - inhand_icon_state = "analyzer" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - throwforce = 0 - throw_speed = 3 - throw_range = 7 - tool_behaviour = TOOL_ANALYZER - custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) - grind_results = list(/datum/reagent/mercury = 5, /datum/reagent/iron = 5, /datum/reagent/silicon = 5) - var/cooldown = FALSE - var/cooldown_time = 250 - var/accuracy // 0 is the best accuracy. - -/obj/item/analyzer/Initialize(mapload) - . = ..() - AddComponent(/datum/component/atmospheric_scanner, requires_sight = TRUE) - - AddComponent(/datum/component/cell) //SKYRAT EDIT ADDITION - -/obj/item/analyzer/examine(mob/user) - . = ..() - . += span_notice("Alt-click [src] to activate the barometer function.") - -/obj/item/analyzer/suicide_act(mob/living/carbon/user) - user.visible_message(span_suicide("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!")) - return BRUTELOSS - -/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens - ..() - - if(user.canUseTopic(src, BE_CLOSE)) - if(cooldown) - to_chat(user, span_warning("[src]'s barometer function is preparing itself.")) - return - //SKYRAT EDIT ADDITION - if(!(item_use_power(power_use_amount, user) & COMPONENT_POWER_SUCCESS)) - return - //SKYRAT EDIT END - - var/turf/T = get_turf(user) - if(!T) - return - - playsound(src, 'sound/effects/pop.ogg', 100) - var/area/user_area = T.loc - var/datum/weather/ongoing_weather = null - - if(!user_area.outdoors) - to_chat(user, span_warning("[src]'s barometer function won't work indoors!")) - return - - for(var/V in SSweather.processing) - var/datum/weather/W = V - if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE)) - ongoing_weather = W - break - - if(ongoing_weather) - if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE)) - to_chat(user, span_warning("[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]")) - return - - to_chat(user, span_notice("The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].")) - if(ongoing_weather.aesthetic) - to_chat(user, span_warning("[src]'s barometer function says that the next storm will breeze on by.")) - else - var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"] - var/fixed = next_hit ? timeleft(next_hit) : -1 - if(fixed < 0) - to_chat(user, span_warning("[src]'s barometer function was unable to trace any weather patterns.")) - else - to_chat(user, span_warning("[src]'s barometer function says a storm will land in approximately [butchertime(fixed)].")) - cooldown = TRUE - addtimer(CALLBACK(src,/obj/item/analyzer/proc/ping), cooldown_time) - -/obj/item/analyzer/proc/ping() - if(isliving(loc)) - var/mob/living/L = loc - to_chat(L, span_notice("[src]'s barometer function is ready!")) - playsound(src, 'sound/machines/click.ogg', 100) - cooldown = FALSE - -/obj/item/analyzer/proc/butchertime(amount) - if(!amount) - return - if(accuracy) - var/inaccurate = round(accuracy*(1/3)) - if(prob(50)) - amount -= inaccurate - if(prob(50)) - amount += inaccurate - return DisplayTimeText(max(1,amount)) - -/proc/atmosanalyzer_scan(mob/user, atom/target, silent=FALSE) - var/mixture = target.return_analyzable_air() - if(!mixture) - return FALSE - - var/icon = target - var/render_list = list() - if(!silent && isliving(user)) - user.visible_message(span_notice("[user] uses the analyzer on [icon2html(icon, viewers(user))] [target]."), span_notice("You use the analyzer on [icon2html(icon, user)] [target].")) - render_list += span_boldnotice("Results of analysis of [icon2html(icon, user)] [target].") - - var/list/airs = islist(mixture) ? mixture : list(mixture) - for(var/g in airs) - if(airs.len > 1) //not a unary gas mixture - render_list += span_boldnotice("Node [airs.Find(g)]") - var/datum/gas_mixture/air_contents = g - - var/total_moles = air_contents.total_moles() - var/pressure = air_contents.return_pressure() - var/volume = air_contents.return_volume() //could just do mixture.volume... but safety, I guess? - var/temperature = air_contents.temperature - var/cached_scan_results = air_contents.analyzer_results - - if(total_moles > 0) - render_list += "[span_notice("Moles: [round(total_moles, 0.01)] mol")]\ - \n[span_notice("Volume: [volume] L")]\ - \n[span_notice("Pressure: [round(pressure,0.01)] kPa")]" - - var/list/cached_gases = air_contents.gases - for(var/id in cached_gases) - var/gas_concentration = cached_gases[id][MOLES]/total_moles - render_list += span_notice("[cached_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(cached_gases[id][MOLES], 0.01)] mol)") - render_list += span_notice("Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)") - else - render_list += airs.len > 1 ? span_notice("This node is empty!") : span_notice("[target] is empty!") - - if(cached_scan_results && cached_scan_results["fusion"]) //notify the user if a fusion reaction was detected - render_list += "[span_boldnotice("Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.")]\ - \n[span_notice("Instability of the last fusion reaction: [round(cached_scan_results["fusion"], 0.01)].")]" - // we let the join apply newlines so we do need handholding - to_chat(user, examine_block(jointext(render_list, "\n")), type = MESSAGE_TYPE_INFO) //SKYRAT EDIT CHANGE - return TRUE - -//slime scanner - -/obj/item/slime_scanner - name = "slime scanner" - desc = "A device that analyzes a slime's internal composition and measures its stats." - icon = 'icons/obj/device.dmi' - icon_state = "adv_spectrometer" - inhand_icon_state = "analyzer" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - throwforce = 0 - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) - - //SKYRAT EDIT ADDITION BEGIN - power_use_amount = POWER_CELL_USE_LOW - -/obj/item/slime_scanner/ComponentInitialize() - . = ..() - AddComponent(/datum/component/cell) - //SKYRAT EDIT END - -/obj/item/slime_scanner/attack(mob/living/M, mob/living/user) - if(user.stat || user.is_blind()) - return - if (!isslime(M)) - to_chat(user, span_warning("This device can only scan slimes!")) - return - //SKYRAT EDIT ADDITION - if(!(item_use_power(power_use_amount, user) & COMPONENT_POWER_SUCCESS)) - return - //SKYRAT EDIT END - var/mob/living/simple_animal/slime/T = M - slime_scan(T, user) - -/proc/slime_scan(mob/living/simple_animal/slime/T, mob/living/user) - var/to_render = "========================\ - \nSlime scan results:\ - \n[span_notice("[T.colour] [T.is_adult ? "adult" : "baby"] slime")]\ - \nNutrition: [T.nutrition]/[T.get_max_nutrition()]" - if (T.nutrition < T.get_starve_nutrition()) - to_render += "\n[span_warning("Warning: slime is starving!")]" - else if (T.nutrition < T.get_hunger_nutrition()) - to_render += "\n[span_warning("Warning: slime is hungry")]" - to_render += "\nElectric change strength: [T.powerlevel]\nHealth: [round(T.health/T.maxHealth,0.01)*100]%" - if (T.slime_mutation[4] == T.colour) - to_render += "\nThis slime does not evolve any further." - else - if (T.slime_mutation[3] == T.slime_mutation[4]) - if (T.slime_mutation[2] == T.slime_mutation[1]) - to_render += "\nPossible mutation: [T.slime_mutation[3]]\ - \nGenetic destability: [T.mutation_chance/2] % chance of mutation on splitting" - else - to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)\ - \nGenetic destability: [T.mutation_chance] % chance of mutation on splitting" - else - to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]\ - \nGenetic destability: [T.mutation_chance] % chance of mutation on splitting" - if (T.cores > 1) - to_render += "\nMultiple cores detected" - to_render += "\nGrowth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]" - if(T.effectmod) - to_render += "\n[span_notice("Core mutation in progress: [T.effectmod]")]\ - \n[span_notice("Progress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]")]" - to_chat(user, examine_block(to_render + "\n========================")) //SKYRAT EDIT CHANGE - -/obj/item/sequence_scanner//SKYRAT EDIT - ICON OVERRIDEN BY AESTHETICS - SEE MODUL - name = "genetic sequence scanner" - icon = 'icons/obj/device.dmi' - icon_state = "gene" - inhand_icon_state = "healthanalyzer" - worn_icon_state = "healthanalyzer" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - desc = "A hand-held scanner for analyzing someones gene sequence on the fly. Hold near a DNA console to update the internal database." - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - throwforce = 3 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=200) - var/list/discovered = list() //hit a dna console to update the scanners database - var/list/buffer - var/ready = TRUE - var/cooldown = 200 - - //SKYRAT EDIT ADDITION BEGIN - power_use_amount = POWER_CELL_USE_VERY_LOW - -/obj/item/sequence_scanner/ComponentInitialize() - . = ..() - AddComponent(/datum/component/cell) - //SKYRAT EDIT END - -/obj/item/sequence_scanner/attack(mob/living/M, mob/living/carbon/human/user) - add_fingerprint(user) - if (!HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) //no scanning if its a husk or DNA-less Species - //SKYRAT EDIT ADDITION - if(!(item_use_power(power_use_amount, user) & COMPONENT_POWER_SUCCESS)) - return - //SKYRAT EDIT END - user.visible_message(span_notice("[user] analyzes [M]'s genetic sequence."), \ - span_notice("You analyze [M]'s genetic sequence.")) - gene_scan(M, user) - - else - user.visible_message(span_notice("[user] fails to analyze [M]'s genetic sequence."), span_warning("[M] has no readable genetic sequence!")) - -/obj/item/sequence_scanner/attack_self(mob/user) - display_sequence(user) - -/obj/item/sequence_scanner/attack_self_tk(mob/user) - return - -/obj/item/sequence_scanner/afterattack(obj/O, mob/user, proximity) - . = ..() - if(!istype(O) || !proximity) - return - - if(istype(O, /obj/machinery/computer/scan_consolenew)) - var/obj/machinery/computer/scan_consolenew/C = O - if(C.stored_research) - to_chat(user, span_notice("[name] linked to central research database.")) - discovered = C.stored_research.discovered_mutations - else - to_chat(user,span_warning("No database to update from.")) - -/obj/item/sequence_scanner/proc/gene_scan(mob/living/carbon/C, mob/living/user) - if(!iscarbon(C) || !C.has_dna()) - return - buffer = C.dna.mutation_index - to_chat(user, span_notice("Subject [C.name]'s DNA sequence has been saved to buffer.")) - if(LAZYLEN(buffer)) - for(var/A in buffer) - to_chat(user, span_notice("[get_display_name(A)]")) - - -/obj/item/sequence_scanner/proc/display_sequence(mob/living/user) - if(!LAZYLEN(buffer) || !ready) - return - var/list/options = list() - for(var/A in buffer) - options += get_display_name(A) - - var/answer = tgui_input_list(user, "Analyze Potential", "Sequence Analyzer", sort_list(options)) - if(isnull(answer)) - return - if(ready && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - var/sequence - for(var/A in buffer) //this physically hurts but i dont know what anything else short of an assoc list - if(get_display_name(A) == answer) - sequence = buffer[A] - break - - if(sequence) - var/display - for(var/i in 0 to length_char(sequence) / DNA_MUTATION_BLOCKS-1) - if(i) - display += "-" - display += copytext_char(sequence, 1 + i*DNA_MUTATION_BLOCKS, DNA_MUTATION_BLOCKS*(1+i) + 1) - - to_chat(user, "[span_boldnotice("[display]")]
") - - ready = FALSE - icon_state = "[icon_state]_recharging" - addtimer(CALLBACK(src, .proc/recharge), cooldown, TIMER_UNIQUE) - -/obj/item/sequence_scanner/proc/recharge() - icon_state = initial(icon_state) - ready = TRUE - -/obj/item/sequence_scanner/proc/get_display_name(mutation) - var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation) - if(!HM) - return "ERROR" - if(mutation in discovered) - return "[HM.name] ([HM.alias])" - else - return HM.alias - -/obj/item/scanner_wand - name = "kiosk scanner wand" - icon = 'icons/obj/device.dmi' - icon_state = "scanner_wand" - inhand_icon_state = "healthanalyzer" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - desc = "A wand that medically scans people. Inserting it into a medical kiosk makes it able to perform a health scan on the patient." - force = 0 - throwforce = 0 - w_class = WEIGHT_CLASS_BULKY - var/selected_target = null - -/obj/item/scanner_wand/attack(mob/living/M, mob/living/carbon/human/user) - flick("[icon_state]_active", src) //nice little visual flash when scanning someone else. - - if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(25)) - user.visible_message(span_warning("[user] targets himself for scanning."), \ - to_chat(user, span_info("You try scanning [M], before realizing you're holding the scanner backwards. Whoops."))) - selected_target = user - return - - if(!ishuman(M)) - to_chat(user, span_info("You can only scan human-like, non-robotic beings.")) - selected_target = null - return - - user.visible_message(span_notice("[user] targets [M] for scanning."), \ - span_notice("You target [M] vitals.")) - selected_target = M - return - -/obj/item/scanner_wand/attack_self(mob/user) - to_chat(user, span_info("You clear the scanner's target.")) - selected_target = null - -/obj/item/scanner_wand/proc/return_patient() - var/returned_target = selected_target - return returned_target - #undef SCANMODE_HEALTH #undef SCANMODE_WOUND #undef SCANMODE_COUNT diff --git a/code/game/objects/items/devices/scanners/scanner_wand.dm b/code/game/objects/items/devices/scanners/scanner_wand.dm new file mode 100644 index 00000000000..e086751045c --- /dev/null +++ b/code/game/objects/items/devices/scanners/scanner_wand.dm @@ -0,0 +1,39 @@ +/obj/item/scanner_wand + name = "kiosk scanner wand" + icon = 'icons/obj/device.dmi' + icon_state = "scanner_wand" + inhand_icon_state = "healthanalyzer" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + desc = "A wand that medically scans people. Inserting it into a medical kiosk makes it able to perform a health scan on the patient." + force = 0 + throwforce = 0 + w_class = WEIGHT_CLASS_BULKY + var/selected_target = null + +/obj/item/scanner_wand/attack(mob/living/M, mob/living/carbon/human/user) + flick("[icon_state]_active", src) //nice little visual flash when scanning someone else. + + if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(25)) + user.visible_message(span_warning("[user] targets himself for scanning."), \ + to_chat(user, span_info("You try scanning [M], before realizing you're holding the scanner backwards. Whoops."))) + selected_target = user + return + + if(!ishuman(M)) + to_chat(user, span_info("You can only scan human-like, non-robotic beings.")) + selected_target = null + return + + user.visible_message(span_notice("[user] targets [M] for scanning."), \ + span_notice("You target [M] vitals.")) + selected_target = M + return + +/obj/item/scanner_wand/attack_self(mob/user) + to_chat(user, span_info("You clear the scanner's target.")) + selected_target = null + +/obj/item/scanner_wand/proc/return_patient() + var/returned_target = selected_target + return returned_target diff --git a/code/game/objects/items/devices/scanners/sequence_scanner.dm b/code/game/objects/items/devices/scanners/sequence_scanner.dm new file mode 100644 index 00000000000..9ad7583523b --- /dev/null +++ b/code/game/objects/items/devices/scanners/sequence_scanner.dm @@ -0,0 +1,103 @@ +/obj/item/sequence_scanner + name = "genetic sequence scanner" + icon = 'icons/obj/device.dmi' + icon_state = "gene" + inhand_icon_state = "healthanalyzer" + worn_icon_state = "healthanalyzer" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + desc = "A hand-held scanner for analyzing someones gene sequence on the fly. Hold near a DNA console to update the internal database." + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=200) + var/list/discovered = list() //hit a dna console to update the scanners database + var/list/buffer + var/ready = TRUE + var/cooldown = 200 + +/obj/item/sequence_scanner/attack(mob/living/M, mob/living/carbon/human/user) + add_fingerprint(user) + if (!HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) //no scanning if its a husk or DNA-less Species + user.visible_message(span_notice("[user] analyzes [M]'s genetic sequence."), \ + span_notice("You analyze [M]'s genetic sequence.")) + gene_scan(M, user) + + else + user.visible_message(span_notice("[user] fails to analyze [M]'s genetic sequence."), span_warning("[M] has no readable genetic sequence!")) + +/obj/item/sequence_scanner/attack_self(mob/user) + display_sequence(user) + +/obj/item/sequence_scanner/attack_self_tk(mob/user) + return + +/obj/item/sequence_scanner/afterattack(obj/O, mob/user, proximity) + . = ..() + if(!istype(O) || !proximity) + return + + if(istype(O, /obj/machinery/computer/scan_consolenew)) + var/obj/machinery/computer/scan_consolenew/C = O + if(C.stored_research) + to_chat(user, span_notice("[name] linked to central research database.")) + discovered = C.stored_research.discovered_mutations + else + to_chat(user,span_warning("No database to update from.")) + +/obj/item/sequence_scanner/proc/gene_scan(mob/living/carbon/C, mob/living/user) + if(!iscarbon(C) || !C.has_dna()) + return + buffer = C.dna.mutation_index + to_chat(user, span_notice("Subject [C.name]'s DNA sequence has been saved to buffer.")) + if(LAZYLEN(buffer)) + for(var/A in buffer) + to_chat(user, span_notice("[get_display_name(A)]")) + + +/obj/item/sequence_scanner/proc/display_sequence(mob/living/user) + if(!LAZYLEN(buffer) || !ready) + return + var/list/options = list() + for(var/A in buffer) + options += get_display_name(A) + + var/answer = tgui_input_list(user, "Analyze Potential", "Sequence Analyzer", sort_list(options)) + if(isnull(answer)) + return + if(ready && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + var/sequence + for(var/A in buffer) //this physically hurts but i dont know what anything else short of an assoc list + if(get_display_name(A) == answer) + sequence = buffer[A] + break + + if(sequence) + var/display + for(var/i in 0 to length_char(sequence) / DNA_MUTATION_BLOCKS-1) + if(i) + display += "-" + display += copytext_char(sequence, 1 + i*DNA_MUTATION_BLOCKS, DNA_MUTATION_BLOCKS*(1+i) + 1) + + to_chat(user, "[span_boldnotice("[display]")]
") + + ready = FALSE + icon_state = "[icon_state]_recharging" + addtimer(CALLBACK(src, .proc/recharge), cooldown, TIMER_UNIQUE) + +/obj/item/sequence_scanner/proc/recharge() + icon_state = initial(icon_state) + ready = TRUE + +/obj/item/sequence_scanner/proc/get_display_name(mutation) + var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation) + if(!HM) + return "ERROR" + if(mutation in discovered) + return "[HM.name] ([HM.alias])" + else + return HM.alias diff --git a/code/game/objects/items/devices/scanners/slime_scanner.dm b/code/game/objects/items/devices/scanners/slime_scanner.dm new file mode 100644 index 00000000000..5b97ac3d75a --- /dev/null +++ b/code/game/objects/items/devices/scanners/slime_scanner.dm @@ -0,0 +1,54 @@ +/obj/item/slime_scanner + name = "slime scanner" + desc = "A device that analyzes a slime's internal composition and measures its stats." + icon = 'icons/obj/device.dmi' + icon_state = "adv_spectrometer" + inhand_icon_state = "analyzer" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + throwforce = 0 + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) + +/obj/item/slime_scanner/attack(mob/living/M, mob/living/user) + if(user.stat || user.is_blind()) + return + if (!isslime(M)) + to_chat(user, span_warning("This device can only scan slimes!")) + return + var/mob/living/simple_animal/slime/T = M + slime_scan(T, user) + +/proc/slime_scan(mob/living/simple_animal/slime/T, mob/living/user) + var/to_render = "========================\ + \nSlime scan results:\ + \n[span_notice("[T.colour] [T.is_adult ? "adult" : "baby"] slime")]\ + \nNutrition: [T.nutrition]/[T.get_max_nutrition()]" + if (T.nutrition < T.get_starve_nutrition()) + to_render += "\n[span_warning("Warning: slime is starving!")]" + else if (T.nutrition < T.get_hunger_nutrition()) + to_render += "\n[span_warning("Warning: slime is hungry")]" + to_render += "\nElectric change strength: [T.powerlevel]\nHealth: [round(T.health/T.maxHealth,0.01)*100]%" + if (T.slime_mutation[4] == T.colour) + to_render += "\nThis slime does not evolve any further." + else + if (T.slime_mutation[3] == T.slime_mutation[4]) + if (T.slime_mutation[2] == T.slime_mutation[1]) + to_render += "\nPossible mutation: [T.slime_mutation[3]]\ + \nGenetic destability: [T.mutation_chance/2] % chance of mutation on splitting" + else + to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)\ + \nGenetic destability: [T.mutation_chance] % chance of mutation on splitting" + else + to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]\ + \nGenetic destability: [T.mutation_chance] % chance of mutation on splitting" + if (T.cores > 1) + to_render += "\nMultiple cores detected" + to_render += "\nGrowth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]" + if(T.effectmod) + to_render += "\n[span_notice("Core mutation in progress: [T.effectmod]")]\ + \n[span_notice("Progress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]")]" + to_chat(user, to_render + "\n========================") diff --git a/code/game/objects/items/devices/scanners/t_scanner.dm b/code/game/objects/items/devices/scanners/t_scanner.dm new file mode 100644 index 00000000000..679f8574dea --- /dev/null +++ b/code/game/objects/items/devices/scanners/t_scanner.dm @@ -0,0 +1,58 @@ +/obj/item/t_scanner + name = "\improper T-ray scanner" + desc = "A terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes." + custom_price = PAYCHECK_ASSISTANT * 0.7 + icon = 'icons/obj/device.dmi' + icon_state = "t-ray0" + var/on = FALSE + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + inhand_icon_state = "electronic" + worn_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + custom_materials = list(/datum/material/iron=150) + +/obj/item/t_scanner/suicide_act(mob/living/carbon/user) + user.visible_message(span_suicide("[user] begins to emit terahertz-rays into [user.p_their()] brain with [src]! It looks like [user.p_theyre()] trying to commit suicide!")) + return TOXLOSS + +/obj/item/t_scanner/proc/toggle_on() + on = !on + icon_state = copytext_char(icon_state, 1, -1) + "[on]" + if(on) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + +/obj/item/t_scanner/attack_self(mob/user) + toggle_on() + +/obj/item/t_scanner/cyborg_unequip(mob/user) + if(!on) + return + toggle_on() + +/obj/item/t_scanner/process() + if(!on) + STOP_PROCESSING(SSobj, src) + return null + scan() + +/obj/item/t_scanner/proc/scan() + t_ray_scan(loc) + +/proc/t_ray_scan(mob/viewer, flick_time = 8, distance = 3) + if(!ismob(viewer) || !viewer.client) + return + var/list/t_ray_images = list() + for(var/obj/O in orange(distance, viewer) ) + if(HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE)) + var/image/I = new(loc = get_turf(O)) + var/mutable_appearance/MA = new(O) + MA.alpha = 128 + MA.dir = O.dir + I.appearance = MA + t_ray_images += I + if(t_ray_images.len) + flick_overlay(t_ray_images, list(viewer.client), flick_time) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 95fbd5c2f77..2537f5b5760 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -356,8 +356,8 @@ return FALSE return TRUE -/obj/analyzer_act(mob/living/user, obj/item/I) - if(atmosanalyzer_scan(user, src)) +/obj/analyzer_act(mob/living/user, obj/item/analyzer/tool) + if(atmos_scan(user=user, target=src, tool=tool, silent=FALSE)) return TRUE return ..() diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index c811894944f..5aa29a80ad3 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -34,7 +34,7 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that var/turf/T = get_turf(mob) if(!isturf(T)) return - atmosanalyzer_scan(usr, T, TRUE) + atmos_scan(user=usr, target=T, tool=null, silent=TRUE) SSblackbox.record_feedback("tally", "admin_verb", 1, "Air Status In Location") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/cmd_admin_robotize(mob/M in GLOB.mob_list) diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm index 336a9228a65..294d6e2e84f 100644 --- a/code/modules/admin/verbs/diagnostics.dm +++ b/code/modules/admin/verbs/diagnostics.dm @@ -4,7 +4,7 @@ if(!isturf(target)) return - atmosanalyzer_scan(usr, target, TRUE) + atmos_scan(user=usr, target=target, tool=null, silent=TRUE) SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Air Status") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/fix_next_move() diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index cfe07506a9b..c7d96d7c721 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -28,8 +28,6 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) var/last_share = 0 /// Tells us what reactions have happened in our gasmix. Assoc list of reaction - moles reacted pair. var/list/reaction_results - /// Used for analyzer feedback - not initialized until its used - var/list/analyzer_results /// Whether to call garbage_collect() on the sharer during shares, used for immutable mixtures var/gc_share = FALSE diff --git a/code/modules/atmospherics/gasmixtures/gas_types.dm b/code/modules/atmospherics/gasmixtures/gas_types.dm index 2607eb9afda..d1b97d5338c 100644 --- a/code/modules/atmospherics/gasmixtures/gas_types.dm +++ b/code/modules/atmospherics/gasmixtures/gas_types.dm @@ -5,7 +5,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g /proc/meta_gas_list() . = subtypesof(/datum/gas) for(var/gas_path in .) - var/list/gas_info = new(7) + var/list/gas_info = new(8) var/datum/gas/gas = gas_path gas_info[META_GAS_SPECIFIC_HEAT] = initial(gas.specific_heat) @@ -20,6 +20,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g 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 /proc/gas_id2path(id) @@ -51,6 +52,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g var/rarity = 0 // relative rarity compared to other gases, used when setting up the reactions list. var/purchaseable = FALSE var/base_value = 0 + var/desc /datum/gas/oxygen id = "o2" @@ -59,6 +61,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g rarity = 900 purchaseable = TRUE base_value = 0.2 + desc = "The gas most life forms need to be able to survive. Also an oxidizer." /datum/gas/nitrogen id = "n2" @@ -67,6 +70,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g rarity = 1000 purchaseable = TRUE base_value = 0.1 + desc = "A very common gas that used to pad artifical atmospheres to habitable pressure." /datum/gas/carbon_dioxide //what the fuck is this? id = "co2" @@ -76,6 +80,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g rarity = 700 purchaseable = TRUE base_value = 0.2 + desc = "What the fuck is carbon dioxide?" /datum/gas/plasma id = "plasma" @@ -86,6 +91,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g dangerous = TRUE rarity = 800 base_value = 2 + desc = "A flammable gas with many other curious properties. It's research is one of NT's primary objective." /datum/gas/water_vapor id = "water_vapor" @@ -97,6 +103,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g rarity = 500 purchaseable = TRUE base_value = 0.5 + desc = "Water, in gas form. Makes things slippery." /datum/gas/hypernoblium id = "nob" @@ -108,6 +115,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g fusion_power = 10 rarity = 50 base_value = 5 + desc = "The most noble gas of them all. High quantities of hyper-noblium actively prevents reactions from occuring." /datum/gas/nitrous_oxide id = "n2o" @@ -120,6 +128,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g rarity = 600 purchaseable = TRUE base_value = 3 + desc = "Causes drowsiness, euphoria, and eventually unconsciousness." /datum/gas/nitrium id = "nitrium" @@ -131,6 +140,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g dangerous = TRUE rarity = 1 base_value = 100 + desc = "An experimental performance enhancing gas. Nitrium can have amplified effects as more of it gets into your bloodstream." /datum/gas/tritium id = "tritium" @@ -142,6 +152,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g fusion_power = 5 rarity = 300 base_value = 5 + desc = "A highly flammable and radioctive gas." /datum/gas/bz id = "bz" @@ -152,6 +163,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g rarity = 400 purchaseable = TRUE base_value = 2 + desc = "A powerful hallucinogenic nerve agent able to induce cognitive damage." /datum/gas/pluoxium id = "pluox" @@ -160,6 +172,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g fusion_power = -10 rarity = 200 base_value = 5 + desc = "A gas that could supply even more oxygen to the bloodstream when inhaled, without being an oxidizer." /datum/gas/miasma id = "miasma" @@ -170,6 +183,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g moles_visible = MOLES_GAS_VISIBLE * 60 rarity = 250 base_value = 2 + desc = "Not necessarily a gas, miasma refers to biological pollutants found in the atmosphere." /datum/gas/freon id = "freon" @@ -181,6 +195,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g fusion_power = -5 rarity = 10 base_value = 15 + desc = "A coolant gas. Mainly used for it's endothermic reaction with oxygen." /datum/gas/hydrogen id = "hydrogen" @@ -190,6 +205,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g fusion_power = 2 rarity = 600 base_value = 1 + desc = "A highly flammable gas." /datum/gas/healium id = "healium" @@ -200,6 +216,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g moles_visible = MOLES_GAS_VISIBLE rarity = 300 base_value = 19 + desc = "Causes deep, regenerative sleep." /datum/gas/proto_nitrate id = "proto_nitrate" @@ -210,6 +227,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g moles_visible = MOLES_GAS_VISIBLE rarity = 200 base_value = 5 + desc = "A very volatile gas that reacts differently with various gases." /datum/gas/zauker id = "zauker" @@ -220,6 +238,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g moles_visible = MOLES_GAS_VISIBLE rarity = 1 base_value = 100 + 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." /datum/gas/halon id = "halon" @@ -230,6 +249,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g moles_visible = MOLES_GAS_VISIBLE rarity = 300 base_value = 9 + desc = "A potent fire supressant. Removes oxygen from high temperature fires and cools down the area" /datum/gas/helium id = "helium" @@ -238,6 +258,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g fusion_power = 7 rarity = 50 base_value = 6 + desc = "A very inert gas produced by the fusion of hydrogen and it's derivatives." /datum/gas/antinoblium id = "antinoblium" @@ -249,6 +270,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g fusion_power = 20 rarity = 1 base_value = 10 + desc = "We still don't know what it does, but it sells for a lot." /obj/effect/overlay/gas icon = 'icons/effects/atmospherics.dmi' diff --git a/code/modules/atmospherics/gasmixtures/reaction_factors.dm b/code/modules/atmospherics/gasmixtures/reaction_factors.dm new file mode 100644 index 00000000000..5b546cca843 --- /dev/null +++ b/code/modules/atmospherics/gasmixtures/reaction_factors.dm @@ -0,0 +1,216 @@ +/datum/gas_reaction/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_reaction/miaster/init_factors() + factor = list( + /datum/gas/miasma = "Miasma is consumed at 1 reaction rate.", + /datum/gas/oxygen = "Oxygen is produced at 1 reaction rate.", + "Temperature" = "Higher temperature increases the reaction rate.", + "Energy" = "[MIASTER_STERILIZATION_ENERGY] joules of energy is released per rate.", + ) + +/datum/gas_reaction/plasmafire/init_factors() + factor = list( + /datum/gas/oxygen = "Oxygen consumption is determined by the temperature, ranging from [OXYGEN_BURN_RATIO_BASE] of the reaction rate at [PLASMA_MINIMUM_BURN_TEMPERATURE] Kelvins to [OXYGEN_BURN_RATIO_BASE-1] at [PLASMA_UPPER_TEMPERATURE] Kelvins. Higher oxygen concentration up to [PLASMA_OXYGEN_FULLBURN]x times the plasma increases the reaction rate.", + /datum/gas/plasma = "Plasma is consumed at 1 reaction rate. It's relationship with oxygen also determines reaction speed", + /datum/gas/tritium = "Tritium is formed at 1 reaction rate if there are 97 times more oxygen than plasma.", + /datum/gas/water_vapor = "Water vapor is formed at 0.25 reaction rate if tritium isn't being formed.", + /datum/gas/carbon_dioxide = "Carbon Dioxide is formed at 0.75 reaction rate if tritium isn't being formed.", + "Temperature" = "Minimum temperature of [PLASMA_MINIMUM_BURN_TEMPERATURE] kelvin to occur. Higher temperature up to [PLASMA_UPPER_TEMPERATURE] increases the oxygen efficiency and also the reaction rate.", + "Energy" = "[FIRE_PLASMA_ENERGY_RELEASED] joules of energy is released per reaction rate", + ) + +/datum/gas_reaction/h2fire/init_factors() + factor = list( + /datum/gas/oxygen = "Oxygen is consumed equal to the amount of hydrogen available on the fast burn. Not consumed on the slow burn. Needs to be more than the hydrogen amount to trigger fast burn. Acts as the reaction rate on slow burn.", + /datum/gas/hydrogen = "[(1/HYDROGEN_BURN_H2_FACTOR)*100]% of the hydrogen is always consumed on the fast burn. [(1/HYDROGEN_BURN_OXY_FACTOR)*100]% of the oxygen amount is consumed on the slow burn. Need to be less than the oxygen amount to trigger fast burn. Acts as the reaction rate on fast burn.", + /datum/gas/water_vapor = "Water vapor is formed at [1/HYDROGEN_BURN_H2_FACTOR] reaction rate for the fast burn, [1/HYDROGEN_BURN_OXY_FACTOR]% reaction rate for the slow burn.", + "Temperature" = "Minimum temperature of [FIRE_MINIMUM_TEMPERATURE_TO_EXIST] kelvin to occur", + "Energy" = "[FIRE_HYDROGEN_ENERGY_RELEASED*HYDROGEN_OXYBURN_MULTIPLIER] joules of energy is released per rate for the fast burn, [FIRE_HYDROGEN_ENERGY_RELEASED] joules for the slow burn. Needs [MINIMUM_TRITIUM_OXYBURN_ENERGY] joules to start the fast burn.", + ) + +/datum/gas_reaction/tritfire/init_factors() + factor = list( + /datum/gas/oxygen = "Oxygen is consumed equal to the amount of tritium available on the fast burn. Not consumed on the slow burn. Need to be more than the tritium amount to trigger fast burn. Acts as the reaction rate on slow burn.", + /datum/gas/tritium = "[(1/TRITIUM_BURN_TRIT_FACTOR)*100]% of the tritium is always consumed on the fast burn. [(1/TRITIUM_BURN_OXY_FACTOR)*100]% of the oxygen amount is consumed on the slow burn. Need to be less than the oxygen amount to trigger fast burn. Acts as the reaction rate on fast burn.", + /datum/gas/water_vapor = "Water vapor is formed at [1/TRITIUM_BURN_TRIT_FACTOR]% reaction rate for the fast burn, [1/TRITIUM_BURN_OXY_FACTOR]% reaction rate for the slow burn.", + "Temperature" = "Minimum temperature of [FIRE_MINIMUM_TEMPERATURE_TO_EXIST] kelvin to occur", + "Energy" = "[FIRE_TRITIUM_ENERGY_RELEASED*TRITIUM_OXYBURN_MULTIPLIER] joules of energy is released per rate for the fast burn, [FIRE_TRITIUM_ENERGY_RELEASED] joules for the slow burn. Needs [MINIMUM_TRITIUM_OXYBURN_ENERGY] joules to start the fast burn.", + "Radiation" = "This reaction emits radiation proportional to the amount of energy released.", + ) + +/datum/gas_reaction/freonfire/init_factors() + factor = list( + /datum/gas/oxygen = "Oxygen consumption is determined by the temperature, ranging from [OXYGEN_BURN_RATIO_BASE] of the reaction rate at [FREON_LOWER_TEMPERATURE] Kelvins to [OXYGEN_BURN_RATIO_BASE-1] at [FREON_MAXIMUM_BURN_TEMPERATURE] Kelvins. Higher oxygen concentration up to [FREON_OXYGEN_FULLBURN] times the freon increases the reaction rate.", + /datum/gas/freon = "Freon is consumed at 1 reaction rate. It's relationship with oxygen also determines reaction speed", + /datum/gas/carbon_dioxide = "Carbon Dioxide is formed at 1 reaction rate.", + "Temperature" = "Can only occur between [FREON_LOWER_TEMPERATURE] - [FREON_MAXIMUM_BURN_TEMPERATURE] Kelvin", + "Energy" = "[FIRE_FREON_ENERGY_CONSUMED] joules of energy is absorbed per reaction rate", + "Hot Ice" = "This reaction has a small chance to produce hot ice when occuring between [HOT_ICE_FORMATION_MINIMUM_TEMPERATURE]-[HOT_ICE_FORMATION_MAXIMUM_TEMPERATURE] kelvins", + ) + + +/datum/gas_reaction/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 1 reaction rate", + /datum/gas/nitrogen = " 20 moles of Nitrogen needs to be present for the reaction to occur. Nitrogen is consumed at 2 reaction rate", + /datum/gas/bz = "5 moles of BZ needs to be present for the reaction to occur. Not consumed.", + /datum/gas/nitrous_oxide = "Nitrous oxide is produced at 1 reaction rate", + "Temperature" = "Can only occur between [N2O_FORMATION_MIN_TEMPERATURE] - [N2O_FORMATION_MAX_TEMPERATURE] Kelvin", + "Energy" = "[N2O_FORMATION_ENERGY] joules of energy is released per reaction rate", + ) + +/datum/gas_reaction/nitrous_decomp/init_factors() + factor = list( + /datum/gas/nitrous_oxide = "Nitrous Oxide is consumed at 1 reaction rate. 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 reaction rate", + /datum/gas/nitrogen = "Nitrogen is formed at 1 reaction rate", + "Temperature" = "Higher temperature increases the reaction rate. Can only happen between [N2O_DECOMPOSITION_MIN_TEMPERATURE] - [N2O_DECOMPOSITION_MAX_TEMPERATURE] kelvin.", + "Energy" = "[N2O_DECOMPOSITION_ENERGY] joules of energy is released per reaction rate", + ) + +/datum/gas_reaction/bzformation/init_factors() + factor = list( + /datum/gas/plasma = "Plasma is consumed at 2 reaction rate. If there is more plasma than nitrous oxide reaction rate is slowed down.", + /datum/gas/nitrous_oxide = "Nitrous oxide is consumed at 1 reaction rate. If there is less nitrous oxide than plasma the reaction rate is slowed down.", + /datum/gas/bz = "BZ is formed at 2.5 reaction rate. A small malus up to half a mole per tick is applied if the reaction rate is constricted by nitrous oxide.", + /datum/gas/oxygen = "Oxygen is produced from the BZ malus. This only happens when the reaction rate is being constricted by the amount of nitrous oxide present. I.E. amount of nitrous oxide is less than the reaction rate.", // Less than the reaction rate AND half the plasma, but suppose that's not necessary to mention. + "Pressure" = "The lower the pressure the faster the reaction rate goes.", + "Energy" = "[FIRE_CARBON_ENERGY_RELEASED] joules of energy is released per reaction rate", + ) + +/datum/gas_reaction/pluox_formation/init_factors() + factor = list( + /datum/gas/carbon_dioxide = "Carbon dioxide is consumed at 1 reaction rate", + /datum/gas/oxygen = "Oxygen is consumed at 0.5 reaction rate", + /datum/gas/tritium = "Tritium is consumed at 0.01 reaction rate", + /datum/gas/pluoxium = "Pluoxium is produced at 1 reaction rate", + /datum/gas/hydrogen = "Hydrogen is produced at 0.01 reaction rate", + "Energy" = "[PLUOXIUM_FORMATION_ENERGY] joules of energy is released per reaction rate", + "Temperature" = "Can only occur between [PLUOXIUM_FORMATION_MIN_TEMP] - [PLUOXIUM_FORMATION_MAX_TEMP] Kelvin", + ) + +/datum/gas_reaction/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 reaction rate.", + /datum/gas/tritium = "20 moles of tritium needs to be present for the reaction to occur. Tritium is consumed at 1 reaction rate", + /datum/gas/nitrogen = "10 moles of tritium needs to be present for the reaction to occur. Nitrogen is consumed at 1 reaction rate", + /datum/gas/nitrium = "Nitrium is produced at 1 reaction rate", + "Temperature" = "Can only occur above [NITRIUM_FORMATION_MIN_TEMP] kelvins", + "Energy" = "[NITRIUM_FORMATION_ENERGY] joules of energy is absorbed per reaction rate", + ) + +/datum/gas_reaction/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 1 reaction rate", + /datum/gas/hydrogen = "Hydrogen is produced at 1 reaction rate", + /datum/gas/nitrogen = "Nitrogen is produced at 1 reaction rate", + "Temperature" = " Can only occur below [NITRIUM_DECOMPOSITION_MAX_TEMP]. Higher temperature increases the reaction rate.", + "Energy" = "[NITRIUM_DECOMPOSITION_ENERGY] joules of energy is released per reaction rate", + ) + +/datum/gas_reaction/freonformation/init_factors() + factor = list( + /datum/gas/plasma = "40 moles of plasma needs to be present for the reaction to occur. Plasma is consumed at 1.5 reaction rate.", + /datum/gas/carbon_dioxide = "20 moles of carbon dioxide needs to be present for the reaction to occur. Carbon dioxide is consumed at 0.75 reaction rate.", + /datum/gas/bz = "20 moles of BZ needs to be present for the reaction to occur. BZ is consumed at 0.25 reaction rate.", + /datum/gas/freon = "Freon is produced at 2.5 reaction rate", + "Energy" = "[FREON_FORMATION_ENERGY] joules of energy is absorbed per reaction rate", + "Temperature" = "Minimum temperature of [FIRE_MINIMUM_TEMPERATURE_TO_EXIST + 100] Kelvin to occur", + ) + +/datum/gas_reaction/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 reaction rate", + /datum/gas/tritium = "5 moles of tritium needs to be present for the reaction to occur. Tritium is consumed at 5 reaction rate", + /datum/gas/hypernoblium = "Hyper-Noblium is produced at 1 reaction rate", + "Energy" = "[NOBLIUM_FORMATION_ENERGY] joules of energy is released per reaction rate.", + /datum/gas/bz = "BZ is not consumed in the reaction but will lower the amount of energy released", + "Temperature" = "Can only occur between [NOBLIUM_FORMATION_MIN_TEMP] - [NOBLIUM_FORMATION_MAX_TEMP] kelvin", + ) + +/datum/gas_reaction/halon_formation/init_factors() + factor = list( + /datum/gas/bz = "BZ is consumed at 0.25 reaction rate", + /datum/gas/tritium = "Tritium is consumed at 4 reaction rate", + /datum/gas/halon = "Halon is produced at 4.25 reaction rate", + "Energy" = "[HALON_FORMATION_ENERGY] joules of energy is released per reaction rate", + "Temperature" = "Can only occur between [HALON_FORMATION_MIN_TEMPERATURE] - [HALON_FORMATION_MAX_TEMPERATURE] kelvin", + ) + +/datum/gas_reaction/halon_o2removal/init_factors() + factor = list( + /datum/gas/halon = "Halon is consumed at 1 reaction rate", + /datum/gas/oxygen = "Oxygen is consumed at 20 reaction rate", + /datum/gas/carbon_dioxide = "Carbon dioxide is produced at 5 reaction rate.", + "Energy" = "[HALON_COMBUSTION_ENERGY] joules of energy is absorbed per reaction rate", + "Temperature" = "Can only occur above [FIRE_MINIMUM_TEMPERATURE_TO_EXIST] kelvin. Higher temperature increases the reaction rate.", + ) + +/datum/gas_reaction/healium_formation/init_factors() + factor = list( + /datum/gas/bz = "BZ is consumed at 0.25 reaction rate", + /datum/gas/freon = "Freon is consumed at 2.75 reaction rate", + /datum/gas/healium = "Healium is produced at 3 reaction rate", + "Temperature" = "Can only occur between [HEALIUM_FORMATION_MIN_TEMP] - [HEALIUM_FORMATION_MAX_TEMP]. Higher temperature increases the reaction rate.", + "Energy" = "[HEALIUM_FORMATION_ENERGY] joules of energy is released per reaction rate.", + ) + +/datum/gas_reaction/zauker_formation/init_factors() + factor = list( + /datum/gas/hypernoblium = "Hyper-Noblium is consumed at 0.01 reaction rate", + /datum/gas/nitrium = "Nitrium is consumed at 0.5 reaction rate", + /datum/gas/zauker = "Zauker is produced at 0.5 reaction rate", + "Temperature" = "Can only occur between [ZAUKER_FORMATION_MIN_TEMPERATURE] - [ZAUKER_FORMATION_MAX_TEMPERATURE] kelvin", + "Energy" = "[ZAUKER_FORMATION_ENERGY] joules of energy is absorbed per reaction rate", + ) + +/datum/gas_reaction/zauker_decomp/init_factors() //Fixed reaction rate + factor = list( + /datum/gas/zauker = "Zauker is consumed at 1 reaction rate", + /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 reaction rate", + /datum/gas/oxygen = "Oxygen is produced at 0.3 reaction rate", + "Energy" = "[ZAUKER_DECOMPOSITION_ENERGY] joules of energy is released per reaction rate", + ) + +/datum/gas_reaction/proto_nitrate_formation/init_factors() + factor = list( + /datum/gas/pluoxium = "Pluoxium is consumed at 0.2 reaction rate", + /datum/gas/hydrogen = "Hydrogen is consumed at 2 reaction rate", + /datum/gas/proto_nitrate = "Proto-Nitrate is produced at 2.2 reaction rate", + "Energy" = "[PN_FORMATION_ENERGY] joules of energy is released per reaction rate", + "Temperature" = "Can only occur between [PN_FORMATION_MIN_TEMPERATURE] - [PN_FORMATION_MAX_TEMPERATURE] kelvin. Higher temperature increases the reaction rate.", + ) + +/datum/gas_reaction/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 1 reaction rate.", + /datum/gas/proto_nitrate = "[MINIMUM_MOLE_COUNT] moles of proto-nitrate needs to be present for the reaction to occur. Proto nitrate is produced at 0.5 reaction rate.", + "Energy" = "[PN_HYDROGEN_CONVERSION_ENERGY] joules of energy is absorbed per reaction rate", + ) + +/datum/gas_reaction/proto_nitrate_tritium_response/init_factors() // Fixed reaction rate + factor = list( + /datum/gas/tritium = "Tritium is consumed at 1 reaction rate.", + /datum/gas/proto_nitrate = "Proto nitrate is consumed at 0.01 reaction rate.", + /datum/gas/hydrogen = "Hydrogen is produced at 1 reaction rate.", + "Energy" = "[PN_TRITIUM_CONVERSION_ENERGY] joules of energy is released per reaction rate", + "Radiation" = "This reaction emits radiation proportional to the reaction rate.", + ) + +/datum/gas_reaction/proto_nitrate_bz_response/init_factors() // Fixed reaction rate + factor = list( + /datum/gas/proto_nitrate = "[MINIMUM_MOLE_COUNT] moles of proto-nitrate needs to be present for the reaction to occur", + /datum/gas/bz = "BZ is consumed at 1 reaction rate.", + /datum/gas/nitrogen = "Nitrogen is produced at 0.4 reaction rate.", + /datum/gas/helium = "Helium is produced at 1.6 reaction rate.", + /datum/gas/plasma = "Plasma is produced at 0.8 reaction rate.", + "Energy" = "[PN_BZASE_ENERGY] joules of energy is released per reaction rate", + "Radiation" = "This reaction emits radiation proportional to the reaction rate.", + "Hallucinations" = "This reaction can cause various carbon based lifeforms in the vicinity to hallucinate.", + ) diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index 43fcc7f579d..7f07541b3f6 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -55,11 +55,28 @@ 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. + * Also include gases formed by the reaction + * Implement various interaction for different keys under subsystem/air/proc/atmos_handbook_init() + * + * E.G. + * factor["Temperature"] = "Minimum temperature of 20 kelvins, maximum temperature of 100 kelvins" + * factor["o2"] = "Minimum oxygen amount of 20 moles, more oxygen increases reaction rate up to 150 moles" + */ + var/list/factor /datum/gas_reaction/New() init_reqs() + init_factors() -/datum/gas_reaction/proc/init_reqs() +/datum/gas_reaction/proc/init_reqs() // Override this + CRASH("Reaction [type] made without specifying requirements.") + +/datum/gas_reaction/proc/init_factors() /datum/gas_reaction/proc/react(datum/gas_mixture/air, atom/location) return NO_REACTION @@ -73,8 +90,9 @@ */ /datum/gas_reaction/water_vapor priority_group = PRIORITY_POST_FORMATION - name = "Water Vapor" + name = "Water Vapor Condensation" id = "vapor" + desc = "Water vapor condensation that can make things slippery." /datum/gas_reaction/water_vapor/init_reqs() requirements = list( @@ -109,6 +127,7 @@ 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() requirements = list( @@ -149,6 +168,7 @@ name = "Plasma Combustion" id = "plasmafire" expands_hotspot = TRUE + desc = "Combustion of oxygen and plasma. Able to produce tritium or carbon dioxade and water vapor." /datum/gas_reaction/plasmafire/init_reqs() requirements = list( @@ -225,6 +245,7 @@ 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." /datum/gas_reaction/h2fire/init_reqs() requirements = list( @@ -287,6 +308,7 @@ 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." /datum/gas_reaction/tritfire/init_reqs() requirements = list( @@ -355,8 +377,9 @@ */ /datum/gas_reaction/freonfire priority_group = PRIORITY_FIRE - name = "Freon combustion" + name = "Freon Combustion" id = "freonfire" + desc = "Reaction between oxygen and freon that consumes a huge amount of energy and can cool things significantly. Also able to produce hot ice." /datum/gas_reaction/freonfire/init_reqs() requirements = list( @@ -418,10 +441,11 @@ * Endothermic. * Requires BZ as a catalyst. */ -/datum/gas_reaction/nitrousformation //formationn of n2o, exothermic, requires bz as catalyst +/datum/gas_reaction/nitrousformation //formation of n2o, exothermic, requires bz as catalyst priority_group = PRIORITY_FORMATION - name = "Nitrous Oxide formation" + name = "Nitrous Oxide Formation" id = "nitrousformation" + desc = "Production of nitrous oxide with BZ as a catalyst." /datum/gas_reaction/nitrousformation/init_reqs() requirements = list( @@ -445,10 +469,10 @@ cached_gases[/datum/gas/nitrous_oxide][MOLES] += heat_efficency SET_REACTION_RESULTS(heat_efficency) - var/energy_used = heat_efficency * N2O_FORMATION_ENERGY + var/energy_released = heat_efficency * 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_used) / new_heat_capacity), TCMB) // The air cools down when reacting. + air.temperature = max(((air.temperature * old_heat_capacity + energy_released) / new_heat_capacity), TCMB) // The air cools down when reacting. return REACTING @@ -462,6 +486,7 @@ priority_group = PRIORITY_POST_FORMATION name = "Nitrous Oxide Decomposition" id = "nitrous_decomp" + desc = "Decomposition of nitrous oxide under high temperature." /datum/gas_reaction/nitrous_decomp/init_reqs() requirements = list( @@ -504,8 +529,9 @@ */ /datum/gas_reaction/bzformation priority_group = PRIORITY_FORMATION - name = "BZ Gas formation" + name = "BZ Gas Formation" id = "bzformation" + desc = "Production of BZ using plasma and nitrous oxide." /datum/gas_reaction/bzformation/init_reqs() requirements = list( @@ -518,7 +544,12 @@ var/list/cached_gases = air.gases var/pressure = air.return_pressure() // This slows down in relation to pressure, very quickly. Please don't expect it to be anything more then a snail - var/reaction_efficency = min(1 / ((pressure / (0.1 * ONE_ATMOSPHERE)) * (max(cached_gases[/datum/gas/plasma][MOLES] / cached_gases[/datum/gas/nitrous_oxide][MOLES], 1))), cached_gases[/datum/gas/nitrous_oxide][MOLES], cached_gases[/datum/gas/plasma][MOLES] * INVERSE(2)) + + // Bigger is better for these two values. + var/pressure_efficiency = (0.1 * ONE_ATMOSPHERE) / pressure // More pressure = more bad + var/ratio_efficiency = min(cached_gases[/datum/gas/nitrous_oxide][MOLES] / cached_gases[/datum/gas/plasma][MOLES], 1) // Malus to production if more plasma than n2o. + + var/reaction_efficency = min(pressure_efficiency * ratio_efficiency, cached_gases[/datum/gas/nitrous_oxide][MOLES], cached_gases[/datum/gas/plasma][MOLES] * INVERSE(2)) if ((cached_gases[/datum/gas/nitrous_oxide][MOLES] - reaction_efficency < 0 )|| (cached_gases[/datum/gas/plasma][MOLES] - (2 * reaction_efficency) < 0) || reaction_efficency <= 0) //Shouldn't produce gas from nothing. return NO_REACTION @@ -553,8 +584,9 @@ */ /datum/gas_reaction/pluox_formation priority_group = PRIORITY_FORMATION - name = "Pluoxium formation" + name = "Pluoxium Formation" id = "pluox_formation" + desc = "Alternate production for pluoxium which uses tritium." /datum/gas_reaction/pluox_formation/init_reqs() requirements = list( @@ -599,8 +631,9 @@ */ /datum/gas_reaction/nitrium_formation priority_group = PRIORITY_FORMATION - name = "Nitrium formation" + name = "Nitrium Formation" id = "nitrium_formation" + desc = "Production of nitrium from BZ, tritium, and nitrogen." /datum/gas_reaction/nitrium_formation/init_reqs() requirements = list( @@ -644,6 +677,7 @@ priority_group = PRIORITY_PRE_FORMATION name = "Nitrium Decomposition" id = "nitrium_decomp" + desc = "Decomposition of nitrium when exposed to oxygen under normal temperatures." /datum/gas_reaction/nitrium_decomposition/init_reqs() requirements = list( @@ -669,10 +703,10 @@ cached_gases[/datum/gas/nitrogen][MOLES] += heat_efficency SET_REACTION_RESULTS(heat_efficency) - var/energy_produced = heat_efficency * NITRIUM_DECOMPOSITION_ENERGY + var/energy_released = heat_efficency * 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_produced) / new_heat_capacity), TCMB) //the air heats up when reacting + air.temperature = max(((temperature * old_heat_capacity + energy_released) / new_heat_capacity), TCMB) //the air heats up when reacting return REACTING @@ -684,8 +718,9 @@ */ /datum/gas_reaction/freonformation priority_group = PRIORITY_FORMATION - name = "Freon formation" + name = "Freon Formation" id = "freonformation" + desc = "Production of freon using plasma, carbon dioxide, and BZ under high temperature." /datum/gas_reaction/freonformation/init_reqs() //minimum requirements for freon formation requirements = list( @@ -722,13 +757,14 @@ * * Extremely exothermic. * Requires very low temperatures. - * Due to its high mass, hyper-nobelium uses large amounts of nitrogen and tritium. + * 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 priority_group = PRIORITY_FORMATION - name = "Hyper-Noblium condensation" + name = "Hyper-Noblium Condensation" id = "nobformation" + desc = "Production of hyper-noblium from nitrogen and tritium under very low temperatures. Extremely energetic." /datum/gas_reaction/nobliumformation/init_reqs() requirements = list( @@ -752,10 +788,10 @@ 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_produced = nob_formed * (NOBLIUM_FORMATION_ENERGY / (max(cached_gases[/datum/gas/bz][MOLES], 1))) + var/energy_released = nob_formed * (NOBLIUM_FORMATION_ENERGY / (max(cached_gases[/datum/gas/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_produced) / new_heat_capacity), TCMB) + air.temperature = max(((air.temperature * old_heat_capacity + energy_released) / new_heat_capacity), TCMB) return REACTING @@ -768,8 +804,9 @@ */ /datum/gas_reaction/halon_formation priority_group = PRIORITY_FORMATION - name = "Halon formation" + name = "Halon Formation" id = "halon_formation" + desc = "Production of halon from BZ and tritium." /datum/gas_reaction/halon_formation/init_reqs() requirements = list( @@ -793,10 +830,10 @@ cached_gases[/datum/gas/halon][MOLES] += heat_efficency * 4.25 SET_REACTION_RESULTS(heat_efficency * 4.25) - var/energy_used = heat_efficency * HALON_FORMATION_ENERGY + var/energy_released = heat_efficency * HALON_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) + air.temperature = max(((temperature * old_heat_capacity + energy_released) / new_heat_capacity), TCMB) return REACTING @@ -809,8 +846,9 @@ */ /datum/gas_reaction/halon_o2removal priority_group = PRIORITY_PRE_FORMATION - name = "Halon o2 removal" + name = "Halon Oxygen Absorption" id = "halon_o2removal" + desc = "Halon interaction with oxygen that can be used to snuff fires out." /datum/gas_reaction/halon_o2removal/init_reqs() requirements = list( @@ -850,8 +888,9 @@ */ /datum/gas_reaction/healium_formation priority_group = PRIORITY_FORMATION - name = "Healium formation" + name = "Healium Formation" id = "healium_formation" + desc = "Production of healium using BZ and freon." /datum/gas_reaction/healium_formation/init_reqs() requirements = list( @@ -875,10 +914,10 @@ cached_gases[/datum/gas/healium][MOLES] += heat_efficency * 3 SET_REACTION_RESULTS(heat_efficency * 3) - var/energy_used = heat_efficency * HEALIUM_FORMATION_ENERGY + var/energy_released = heat_efficency * 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_used) / new_heat_capacity), TCMB) + air.temperature = max(((temperature * old_heat_capacity + energy_released) / new_heat_capacity), TCMB) return REACTING /** @@ -889,8 +928,9 @@ */ /datum/gas_reaction/zauker_formation priority_group = PRIORITY_FORMATION - name = "Zauker formation" + name = "Zauker Formation" id = "zauker_formation" + desc = "Production of zauker using hyper-noblium and nitrium under very high temperatures." /datum/gas_reaction/zauker_formation/init_reqs() requirements = list( @@ -930,8 +970,9 @@ */ /datum/gas_reaction/zauker_decomp priority_group = PRIORITY_POST_FORMATION - name = "Zauker decomposition" + name = "Zauker Decomposition" id = "zauker_decomp" + desc = "Decomposition of zauker when exposed to nitrogen." /datum/gas_reaction/zauker_decomp/init_reqs() requirements = list( @@ -969,8 +1010,9 @@ */ /datum/gas_reaction/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." /datum/gas_reaction/proto_nitrate_formation/init_reqs() requirements = list( @@ -995,10 +1037,10 @@ cached_gases[/datum/gas/proto_nitrate][MOLES] += heat_efficency * 2.2 SET_REACTION_RESULTS(heat_efficency * 2.2) - var/energy_used = heat_efficency * PN_FORMATION_ENERGY + var/energy_released = heat_efficency * 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_used) / new_heat_capacity), TCMB) + air.temperature = max(((temperature * old_heat_capacity + energy_released) / new_heat_capacity), TCMB) return REACTING /** @@ -1009,8 +1051,9 @@ */ /datum/gas_reaction/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." /datum/gas_reaction/proto_nitrate_hydrogen_response/init_reqs() requirements = list( @@ -1029,10 +1072,10 @@ cached_gases[/datum/gas/proto_nitrate][MOLES] += produced_amount * 0.5 SET_REACTION_RESULTS(produced_amount * 0.5) - var/energy_released = produced_amount * PN_HYDROGEN_CONVERSION_ENERGY + var/energy_used = produced_amount * PN_HYDROGEN_CONVERSION_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) + air.temperature = max((air.temperature * old_heat_capacity - energy_used) / new_heat_capacity, TCMB) return REACTING /** @@ -1044,8 +1087,9 @@ */ /datum/gas_reaction/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." /datum/gas_reaction/proto_nitrate_tritium_response/init_reqs() requirements = list( @@ -1092,8 +1136,9 @@ */ /datum/gas_reaction/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." /datum/gas_reaction/proto_nitrate_bz_response/init_reqs() requirements = list( diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 9bac4961e7e..8692049c791 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -100,6 +100,9 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) else 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]]" + update_window() var/random_quality = rand() @@ -133,7 +136,6 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister/antinoblium name = "Antinoblium canister" - desc = "Antinoblium, we still don't know what it does, but it sells for a lot" gas_type = /datum/gas/antinoblium filled = 1 greyscale_config = /datum/greyscale_config/canister/double_stripe @@ -141,21 +143,18 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister/bz name = "\improper BZ canister" - desc = "BZ, a powerful hallucinogenic nerve agent." gas_type = /datum/gas/bz greyscale_config = /datum/greyscale_config/canister/double_stripe greyscale_colors = "#9b5d7f#d0d2a0" /obj/machinery/portable_atmospherics/canister/carbon_dioxide name = "Carbon dioxide canister" - desc = "Carbon dioxide. What the fuck is carbon dioxide?" gas_type = /datum/gas/carbon_dioxide greyscale_config = /datum/greyscale_config/canister greyscale_colors = "#4e4c48" /obj/machinery/portable_atmospherics/canister/freon name = "Freon canister" - desc = "Freon. Can absorb heat" gas_type = /datum/gas/freon filled = 1 greyscale_config = /datum/greyscale_config/canister/double_stripe @@ -163,7 +162,6 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister/halon name = "Halon canister" - desc = "Halon, removes oxygen from high temperature fires and cools down the area" gas_type = /datum/gas/halon filled = 1 greyscale_config = /datum/greyscale_config/canister/double_stripe @@ -171,7 +169,6 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister/healium name = "Healium canister" - desc = "Healium, causes deep sleep" gas_type = /datum/gas/healium filled = 1 greyscale_config = /datum/greyscale_config/canister/double_stripe @@ -179,7 +176,6 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister/helium name = "Helium canister" - desc = "Helium, inert gas" gas_type = /datum/gas/helium filled = 1 greyscale_config = /datum/greyscale_config/canister/double_stripe @@ -187,7 +183,6 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister/hydrogen name = "Hydrogen canister" - desc = "Hydrogen, highly flammable" gas_type = /datum/gas/hydrogen filled = 1 greyscale_config = /datum/greyscale_config/canister/stripe @@ -195,7 +190,6 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister/miasma name = "Miasma canister" - desc = "Miasma. Makes you wish your nose was blocked." gas_type = /datum/gas/miasma filled = 1 greyscale_config = /datum/greyscale_config/canister/double_stripe @@ -203,49 +197,42 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister/nitrogen name = "Nitrogen canister" - desc = "Nitrogen gas. Reportedly useful for something." gas_type = /datum/gas/nitrogen greyscale_config = /datum/greyscale_config/canister greyscale_colors = "#d41010" /obj/machinery/portable_atmospherics/canister/nitrous_oxide name = "Nitrous oxide canister" - desc = "Nitrous oxide gas. Known to cause drowsiness." gas_type = /datum/gas/nitrous_oxide greyscale_config = /datum/greyscale_config/canister/double_stripe greyscale_colors = "#c63e3b#f7d5d3" /obj/machinery/portable_atmospherics/canister/nitrium name = "Nitrium canister" - desc = "Nitrium gas. Feels great 'til the acid eats your lungs." gas_type = /datum/gas/nitrium greyscale_config = /datum/greyscale_config/canister greyscale_colors = "#7b4732" /obj/machinery/portable_atmospherics/canister/nob name = "Hyper-noblium canister" - desc = "Hyper-Noblium. More noble than all other gases." gas_type = /datum/gas/hypernoblium greyscale_config = /datum/greyscale_config/canister/double_stripe greyscale_colors = "#6399fc#b2b2b2" /obj/machinery/portable_atmospherics/canister/oxygen name = "Oxygen canister" - desc = "Oxygen. Necessary for human life." gas_type = /datum/gas/oxygen greyscale_config = /datum/greyscale_config/canister/stripe greyscale_colors = "#2786e5#e8fefe" /obj/machinery/portable_atmospherics/canister/pluoxium name = "Pluoxium canister" - desc = "Pluoxium. Like oxygen, but more bang for your buck." gas_type = /datum/gas/pluoxium greyscale_config = /datum/greyscale_config/canister greyscale_colors = "#2786e5" /obj/machinery/portable_atmospherics/canister/proto_nitrate name = "Proto Nitrate canister" - desc = "Proto Nitrate, reacts differently with various gases" gas_type = /datum/gas/proto_nitrate filled = 1 greyscale_config = /datum/greyscale_config/canister/double_stripe @@ -253,21 +240,18 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister/plasma name = "Plasma canister" - desc = "Plasma gas. The reason YOU are here. Highly toxic." gas_type = /datum/gas/plasma greyscale_config = /datum/greyscale_config/canister/hazard greyscale_colors = "#f62800#000000" /obj/machinery/portable_atmospherics/canister/tritium name = "Tritium canister" - desc = "Tritium. Inhalation might cause irradiation." gas_type = /datum/gas/tritium greyscale_config = /datum/greyscale_config/canister/hazard greyscale_colors = "#3fcd40#000000" /obj/machinery/portable_atmospherics/canister/water_vapor name = "Water vapor canister" - desc = "Water Vapor. We get it, you vape." gas_type = /datum/gas/water_vapor filled = 1 greyscale_config = /datum/greyscale_config/canister/double_stripe @@ -275,7 +259,6 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister/zauker name = "Zauker canister" - desc = "Zauker, highly toxic" gas_type = /datum/gas/zauker filled = 1 greyscale_config = /datum/greyscale_config/canister/double_stripe diff --git a/code/modules/modular_computers/file_system/programs/atmosscan.dm b/code/modules/modular_computers/file_system/programs/atmosscan.dm index e9ae86d3a3b..fe09b4b971e 100644 --- a/code/modules/modular_computers/file_system/programs/atmosscan.dm +++ b/code/modules/modular_computers/file_system/programs/atmosscan.dm @@ -5,7 +5,7 @@ program_icon_state = "air" extended_desc = "A small built-in sensor reads out the atmospheric conditions around the device." size = 4 - tgui_id = "NtosAtmos" + tgui_id = "NtosGasAnalyzer" program_icon = "thermometer-half" /datum/computer_file/program/atmosscan/run_program(mob/living/user) @@ -15,31 +15,20 @@ if(!computer?.get_modular_computer_part(MC_SENSORS)) //Giving a clue to users why the program is spitting out zeros. to_chat(user, span_warning("\The [computer] flashes an error: \"hardware\\sensorpackage\\startup.bin -- file not found\".")) +/datum/computer_file/program/atmosscan/ui_static_data(mob/user) + return return_atmos_handbooks() /datum/computer_file/program/atmosscan/ui_data(mob/user) var/list/data = get_header_data() - var/list/airlist = list() - var/turf/T = get_turf(ui_host()) - var/obj/item/computer_hardware/sensorpackage/sensors = computer?.get_modular_computer_part(MC_SENSORS) - if(T && sensors?.check_functionality()) - var/datum/gas_mixture/environment = T.return_air() - var/list/env_gases = environment.gases - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - data["AirPressure"] = round(pressure,0.1) - data["AirTempC"] = round(environment.temperature-T0C) - data["AirTempK"] = round(environment.temperature) - if (total_moles) - for(var/id in env_gases) - var/gas_level = env_gases[id][MOLES]/total_moles - if(gas_level > 0) - airlist += list(list("name" = "[env_gases[id][GAS_META][META_GAS_NAME]]", "percentage" = round(gas_level*100, 0.01))) - data["AirData"] = airlist - else - data["AirPressure"] = 0 - data["AirTempC"] = 0 - data["AirTempK"] = 0 - data["AirData"] = list(list()) + var/turf/turf = get_turf(computer) + var/datum/gas_mixture/air = turf?.return_air() + var/obj/item/computer_hardware/sensorpackage/air_sensor = computer?.get_modular_computer_part(MC_SENSORS) + + if(!air_sensor) + data["gasmixes"] = list(gas_mixture_parser(null, "No Sensors Detected!")) + return data + + data["gasmixes"] = list(gas_mixture_parser(air, "Sensor Reading")) //Null air wont cause errors, don't worry. return data /datum/computer_file/program/atmosscan/ui_act(action, list/params) diff --git a/tgstation.dme b/tgstation.dme index 301e3608a9b..91c1eb9c885 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -731,7 +731,6 @@ #include "code\datums\components\areabound.dm" #include "code\datums\components\armor_plate.dm" #include "code\datums\components\atmos_reaction_recorder.dm" -#include "code\datums\components\atmospheric_scanner.dm" #include "code\datums\components\aura_healing.dm" #include "code\datums\components\bakeable.dm" #include "code\datums\components\beetlejuice.dm" @@ -1577,7 +1576,6 @@ #include "code\game\objects\items\devices\pressureplates.dm" #include "code\game\objects\items\devices\quantum_keycard.dm" #include "code\game\objects\items\devices\reverse_bear_trap.dm" -#include "code\game\objects\items\devices\scanners.dm" #include "code\game\objects\items\devices\sensor_device.dm" #include "code\game\objects\items\devices\spyglasses.dm" #include "code\game\objects\items\devices\swapper.dm" @@ -1594,6 +1592,12 @@ #include "code\game\objects\items\devices\radio\headset.dm" #include "code\game\objects\items\devices\radio\intercom.dm" #include "code\game\objects\items\devices\radio\radio.dm" +#include "code\game\objects\items\devices\scanners\gas_analyzer.dm" +#include "code\game\objects\items\devices\scanners\health_analyzer.dm" +#include "code\game\objects\items\devices\scanners\scanner_wand.dm" +#include "code\game\objects\items\devices\scanners\sequence_scanner.dm" +#include "code\game\objects\items\devices\scanners\slime_scanner.dm" +#include "code\game\objects\items\devices\scanners\t_scanner.dm" #include "code\game\objects\items\food\_food.dm" #include "code\game\objects\items\food\bread.dm" #include "code\game\objects\items\food\burgers.dm" @@ -2312,6 +2316,7 @@ #include "code\modules\atmospherics\gasmixtures\gas_mixture.dm" #include "code\modules\atmospherics\gasmixtures\gas_types.dm" #include "code\modules\atmospherics\gasmixtures\immutable_mixtures.dm" +#include "code\modules\atmospherics\gasmixtures\reaction_factors.dm" #include "code\modules\atmospherics\gasmixtures\reactions.dm" #include "code\modules\atmospherics\machinery\airalarm.dm" #include "code\modules\atmospherics\machinery\atmosmachinery.dm" diff --git a/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx b/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx index c87c8d091f9..3322705d090 100644 --- a/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx +++ b/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx @@ -9,6 +9,10 @@ import { Stack, } from '../components'; import { Window } from '../layouts'; +import { + AtmosHandbookContent, + atmosHandbookHooks, +} from './common/AtmosHandbook'; import { Gasmix, GasmixParser } from './common/GasmixParser'; type Chamber = { @@ -37,8 +41,9 @@ export const AtmosControlConsole = (props, context) => { = chambers.length === 1 ? chambers[0] : chambers.find((chamber) => chamber.id === chamberId); + const [setActiveGasId, setActiveReactionId] = atmosHandbookHooks(context); return ( - + {chambers.length > 1 && (
@@ -64,7 +69,11 @@ export const AtmosControlConsole = (props, context) => { />) }> {!!selectedChamber && !!selectedChamber.gasmix ? ( - + ) : ( {'No Sensors Detected!'} )} @@ -162,6 +171,7 @@ export const AtmosControlConsole = (props, context) => {
)} +
); diff --git a/tgui/packages/tgui/interfaces/GasAnalyzer.tsx b/tgui/packages/tgui/interfaces/GasAnalyzer.tsx new file mode 100644 index 00000000000..96695edc2c5 --- /dev/null +++ b/tgui/packages/tgui/interfaces/GasAnalyzer.tsx @@ -0,0 +1,40 @@ +import { useBackend } from '../backend'; +import { GasmixParser } from './common/GasmixParser'; +import type { Gasmix } from './common/GasmixParser'; +import { + AtmosHandbookContent, + atmosHandbookHooks, +} from './common/AtmosHandbook'; +import { Window } from '../layouts'; +import { Section } from '../components'; + +export const GasAnalyzerContent = (props, context) => { + const { act, data } = useBackend<{ gasmixes: Gasmix[] }>(context); + const { gasmixes } = data; + const [setActiveGasId, setActiveReactionId] = atmosHandbookHooks(context); + return ( + <> + {gasmixes.map((gasmix) => ( +
+ +
+ ))} + + + ); +}; + +export const GasAnalyzer = (props, context) => { + return ( + + + + + + ); +}; + \ No newline at end of file diff --git a/tgui/packages/tgui/interfaces/NtosAtmos.js b/tgui/packages/tgui/interfaces/NtosAtmos.js deleted file mode 100644 index 94a4a4cbd67..00000000000 --- a/tgui/packages/tgui/interfaces/NtosAtmos.js +++ /dev/null @@ -1,56 +0,0 @@ -import { filter, sortBy } from 'common/collections'; -import { flow } from 'common/fp'; -import { toFixed } from 'common/math'; -import { useBackend } from '../backend'; -import { LabeledList, ProgressBar, Section } from '../components'; -import { getGasColor, getGasLabel } from '../constants'; -import { NtosWindow } from '../layouts'; - -export const NtosAtmos = (props, context) => { - const { act, data } = useBackend(context); - const { - AirTempC, - AirTempK, - AirPressure, - } = data; - const gases = flow([ - filter(gas => gas.percentage >= 0.01), - sortBy(gas => -gas.percentage), - ])(data.AirData || []); - const gasMaxPercentage = Math.max(1, ...gases.map(gas => gas.percentage)); - return ( - - -
- - - {AirTempC}°C | {AirTempK}K - - - {AirPressure} kPa - - -
-
- - {gases.map(gas => ( - - - {toFixed(gas.percentage, 2) + '%'} - - - ))} - -
-
-
- ); -}; diff --git a/tgui/packages/tgui/interfaces/NtosGasAnalyzer.tsx b/tgui/packages/tgui/interfaces/NtosGasAnalyzer.tsx new file mode 100644 index 00000000000..2ac0a1fd7db --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosGasAnalyzer.tsx @@ -0,0 +1,12 @@ +import { NtosWindow } from '../layouts'; +import { GasAnalyzerContent } from './GasAnalyzer'; + +export const NtosGasAnalyzer = (props, context) => { + return ( + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/common/AtmosHandbook.tsx b/tgui/packages/tgui/interfaces/common/AtmosHandbook.tsx new file mode 100644 index 00000000000..a423fae5078 --- /dev/null +++ b/tgui/packages/tgui/interfaces/common/AtmosHandbook.tsx @@ -0,0 +1,244 @@ +import { InfernoNode } from 'inferno'; +import { useBackend, useLocalState } from '../../backend'; +import { + Box, + Button, + Flex, + Input, + LabeledList, + Section, + Stack, + Tooltip, +} from '../../components'; + +/** + * This describes something that influences a particular reaction + * E.G. + * factor_id = 'oxygen' + * factor_id_type = 'gas' + * factor_name = 'Oxygen + * reaction_id = 'plasmafire' + * desc = 'Influences the burn rate and consumption ratio.' + */ + +type Factor = { + factor_id?: string; + factor_type: 'gas' | 'misc'; + factor_name: string; + + desc: string; + tooltip?: string; +}; + +type Reaction = { + id: string; + name: string; + description: string; + factors: Factor[]; +}; + +type Gas = { + id: string; + name: string; + description: string; + specific_heat: number; + reactions: { [key: string]: string } | []; +}; + +const GasSearchBar = ( + props: { + title: InfernoNode; + onChange: (inputValue: string) => void; + activeInput: boolean; + setActiveInput: (toggle: boolean) => void; + }, + context +) => { + const { title, onChange, activeInput, setActiveInput } = props; + return ( + + + {activeInput ? ( + { + setActiveInput(false); + onChange(value); + }} + /> + ) : ( + title + )} + + +