Allows adding a hypernoblium crystal to portable atmospheric devices to suppress reactions (#68447)

The main functionality is allowing for reaction suppression in canisters to still be a thing by allowing people to add a hypernoblium crystal to a can/pump/scrubber to add a toggleable option to stop it from reacting. The crystal can only be retrieved by deconstructing the canister to make transferring 1 crystal between 5 different cans less of an option.
Some other changes included in this:
Hypernoblium crystals are now single use, and their cost has been halved to reflect this. (So players dont have to waste 2 uses of a crystal on a can)
Canister UI's cell charge display style has been slightly changed.
Removed unused id_tag variable from scrubber UI code that was from 5 years ago
Some various variable name changes (snake_case to camelCase) in relevant UI code for consistency with other JS UI code.

Allows some preservation of the ability to stop canisters from reacting when wrenched in #68420, while also making it more intuitive and giving an incentive for people to make hypernob crystals

You can now insert a hypernoblium crystal into portable atmospheric devices (ie. canisters, pumps, and scrubbers) to allow you to toggle reactions inside the can on and off, similar to how wrenching and unwrenching it on a connector did previously.
The crystal once inserted cannot be removed without deconstructing the canister. However, hypernoblium crystals are now single-use items, and their cost has been halved to account for it.
Variable names in canister, pump, and scrubber UI code have been changed to camel case for consistency.
The canister UI has been slightly changed in terms of how it displays cell charge.
This commit is contained in:
RandomGamer123
2022-09-15 15:12:05 -07:00
committed by GitHub
parent 9ab95da46a
commit 9f65a2334a
10 changed files with 150 additions and 56 deletions
@@ -42,7 +42,7 @@ GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list())
min_temp = 3
max_temp = 250
energy_release = -250000
requirements = list(/datum/gas/oxygen = 2000, /datum/gas/hypernoblium = 175)
requirements = list(/datum/gas/oxygen = 1000, /datum/gas/hypernoblium = 85)
products = list(/obj/item/hypernoblium_crystal = 1)
/datum/gas_recipe/crystallizer/metallic_hydrogen
@@ -1,30 +1,39 @@
/obj/item/hypernoblium_crystal
name = "Hypernoblium Crystal"
desc = "Crystalized oxygen and hypernoblium to pressureproof your clothes."
desc = "Crystalized oxygen and hypernoblium stored in a bottle to pressureproof your clothes or stop reactions occuring in portable atmospheric devices."
icon = 'icons/obj/atmospherics/atmos.dmi'
icon_state = "hypernoblium_crystal"
var/uses = 2
var/uses = 1
/obj/item/hypernoblium_crystal/afterattack(obj/item/clothing/worn_item, mob/user, proximity)
/obj/item/hypernoblium_crystal/afterattack(obj/target_object, mob/user, proximity)
. = ..()
if(!proximity)
return
if(!istype(worn_item))
to_chat(user, span_warning("The crystal can only be used on clothing!"))
var/obj/machinery/portable_atmospherics/atmos_device = target_object
if(istype(atmos_device))
if(atmos_device.nob_crystal_inserted)
to_chat(user, span_warning("[atmos_device] already has a hypernoblium crystal inserted in it!"))
return
atmos_device.nob_crystal_inserted = TRUE
to_chat(user, span_notice("You insert the [src] into [atmos_device]."))
var/obj/item/clothing/worn_item = target_object
if(!istype(worn_item) && !istype(atmos_device))
to_chat(user, span_warning("The crystal can only be used on clothing and portable atmospheric devices!"))
return
if(istype(worn_item, /obj/item/clothing/suit/space))
to_chat(user, span_warning("The [worn_item] is already pressure-resistant!"))
return
if(worn_item.min_cold_protection_temperature == SPACE_SUIT_MIN_TEMP_PROTECT && worn_item.clothing_flags & STOPSPRESSUREDAMAGE)
to_chat(user, span_warning("[worn_item] is already pressure-resistant!"))
return
to_chat(user, span_notice("You see how the [worn_item] changes color, it's now pressure proof."))
worn_item.name = "pressure-resistant [worn_item.name]"
worn_item.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
worn_item.add_atom_colour("#00fff7", FIXED_COLOUR_PRIORITY)
worn_item.min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
worn_item.cold_protection = worn_item.body_parts_covered
worn_item.clothing_flags |= STOPSPRESSUREDAMAGE
if(istype(worn_item))
if(istype(worn_item, /obj/item/clothing/suit/space))
to_chat(user, span_warning("The [worn_item] is already pressure-resistant!"))
return
if(worn_item.min_cold_protection_temperature == SPACE_SUIT_MIN_TEMP_PROTECT && worn_item.clothing_flags & STOPSPRESSUREDAMAGE)
to_chat(user, span_warning("[worn_item] is already pressure-resistant!"))
return
to_chat(user, span_notice("You see how the [worn_item] changes color, it's now pressure proof."))
worn_item.name = "pressure-resistant [worn_item.name]"
worn_item.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
worn_item.add_atom_colour("#00fff7", FIXED_COLOUR_PRIORITY)
worn_item.min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
worn_item.cold_protection = worn_item.body_parts_covered
worn_item.clothing_flags |= STOPSPRESSUREDAMAGE
uses--
if(!uses)
qdel(src)
@@ -620,7 +620,9 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister())
"releasePressure" = round(release_pressure),
"valveOpen" = !!valve_open,
"isPrototype" = !!prototype,
"hasHoldingTank" = !!holding
"hasHoldingTank" = !!holding,
"hasHypernobCrystal" = !!nob_crystal_inserted,
"reactionSuppressionEnabled" = !!suppress_reactions
)
if (prototype)
@@ -644,8 +646,8 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister())
)
. += list(
"shielding" = shielding_powered,
"has_cell" = (internal_cell ? TRUE : FALSE),
"cell_charge" = internal_cell?.percent()
"hasCell" = (internal_cell ? TRUE : FALSE),
"cellCharge" = internal_cell?.percent()
)
/obj/machinery/portable_atmospherics/canister/ui_act(action, params)
@@ -761,6 +763,15 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister())
message_admins("[ADMIN_LOOKUPFLW(usr)] turned [shielding_powered ? "on" : "off"] the [src] powered shielding.")
investigate_log("[key_name(usr)] turned [shielding_powered ? "on" : "off"] the [src] powered shielding.")
. = TRUE
if("reaction_suppression")
if(!nob_crystal_inserted)
stack_trace("[usr] tried to toggle reaction suppression on a canister without a noblium crystal inside, possible href exploit attempt.")
return
suppress_reactions = !suppress_reactions
SSair.start_processing_machine(src)
message_admins("[ADMIN_LOOKUPFLW(usr)] turned [suppress_reactions ? "on" : "off"] the [src] reaction suppression.")
investigate_log("[key_name(usr)] turned [suppress_reactions ? "on" : "off"] the [src] reaction suppression.")
. = TRUE
update_appearance()
@@ -26,6 +26,11 @@
/// Max amount of pressure allowed inside of the canister before it starts to break. [PORTABLE_ATMOS_IGNORE_ATMOS_LIMIT] is special value meaning we are immune.
var/pressure_limit = 500000
/// Should reactions inside the object be suppressed
var/suppress_reactions = FALSE
/// Is there a hypernoblium crystal inserted into this
var/nob_crystal_inserted = FALSE
/obj/machinery/portable_atmospherics/Initialize(mapload)
. = ..()
air_contents = new
@@ -38,8 +43,18 @@
air_contents = null
SSair.stop_processing_machine(src)
if(nob_crystal_inserted)
new /obj/item/hypernoblium_crystal(src)
return ..()
/obj/machinery/portable_atmospherics/examine(mob/user)
. = ..()
if(nob_crystal_inserted)
. += "There is a hypernoblium crystal inside it that allows for reactions inside to be suppressed."
if(suppress_reactions)
. += "The hypernoblium crystal inside is glowing with a faint blue colour, indicating reactions inside are currently being suppressed."
/obj/machinery/portable_atmospherics/ex_act(severity, target)
if(resistance_flags & INDESTRUCTIBLE)
return FALSE //Indestructible cans shouldn't release air
@@ -52,7 +67,7 @@
return ..()
/obj/machinery/portable_atmospherics/process_atmos()
excited = (excited | air_contents.react(src))
excited = (!suppress_reactions && (excited || air_contents.react(src)))
if(!excited)
return PROCESS_KILL
excited = FALSE
@@ -103,10 +103,12 @@
data["direction"] = direction
data["connected"] = !!connected_port
data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
data["target_pressure"] = round(target_pressure ? target_pressure : 0)
data["default_pressure"] = round(PUMP_DEFAULT_PRESSURE)
data["min_pressure"] = round(PUMP_MIN_PRESSURE)
data["max_pressure"] = round(PUMP_MAX_PRESSURE)
data["targetPressure"] = round(target_pressure ? target_pressure : 0)
data["defaultPressure"] = round(PUMP_DEFAULT_PRESSURE)
data["minPressure"] = round(PUMP_MIN_PRESSURE)
data["maxPressure"] = round(PUMP_MAX_PRESSURE)
data["hasHypernobCrystal"] = !!nob_crystal_inserted
data["reactionSuppressionEnabled"] = !!suppress_reactions
if(holding)
data["holding"] = list()
@@ -164,6 +166,15 @@
if(holding)
replace_tank(usr, FALSE)
. = TRUE
if("reaction_suppression")
if(!nob_crystal_inserted)
stack_trace("[usr] tried to toggle reaction suppression on a pump without a noblium crystal inside, possible href exploit attempt.")
return
suppress_reactions = !suppress_reactions
SSair.start_processing_machine(src)
message_admins("[ADMIN_LOOKUPFLW(usr)] turned [suppress_reactions ? "on" : "off"] the [src] reaction suppression.")
investigate_log("[key_name(usr)] turned [suppress_reactions ? "on" : "off"] the [src] reaction suppression.")
. = TRUE
update_appearance()
/obj/machinery/portable_atmospherics/pump/unregister_holding()
@@ -114,11 +114,13 @@
data["connected"] = connected_port ? 1 : 0
data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
data["id_tag"] = -1 //must be defined in order to reuse code between portable and vent scrubbers
data["filter_types"] = list()
data["hasHypernobCrystal"] = !!nob_crystal_inserted
data["reactionSuppressionEnabled"] = !!suppress_reactions
data["filterTypes"] = list()
for(var/path in GLOB.meta_gas_info)
var/list/gas = GLOB.meta_gas_info[path]
data["filter_types"] += list(list("gas_id" = gas[META_GAS_ID], "gas_name" = gas[META_GAS_NAME], "enabled" = (path in scrubbing)))
data["filterTypes"] += list(list("gasId" = gas[META_GAS_ID], "gasName" = gas[META_GAS_NAME], "enabled" = (path in scrubbing)))
if(holding)
data["holding"] = list()
@@ -157,6 +159,15 @@
if("toggle_filter")
scrubbing ^= gas_id2path(params["val"])
. = TRUE
if("reaction_suppression")
if(!nob_crystal_inserted)
message_admins("[ADMIN_LOOKUPFLW(usr)] tried to toggle reaction suppression on a scrubber without a noblium crystal inside, possible href exploit attempt.")
return
suppress_reactions = !suppress_reactions
SSair.start_processing_machine(src)
message_admins("[ADMIN_LOOKUPFLW(usr)] turned [suppress_reactions ? "on" : "off"] the [src] reaction suppression.")
investigate_log("[key_name(usr)] turned [suppress_reactions ? "on" : "off"] the [src] reaction suppression.")
. = TRUE
update_appearance()
/obj/machinery/portable_atmospherics/scrubber/unregister_holding()
+24 -6
View File
@@ -20,6 +20,10 @@ export const Canister = (props, context) => {
defaultReleasePressure,
minReleasePressure,
maxReleasePressure,
hasHypernobCrystal,
reactionSuppressionEnabled,
hasCell,
cellCharge,
pressureLimit,
valveOpen,
isPrototype,
@@ -30,7 +34,7 @@ export const Canister = (props, context) => {
restricted,
} = data;
return (
<Window width={350} height={305}>
<Window width={350} height={335}>
<Window.Content>
<Flex direction="column" height="100%">
<Flex.Item mb={1}>
@@ -150,11 +154,25 @@ export const Canister = (props, context) => {
</LabeledControls>
</Section>
<Section>
<Box>
{data.has_cell
? 'Cell charge at: ' + data.cell_charge + '%'
: 'Missing Cell'}
</Box>
<LabeledList>
<LabeledList.Item label="Cell Charge">
{hasCell ? cellCharge + '%' : 'Missing Cell'}
</LabeledList.Item>
{!!hasHypernobCrystal && (
<LabeledList.Item label="Reaction Suppression">
<Button
icon={
data.reactionSuppressionEnabled ? 'snowflake' : 'times'
}
content={
data.reactionSuppressionEnabled ? 'Enabled' : 'Disabled'
}
selected={data.reactionSuppressionEnabled}
onClick={() => act('reaction_suppression')}
/>
</LabeledList.Item>
)}
</LabeledList>
</Section>
</Flex.Item>
<Flex.Item grow={1}>
+11 -11
View File
@@ -9,15 +9,15 @@ export const PortablePump = (props, context) => {
direction,
connected,
holding,
target_pressure,
default_pressure,
min_pressure,
max_pressure,
targetPressure,
defaultPressure,
minPressure,
maxPressure,
} = data;
const pump_or_port = connected ? 'Port' : 'Pump';
const area_or_tank = holding ? 'Tank' : 'Area';
return (
<Window width={300} height={315}>
<Window width={300} height={340}>
<Window.Content>
<PortableBasicInfo />
<Section
@@ -36,11 +36,11 @@ export const PortablePump = (props, context) => {
<LabeledList>
<LabeledList.Item label="Output">
<NumberInput
value={target_pressure}
value={targetPressure}
unit="kPa"
width="75px"
minValue={min_pressure}
maxValue={max_pressure}
minValue={minPressure}
maxValue={maxPressure}
step={10}
onChange={(e, value) =>
act('pressure', {
@@ -52,7 +52,7 @@ export const PortablePump = (props, context) => {
<LabeledList.Item label="Presets">
<Button
icon="minus"
disabled={target_pressure === min_pressure}
disabled={targetPressure === minPressure}
onClick={() =>
act('pressure', {
pressure: 'min',
@@ -61,7 +61,7 @@ export const PortablePump = (props, context) => {
/>
<Button
icon="sync"
disabled={target_pressure === default_pressure}
disabled={targetPressure === defaultPressure}
onClick={() =>
act('pressure', {
pressure: 'reset',
@@ -70,7 +70,7 @@ export const PortablePump = (props, context) => {
/>
<Button
icon="plus"
disabled={target_pressure === max_pressure}
disabled={targetPressure === maxPressure}
onClick={() =>
act('pressure', {
pressure: 'max',
@@ -6,34 +6,34 @@ import { Window } from '../layouts';
import { PortableBasicInfo } from './common/PortableAtmos';
type Data = {
filter_types: Filter[];
filterTypes: Filter[];
};
type Filter = {
id: string;
enabled: BooleanLike;
gas_id: string;
gas_name: string;
gasId: string;
gasName: string;
};
export const PortableScrubber = (props, context) => {
const { act, data } = useBackend<Data>(context);
const { filter_types = [] } = data;
const { filterTypes = [] } = data;
return (
<Window width={320} height={396}>
<Window width={320} height={420}>
<Window.Content>
<PortableBasicInfo />
<Section title="Filters">
{filter_types.map((filter) => (
{filterTypes.map((filter) => (
<Button
key={filter.id}
icon={filter.enabled ? 'check-square-o' : 'square-o'}
content={getGasLabel(filter.gas_id, filter.gas_name)}
content={getGasLabel(filter.gasId, filter.gasName)}
selected={filter.enabled}
onClick={() =>
act('toggle_filter', {
val: filter.gas_id,
val: filter.gasId,
})
}
/>
@@ -3,7 +3,14 @@ import { AnimatedNumber, Box, Button, LabeledList, Section } from '../../compone
export const PortableBasicInfo = (props, context) => {
const { act, data } = useBackend(context);
const { connected, holding, on, pressure } = data;
const {
connected,
holding,
on,
pressure,
hasHypernobCrystal,
reactionSuppressionEnabled,
} = data;
return (
<>
<Section
@@ -24,6 +31,18 @@ export const PortableBasicInfo = (props, context) => {
<LabeledList.Item label="Port" color={connected ? 'good' : 'average'}>
{connected ? 'Connected' : 'Not Connected'}
</LabeledList.Item>
{!!hasHypernobCrystal && (
<LabeledList.Item label="Reaction Suppression">
<Button
icon={data.reactionSuppressionEnabled ? 'snowflake' : 'times'}
content={
data.reactionSuppressionEnabled ? 'Enabled' : 'Disabled'
}
selected={data.reactionSuppressionEnabled}
onClick={() => act('reaction_suppression')}
/>
</LabeledList.Item>
)}
</LabeledList>
</Section>
<Section