diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm index e481b30b8abb3..0b9ab4bee5213 100644 --- a/code/__DEFINES/atmospherics.dm +++ b/code/__DEFINES/atmospherics.dm @@ -326,6 +326,15 @@ GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0)) #define GAS_ETHANOL "ethanol" #define GAS_MOTOR_OIL "motor_oil" // BLUEMOON ADD - Напитки для синтетиков #define GAS_QCD "qcd" +// HFR / fusion gases (from WhiteMoon HFR port) +#define GAS_HELIUM "helium" +#define GAS_FREON "freon" +#define GAS_HALON "halon" +#define GAS_ANTINOBLIUM "antinoblium" +#define GAS_PROTO_NITRATE "proto_nitrate" +#define GAS_ZAUKER "zauker" +#define GAS_HEALIUM "healium" +#define GAS_NITRIUM "nitrium" #define GAS_GROUP_CHEMICALS "Chemicals" diff --git a/code/__DEFINES/construction.dm b/code/__DEFINES/construction.dm index 45ab215e30058..f486ba9ac1853 100644 --- a/code/__DEFINES/construction.dm +++ b/code/__DEFINES/construction.dm @@ -105,6 +105,9 @@ #define CAT_SPAGHETTI "Spaghettis" #define CAT_ICE "Frozen" #define CAT_EAST "East foods" //BLUEMOON ADD +// Category tab; single subcategory for crystallizer crystals and gas crafts (BLUEMOON ADD) +#define CAT_ATMOSPHERIC "Atmospherics" +#define CAT_ATMOSPHERICS "Gas Crystals" #define RCD_FLOORWALL 1 #define RCD_AIRLOCK 2 diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm index d0532d658b5a3..fcc625627e521 100644 --- a/code/__DEFINES/inventory.dm +++ b/code/__DEFINES/inventory.dm @@ -88,6 +88,8 @@ //sandstorm edit #define HIDEUNDERWEAR (1<<14) //hides underwear, socks and shirt #define HIDEWRISTS (1<<15) //hides wrists +#define HIDEBACK (1<<16) //suit obscures back slot (no backpack/tank on back) +#define ALLOWS_BACK_TANK (1<<17) //suit explicitly allows back slot despite being full-body // //bitflags for clothing coverage - also used for limbs diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm index b047f189b0fbe..1128354c19733 100644 --- a/code/__DEFINES/logging.dm +++ b/code/__DEFINES/logging.dm @@ -19,6 +19,7 @@ #define INVESTIGATE_RESEARCH "research" #define INVESTIGATE_SINGULO "singulo" #define INVESTIGATE_SUPERMATTER "supermatter" +#define INVESTIGATE_HYPERTORUS "hypertorus" #define INVESTIGATE_TELESCI "telesci" #define INVESTIGATE_WIRES "wires" diff --git a/code/__DEFINES/reactions.dm b/code/__DEFINES/reactions.dm index 061ac289d050e..5cdc9b9aae4e4 100644 --- a/code/__DEFINES/reactions.dm +++ b/code/__DEFINES/reactions.dm @@ -21,8 +21,9 @@ #define STIMULUM_FIRST_DROP 0.065 #define STIMULUM_SECOND_RISE 0.0009 #define STIMULUM_ABSOLUTE_DROP 0.00000335 -#define REACTION_OPPRESSION_THRESHOLD 10 -#define NOBLIUM_FORMATION_ENERGY 2e9 //1 Mole of Noblium takes the planck energy to condense. +#define REACTION_OPPRESSION_THRESHOLD 5 // stops reactions when >5 mol and temp > 20 K +#define NOBLIUM_FORMATION_ENERGY 2e9 // energy released per mole (exothermic); BZ reduces amount +#define NOBLIUM_FORMATION_MAX_TEMP 15 // below 15 K only //Research point amounts #define NOBLIUM_RESEARCH_AMOUNT 25 #define BZ_RESEARCH_SCALE 4 @@ -43,5 +44,71 @@ #define FUSION_RAD_MAX 2000 #define FUSION_RAD_COEFFICIENT (-1000) #define FUSION_INSTABILITY_ENDOTHERMALITY 2 +#define FUSION_MAXIMUM_TEMPERATURE 1e8 // Snowflake fire product types #define FIRE_PRODUCT_PLASMA 0 + +// Freon — below 0°C (273.15 K) endothermic with O2, down to ~50 K; Proto-Nitrate catalyst up to 310 K; hot ice 120–160 K +#define FREON_MAXIMUM_BURN_TEMPERATURE T0C +#define FREON_CATALYST_MAX_TEMPERATURE 310 +#define FREON_LOWER_TEMPERATURE 60 +#define FREON_TERMINAL_TEMPERATURE 50 +#define FREON_HOT_ICE_MIN_TEMP 120 +#define FREON_HOT_ICE_MAX_TEMP 160 +#define FREON_OXYGEN_FULLBURN 10 +#define FREON_BURN_RATE_DELTA 4 +#define FIRE_FREON_ENERGY_CONSUMED 3e5 +#define FREON_FORMATION_MIN_TEMPERATURE (FIRE_MINIMUM_TEMPERATURE_TO_EXIST + 100) +#define FREON_FORMATION_ENERGY_CONSUMED 2e5 +#define OXYGEN_BURN_RATIO_BASE 2 + +// Halon +#define HALON_COMBUSTION_ENERGY 2500 +#define HALON_COMBUSTION_MIN_TEMPERATURE (T0C + 70) +#define HALON_COMBUSTION_TEMPERATURE_SCALE (FIRE_MINIMUM_TEMPERATURE_TO_EXIST * 10) +#define HALON_COMBUSTION_MINIMUM_RESIN_MOLES (0.99 * HALON_COMBUSTION_MIN_TEMPERATURE / HALON_COMBUSTION_TEMPERATURE_SCALE) + +// Healium +#define HEALIUM_FORMATION_MIN_TEMP 25 +#define HEALIUM_FORMATION_MAX_TEMP 300 +#define HEALIUM_FORMATION_ENERGY 9000 + +// Zauker +#define ZAUKER_FORMATION_MIN_TEMPERATURE 50000 +#define ZAUKER_FORMATION_MAX_TEMPERATURE 75000 +#define ZAUKER_FORMATION_TEMPERATURE_SCALE 5e-6 +#define ZAUKER_FORMATION_ENERGY 5000 +#define ZAUKER_DECOMPOSITION_MAX_RATE 20 +#define ZAUKER_DECOMPOSITION_ENERGY 460 + +// Nitrium +#define NITRIUM_FORMATION_MIN_TEMP 1500 +#define NITRIUM_FORMATION_TEMP_DIVISOR (FIRE_MINIMUM_TEMPERATURE_TO_EXIST * 8) +#define NITRIUM_FORMATION_ENERGY 100000 +#define NITRIUM_DECOMPOSITION_MAX_TEMP (T0C + 70) +#define NITRIUM_DECOMPOSITION_TEMP_DIVISOR (FIRE_MINIMUM_TEMPERATURE_TO_EXIST * 8) +#define NITRIUM_DECOMPOSITION_ENERGY 30000 + +// Pluoxium formation (CO2 + O2 + Tritium) +#define PLUOXIUM_FORMATION_MIN_TEMP 50 +#define PLUOXIUM_FORMATION_MAX_TEMP T0C +#define PLUOXIUM_FORMATION_MAX_RATE 5 +#define PLUOXIUM_FORMATION_ENERGY 250 + +// Proto-Nitrate +#define PN_FORMATION_MIN_TEMPERATURE 5000 +#define PN_FORMATION_MAX_TEMPERATURE 10000 +#define PN_FORMATION_ENERGY 650 +#define PN_HYDROGEN_CONVERSION_THRESHOLD 150 +#define PN_HYDROGEN_CONVERSION_MAX_RATE 5 +#define PN_HYDROGEN_CONVERSION_ENERGY 2500 +#define PN_TRITIUM_CONVERSION_MIN_TEMP 150 +#define PN_TRITIUM_CONVERSION_MAX_TEMP 340 +#define PN_TRITIUM_CONVERSION_ENERGY 10000 +#define PN_BZASE_MIN_TEMP 260 +#define PN_BZASE_MAX_TEMP 280 +#define PN_BZASE_ENERGY 60000 + +// Antinoblium +#define ANTINOBLIUM_CONVERSION_DIVISOR 90 +#define REACTION_OPPRESSION_MIN_TEMP 20 diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm index 8ddfd38cccc77..76512fde4fabb 100644 --- a/code/__DEFINES/sound.dm +++ b/code/__DEFINES/sound.dm @@ -507,3 +507,5 @@ GLOBAL_LIST_EMPTY(sfx_datum_by_key) #define SFX_DRAWER_CLOSE "drawer_close" #define SFX_ROLLING_PIN_ROLLING "rolling_pin_rolling" #define SFX_KNIFE_SLICE "knife_slice" +#define SFX_HYPERTORUS_CALM "hypertorus_calm" +#define SFX_HYPERTORUS_MELTING "hypertorus_melting" diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm index 28d9510990053..e2c31fc848a78 100644 --- a/code/datums/components/crafting/crafting.dm +++ b/code/datums/components/crafting/crafting.dm @@ -25,6 +25,9 @@ CAT_TOOL, CAT_FURNITURE, ), + CAT_ATMOSPHERIC = list( + CAT_ATMOSPHERICS, + ), CAT_PRIMAL = CAT_NONE, CAT_FOOD = list( CAT_BREAD, diff --git a/code/datums/components/crafting/recipes/recipes_atmospheric_crystals.dm b/code/datums/components/crafting/recipes/recipes_atmospheric_crystals.dm new file mode 100644 index 0000000000000..0b3eb0c706eb1 --- /dev/null +++ b/code/datums/components/crafting/recipes/recipes_atmospheric_crystals.dm @@ -0,0 +1,142 @@ +// Крафты из продуктов кристаллайзера и атмос-оборудования (вкладка Atmospherics) + +/datum/crafting_recipe/zaukerite_bolt + name = "Zaukerite bolt" + result = /obj/item/zaukerite_bolt + reqs = list( + /obj/item/stack/sheet/mineral/zaukerite = 1, + /obj/item/stack/rods = 1, + ) + time = 25 + category = CAT_ATMOSPHERIC + subcategory = CAT_ATMOSPHERICS + +/datum/crafting_recipe/hot_ice_pack + name = "Hot ice cooling pack" + result = /obj/item/hot_ice_pack + reqs = list( + /obj/item/stack/sheet/hot_ice = 3, + /obj/item/stack/sheet/cloth = 2, + ) + time = 30 + category = CAT_ATMOSPHERIC + subcategory = CAT_ATMOSPHERICS + +// --- Atmos equipment (из WhiteMoon tailoring.dm + atmospheric.dm) --- +/datum/crafting_recipe/atmospherics_gas_mask + name = "Atmospherics gas mask" + result = /obj/item/clothing/mask/gas/atmos + tools = list(TOOL_WELDER) + time = 80 + reqs = list( + /obj/item/stack/sheet/mineral/metal_hydrogen = 1, + /obj/item/stack/sheet/mineral/zaukerite = 1, + ) + category = CAT_ATMOSPHERIC + subcategory = CAT_ATMOSPHERICS + +/datum/crafting_recipe/igniter + name = "Igniter" + result = /obj/machinery/igniter + reqs = list( + /obj/item/stack/sheet/metal = 5, + /obj/item/assembly/igniter = 1, + ) + time = 20 + category = CAT_ATMOSPHERIC + subcategory = CAT_ATMOSPHERICS + +/datum/crafting_recipe/ammonia_pack + name = "Ammonia pack" + result = /obj/item/ammonia_pack + reqs = list( + /obj/item/stack/ammonia_crystals = 3, + /obj/item/stack/sheet/cloth = 2, + ) + time = 25 + category = CAT_ATMOSPHERIC + subcategory = CAT_ATMOSPHERICS + +/datum/crafting_recipe/metallic_hydrogen_rod + name = "Metallic hydrogen rod" + result = /obj/item/metallic_hydrogen_rod + reqs = list( + /obj/item/stack/sheet/mineral/metal_hydrogen = 1, + /obj/item/stack/rods = 1, + ) + time = 30 + category = CAT_ATMOSPHERIC + subcategory = CAT_ATMOSPHERICS + +/datum/crafting_recipe/metallic_hydrogen_cooling_pack + name = "Metallic hydrogen cooling pack" + result = /obj/item/metallic_hydrogen_cooling_pack + reqs = list( + /obj/item/stack/sheet/mineral/metal_hydrogen = 2, + /obj/item/stack/sheet/cloth = 2, + ) + time = 35 + category = CAT_ATMOSPHERIC + subcategory = CAT_ATMOSPHERICS + +/datum/crafting_recipe/elder_atmosian_statue + name = "Elder Atmosian statue" + result = /obj/structure/statue/elder_atmosian + reqs = list( + /obj/item/stack/sheet/mineral/metal_hydrogen = 20, + /obj/item/stack/sheet/mineral/zaukerite = 15, + /obj/item/stack/sheet/metal = 30, + ) + time = 60 + category = CAT_ATMOSPHERIC + subcategory = CAT_ATMOSPHERICS + +/datum/crafting_recipe/elder_atmosian_armor + name = "Elder Atmosian armor" + result = /obj/item/clothing/suit/armor/elder_atmosian + reqs = list( + /obj/item/stack/sheet/mineral/metal_hydrogen = 5, + /obj/item/stack/sheet/mineral/zaukerite = 3, + /obj/item/stack/sheet/metal = 10, + /obj/item/clothing/suit/fire/atmos = 1, + ) + time = 40 + category = CAT_ATMOSPHERIC + subcategory = CAT_ATMOSPHERICS + +/datum/crafting_recipe/elder_atmosian_helmet + name = "Elder Atmosian helmet" + result = /obj/item/clothing/head/helmet/elder_atmosian + reqs = list( + /obj/item/stack/sheet/mineral/metal_hydrogen = 3, + /obj/item/stack/sheet/mineral/zaukerite = 2, + /obj/item/stack/sheet/metal = 5, + /obj/item/clothing/head/hardhat/atmos = 1, + ) + time = 40 + category = CAT_ATMOSPHERIC + subcategory = CAT_ATMOSPHERICS + +/datum/crafting_recipe/metal_h2_fireaxe + name = "Metal hydrogen fire axe" + result = /obj/item/fireaxe/metal_h2_axe + reqs = list( + /obj/item/stack/sheet/mineral/metal_hydrogen = 7, + ) + time = 30 + category = CAT_ATMOSPHERIC + subcategory = CAT_ATMOSPHERICS + +/datum/crafting_recipe/crystal_cell_assembly + name = "Crystal cell assembly" + result = /obj/item/stock_parts/cell/crystal_cell + reqs = list( + /obj/item/stack/sheet/mineral/plasma = 2, + /obj/item/stack/sheet/mineral/diamond = 1, + /obj/item/stack/cable_coil = 5, + /obj/item/stack/sheet/glass = 1, + ) + tools = list(TOOL_WELDER, TOOL_SCREWDRIVER) + time = 40 + category = CAT_ATMOSPHERIC + subcategory = CAT_ATMOSPHERICS diff --git a/code/datums/looping_sounds/machinery_sounds.dm b/code/datums/looping_sounds/machinery_sounds.dm index eb1d0e1a30acc..1afe65e2339e7 100644 --- a/code/datums/looping_sounds/machinery_sounds.dm +++ b/code/datums/looping_sounds/machinery_sounds.dm @@ -28,12 +28,12 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// /datum/looping_sound/hypertorus -// mid_sounds = list('sound/machines/hypertorus/loops/hypertorus_nominal.ogg' = 1) -// mid_length = 60 -// volume = 55 -// extra_range = 15 -// vary = TRUE +/datum/looping_sound/hypertorus + mid_sounds = list('sound/machines/sm/supermatter1.ogg'=1,'sound/machines/sm/supermatter2.ogg'=1,'sound/machines/sm/supermatter3.ogg'=1) + mid_length = 10 + volume = 5 + extra_range = 15 + vary = TRUE /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/code/datums/materials/basemats.dm b/code/datums/materials/basemats.dm index 476deffe89291..00b073680d7de 100644 --- a/code/datums/materials/basemats.dm +++ b/code/datums/materials/basemats.dm @@ -138,6 +138,16 @@ Unless you know what you're doing, only use the first three numbers. They're in qdel(source.GetComponent(/datum/component/slippery)) qdel(source.GetComponent(/datum/component/squeak)) +/// Metallic hydrogen (crystallizer product); used in atmos crafts and Elder Atmosian statue +/datum/material/metalhydrogen + name = "Metal Hydrogen" + desc = "Solid metallic hydrogen. Some say it should be impossible." + color = "#62708A" + categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE) + sheet_type = /obj/item/stack/sheet/mineral/metal_hydrogen + value_per_unit = 0.07 + beauty_modifier = 0.35 + strength_modifier = 1.2 ///Mediocre force increase /datum/material/titanium diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm index 6169be0867ee9..584a8595be00c 100644 --- a/code/game/objects/items/circuitboards/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm @@ -405,51 +405,60 @@ #undef PATH_FREEZER #undef PATH_HEATER -// /obj/item/circuitboard/machine/HFR_fuel_input -// name = "HFR Fuel Input (Machine Board)" -// icon_state = "engineering" -// build_path = /obj/machinery/atmospherics/components/unary/hypertorus/fuel_input -// req_components = list( -// /obj/item/stack/sheet/plasteel = 5) +/obj/item/circuitboard/machine/crystallizer + name = "Crystallizer (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/atmospherics/components/binary/crystallizer + req_components = list( + /obj/item/stack/cable_coil = 10, + /obj/item/stack/sheet/glass = 10, + /obj/item/stack/sheet/plasteel = 5) -// /obj/item/circuitboard/machine/HFR_waste_output -// name = "HFR Waste Output (Machine Board)" -// icon_state = "engineering" -// build_path = /obj/machinery/atmospherics/components/unary/hypertorus/waste_output -// req_components = list( -// /obj/item/stack/sheet/plasteel = 5) +/obj/item/circuitboard/machine/HFR_fuel_input + name = "HFR Fuel Input (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/atmospherics/components/unary/hypertorus/fuel_input + req_components = list( + /obj/item/stack/sheet/plasteel = 5) -// /obj/item/circuitboard/machine/HFR_moderator_input -// name = "HFR Moderator Input (Machine Board)" -// icon_state = "engineering" -// build_path = /obj/machinery/atmospherics/components/unary/hypertorus/moderator_input -// req_components = list( -// /obj/item/stack/sheet/plasteel = 5) +/obj/item/circuitboard/machine/HFR_waste_output + name = "HFR Waste Output (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/atmospherics/components/unary/hypertorus/waste_output + req_components = list( + /obj/item/stack/sheet/plasteel = 5) -// /obj/item/circuitboard/machine/HFR_core -// name = "HFR core (Machine Board)" -// icon_state = "engineering" -// build_path = /obj/machinery/atmospherics/components/unary/hypertorus/core -// req_components = list( -// /obj/item/stack/cable_coil = 10, -// /obj/item/stack/sheet/glass = 10, -// /obj/item/stack/sheet/plasteel = 10) +/obj/item/circuitboard/machine/HFR_moderator_input + name = "HFR Moderator Input (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/atmospherics/components/unary/hypertorus/moderator_input + req_components = list( + /obj/item/stack/sheet/plasteel = 5) -// /obj/item/circuitboard/machine/HFR_corner -// name = "HFR Corner (Machine Board)" -// icon_state = "engineering" -// build_path = /obj/machinery/hypertorus/corner -// req_components = list( -// /obj/item/stack/sheet/plasteel = 5) +/obj/item/circuitboard/machine/HFR_core + name = "HFR core (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/atmospherics/components/unary/hypertorus/core + req_components = list( + /obj/item/stack/cable_coil = 10, + /obj/item/stack/sheet/glass = 10, + /obj/item/stack/sheet/plasteel = 10) -// /obj/item/circuitboard/machine/HFR_interface -// name = "HFR Interface (Machine Board)" -// icon_state = "engineering" -// build_path = /obj/machinery/hypertorus/interface -// req_components = list( -// /obj/item/stack/cable_coil = 10, -// /obj/item/stack/sheet/glass = 10, -// /obj/item/stack/sheet/plasteel = 5) +/obj/item/circuitboard/machine/HFR_corner + name = "HFR Corner (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/hypertorus/corner + req_components = list( + /obj/item/stack/sheet/plasteel = 5) + +/obj/item/circuitboard/machine/HFR_interface + name = "HFR Interface (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/hypertorus/interface + req_components = list( + /obj/item/stack/cable_coil = 10, + /obj/item/stack/sheet/glass = 10, + /obj/item/stack/sheet/plasteel = 5) //Generic @@ -577,17 +586,16 @@ /obj/item/stack/cable_coil = 3) needs_anchored = FALSE -// /obj/item/circuitboard/machine/electrolyzer -// name = "Electrolyzer (Machine Board)" -// icon_state = "generic" -// build_path = /obj/machinery/electrolyzer -// req_components = list( -// /obj/item/stock_parts/electrolite = 2, -// /obj/item/stock_parts/capacitor = 2, -// /obj/item/stack/cable_coil = 5, -// /obj/item/stack/sheet/glass = 1) - -// needs_anchored = FALSE +/obj/item/circuitboard/machine/electrolyzer + name = "Electrolyzer (Machine Board)" + icon_state = "generic" + build_path = /obj/machinery/electrolyzer + req_components = list( + /obj/item/stock_parts/manipulator = 2, + /obj/item/stock_parts/capacitor = 2, + /obj/item/stack/cable_coil = 5, + /obj/item/stack/sheet/glass = 1) + needs_anchored = FALSE /obj/item/circuitboard/machine/techfab diff --git a/code/game/objects/items/fireaxe.dm b/code/game/objects/items/fireaxe.dm index 0317de9cee3a3..0e84f7f53069d 100644 --- a/code/game/objects/items/fireaxe.dm +++ b/code/game/objects/items/fireaxe.dm @@ -86,6 +86,20 @@ . = ..() AddComponent(/datum/component/two_handed, force_unwielded=12, force_wielded=35, icon_wielded="bonemetal_axe1") +// Металл-водородный топор — мощнее пожарного, можно вешать на слот костюма Elder Atmosian (из WhiteMoon-station) +/obj/item/fireaxe/metal_h2_axe + name = "metal hydrogen fire axe" + desc = "Монтировочный топор с очень острым лезвием из металлического водорода — прочнее и опаснее обычного пожарного. Удобно носить в слоте костюма Elder Atmosian, не занимая рюкзак." + armour_penetration = 40 + wound_bonus = 12 + bare_wound_bonus = 18 + w_class = WEIGHT_CLASS_NORMAL + slot_flags = ITEM_SLOT_BACK | ITEM_SLOT_SUITSTORE + +/obj/item/fireaxe/metal_h2_axe/ComponentInitialize() + . = ..() + AddComponent(/datum/component/two_handed, force_unwielded=8, force_wielded=38, icon_wielded="fireaxe1") + /obj/item/fireaxe/energized desc = "Someone with a love for fire axes decided to turn this one into a high-powered energy weapon. Seems excessive." armour_penetration = 50 diff --git a/code/game/objects/structures/statues.dm b/code/game/objects/structures/statues.dm index 40677e115d642..7d3f5d0d7355e 100644 --- a/code/game/objects/structures/statues.dm +++ b/code/game/objects/structures/statues.dm @@ -279,6 +279,18 @@ icon_state = "marx" art_type = /datum/element/art/rev +/////////// Elder Atmosian (craft: metal_hydrogen + zaukerite + metal) /////////// +/obj/structure/statue/elder_atmosian + name = "Elder Atmosian" + desc = "A statue of an Elder Atmosian, capable of bending the laws of thermodynamics to their will." + icon_state = "eng" + custom_materials = list( + /datum/material/metalhydrogen = SHEET_MATERIAL_AMOUNT * 20, + /datum/material/iron = SHEET_MATERIAL_AMOUNT * 30, + ) + max_integrity = 1000 + impressiveness = 100 + /obj/item/chisel name = "chisel" desc = "Breaking and making art since 4000 BC. This one uses advanced technology to allow creation of lifelike moving statues." diff --git a/code/game/sound_keys.dm b/code/game/sound_keys.dm index 0d2de11c30889..39867796b03ff 100644 --- a/code/game/sound_keys.dm +++ b/code/game/sound_keys.dm @@ -493,3 +493,20 @@ 'sound/items/knife/knife_slice5.ogg', 'sound/items/knife/knife_slice6.ogg' ) + +/datum/sound_effect/hypertorus_calm + key = SFX_HYPERTORUS_CALM + file_paths = list( + 'sound/machines/sm/accent/normal/1.ogg', + 'sound/machines/sm/accent/normal/2.ogg', + 'sound/machines/sm/accent/normal/3.ogg', + ) + +/datum/sound_effect/hypertorus_melting + key = SFX_HYPERTORUS_MELTING + file_paths = list( + 'sound/machines/sm/accent/delam/1.ogg', + 'sound/machines/sm/accent/delam/2.ogg', + 'sound/machines/warning-buzzer.ogg', + 'sound/machines/engine_alert1.ogg', + ) diff --git a/code/modules/admin/admin_investigate.dm b/code/modules/admin/admin_investigate.dm index c9185439b717c..aa04fc476e79b 100644 --- a/code/modules/admin/admin_investigate.dm +++ b/code/modules/admin/admin_investigate.dm @@ -10,7 +10,7 @@ if(!holder) return - var/list/investigates = list(INVESTIGATE_RCD, INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_CIRCUIT, INVESTIGATE_NANITES, INVESTIGATE_CRYOGENICS) + var/list/investigates = list(INVESTIGATE_RCD, INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_HYPERTORUS, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_CIRCUIT, INVESTIGATE_NANITES, INVESTIGATE_CRYOGENICS) var/list/logs_present = list("notes, memos, watchlist") var/list/logs_missing = list("---") diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index 3ca10cf8fbcd2..09e3dc661b475 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -332,7 +332,7 @@ body_parts_covered = CHEST|GROIN|LEGS|ARMS allowed = list(/obj/item/tome, /obj/item/melee/cultblade) armor = list(MELEE = 40, BULLET = 30, LASER = 40,ENERGY = 20, BOMB = 65, BIO = 10, RAD = 0, FIRE = 10, ACID = 10) - flags_inv = HIDEJUMPSUIT + flags_inv = HIDEJUMPSUIT|ALLOWS_BACK_TANK cold_protection = CHEST|GROIN|LEGS|ARMS min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT heat_protection = CHEST|GROIN|LEGS|ARMS @@ -387,7 +387,7 @@ body_parts_covered = CHEST|GROIN|LEGS|ARMS allowed = list(/obj/item/tome, /obj/item/melee/cultblade) armor = list(MELEE = 50, BULLET = 30, LASER = 50,ENERGY = 20, BOMB = 25, BIO = 10, RAD = 0, FIRE = 10, ACID = 10) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK alternate_screams = BLOOD_SCREAMS /obj/item/clothing/head/helmet/space/hardsuit/cult @@ -506,7 +506,7 @@ desc = "Blood-soaked robes infused with dark magic; allows the user to move at inhuman speeds, but at the cost of increased damage." icon_state = "cultrobes" item_state = "cultrobes" - flags_inv = HIDEJUMPSUIT + flags_inv = HIDEJUMPSUIT|ALLOWS_BACK_TANK allowed = list(/obj/item/tome, /obj/item/melee/cultblade) body_parts_covered = CHEST|GROIN|LEGS|ARMS armor = list(MELEE = -50, BULLET = -50, LASER = -50,ENERGY = -50, BOMB = -50, BIO = -50, RAD = -50, FIRE = 0, ACID = 0) diff --git a/code/modules/antagonists/eldritch_cult/eldritch_items.dm b/code/modules/antagonists/eldritch_cult/eldritch_items.dm index 76074006d64a5..232d65f7873d9 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_items.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_items.dm @@ -205,7 +205,7 @@ desc = "Рваная, пыльная роба. Странные глаза смотрят на вас изнутри.." icon_state = "eldritch_armor" item_state = "eldritch_armor" - flags_inv = HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS allowed = list(/obj/item/melee/sickly_blade, /obj/item/forbidden_book, /obj/item/living_heart) hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch diff --git a/code/modules/asset_cache/assets/sheetmaterials.dm b/code/modules/asset_cache/assets/sheetmaterials.dm index 2735767b9f207..e68eda6d215d0 100644 --- a/code/modules/asset_cache/assets/sheetmaterials.dm +++ b/code/modules/asset_cache/assets/sheetmaterials.dm @@ -2,8 +2,10 @@ name = "sheetmaterials" /datum/asset/spritesheet/sheetmaterials/register() - InsertAll("", 'icons/obj/stack_objects.dmi') - - // Special case to handle Bluespace Crystals + // Insert polycrystal from telescience first so it won't be duplicated when stacking from stack_objects.dmi Insert("polycrystal", 'icons/obj/telescience.dmi', "polycrystal") + for (var/icon_state_name in icon_states('icons/obj/stack_objects.dmi')) + if (icon_state_name == "polycrystal") + continue + Insert(icon_state_name, 'icons/obj/stack_objects.dmi', icon_state_name) ..() diff --git a/code/modules/asset_cache/assets/vending.dm b/code/modules/asset_cache/assets/vending.dm index 03a2031f974f6..11989c69c7e0c 100644 --- a/code/modules/asset_cache/assets/vending.dm +++ b/code/modules/asset_cache/assets/vending.dm @@ -15,18 +15,10 @@ icon_file = initial(item.icon) var/icon_state = initial(item.icon_state) - // BLUEMOON EDIT - Vending Update: START + // BLUEMOON EDIT - Vending Update: skip items with missing icon state instead of crashing (CI) #ifdef UNIT_TESTS var/icon_states_list = icon_states(icon_file) if (!(icon_state in icon_states_list)) - var/icon_states_string - for (var/an_icon_state in icon_states_list) - if (!icon_states_string) - icon_states_string = "[json_encode(an_icon_state)](\ref[an_icon_state])" - else - icon_states_string += ", [json_encode(an_icon_state)](\ref[an_icon_state])" - - stack_trace("[item] does not have a valid icon state, icon=[icon_file], icon_state=[json_encode(icon_state)](\ref[icon_state]), icon_states=[icon_states_string]") continue #endif diff --git a/code/modules/atmospherics/auxgm/gas_types.dm b/code/modules/atmospherics/auxgm/gas_types.dm index e48a49d5eeb1a..c7fbd251e37a7 100644 --- a/code/modules/atmospherics/auxgm/gas_types.dm +++ b/code/modules/atmospherics/auxgm/gas_types.dm @@ -76,6 +76,7 @@ gas_overlay = "nitrous_oxide" moles_visible = MOLES_GAS_VISIBLE * 2 flags = GAS_FLAG_DANGEROUS + fusion_power = 10 fire_products = list(GAS_N2 = 1) oxidation_rate = 0.5 oxidation_temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST + 100 @@ -100,6 +101,8 @@ id = GAS_PLUOXIUM specific_heat = 80 name = "Pluoxium" + gas_overlay = "pluoxium" + moles_visible = MOLES_GAS_VISIBLE * 0.5 fusion_power = 10 oxidation_temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST * 25 // it is VERY stable oxidation_rate = 8 // when it can oxidize, it can oxidize a LOT @@ -108,7 +111,7 @@ heat_penalty = -1 transmit_modifier = -5 heat_resistance = 3 - price = 4 + price = 2.5 /datum/gas/pluoxium/generate_TLV() return new/datum/tlv(-1, -1, 5, 6) @@ -147,8 +150,9 @@ id = GAS_NITRYL specific_heat = 20 name = "Nitrogen dioxide" + gas_overlay = "nitryl" color = "#963" - moles_visible = MOLES_GAS_VISIBLE + moles_visible = MOLES_GAS_VISIBLE * 0.5 flags = GAS_FLAG_DANGEROUS fusion_power = 15 fire_products = list(GAS_N2 = 0.5) @@ -161,8 +165,10 @@ specific_heat = 2000 name = "Hyper-noblium" gas_overlay = "freon" - moles_visible = MOLES_GAS_VISIBLE - price = 17 + color = "#4488ff" + moles_visible = MOLES_GAS_VISIBLE * 0.5 + fusion_power = 10 + price = 2.5 /datum/gas/hydrogen id = GAS_HYDROGEN @@ -178,6 +184,7 @@ fire_products = list(GAS_H2O = 1) fire_burn_rate = 2 fire_temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST - 50 + price = 1 /datum/gas/bz id = GAS_BZ @@ -190,7 +197,7 @@ enthalpy = FIRE_CARBON_ENERGY_RELEASED // it is a mystery transmit_modifier = -2 radioactivity_modifier = 5 - price = 2 + price = 1.5 /datum/gas/stimulum id = GAS_STIMULUM @@ -211,7 +218,7 @@ gas_overlay = "miasma" color = "#963" moles_visible = MOLES_GAS_VISIBLE * 60 -// price = 2 BLUEMOON DELETE кому впринципе нужны миазмы? + price = 1 /datum/gas/methane id = GAS_METHANE @@ -298,3 +305,85 @@ transmit_modifier = -10 heat_penalty = -10 price = 5 // IT'S NOT ACTUALLY THAT HARD TO GET INTO A CANISTER LOL + +/datum/gas/helium + id = GAS_HELIUM + specific_heat = 15 + name = "Helium" + fusion_power = 7 + price = 3.5 + +/datum/gas/freon + id = GAS_FREON + specific_heat = 600 + name = "Freon" + gas_overlay = "freon" + color = "#66ccff" + moles_visible = MOLES_GAS_VISIBLE * 15 + fusion_power = -5 + flags = GAS_FLAG_DANGEROUS + breath_reagent = /datum/reagent/freon + price = 5 + +/datum/gas/halon + id = GAS_HALON + specific_heat = 175 + name = "Halon" + gas_overlay = "halon" + color = "#44cc44" + moles_visible = MOLES_GAS_VISIBLE * 0.5 + flags = GAS_FLAG_DANGEROUS + breath_reagent = /datum/reagent/halon + price = 4 + +/datum/gas/antinoblium + id = GAS_ANTINOBLIUM + specific_heat = 1 + name = "Antinoblium" + gas_overlay = "antinoblium" + color = "#9966ff" + moles_visible = MOLES_GAS_VISIBLE * 0.5 + fusion_power = 20 + flags = GAS_FLAG_DANGEROUS + price = 10 + +/datum/gas/proto_nitrate + id = GAS_PROTO_NITRATE + specific_heat = 30 + name = "Proto Nitrate" + gas_overlay = "proto_nitrate" + color = "#44dd66" + moles_visible = MOLES_GAS_VISIBLE * 0.5 + flags = GAS_FLAG_DANGEROUS + price = 2.5 + +/datum/gas/zauker + id = GAS_ZAUKER + specific_heat = 350 + name = "Zauker" + gas_overlay = "zauker" + color = "#6644aa" + moles_visible = MOLES_GAS_VISIBLE * 0.5 + flags = GAS_FLAG_DANGEROUS + price = 7 + +/datum/gas/healium + id = GAS_HEALIUM + specific_heat = 10 + name = "Healium" + gas_overlay = "generic" + color = "#ff4444" + moles_visible = MOLES_GAS_VISIBLE * 0.5 + flags = GAS_FLAG_DANGEROUS + price = 5.5 + +/datum/gas/nitrium + id = GAS_NITRIUM + specific_heat = 10 + name = "Nitrium" + gas_overlay = "nitrium" + color = "#8b7355" + moles_visible = MOLES_GAS_VISIBLE * 0.5 + fusion_power = 7 + flags = GAS_FLAG_DANGEROUS + price = 6 diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index c9ff05d1b2d58..7ef20f56685ef 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -182,6 +182,25 @@ we use a hook instead return removed +/// Removes a specific amount of one gas. Returns a gas_mixture with that gas, or null if amount <= 0. +/// If into is supplied, that mixture is cleared and filled (no allocation); otherwise a new mixture is created. +/datum/gas_mixture/proc/remove_specific(gas_id, amount, datum/gas_mixture/into) + var/current = get_moles(gas_id) + amount = min(amount, current) + if(amount <= 0) + return null + if(into) + into.clear() + into.set_moles(gas_id, amount) + into.set_temperature(return_temperature()) + adjust_moles(gas_id, -amount) + return into + var/datum/gas_mixture/removed = new type(return_volume()) + removed.set_moles(gas_id, amount) + removed.set_temperature(return_temperature()) + adjust_moles(gas_id, -amount) + return removed + /datum/gas_mixture/copy() var/datum/gas_mixture/copy = new type copy.copy_from(src) diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index 95baff7625ac1..6560213a97563 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -39,7 +39,10 @@ id = "nobstop" /datum/gas_reaction/nobliumsupression/init_reqs() - min_requirements = list(GAS_HYPERNOB = REACTION_OPPRESSION_THRESHOLD) + min_requirements = list( + GAS_HYPERNOB = REACTION_OPPRESSION_THRESHOLD, + "TEMP" = REACTION_OPPRESSION_MIN_TEMP // only stops reactions when temp > 20 K + ) /datum/gas_reaction/nobliumsupression/react() return STOP_REACTIONS @@ -195,7 +198,7 @@ //plasma combustion: combustion of oxygen and plasma (treated as hydrocarbons). creates hotspots. exothermic /datum/gas_reaction/plasmafire - priority = -2 //fire should ALWAYS be last, but plasma fires happen after tritium fires + priority = -2 name = "Plasma Combustion" exclude = TRUE // generic fire now takes care of this id = "plasmafire" @@ -537,7 +540,7 @@ return list("success" = FALSE, "message" = "Nitryl isn't being generated correctly!") return ..() -/datum/gas_reaction/bzformation //Formation of BZ by combining plasma and tritium at low pressures. Exothermic. +/datum/gas_reaction/bzformation // Formation of BZ: at least 10 mol each N2O and Plasma at low pressure (optimal ~10 kPa). Plasma 2x N2O. Exothermic. priority = 4 name = "BZ Gas formation" id = "bzformation" @@ -634,7 +637,7 @@ return list("success" = FALSE, "message" = "Stimulum isn't being generated correctly!") return ..() -/datum/gas_reaction/nobliumformation //Hyper-Noblium formation is extrememly endothermic, but requires high temperatures to start. Due to its high mass, hyper-nobelium uses large amounts of nitrogen and tritium. BZ can be used as a catalyst to make it less endothermic. +/datum/gas_reaction/nobliumformation // Hyper-Noblium at extremely low temps (below 15 K). N2 + Tritium, exothermic. 10 N2 per mol; Tritium 5 down to 0.005 with BZ. priority = 6 name = "Hyper-Noblium condensation" id = "nobformation" @@ -643,76 +646,88 @@ min_requirements = list( GAS_N2 = 10, GAS_TRITIUM = 5, - "ENER" = NOBLIUM_FORMATION_ENERGY) + "MAX_TEMP" = NOBLIUM_FORMATION_MAX_TEMP + ) /datum/gas_reaction/nobliumformation/react(datum/gas_mixture/air) - var/old_heat_capacity = air.heat_capacity() - var/nob_formed = min((air.get_moles(GAS_N2)+air.get_moles(GAS_TRITIUM))/100,air.get_moles(GAS_TRITIUM)/10,air.get_moles(GAS_N2)/20) - var/energy_taken = nob_formed*(NOBLIUM_FORMATION_ENERGY/(max(air.get_moles(GAS_BZ),1))) - if ((air.get_moles(GAS_TRITIUM) - 10*nob_formed < 0) || (air.get_moles(GAS_N2) - 20*nob_formed < 0)) + var/temperature = air.return_temperature() + if(temperature > NOBLIUM_FORMATION_MAX_TEMP) return NO_REACTION - air.adjust_moles(GAS_TRITIUM, -10*nob_formed) - air.adjust_moles(GAS_N2, -20*nob_formed) - air.adjust_moles(GAS_HYPERNOB,nob_formed) - + var/n2_moles = air.get_moles(GAS_N2) + var/tritium_moles = air.get_moles(GAS_TRITIUM) + var/bz_moles = air.get_moles(GAS_BZ) + // 10 N2 per mol Hyper-noblium; Tritium used = 5 * trit/(trit + 1000*bz) per mol (5 min, down to ~0.005 at 1:1000 Trit:BZ) + var/trit_per_nob = 5 * tritium_moles / max(tritium_moles + 1000 * bz_moles, 0.001) + var/nob_formed = min(n2_moles / 10, tritium_moles / max(trit_per_nob, 0.005)) + if(nob_formed <= 0) + return NO_REACTION + var/trit_consumed = nob_formed * trit_per_nob + if(trit_consumed > tritium_moles || nob_formed * 10 > n2_moles) + return NO_REACTION + var/old_heat_capacity = air.heat_capacity() + air.adjust_moles(GAS_N2, -nob_formed * 10) + air.adjust_moles(GAS_TRITIUM, -trit_consumed) + air.adjust_moles(GAS_HYPERNOB, nob_formed) + // Exothermic; BZ reduces energy released + var/energy_released = nob_formed * NOBLIUM_FORMATION_ENERGY / max(1, bz_moles * 10) SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, nob_formed*NOBLIUM_RESEARCH_AMOUNT) - - if (nob_formed) - var/new_heat_capacity = air.heat_capacity() - if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) - air.set_temperature(max(((air.return_temperature()*old_heat_capacity - energy_taken)/new_heat_capacity),TCMB)) + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB)) + return REACTING /datum/gas_reaction/nobliumformation/test() var/datum/gas_mixture/G = new - G.set_moles(GAS_N2,100) - G.set_moles(GAS_TRITIUM,500) + G.set_moles(GAS_N2, 100) + G.set_moles(GAS_TRITIUM, 50) G.set_volume(1000) - G.set_temperature(5000000) // yeah, really + G.set_temperature(10) // below 15 K var/result = G.react() if(result != REACTING) - return list("success" = FALSE, "message" = "Reaction didn't go at all!") - if(abs(G.thermal_energy() - 23000000000) > 1000000) // god i hate floating points - return list("success" = FALSE, "message" = "Hyper-nob formation isn't removing the right amount of heat! Should be 23,000,000,000, is instead [G.thermal_energy()]") + return list("success" = FALSE, "message" = "Hyper-nob formation didn't run at 10 K!") return ..() -/datum/gas_reaction/miaster //dry heat sterilization: clears out pathogens in the air - priority = -10 //after all the heating from fires etc. is done +/datum/gas_reaction/miaster //dry heat sterilization: sterilized into oxygen at 170°C (443.15 K) + priority = -999 // lowest priority of all reactions name = "Dry Heat Sterilization" id = "sterilization" /datum/gas_reaction/miaster/init_reqs() min_requirements = list( - "TEMP" = FIRE_MINIMUM_TEMPERATURE_TO_EXIST+70, + "TEMP" = T0C + 170, // 170°C GAS_MIASMA = MINIMUM_MOLE_COUNT ) /datum/gas_reaction/miaster/react(datum/gas_mixture/air, datum/holder) - // As the name says it, it needs to be dry - if(air.get_moles(GAS_H2O) && air.get_moles(GAS_H2O)/air.total_moles() > 0.1) + // Presence of water vapor in quantities higher than 0.1 moles prevents this + if(air.get_moles(GAS_H2O) > 0.1) return - //Replace miasma with oxygen - var/cleaned_air = min(air.get_moles(GAS_MIASMA), 20 + (air.return_temperature() - FIRE_MINIMUM_TEMPERATURE_TO_EXIST - 70) / 20) + // Replace miasma with oxygen (slightly exothermic) + var/cleaned_air = min(air.get_moles(GAS_MIASMA), 20 + (air.return_temperature() - (T0C + 170)) / 20) + if(cleaned_air <= 0) + return air.adjust_moles(GAS_MIASMA, -cleaned_air) - air.adjust_moles(GAS_METHANE, cleaned_air) + air.adjust_moles(GAS_O2, cleaned_air) - //Possibly burning a bit of organic matter through maillard reaction, so a *tiny* bit more heat would be understandable + // Slightly exothermic air.set_temperature(air.return_temperature() + cleaned_air * 0.002) - SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, cleaned_air*MIASMA_RESEARCH_AMOUNT)//Turns out the burning of miasma is kinda interesting to scientists + SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, cleaned_air*MIASMA_RESEARCH_AMOUNT) + return REACTING /datum/gas_reaction/miaster/test() var/datum/gas_mixture/G = new G.set_moles(GAS_MIASMA,1) G.set_volume(1000) - G.set_temperature(450) + G.set_temperature(T0C + 170 + 10) // above 170°C var/result = G.react() if(result != REACTING) return list("success" = FALSE, "message" = "Reaction didn't go at all!") G.clear() G.set_moles(GAS_MIASMA,1) - G.set_temperature(450) - G.set_moles(GAS_H2O,0.5) + G.set_moles(GAS_H2O, 0.2) // >0.1 moles prevents + G.set_temperature(T0C + 200) result = G.react() if(result != NO_REACTION) return list("success" = FALSE, "message" = "Miasma sterilization not stopping due to water vapor correctly!") @@ -804,3 +819,460 @@ energy_remaining = initial_energy - air.thermal_energy() air.set_temperature(initial_energy / air.heat_capacity()) return REACTING + +// === Fusion/exotic gas reactions — синтез вручную, полная картина атмоса === + +/datum/gas_reaction/freonfire + priority = -12 + name = "Freon Combustion" + id = "freonfire" + +/datum/gas_reaction/freonfire/init_reqs() + min_requirements = list( + GAS_O2 = MINIMUM_MOLE_COUNT, + GAS_FREON = MINIMUM_MOLE_COUNT, + "TEMP" = FREON_TERMINAL_TEMPERATURE + ) + +/datum/gas_reaction/freonfire/react(datum/gas_mixture/air, datum/holder) + var/temperature = air.return_temperature() + var/max_burn_temp = FREON_MAXIMUM_BURN_TEMPERATURE + if(air.get_moles(GAS_PROTO_NITRATE) > MINIMUM_MOLE_COUNT) + max_burn_temp = FREON_CATALYST_MAX_TEMPERATURE + if(temperature > max_burn_temp) + return NO_REACTION + var/temperature_scale + if(temperature < FREON_TERMINAL_TEMPERATURE) + temperature_scale = 0 + else if(temperature < FREON_LOWER_TEMPERATURE) + temperature_scale = 0.5 + else + temperature_scale = (max_burn_temp - temperature) / (max_burn_temp - FREON_TERMINAL_TEMPERATURE) + if(temperature_scale <= 0) + return NO_REACTION + var/oxygen_burn_ratio = OXYGEN_BURN_RATIO_BASE - temperature_scale + var/freon_moles = air.get_moles(GAS_FREON) + var/oxygen_moles = air.get_moles(GAS_O2) + var/freon_burn_rate + if(oxygen_moles < freon_moles * FREON_OXYGEN_FULLBURN) + freon_burn_rate = ((oxygen_moles / FREON_OXYGEN_FULLBURN) / FREON_BURN_RATE_DELTA) * temperature_scale + else + freon_burn_rate = (freon_moles / FREON_BURN_RATE_DELTA) * temperature_scale + if(freon_burn_rate < MINIMUM_HEAT_CAPACITY) + return NO_REACTION + var/old_heat_capacity = air.heat_capacity() + freon_burn_rate = min(freon_burn_rate, freon_moles, oxygen_moles * INVERSE(oxygen_burn_ratio)) + air.adjust_moles(GAS_FREON, -freon_burn_rate) + air.adjust_moles(GAS_O2, -(freon_burn_rate * oxygen_burn_ratio)) + air.adjust_moles(GAS_CO2, freon_burn_rate) + var/energy_consumed = FIRE_FREON_ENERGY_CONSUMED * freon_burn_rate + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((temperature * old_heat_capacity - energy_consumed) / new_heat_capacity, TCMB)) + if(isopenturf(holder) && temperature >= FREON_HOT_ICE_MIN_TEMP && temperature <= FREON_HOT_ICE_MAX_TEMP && prob(5)) + new /obj/item/stack/sheet/hot_ice(get_turf(holder), 1) + return REACTING + +/datum/gas_reaction/freonformation + priority = 33 + name = "Freon Formation" + id = "freonformation" + +/datum/gas_reaction/freonformation/init_reqs() + min_requirements = list( + GAS_PLASMA = MINIMUM_MOLE_COUNT, + GAS_CO2 = MINIMUM_MOLE_COUNT, + GAS_BZ = MINIMUM_MOLE_COUNT, + "TEMP" = FREON_FORMATION_MIN_TEMPERATURE + ) + +/datum/gas_reaction/freonformation/react(datum/gas_mixture/air, datum/holder) + var/temperature = air.return_temperature() + var/plasma_moles = air.get_moles(GAS_PLASMA) + var/co2_moles = air.get_moles(GAS_CO2) + var/bz_moles = air.get_moles(GAS_BZ) + var/heat_factor = (temperature - FREON_FORMATION_MIN_TEMPERATURE) / 100 + var/minimal_mole_factor = min(plasma_moles / 0.6, co2_moles / 0.3, bz_moles / 0.1) + var/reaction_units = min(heat_factor * minimal_mole_factor * 0.05, plasma_moles * INVERSE(0.6), co2_moles * INVERSE(0.3), bz_moles * INVERSE(0.1)) + if(reaction_units <= 0) + return NO_REACTION + air.adjust_moles(GAS_PLASMA, -reaction_units * 0.6) + air.adjust_moles(GAS_CO2, -reaction_units * 0.3) + air.adjust_moles(GAS_BZ, -reaction_units * 0.1) + air.adjust_moles(GAS_FREON, reaction_units * 10) + var/old_heat_capacity = air.heat_capacity() + var/energy_consumed = FREON_FORMATION_ENERGY_CONSUMED * reaction_units + if(old_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((air.return_temperature() * old_heat_capacity - energy_consumed) / air.heat_capacity(), TCMB)) + return REACTING + +/datum/gas_reaction/halon_o2removal + priority = 22 + name = "Halon Oxygen Absorption" + id = "halon_o2removal" + +/datum/gas_reaction/halon_o2removal/init_reqs() + min_requirements = list( + GAS_HALON = MINIMUM_MOLE_COUNT, + GAS_O2 = MINIMUM_MOLE_COUNT, + "TEMP" = HALON_COMBUSTION_MIN_TEMPERATURE + ) + +/datum/gas_reaction/halon_o2removal/react(datum/gas_mixture/air, datum/holder) + var/temperature = air.return_temperature() + var/halon_moles = air.get_moles(GAS_HALON) + var/oxygen_moles = air.get_moles(GAS_O2) + var/heat_efficiency = min(temperature / HALON_COMBUSTION_TEMPERATURE_SCALE, halon_moles, oxygen_moles * INVERSE(20)) + if(heat_efficiency <= 0) + return NO_REACTION + var/old_heat_capacity = air.heat_capacity() + air.adjust_moles(GAS_HALON, -heat_efficiency) + air.adjust_moles(GAS_O2, -(heat_efficiency * 20)) + air.adjust_moles(GAS_PLUOXIUM, heat_efficiency * 2.5) + var/energy_used = heat_efficiency * HALON_COMBUSTION_ENERGY + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((temperature * old_heat_capacity - energy_used) / new_heat_capacity, TCMB)) + return REACTING + +/datum/gas_reaction/healium_formation + priority = 34 + name = "Healium Formation" + id = "healium_formation" + +/datum/gas_reaction/healium_formation/init_reqs() + min_requirements = list( + GAS_BZ = MINIMUM_MOLE_COUNT, + GAS_FREON = MINIMUM_MOLE_COUNT, + "TEMP" = HEALIUM_FORMATION_MIN_TEMP + ) + +/datum/gas_reaction/healium_formation/react(datum/gas_mixture/air, datum/holder) + var/temperature = air.return_temperature() + if(temperature > HEALIUM_FORMATION_MAX_TEMP) + return NO_REACTION + var/freon_moles = air.get_moles(GAS_FREON) + var/bz_moles = air.get_moles(GAS_BZ) + var/heat_efficiency = min(temperature * 0.3, freon_moles * INVERSE(2.75), bz_moles * INVERSE(0.25)) + if(heat_efficiency <= 0) + return NO_REACTION + var/old_heat_capacity = air.heat_capacity() + air.adjust_moles(GAS_FREON, -heat_efficiency * 2.75) + air.adjust_moles(GAS_BZ, -heat_efficiency * 0.25) + air.adjust_moles(GAS_HEALIUM, heat_efficiency * 3) + var/energy_released = heat_efficiency * HEALIUM_FORMATION_ENERGY + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB)) + return REACTING + +/datum/gas_reaction/zauker_formation + priority = 35 + name = "Zauker Formation" + id = "zauker_formation" + +/datum/gas_reaction/zauker_formation/init_reqs() + min_requirements = list( + GAS_HYPERNOB = MINIMUM_MOLE_COUNT, + GAS_NITRIUM = MINIMUM_MOLE_COUNT, + "TEMP" = ZAUKER_FORMATION_MIN_TEMPERATURE + ) + +/datum/gas_reaction/zauker_formation/react(datum/gas_mixture/air, datum/holder) + var/temperature = air.return_temperature() + if(temperature > ZAUKER_FORMATION_MAX_TEMPERATURE) + return NO_REACTION + var/hypernob_moles = air.get_moles(GAS_HYPERNOB) + var/nitrium_moles = air.get_moles(GAS_NITRIUM) + var/heat_efficiency = min(temperature * ZAUKER_FORMATION_TEMPERATURE_SCALE, hypernob_moles * INVERSE(0.01), nitrium_moles * INVERSE(0.5)) + if(heat_efficiency <= 0) + return NO_REACTION + var/old_heat_capacity = air.heat_capacity() + air.adjust_moles(GAS_HYPERNOB, -heat_efficiency * 0.01) + air.adjust_moles(GAS_NITRIUM, -heat_efficiency * 0.5) + air.adjust_moles(GAS_ZAUKER, heat_efficiency * 0.5) + var/energy_used = heat_efficiency * ZAUKER_FORMATION_ENERGY + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((temperature * old_heat_capacity - energy_used) / new_heat_capacity, TCMB)) + return REACTING + +/datum/gas_reaction/zauker_decomp + priority = 23 + name = "Zauker Decomposition" + id = "zauker_decomp" + +/datum/gas_reaction/zauker_decomp/init_reqs() + min_requirements = list( + GAS_ZAUKER = MINIMUM_MOLE_COUNT, + GAS_N2 = MINIMUM_MOLE_COUNT + ) + +/datum/gas_reaction/zauker_decomp/react(datum/gas_mixture/air, datum/holder) + var/n2_moles = air.get_moles(GAS_N2) + var/zauker_moles = air.get_moles(GAS_ZAUKER) + var/burned_fuel = min(ZAUKER_DECOMPOSITION_MAX_RATE, n2_moles, zauker_moles) + if(burned_fuel <= 0) + return NO_REACTION + var/old_heat_capacity = air.heat_capacity() + var/temperature = air.return_temperature() + air.adjust_moles(GAS_ZAUKER, -burned_fuel) + air.adjust_moles(GAS_O2, burned_fuel * 0.3) + air.adjust_moles(GAS_N2, burned_fuel * 0.7) + var/energy_released = ZAUKER_DECOMPOSITION_ENERGY * burned_fuel + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB)) + return REACTING + +/datum/gas_reaction/nitrium_formation + priority = 36 + name = "Nitrium Formation" + id = "nitrium_formation" + +/datum/gas_reaction/nitrium_formation/init_reqs() + min_requirements = list( + GAS_TRITIUM = 20, + GAS_N2 = 10, + GAS_BZ = 5, + "TEMP" = NITRIUM_FORMATION_MIN_TEMP + ) + +/datum/gas_reaction/nitrium_formation/react(datum/gas_mixture/air, datum/holder) + var/temperature = air.return_temperature() + var/tritium_moles = air.get_moles(GAS_TRITIUM) + var/n2_moles = air.get_moles(GAS_N2) + var/bz_moles = air.get_moles(GAS_BZ) + var/heat_efficiency = min(temperature / NITRIUM_FORMATION_TEMP_DIVISOR, tritium_moles, n2_moles, bz_moles * INVERSE(0.05)) + if(heat_efficiency <= 0) + return NO_REACTION + var/old_heat_capacity = air.heat_capacity() + air.adjust_moles(GAS_TRITIUM, -heat_efficiency) + air.adjust_moles(GAS_N2, -heat_efficiency) + air.adjust_moles(GAS_BZ, -heat_efficiency * 0.05) + air.adjust_moles(GAS_NITRIUM, heat_efficiency) + var/energy_used = heat_efficiency * NITRIUM_FORMATION_ENERGY + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((temperature * old_heat_capacity - energy_used) / new_heat_capacity, TCMB)) + return REACTING + +/datum/gas_reaction/nitrium_decomposition + priority = 24 + name = "Nitrium Decomposition" + id = "nitrium_decomp" + +/datum/gas_reaction/nitrium_decomposition/init_reqs() + min_requirements = list( + GAS_NITRIUM = MINIMUM_MOLE_COUNT, + GAS_O2 = MINIMUM_MOLE_COUNT, // decomposes when in contact with Oxygen + "TEMP" = 1 + ) + +/datum/gas_reaction/nitrium_decomposition/react(datum/gas_mixture/air, datum/holder) + var/temperature = air.return_temperature() + if(temperature > NITRIUM_DECOMPOSITION_MAX_TEMP) + return NO_REACTION + var/nitrium_moles = air.get_moles(GAS_NITRIUM) + var/heat_efficiency = min(temperature / NITRIUM_DECOMPOSITION_TEMP_DIVISOR, nitrium_moles) + if(heat_efficiency <= 0) + return NO_REACTION + var/old_heat_capacity = air.heat_capacity() + air.adjust_moles(GAS_NITRIUM, -heat_efficiency) + air.adjust_moles(GAS_N2, heat_efficiency) + air.adjust_moles(GAS_HYDROGEN, heat_efficiency) + var/energy_released = heat_efficiency * NITRIUM_DECOMPOSITION_ENERGY + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB)) + return REACTING + +/datum/gas_reaction/pluox_formation + priority = 37 + name = "Pluoxium Formation" + id = "pluox_formation" + +/datum/gas_reaction/pluox_formation/init_reqs() + min_requirements = list( + GAS_CO2 = MINIMUM_MOLE_COUNT, + GAS_O2 = MINIMUM_MOLE_COUNT, + GAS_TRITIUM = MINIMUM_MOLE_COUNT, + "TEMP" = PLUOXIUM_FORMATION_MIN_TEMP + ) + +/datum/gas_reaction/pluox_formation/react(datum/gas_mixture/air, datum/holder) + var/temperature = air.return_temperature() + if(temperature > PLUOXIUM_FORMATION_MAX_TEMP) + return NO_REACTION + var/co2_moles = air.get_moles(GAS_CO2) + var/o2_moles = air.get_moles(GAS_O2) + var/tritium_moles = air.get_moles(GAS_TRITIUM) + // Consumption ratio 100 O2 : 50 CO2 : 1 Tritium per 50 pluoxium (i.e. 2 O2 : 1 CO2 : 0.01 Trit per 1 pluox) + var/produced_amount = min(PLUOXIUM_FORMATION_MAX_RATE, o2_moles * 0.5, co2_moles, tritium_moles * INVERSE(0.01)) + if(produced_amount <= 0) + return NO_REACTION + var/old_heat_capacity = air.heat_capacity() + air.adjust_moles(GAS_CO2, -produced_amount) + air.adjust_moles(GAS_O2, -produced_amount * 2) + air.adjust_moles(GAS_TRITIUM, -produced_amount * 0.01) + air.adjust_moles(GAS_PLUOXIUM, produced_amount) + air.adjust_moles(GAS_HYDROGEN, produced_amount * 0.01) // 1% H2 byproduct + var/energy_released = produced_amount * PLUOXIUM_FORMATION_ENERGY + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB)) + return REACTING + +/datum/gas_reaction/proto_nitrate_formation + priority = 38 + name = "Proto Nitrate Formation" + id = "proto_nitrate_formation" + +/datum/gas_reaction/proto_nitrate_formation/init_reqs() + min_requirements = list( + GAS_PLUOXIUM = MINIMUM_MOLE_COUNT, + GAS_HYDROGEN = MINIMUM_MOLE_COUNT, + "TEMP" = PN_FORMATION_MIN_TEMPERATURE + ) + +/datum/gas_reaction/proto_nitrate_formation/react(datum/gas_mixture/air, datum/holder) + var/temperature = air.return_temperature() + if(temperature > PN_FORMATION_MAX_TEMPERATURE) + return NO_REACTION + var/pluox_moles = air.get_moles(GAS_PLUOXIUM) + var/h2_moles = air.get_moles(GAS_HYDROGEN) + var/heat_efficiency = min(temperature * 0.005, pluox_moles * INVERSE(0.2), h2_moles * INVERSE(2)) + if(heat_efficiency <= 0) + return NO_REACTION + var/old_heat_capacity = air.heat_capacity() + air.adjust_moles(GAS_HYDROGEN, -heat_efficiency * 2) + air.adjust_moles(GAS_PLUOXIUM, -heat_efficiency * 0.2) + air.adjust_moles(GAS_PROTO_NITRATE, heat_efficiency * 2.2) + var/energy_released = heat_efficiency * PN_FORMATION_ENERGY + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB)) + return REACTING + +/datum/gas_reaction/proto_nitrate_hydrogen_response + priority = 25 + name = "Proto Nitrate Hydrogen Response" + id = "proto_nitrate_hydrogen_response" + +/datum/gas_reaction/proto_nitrate_hydrogen_response/init_reqs() + min_requirements = list( + GAS_PROTO_NITRATE = MINIMUM_MOLE_COUNT, + GAS_HYDROGEN = PN_HYDROGEN_CONVERSION_THRESHOLD + ) + +/datum/gas_reaction/proto_nitrate_hydrogen_response/react(datum/gas_mixture/air, datum/holder) + var/proto_moles = air.get_moles(GAS_PROTO_NITRATE) + var/h2_moles = air.get_moles(GAS_HYDROGEN) + var/produced_amount = min(PN_HYDROGEN_CONVERSION_MAX_RATE, h2_moles, proto_moles) + if(produced_amount <= 0) + return NO_REACTION + var/old_heat_capacity = air.heat_capacity() + var/temperature = air.return_temperature() + air.adjust_moles(GAS_HYDROGEN, -produced_amount) + air.adjust_moles(GAS_PROTO_NITRATE, produced_amount * 0.5) + var/energy_used = produced_amount * PN_HYDROGEN_CONVERSION_ENERGY + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((temperature * old_heat_capacity - energy_used) / new_heat_capacity, TCMB)) + return REACTING + +/datum/gas_reaction/proto_nitrate_tritium_response + priority = 26 + name = "Proto Nitrate Tritium Response" + id = "proto_nitrate_tritium_response" + +/datum/gas_reaction/proto_nitrate_tritium_response/init_reqs() + min_requirements = list( + GAS_PROTO_NITRATE = MINIMUM_MOLE_COUNT, + GAS_TRITIUM = MINIMUM_MOLE_COUNT, + "TEMP" = PN_TRITIUM_CONVERSION_MIN_TEMP + ) + +/datum/gas_reaction/proto_nitrate_tritium_response/react(datum/gas_mixture/air, datum/holder) + var/temperature = air.return_temperature() + if(temperature > PN_TRITIUM_CONVERSION_MAX_TEMP) + return NO_REACTION + var/proto_moles = air.get_moles(GAS_PROTO_NITRATE) + var/tritium_moles = air.get_moles(GAS_TRITIUM) + var/produced_amount = min(temperature / 34 * (tritium_moles * proto_moles) / (tritium_moles + 10 * proto_moles), tritium_moles, proto_moles * INVERSE(0.01)) + if(produced_amount <= 0) + return NO_REACTION + var/old_heat_capacity = air.heat_capacity() + air.adjust_moles(GAS_PROTO_NITRATE, -produced_amount * 0.01) + air.adjust_moles(GAS_TRITIUM, -produced_amount) + air.adjust_moles(GAS_HYDROGEN, produced_amount) + var/energy_released = produced_amount * PN_TRITIUM_CONVERSION_ENERGY + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB)) + return REACTING + +/datum/gas_reaction/proto_nitrate_bz_response + priority = 27 + name = "Proto Nitrate BZ Response" + id = "proto_nitrate_bz_response" + +/datum/gas_reaction/proto_nitrate_bz_response/init_reqs() + min_requirements = list( + GAS_PROTO_NITRATE = MINIMUM_MOLE_COUNT, + GAS_BZ = MINIMUM_MOLE_COUNT, + "TEMP" = PN_BZASE_MIN_TEMP + ) + +/datum/gas_reaction/proto_nitrate_bz_response/react(datum/gas_mixture/air, datum/holder) + var/temperature = air.return_temperature() + if(temperature > PN_BZASE_MAX_TEMP) + return NO_REACTION + var/old_heat_capacity = air.heat_capacity() + var/proto_moles = air.get_moles(GAS_PROTO_NITRATE) + var/bz_moles = air.get_moles(GAS_BZ) + var/consumed_amount = min(temperature / 2240 * bz_moles * proto_moles / (bz_moles + proto_moles), bz_moles, proto_moles) + if(consumed_amount <= 0) + return NO_REACTION + air.adjust_moles(GAS_BZ, -consumed_amount) + air.adjust_moles(GAS_PROTO_NITRATE, -consumed_amount) + air.adjust_moles(GAS_N2, consumed_amount * 0.4) + air.adjust_moles(GAS_HELIUM, consumed_amount * 1.6) + air.adjust_moles(GAS_PLASMA, consumed_amount * 0.8) + var/energy_released = consumed_amount * PN_BZASE_ENERGY + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB)) + return REACTING + +/datum/gas_reaction/antinoblium_replication + priority = 40 + name = "Antinoblium Replication" + id = "antinoblium_replication" + +/datum/gas_reaction/antinoblium_replication/init_reqs() + min_requirements = list( + GAS_ANTINOBLIUM = MOLES_GAS_VISIBLE, + "TEMP" = REACTION_OPPRESSION_MIN_TEMP + ) + +/datum/gas_reaction/antinoblium_replication/react(datum/gas_mixture/air, datum/holder) + var/old_heat_capacity = air.heat_capacity() + var/total_moles = air.total_moles() + var/antinoblium_moles = air.get_moles(GAS_ANTINOBLIUM) + var/total_not_antinoblium_moles = total_moles - antinoblium_moles + if(total_not_antinoblium_moles < MINIMUM_MOLE_COUNT) + return NO_REACTION + var/reaction_rate = min(antinoblium_moles / ANTINOBLIUM_CONVERSION_DIVISOR, total_not_antinoblium_moles) + var/list/gases = air.get_gases() + for(var/g in gases) + if(g == GAS_ANTINOBLIUM) + continue + var/m = air.get_moles(g) + if(m > 0) + air.adjust_moles(g, -reaction_rate * (m / total_not_antinoblium_moles)) + air.adjust_moles(GAS_ANTINOBLIUM, reaction_rate) + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max(air.return_temperature() * old_heat_capacity / new_heat_capacity, TCMB)) + return REACTING diff --git a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm new file mode 100644 index 0000000000000..5c7139de5d0f5 --- /dev/null +++ b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm @@ -0,0 +1,213 @@ +#define ELECTROLYZER_MODE_STANDBY "standby" +#define ELECTROLYZER_MODE_WORKING "working" + +/obj/machinery/electrolyzer + anchored = FALSE + density = TRUE + interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN + icon = 'icons/obj/pipes_n_cables/atmos.dmi' + icon_state = "electrolyzer-off" + name = "space electrolyzer" + desc = "Thanks to the fast and dynamic response of our electrolyzers, on-site hydrogen production is guaranteed. Warranty void if used by clowns." + max_integrity = 250 + armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 0, BIO = 0, RAD = 0, FIRE = 80, ACID = 10) + circuit = /obj/item/circuitboard/machine/electrolyzer + use_power = NO_POWER_USE + var/obj/item/stock_parts/cell/cell + var/on = FALSE + var/mode = ELECTROLYZER_MODE_STANDBY + var/working_power = 1 + var/efficiency = 0.5 + +/obj/machinery/electrolyzer/get_cell() + return cell + +/obj/machinery/electrolyzer/Initialize(mapload) + . = ..() + SSair.start_processing_machine(src) + update_appearance(UPDATE_ICON) + register_context() + +/obj/machinery/electrolyzer/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + context[SCREENTIP_CONTEXT_ALT_LMB] = "Turn [on ? "off" : "on"]" + if(!held_item) + return CONTEXTUAL_SCREENTIP_SET + switch(held_item.tool_behaviour) + if(TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel" + if(TOOL_WRENCH) + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Unan" : "An"]chor" + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/electrolyzer/Destroy() + if(cell) + QDEL_NULL(cell) + return ..() + +/obj/machinery/electrolyzer/on_deconstruction(disassembled) + if(cell) + LAZYADD(component_parts, cell) + cell = null + return ..() + +/obj/machinery/electrolyzer/examine(mob/user) + . = ..() + . += "\The [src] is [on ? "on" : "off"], and the panel is [panel_open ? "open" : "closed"]." + if(cell) + . += "The charge meter reads [cell ? round(cell.percent(), 1) : 0]%." + else + . += "There is no power cell installed." + if(in_range(user, src) || isobserver(user)) + . += span_notice("Alt-click to toggle [on ? "off" : "on"].") + . += span_notice("It will drain power from the [anchored ? "area's APC" : "internal power cell"].") + +/obj/machinery/electrolyzer/update_icon_state() + icon_state = "electrolyzer-[on ? "[mode]" : "off"]" + return ..() + +/obj/machinery/electrolyzer/update_overlays() + . = ..() + if(panel_open) + . += "electrolyzer-open" + +/obj/machinery/electrolyzer/process_atmos() + if(!is_operational && on) + on = FALSE + if(!on) + return PROCESS_KILL + if((!cell || cell.charge <= 0) && !anchored) + on = FALSE + update_appearance(UPDATE_ICON) + return PROCESS_KILL + + var/turf/our_turf = loc + if(!isturf(our_turf)) + if(mode != ELECTROLYZER_MODE_STANDBY) + mode = ELECTROLYZER_MODE_STANDBY + update_appearance(UPDATE_ICON) + return + + var/new_mode = on ? ELECTROLYZER_MODE_WORKING : ELECTROLYZER_MODE_STANDBY + if(mode != new_mode) + mode = new_mode + update_appearance(UPDATE_ICON) + if(mode == ELECTROLYZER_MODE_STANDBY) + return + + var/datum/gas_mixture/env = our_turf.return_air() + if(!env) + return + call_reactions(env) + our_turf.air_update_turf(FALSE, FALSE) + + var/power_to_use = (5 * (3 * working_power) * working_power) / (efficiency + working_power) + if(anchored) + use_power(power_to_use) + else + cell.use(power_to_use) + +/obj/machinery/electrolyzer/proc/call_reactions(datum/gas_mixture/env) + var/list/electrolyzer_args = list() + for(var/reaction_id in GLOB.electrolyzer_reactions) + var/datum/electrolyzer_reaction/R = GLOB.electrolyzer_reactions[reaction_id] + if(!R.reaction_check(env, electrolyzer_args)) + continue + R.react(env, working_power, electrolyzer_args) + +/obj/machinery/electrolyzer/RefreshParts() + . = ..() + var/power = 0 + var/cap = 0 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + power += M.rating + for(var/obj/item/stock_parts/capacitor/C in component_parts) + cap += C.rating + working_power = max(power, 1) + efficiency = (cap + 1) * 0.5 + +/obj/machinery/electrolyzer/screwdriver_act(mob/living/user, obj/item/tool) + tool.play_tool_sound(src, 50) + panel_open = !panel_open + balloon_alert(user, "[panel_open ? "opened" : "closed"] panel") + update_appearance(UPDATE_ICON) + return TRUE + +/obj/machinery/electrolyzer/wrench_act(mob/living/user, obj/item/tool) + . = ..() + default_unfasten_wrench(user, tool) + return TRUE + +/obj/machinery/electrolyzer/crowbar_act(mob/living/user, obj/item/tool) + return default_deconstruction_crowbar(tool) + +/obj/machinery/electrolyzer/attackby(obj/item/I, mob/user, list/modifiers, list/attack_modifiers) + add_fingerprint(user) + if(istype(I, /obj/item/stock_parts/cell)) + if(!panel_open) + balloon_alert(user, "open panel!") + return + if(cell) + balloon_alert(user, "cell inside!") + return + if(!user.transferItemToLoc(I, src)) + return + cell = I + I.add_fingerprint(user) + balloon_alert(user, "inserted cell") + SStgui.update_uis(src) + return + return ..() + +/obj/machinery/electrolyzer/AltClick(mob/user) + if(panel_open) + balloon_alert(user, "close panel!") + return + toggle_power(user) + +/obj/machinery/electrolyzer/proc/toggle_power(mob/user) + if(!anchored && !cell) + balloon_alert(user, "insert cell or anchor!") + return + on = !on + mode = ELECTROLYZER_MODE_STANDBY + update_appearance(UPDATE_ICON) + balloon_alert(user, "turned [on ? "on" : "off"]") + if(on) + SSair.start_processing_machine(src) + +/obj/machinery/electrolyzer/ui_state(mob/user) + return GLOB.physical_state + +/obj/machinery/electrolyzer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Electrolyzer", name) + ui.open() + +/obj/machinery/electrolyzer/ui_data() + var/list/data = list() + data["open"] = panel_open + data["on"] = on + data["hasPowercell"] = !isnull(cell) + data["anchored"] = anchored + if(cell) + data["powerLevel"] = round(cell.percent(), 1) + return data + +/obj/machinery/electrolyzer/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + switch(action) + if("power") + toggle_power(ui.user) + . = TRUE + if("eject") + if(panel_open && cell) + cell.forceMove(drop_location()) + cell = null + . = TRUE + +#undef ELECTROLYZER_MODE_STANDBY +#undef ELECTROLYZER_MODE_WORKING diff --git a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm new file mode 100644 index 0000000000000..daac2910bca39 --- /dev/null +++ b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer_reactions.dm @@ -0,0 +1,75 @@ +// Electrolyzer reactions (from WhiteMoon). Uses string gas IDs (GAS_*). +#define HALON_FORMATION_ENERGY 91232.1 + +GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list()) + +/proc/electrolyzer_reactions_list() + var/list/built = list() + for(var/reaction_path in subtypesof(/datum/electrolyzer_reaction)) + var/datum/electrolyzer_reaction/R = new reaction_path() + built[R.id] = R + return built + +/datum/electrolyzer_reaction + var/list/requirements + var/name = "reaction" + var/id = "r" + var/desc = "" + var/list/factor + +/datum/electrolyzer_reaction/proc/react(datum/gas_mixture/air_mixture, working_power, list/electrolyzer_args = list()) + return + +/datum/electrolyzer_reaction/proc/reaction_check(datum/gas_mixture/air_mixture, list/electrolyzer_args = list()) + var/temp = air_mixture.return_temperature() + if(requirements["MIN_TEMP"] && temp < requirements["MIN_TEMP"]) + return FALSE + if(requirements["MAX_TEMP"] && temp > requirements["MAX_TEMP"]) + return FALSE + for(var/gas_id in requirements) + if(gas_id == "MIN_TEMP" || gas_id == "MAX_TEMP") + continue + if(air_mixture.get_moles(gas_id) < requirements[gas_id]) + return FALSE + return TRUE + +// H2O -> O2 + 2 H2 +/datum/electrolyzer_reaction/h2o_conversion + name = "H2O Conversion" + id = "h2o_conversion" + desc = "Conversion of H2O into O2 and H2" + requirements = list(GAS_H2O = MINIMUM_MOLE_COUNT) + factor = list() + +/datum/electrolyzer_reaction/h2o_conversion/react(datum/gas_mixture/air_mixture, working_power, list/electrolyzer_args = list()) + var/old_heat = air_mixture.heat_capacity() + var/h2o_moles = air_mixture.get_moles(GAS_H2O) + var/proportion = min(h2o_moles * INVERSE(2), (2.5 * (working_power ** 2))) + air_mixture.adjust_moles(GAS_H2O, -proportion * 2) + air_mixture.adjust_moles(GAS_O2, proportion) + air_mixture.adjust_moles(GAS_HYDROGEN, proportion * 2) + var/new_heat = air_mixture.heat_capacity() + if(new_heat > MINIMUM_HEAT_CAPACITY) + air_mixture.set_temperature(max(air_mixture.return_temperature() * old_heat / new_heat, TCMB)) + +// BZ -> O2 + Halon (temperature‑dependent efficiency) +/datum/electrolyzer_reaction/halon_generation + name = "Halon generation" + id = "halon_generation" + desc = "Production of halon from the electrolysis of BZ." + requirements = list(GAS_BZ = MINIMUM_MOLE_COUNT) + factor = list() + +/datum/electrolyzer_reaction/halon_generation/react(datum/gas_mixture/air_mixture, working_power, list/electrolyzer_args = list()) + var/old_heat = air_mixture.heat_capacity() + var/bz_moles = air_mixture.get_moles(GAS_BZ) + var/reaction_efficency = min(bz_moles * (1 - NUM_E ** (-0.5 * air_mixture.return_temperature() * working_power / FIRE_MINIMUM_TEMPERATURE_TO_EXIST)), bz_moles) + air_mixture.adjust_moles(GAS_BZ, -reaction_efficency) + air_mixture.adjust_moles(GAS_O2, reaction_efficency * 0.2) + air_mixture.adjust_moles(GAS_HALON, reaction_efficency * 2) + var/energy_used = reaction_efficency * HALON_FORMATION_ENERGY + var/new_heat = air_mixture.heat_capacity() + if(new_heat > MINIMUM_HEAT_CAPACITY) + air_mixture.set_temperature(max(((air_mixture.return_temperature() * old_heat + energy_used) / new_heat), TCMB)) + +#undef HALON_FORMATION_ENERGY diff --git a/code/modules/atmospherics/machinery/components/fusion/_hfr_defines.dm b/code/modules/atmospherics/machinery/components/fusion/_hfr_defines.dm new file mode 100644 index 0000000000000..8fb88485f6e09 --- /dev/null +++ b/code/modules/atmospherics/machinery/components/fusion/_hfr_defines.dm @@ -0,0 +1,237 @@ +// +// HFR formula constants (what they scale: modifiers, heat, damage, thresholds) +// + +///Speed of light, in m/s +#define LIGHT_SPEED 299792458 +/// Scale factor for (LIGHT_SPEED**2) to avoid 32-bit float overflow: compute (c²/scale) then multiply by scale. +#define LIGHT_SPEED_SQ_SCALE 1e10 +#define LIGHT_SPEED_SQ_SCALED (LIGHT_SPEED * LIGHT_SPEED / LIGHT_SPEED_SQ_SCALE) +///Calculation between the plank constant and the lambda of the lightwave +#define PLANCK_LIGHT_CONSTANT 2e-16 +///Radius of the h2 calculated based on the amount of number of atom in a mole (and some addition for balancing issues) +#define CALCULATED_H2RADIUS 120e-4 +///Radius of the trit calculated based on the amount of number of atom in a mole (and some addition for balancing issues) +#define CALCULATED_TRITRADIUS 230e-3 +///Power conduction in the void, used to calculate the efficiency of the reaction +#define VOID_CONDUCTION 1e-2 +/// Volume scaling for scaled_fuel/scaled_moderator lists (scale_factor = volume * this) +#define HFR_VOLUME_SCALE 0.5 +/// Moderator gas counts toward gas_power at this fraction (fusion mix = 1.0) +#define HFR_MODERATOR_GAS_POWER_FRAC 0.75 +/// Instability: current_damper contribution per 0.01 +#define HFR_INSTABILITY_DAMPER_FACTOR 0.01 +/// Instability: iron_content penalty per point +#define HFR_INSTABILITY_IRON_PENALTY 0.05 +/// Modifier clamp min (power/heat), max 100 +#define HFR_MODIFIER_CLAMP_MIN 0.25 +#define HFR_MODIFIER_CLAMP_MAX 100 +/// Radiation modifier clamp min +#define HFR_RADIATION_MODIFIER_MIN 0.005 +#define HFR_RADIATION_MODIFIER_MAX 1000 +/// Energy upper bound (float safety) +#define HFR_ENERGY_CLAMP_MAX 1e35 +/// core_temperature from internal_power: divide by this +#define HFR_CORE_TEMP_DIVISOR 1000 +/// Conduction: magnetic_constrictor multiplier (0.001 = 0.1% per point) +#define HFR_CONDUCTION_MAGNETIC_FACTOR 0.001 +/// Radiation formula: Planck constant divisor (scale to usable range) +#define HFR_PLANCK_RADIATION_DIVISOR 5e-18 +/// Heat limiter base: 5 * (10 ** power_level) * (heating_conductor/100) +#define HFR_HEAT_LIMITER_BASE 5 +/// Heat output formula divisor (internal_instability * power_output * heat_modifier / this) +#define HFR_HEAT_OUTPUT_DIVISOR 200 +/// Fuel consumption: fuel_injection_rate * this * power_level, then clamp +#define HFR_FUEL_CONSUMPTION_RATE_FACTOR (0.01 * 5) +#define HFR_FUEL_CONSUMPTION_CLAMP_MIN 0.05 +#define HFR_FUEL_CONSUMPTION_CLAMP_MAX 30 +/// Production at level 3/4: heat_output / this +#define HFR_PRODUCTION_HEAT_DIVISOR 1000 +/// Production at other levels: heat_output * 2 / (10 ** (power_level+1)) +#define HFR_PRODUCTION_HEAT_MULT 2 +/// Fuel consumption in moderator_fuel_process: consumption_amount * this * fuel_consumption_multiplier +#define HFR_FUEL_CONSUMPTION_MULT 0.85 +/// Primary product moles added per fuel_consumption +#define HFR_PRIMARY_PRODUCTION_FRAC 0.5 +/// Heat limiter cooling: per-tick multiplier (heat_limiter_modifier * this * seconds_per_tick) +#define HFR_COOLING_PER_TICK_FACTOR 0.01 +/// Evaporate moderator: (1 - (1 - this * power_level)^seconds_per_tick) fraction removed per tick +#define HFR_EVAPORATE_RATE_BASE 0.0005 +/// Iron content damage: (round(iron_content)-1) * this * seconds_per_tick per point above 1 +#define HFR_IRON_DAMAGE_PER_POINT 2.5 +/// Healium heal: only when critical_threshold_proximity > this +#define HFR_HEALIUM_HEAL_PROXIMITY_THRESHOLD 400 +/// Healium heal rate: (moderator_list[GAS_HEALIUM]/100) * this * melting_point * seconds_per_tick +#define HFR_HEALIUM_HEAL_RATE_FACTOR 0.0011 +/// Antinoblium production temp threshold (K): below this or (plasma+BZ) condition +#define HFR_ANTINOBLIUM_TEMP_THRESHOLD 1e7 +/// Overmole: moles above this trigger 2% integrity damage every 5 sec +#define HFR_OVERMOLE_MOLES 5000 +/// Overmole: deciseconds between damage applications (5 sec) +#define HFR_OVERMOLE_INTERVAL_DS 50 +/// Overmole: integrity damage per trigger (2% of melting_point) +#define HFR_OVERMOLE_DAMAGE_FRAC 0.02 +/// Iron heal chance: 25 / (power_level+1) prob per tick when power_level <= 4 +#define HFR_IRON_HEAL_CHANCE_DIVISOR 25 +/// Iron passive decay when power_level <= 4: this * seconds_per_tick per tick +#define HFR_IRON_DECAY_RATE 0.01 +/// Iron content clamp max +#define HFR_IRON_CONTENT_MAX 5 +/// O2 iron heal: moderator_list[GAS_O2] > this to allow iron heal +#define HFR_IRON_HEAL_O2_THRESHOLD 150 +/// Lightning/tesla: radiation_pulse range +#define HFR_LIGHTNING_RADIATION_RANGE 6 +/// Lightning: only when moderator_list[GAS_ANTINOBLIUM] > this and proximity <= 500 +#define HFR_LIGHTNING_ANTINOBLIUM_MIN 50 +#define HFR_LIGHTNING_PROXIMITY_MAX 500 + +/// Fallback when no power / unanchored: default values written each tick +#define HFR_FALLBACK_MAGNETIC_CONSTRICTOR 100 +#define HFR_FALLBACK_HEATING_CONDUCTOR 500 +#define HFR_FALLBACK_CURRENT_DAMPER 0 +#define HFR_FALLBACK_FUEL_INJECTION_RATE 200 +#define HFR_FALLBACK_MODERATOR_INJECTION_RATE 500 +/// Iron accumulation per second when no power +#define HFR_FALLBACK_IRON_RATE 0.10 +/// Volume = internal_fusion.return_volume() * (magnetic_constrictor * this) +#define HFR_MAGNETIC_VOLUME_FRAC 0.01 + +///Mole count required (tritium/hydrogen) to start a fusion reaction in HFR (reactions.dm uses 250 for other fusion) +#define HFR_FUSION_MOLE_THRESHOLD 25 +///Used to reduce the gas_power to a more useful amount +#ifndef INSTABILITY_GAS_POWER_FACTOR +#define INSTABILITY_GAS_POWER_FACTOR 0.003 +#endif +///Used to calculate the toroidal_size for the instability +#ifndef TOROID_VOLUME_BREAKEVEN +#define TOROID_VOLUME_BREAKEVEN 1000 +#endif +///Constant used when calculating the chance of emitting a radioactive particle +#ifndef PARTICLE_CHANCE_CONSTANT +#define PARTICLE_CHANCE_CONSTANT (-20000000) +#endif +///Conduction of heat inside the fusion reactor +#define METALLIC_VOID_CONDUCTIVITY 0.38 +///Conduction of heat near the external cooling loop (output gases at 95% of moderator temp) +#define HIGH_EFFICIENCY_CONDUCTIVITY 0.95 +///Sets the minimum amount of power the machine uses +#define MIN_POWER_USAGE (50 KILO WATTS) +///Sets the multiplier for the damage +#define DAMAGE_CAP_MULTIPLIER 0.005 +/// Max overmole (5000+ moles) damage per 5-second trigger so huge melting_point doesn't overshoot (still subject to cap) +#define HYPERTORUS_OVERMOLE_MAX_ADD 50 +///Sets the range of the hallucinations +#define HALLUCINATION_HFR(P) (min(7, round(abs(P) ** 0.25))) +///Chance in percentage points per fusion level of iron accumulation when operating at unsafe levels +#define IRON_CHANCE_PER_FUSION_LEVEL 17 +///Amount of iron accumulated per second whenever we fail our saving throw, using the chance above +#define IRON_ACCUMULATED_PER_SECOND 0.005 +///Maximum amount of iron that can be healed per second. Calculated to mostly keep up with fusion level 5. +#define IRON_OXYGEN_HEAL_PER_SECOND (IRON_ACCUMULATED_PER_SECOND * (100 - IRON_CHANCE_PER_FUSION_LEVEL) / 100) +///Amount of oxygen in moles required to fully remove 100% iron content. Currently about 2409mol. Calculated to consume at most 10mol/s. +#define OXYGEN_MOLES_CONSUMED_PER_IRON_HEAL (10 / IRON_OXYGEN_HEAL_PER_SECOND) + +//If integrity percent remaining is less than these values, the monitor sets off the relevant alarm. +#define HYPERTORUS_MELTING_PERCENT 5 +#define HYPERTORUS_EMERGENCY_PERCENT 25 +#define HYPERTORUS_DANGER_PERCENT 50 +#define HYPERTORUS_WARNING_PERCENT 100 + +#define WARNING_TIME_DELAY 60 +///to prevent accent sounds from layering +#define HYPERTORUS_ACCENT_SOUND_MIN_COOLDOWN (3 SECONDS) + +#define HYPERTORUS_COUNTDOWN_TIME (30 SECONDS) + +// +// Damage source: Too much mass in the fusion mix at high fusion levels +// + +#define HYPERTORUS_OVERFULL_MIN_POWER_LEVEL 5 +#define HYPERTORUS_OVERFULL_MAX_SAFE_COLD_FUSION_MOLES 2700 +#define HYPERTORUS_OVERFULL_MAX_SAFE_HOT_FUSION_MOLES 1800 +#define HYPERTORUS_OVERFULL_MOLAR_SLOPE (1/80) +#define HYPERTORUS_OVERFULL_TEMPERATURE_SLOPE (HYPERTORUS_OVERFULL_MOLAR_SLOPE * (HYPERTORUS_OVERFULL_MAX_SAFE_COLD_FUSION_MOLES - HYPERTORUS_OVERFULL_MAX_SAFE_HOT_FUSION_MOLES) / (FUSION_MAXIMUM_TEMPERATURE - 1)) +#define HYPERTORUS_OVERFULL_CONSTANT (-(HYPERTORUS_OVERFULL_MOLAR_SLOPE * HYPERTORUS_OVERFULL_MAX_SAFE_HOT_FUSION_MOLES + HYPERTORUS_OVERFULL_TEMPERATURE_SLOPE * FUSION_MAXIMUM_TEMPERATURE)) + +// +// Heal source: Small enough mass in the fusion mix +// + +#define HYPERTORUS_SUBCRITICAL_MOLES 800 +#define HYPERTORUS_SUBCRITICAL_SCALE 150 + +// +// Heal source: Cold enough coolant +// + +#define HYPERTORUS_COLD_COOLANT_MAX_RESTORE 2.5 +#define HYPERTORUS_COLD_COOLANT_THRESHOLD (10 ** 5) +#define HYPERTORUS_COLD_COOLANT_SCALE (HYPERTORUS_COLD_COOLANT_MAX_RESTORE / log(10, HYPERTORUS_COLD_COOLANT_THRESHOLD)) + +// +// Damage source: Iron content +// + +#define HYPERTORUS_MAX_SAFE_IRON 0.35 + +// +// Damage source: Extreme levels of mass in fusion mix at any power level +// + +#define HYPERTORUS_HYPERCRITICAL_MOLES 10000 +#define HYPERTORUS_HYPERCRITICAL_SCALE 0.005 +#define HYPERTORUS_HYPERCRITICAL_MAX_DAMAGE 40 + +#define HYPERTORUS_WEAK_SPILL_RATE 0.0005 +#define HYPERTORUS_WEAK_SPILL_CHANCE 1 +#define HYPERTORUS_MEDIUM_SPILL_PRESSURE 10000 +#define HYPERTORUS_MEDIUM_SPILL_INITIAL 0.25 +#define HYPERTORUS_MEDIUM_SPILL_RATE 0.01 +#define HYPERTORUS_STRONG_SPILL_PRESSURE 12000 +#define HYPERTORUS_STRONG_SPILL_INITIAL 0.75 +#define HYPERTORUS_STRONG_SPILL_RATE 0.05 + +// +// Explosion flags for use in fuel recipes +// +#define HYPERTORUS_FLAG_BASE_EXPLOSION (1<<0) +#define HYPERTORUS_FLAG_MEDIUM_EXPLOSION (1<<1) +#define HYPERTORUS_FLAG_DEVASTATING_EXPLOSION (1<<2) +#define HYPERTORUS_FLAG_RADIATION_PULSE (1<<3) +#define HYPERTORUS_FLAG_EMP (1<<4) +#define HYPERTORUS_FLAG_MINIMUM_SPREAD (1<<5) +#define HYPERTORUS_FLAG_MEDIUM_SPREAD (1<<6) +#define HYPERTORUS_FLAG_BIG_SPREAD (1<<7) +#define HYPERTORUS_FLAG_MASSIVE_SPREAD (1<<8) +#define HYPERTORUS_FLAG_CRITICAL_MELTDOWN (1<<9) + +///High power damage +#define HYPERTORUS_FLAG_HIGH_POWER_DAMAGE (1<<0) +///High fuel mix mole +#define HYPERTORUS_FLAG_HIGH_FUEL_MIX_MOLE (1<<1) +///iron content damage +#define HYPERTORUS_FLAG_IRON_CONTENT_DAMAGE (1<<2) +///Iron content increasing +#define HYPERTORUS_FLAG_IRON_CONTENT_INCREASE (1<<3) +///Emped hypertorus +#define HYPERTORUS_FLAG_EMPED (1<<4) + +// Status for get_status() +#define HYPERTORUS_MELTING 1 +#define HYPERTORUS_EMERGENCY 2 +#define HYPERTORUS_DANGER 3 +#define HYPERTORUS_WARNING 4 +#define HYPERTORUS_NOMINAL 5 +#define HYPERTORUS_INACTIVE 6 + +// BlueMoon compatibility (do not define zap/COMSIG here; supermatter.dm and modular define them) +#ifndef BASE_MACHINE_IDLE_CONSUMPTION +#define BASE_MACHINE_IDLE_CONSUMPTION 50 +#endif +#ifndef ZAP_SUPERMATTER_FLAGS +#define ZAP_SUPERMATTER_FLAGS 0 +#endif +#ifndef AREA_USAGE_ENVIRON +#define AREA_USAGE_ENVIRON ENVIRON +#endif diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_core.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_core.dm new file mode 100644 index 0000000000000..fde45a37702f2 --- /dev/null +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_core.dm @@ -0,0 +1,151 @@ +/** + * HFR core - variables, Initialize(), Destroy() + * BlueMoon: uses gas ID strings and get_moles/set_moles + */ +/obj/machinery/atmospherics/components/unary/hypertorus/core + name = "HFR core" + desc = "This is the Hypertorus Fusion Reactor core, an advanced piece of technology to finely tune the reaction inside of the machine. It has I/O for cooling gases." + icon = 'icons/obj/machines/atmospherics/hypertorus.dmi' + icon_state = "core_off" + circuit = /obj/item/circuitboard/machine/HFR_core + use_power = IDLE_POWER_USE + idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION + icon_state_open = "core_open" + icon_state_off = "core_off" + icon_state_active = "core_active" + + var/start_power = FALSE + var/start_cooling = FALSE + var/start_fuel = FALSE + var/start_moderator = FALSE + + var/obj/machinery/hypertorus/interface/linked_interface + var/obj/machinery/atmospherics/components/unary/hypertorus/moderator_input/linked_moderator + var/obj/machinery/atmospherics/components/unary/hypertorus/fuel_input/linked_input + var/obj/machinery/atmospherics/components/unary/hypertorus/waste_output/linked_output + var/list/corners = list() + var/list/machine_parts = list() + var/datum/gas_mixture/internal_fusion + var/datum/gas_mixture/moderator_internal + var/list/moderator_scrubbing = list(GAS_HELIUM) + var/moderator_filtering_rate = 20 + var/datum/hfr_fuel/selected_fuel + + var/energy = 0 + var/core_temperature = T20C + var/internal_power = 0 + var/power_output = 0 + var/instability = 0 + var/delta_temperature = 0 + var/conduction = 0 + var/radiation = 0 + var/efficiency = 0 + var/heat_limiter_modifier = 0 + var/heat_output_max = 0 + var/heat_output_min = 0 + var/heat_output = 0 + + var/waste_remove = FALSE + var/heating_conductor = 100 + var/magnetic_constrictor = 100 + var/current_damper = 0 + var/power_level = 0 + var/iron_content = 0 + var/fuel_injection_rate = 25 + var/moderator_injection_rate = 25 + + var/critical_threshold_proximity = 0 + var/critical_threshold_proximity_archived = 0 + var/safe_alert = "Main containment field returning to safe operating parameters." + var/warning_point = 50 + var/warning_alert = "Danger! Magnetic containment field faltering!" + var/emergency_point = 700 + var/emergency_alert = "HYPERTORUS MELTDOWN IMMINENT." + var/melting_point = 900 + var/has_reached_emergency = FALSE + var/lastwarning = 0 + + var/obj/item/radio/radio + var/radio_key = /obj/item/encryptionkey/headset_eng + var/engineering_channel = "Engineering" + var/common_channel = null + + var/datum/looping_sound/hypertorus/soundloop + var/last_accent_sound = 0 + + var/fusion_temperature_archived = 0 + var/fusion_temperature = 0 + var/moderator_temperature_archived = 0 + var/moderator_temperature = 0 + var/coolant_temperature_archived = 0 + var/coolant_temperature = 0 + var/output_temperature_archived = 0 + var/output_temperature = 0 + var/temperature_period = 1 + var/final_countdown = FALSE + + var/warning_damage_flags = NONE + /// Last world.time when overmole (5000+) integrity damage was applied + var/last_overmole_damage = 0 + + /// Cached lists reused every process_atmos to avoid allocations (fusion_process) + var/list/hfr_fuel_list = list() + var/list/hfr_scaled_fuel_list = list() + var/list/hfr_moderator_list = list() + var/list/hfr_scaled_moderator_list = list() + /// Reused gas_mixture for output each tick instead of new + var/datum/gas_mixture/hfr_internal_output + /// Reused for remove_specific in remove_waste and inject_fuel to avoid 10+ allocations per tick + var/datum/gas_mixture/hfr_removed_waste + +/obj/machinery/atmospherics/components/unary/hypertorus/core/Initialize(mapload) + . = ..() + internal_fusion = new(5000) + moderator_internal = new(10000) + hfr_internal_output = new + hfr_removed_waste = new + + radio = new(src) + radio.keyslot = new radio_key + radio.recalculateChannels() + investigate_log("has been created.", INVESTIGATE_HYPERTORUS) + +/obj/machinery/atmospherics/components/unary/hypertorus/core/Destroy() + unregister_signals(TRUE) + if(internal_fusion) + internal_fusion = null + if(moderator_internal) + moderator_internal = null + if(linked_input) + QDEL_NULL(linked_input) + if(linked_output) + QDEL_NULL(linked_output) + if(linked_moderator) + QDEL_NULL(linked_moderator) + if(linked_interface) + QDEL_NULL(linked_interface) + var/list/corners_to_del = corners.Copy() + for(var/obj/machinery/hypertorus/corner/corner in corners_to_del) + QDEL_NULL(corner) + QDEL_NULL(radio) + QDEL_NULL(soundloop) + machine_parts = null + return ..() + +/obj/machinery/atmospherics/components/unary/hypertorus/core/on_deconstruction(disassembled) + var/turf/local_turf = get_turf(loc) + var/datum/gas_mixture/to_release = moderator_internal || internal_fusion + if(to_release == moderator_internal && internal_fusion) + to_release.merge(internal_fusion) + if(to_release && to_release.total_moles() > 0) + local_turf.assume_air(to_release) + +/obj/machinery/atmospherics/components/unary/hypertorus/core/crowbar_act(mob/living/user, obj/item/tool) + var/internal_pressure = 0 + if(internal_fusion) + internal_pressure = max(internal_pressure, internal_fusion.return_pressure()) + if(moderator_internal) + internal_pressure = max(internal_pressure, moderator_internal.return_pressure()) + if(internal_pressure > 0) + say("WARNING - Core can contain hazardous gases, deconstruct with caution!") + return default_deconstruction_crowbar(tool) diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_fuel_datums.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_fuel_datums.dm new file mode 100644 index 0000000000000..b0bf25b843539 --- /dev/null +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_fuel_datums.dm @@ -0,0 +1,130 @@ +///Global list of recipes for atmospheric machines to use (id -> datum) +GLOBAL_LIST_INIT(hfr_fuels_list, hfr_fuels_create_list()) + +/proc/hfr_fuels_create_list() + . = list() + for(var/fuel_mix_path in subtypesof(/datum/hfr_fuel)) + var/datum/hfr_fuel/fuel_mix = new fuel_mix_path() + if(!fuel_mix.id || .[fuel_mix.id]) + stack_trace("hfr_fuel: skipping [fuel_mix_path] with empty or duplicate id '[fuel_mix.id]'") + continue + .[fuel_mix.id] = fuel_mix + +/datum/hfr_fuel + var/id = "" + var/name = "" + var/negative_temperature_multiplier = 1 + var/positive_temperature_multiplier = 1 + var/energy_concentration_multiplier = 1 + var/fuel_consumption_multiplier = 1 + var/gas_production_multiplier = 1 + var/temperature_change_multiplier = 1 + /// Main fuels: list of gas ID strings (e.g. GAS_PLASMA, GAS_O2) + var/list/requirements = list() + /// Gases produced in internal fusion mix: list of gas ID strings + var/list/primary_products = list() + /// Gases produced in moderator (power level 1-6): list of 6 gas ID strings + var/list/secondary_products = list() + var/meltdown_flags = HYPERTORUS_FLAG_BASE_EXPLOSION + +/datum/hfr_fuel/New() + . = ..() + temperature_change_multiplier = min(temperature_change_multiplier, 1) + +/datum/hfr_fuel/plasma_oxy_fuel + id = "plasma_o2_fuel" + name = "Plasma + Oxygen fuel" + negative_temperature_multiplier = 2.5 + positive_temperature_multiplier = 0.1 + energy_concentration_multiplier = 10 + fuel_consumption_multiplier = 3.3 + gas_production_multiplier = 1.4 + temperature_change_multiplier = 0.6 + requirements = list(GAS_PLASMA, GAS_O2) + primary_products = list(GAS_CO2, GAS_H2O) + secondary_products = list(GAS_CO2, GAS_H2O, GAS_FREON, GAS_NITROUS, GAS_PLUOXIUM, GAS_HALON) + meltdown_flags = HYPERTORUS_FLAG_BASE_EXPLOSION | HYPERTORUS_FLAG_MINIMUM_SPREAD + +/datum/hfr_fuel/hydrogen_oxy_fuel + id = "h2_o2_fuel" + name = "Hydrogen + Oxygen fuel" + negative_temperature_multiplier = 2 + positive_temperature_multiplier = 0.6 + energy_concentration_multiplier = 3 + fuel_consumption_multiplier = 1.1 + gas_production_multiplier = 0.9 + temperature_change_multiplier = 0.75 + requirements = list(GAS_HYDROGEN, GAS_O2) + primary_products = list(GAS_HELIUM, GAS_N2) + secondary_products = list(GAS_HELIUM, GAS_PLASMA, GAS_O2, GAS_N2, GAS_BZ, GAS_HYPERNOB) + meltdown_flags = HYPERTORUS_FLAG_BASE_EXPLOSION | HYPERTORUS_FLAG_EMP | HYPERTORUS_FLAG_MEDIUM_SPREAD + +/datum/hfr_fuel/tritium_oxy_fuel + id = "t2_o2_fuel" + name = "Tritium + Oxygen fuel" + negative_temperature_multiplier = 2.1 + positive_temperature_multiplier = 0.5 + energy_concentration_multiplier = 2 + fuel_consumption_multiplier = 1.2 + gas_production_multiplier = 0.8 + temperature_change_multiplier = 0.8 + requirements = list(GAS_TRITIUM, GAS_O2) + primary_products = list(GAS_HELIUM, GAS_PLUOXIUM) + secondary_products = list(GAS_HELIUM, GAS_PLASMA, GAS_O2, GAS_N2, GAS_BZ, GAS_HYPERNOB) + meltdown_flags = HYPERTORUS_FLAG_BASE_EXPLOSION | HYPERTORUS_FLAG_RADIATION_PULSE | HYPERTORUS_FLAG_MEDIUM_SPREAD + +/datum/hfr_fuel/hydrogen_tritium_fuel + id = "h2_t2_fuel" + name = "Hydrogen + Tritium fuel" + negative_temperature_multiplier = 1 + positive_temperature_multiplier = 1 + energy_concentration_multiplier = 1 + fuel_consumption_multiplier = 1 + gas_production_multiplier = 1 + temperature_change_multiplier = 0.85 + requirements = list(GAS_HYDROGEN, GAS_TRITIUM) + primary_products = list(GAS_HELIUM) + secondary_products = list(GAS_HELIUM, GAS_PLASMA, GAS_O2, GAS_N2, GAS_BZ, GAS_HYPERNOB) + meltdown_flags = HYPERTORUS_FLAG_MEDIUM_EXPLOSION | HYPERTORUS_FLAG_RADIATION_PULSE | HYPERTORUS_FLAG_EMP | HYPERTORUS_FLAG_MEDIUM_SPREAD + +/datum/hfr_fuel/hypernob_hydrogen_fuel + id = "hypernob_hydrogen_fuel" + name = "Hypernoblium + Hydrogen fuel" + negative_temperature_multiplier = 0.2 + positive_temperature_multiplier = 2.2 + energy_concentration_multiplier = 0.2 + fuel_consumption_multiplier = 0.55 + gas_production_multiplier = 1.4 + temperature_change_multiplier = 0.9 + requirements = list(GAS_HYPERNOB, GAS_HYDROGEN) + primary_products = list(GAS_ANTINOBLIUM) + secondary_products = list(GAS_ANTINOBLIUM, GAS_HELIUM, GAS_PROTO_NITRATE, GAS_ZAUKER, GAS_HEALIUM, GAS_MIASMA) + meltdown_flags = HYPERTORUS_FLAG_DEVASTATING_EXPLOSION | HYPERTORUS_FLAG_RADIATION_PULSE | HYPERTORUS_FLAG_EMP | HYPERTORUS_FLAG_BIG_SPREAD + +/datum/hfr_fuel/hypernob_trit_fuel + id = "hypernob_trit_fuel" + name = "Hypernoblium + Tritium fuel" + negative_temperature_multiplier = 0.1 + positive_temperature_multiplier = 2.5 + energy_concentration_multiplier = 0.1 + fuel_consumption_multiplier = 0.45 + gas_production_multiplier = 1.7 + temperature_change_multiplier = 0.95 + requirements = list(GAS_HYPERNOB, GAS_TRITIUM) + primary_products = list(GAS_ANTINOBLIUM) + secondary_products = list(GAS_ANTINOBLIUM, GAS_HELIUM, GAS_PROTO_NITRATE, GAS_ZAUKER, GAS_HEALIUM, GAS_MIASMA) + meltdown_flags = HYPERTORUS_FLAG_DEVASTATING_EXPLOSION | HYPERTORUS_FLAG_RADIATION_PULSE | HYPERTORUS_FLAG_EMP | HYPERTORUS_FLAG_BIG_SPREAD + +/datum/hfr_fuel/hypernob_antinob_fuel + id = "hypernob_antinob_fuel" + name = "Hypernoblium + Antinoblium fuel" + negative_temperature_multiplier = 0.01 + positive_temperature_multiplier = 3.5 + energy_concentration_multiplier = 2 + fuel_consumption_multiplier = 0.01 + gas_production_multiplier = 3 + temperature_change_multiplier = 1 + requirements = list(GAS_HYPERNOB, GAS_ANTINOBLIUM) + primary_products = list(GAS_HELIUM) + secondary_products = list(GAS_PLASMA, GAS_O2, GAS_N2, GAS_PROTO_NITRATE, GAS_NITRIUM, GAS_MIASMA) + meltdown_flags = HYPERTORUS_FLAG_DEVASTATING_EXPLOSION | HYPERTORUS_FLAG_RADIATION_PULSE | HYPERTORUS_FLAG_EMP | HYPERTORUS_FLAG_MASSIVE_SPREAD | HYPERTORUS_FLAG_CRITICAL_MELTDOWN diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm new file mode 100644 index 0000000000000..aadf95ae6a932 --- /dev/null +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm @@ -0,0 +1,540 @@ +// Stub: no hallucination pulse in BlueMoon +/proc/visible_hallucination_pulse(atom/source, range, duration) + return + +/obj/machinery/atmospherics/components/unary/hypertorus/core/process_atmos(seconds_per_tick) + /* + *Pre-checks + */ + if(!active) + return + + if(!check_part_connectivity()) + deactivate() + return + + CHECK_TICK + + if (start_power || power_level) + play_ambience(seconds_per_tick) + fusion_process(seconds_per_tick) + CHECK_TICK + process_moderator_overflow(seconds_per_tick) + CHECK_TICK + process_damageheal(seconds_per_tick) + CHECK_TICK + check_alert() + if (start_power) + remove_waste(seconds_per_tick) + CHECK_TICK + update_pipenets() + + check_deconstructable() + + if(linked_interface) + SStgui.update_uis(linked_interface) + +/// Считает мощность, нестабильность, тепло и газы за тик. Без питания выставляет фолбек-значения и копит железо. +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/fusion_process(seconds_per_tick) + CHECK_TICK + if (check_power_use()) + if (start_cooling) + inject_from_side_components(seconds_per_tick) + process_internal_cooling(seconds_per_tick) + else + magnetic_constrictor = HFR_FALLBACK_MAGNETIC_CONSTRICTOR + heating_conductor = HFR_FALLBACK_HEATING_CONDUCTOR + current_damper = HFR_FALLBACK_CURRENT_DAMPER + fuel_injection_rate = HFR_FALLBACK_FUEL_INJECTION_RATE + moderator_injection_rate = HFR_FALLBACK_MODERATOR_INJECTION_RATE + waste_remove = FALSE + iron_content += HFR_FALLBACK_IRON_RATE * seconds_per_tick + + update_temperature_status(seconds_per_tick) + + // Объём учёта: от объёма смеси и магнитного сужения (проценты). Дальше от него scale_factor и торoidal_size. + var/archived_heat = internal_fusion.return_temperature() + var/volume = internal_fusion.return_volume() * (magnetic_constrictor * HFR_MAGNETIC_VOLUME_FRAC) + + var/energy_concentration_multiplier = 1 + var/positive_temperature_multiplier = 1 + var/negative_temperature_multiplier = 1 + + var/scale_factor = volume * HFR_VOLUME_SCALE + + hfr_fuel_list.Cut() + hfr_scaled_fuel_list.Cut() + if (selected_fuel) + energy_concentration_multiplier = selected_fuel.energy_concentration_multiplier + positive_temperature_multiplier = selected_fuel.positive_temperature_multiplier + negative_temperature_multiplier = selected_fuel.negative_temperature_multiplier + + for(var/gas_id in selected_fuel.requirements | selected_fuel.primary_products) + var/amount = internal_fusion.get_moles(gas_id) + hfr_fuel_list[gas_id] = amount + hfr_scaled_fuel_list[gas_id] = max((amount - HFR_FUSION_MOLE_THRESHOLD) / scale_factor, 0) + + hfr_moderator_list.Cut() + hfr_scaled_moderator_list.Cut() + var/list/moderator_gases = moderator_internal.get_gases() + for(var/gas_id in moderator_gases) + var/amount = moderator_internal.get_moles(gas_id) + hfr_moderator_list[gas_id] = amount + hfr_scaled_moderator_list[gas_id] = max((amount - HFR_FUSION_MOLE_THRESHOLD) / scale_factor, 0) + + CHECK_TICK + + // Нестабильность: gas_power по fusion_powers из обеих смесей, потом (gas_power*factor)^2 mod toroidal_size плюс дампер, минус железо. + var/toroidal_size = (2 * PI) + TORADIANS(arctan((volume - TOROID_VOLUME_BREAKEVEN) / TOROID_VOLUME_BREAKEVEN)) + var/list/fusion_powers = GLOB.gas_data.fusion_powers + var/gas_power = 0 + var/list/fusion_gases = internal_fusion.get_gases() + for (var/gas_id in fusion_gases) + gas_power += (fusion_powers[gas_id] * internal_fusion.get_moles(gas_id)) + for (var/gas_id in moderator_gases) + gas_power += (fusion_powers[gas_id] * moderator_internal.get_moles(gas_id) * HFR_MODERATOR_GAS_POWER_FRAC) + + instability = MODULUS((gas_power * INSTABILITY_GAS_POWER_FACTOR)**2, toroidal_size) + (current_damper * HFR_INSTABILITY_DAMPER_FACTOR) - iron_content * HFR_INSTABILITY_IRON_PENALTY + // Знак нестабильности: ниже порога эндотермичность (1), иначе экзотермичность (-1). Влияет на знак heat_output. + var/internal_instability = 0 + if(instability * 0.5 < FUSION_INSTABILITY_ENDOTHERMALITY) + internal_instability = 1 + else + internal_instability = -1 + + // Модификаторы от модератора и топлива: вклад каждого газа (scaled), потом clamp. Входят в energy, power_output, heat, radiation. + var/energy_modifiers = hfr_scaled_moderator_list[GAS_N2] * 0.35 + \ + hfr_scaled_moderator_list[GAS_CO2] * 0.55 + \ + hfr_scaled_moderator_list[GAS_NITROUS] * 0.95 + \ + hfr_scaled_moderator_list[GAS_ZAUKER] * 1.55 + \ + hfr_scaled_moderator_list[GAS_ANTINOBLIUM] * 20 + energy_modifiers -= hfr_scaled_moderator_list[GAS_HYPERNOB] * 10 + \ + hfr_scaled_moderator_list[GAS_H2O] * 0.75 + \ + hfr_scaled_moderator_list[GAS_NITRIUM] * 0.15 + \ + hfr_scaled_moderator_list[GAS_HEALIUM] * 0.45 + \ + hfr_scaled_moderator_list[GAS_FREON] * 1.15 + var/power_modifier = hfr_scaled_moderator_list[GAS_O2] * 0.55 + \ + hfr_scaled_moderator_list[GAS_CO2] * 0.95 + \ + hfr_scaled_moderator_list[GAS_NITRIUM] * 1.45 + \ + hfr_scaled_moderator_list[GAS_ZAUKER] * 5.55 + \ + hfr_scaled_moderator_list[GAS_PLASMA] * 0.05 - \ + hfr_scaled_moderator_list[GAS_NITROUS] * 0.05 - \ + hfr_scaled_moderator_list[GAS_FREON] * 0.75 + var/heat_modifier = hfr_scaled_moderator_list[GAS_PLASMA] * 1.25 - \ + hfr_scaled_moderator_list[GAS_N2] * 0.75 - \ + hfr_scaled_moderator_list[GAS_NITROUS] * 1.45 - \ + hfr_scaled_moderator_list[GAS_FREON] * 0.95 + var/radiation_modifier = hfr_scaled_moderator_list[GAS_FREON] * 1.15 - \ + hfr_scaled_moderator_list[GAS_N2] * 0.45 - \ + hfr_scaled_moderator_list[GAS_PLASMA] * 0.95 + \ + hfr_scaled_moderator_list[GAS_BZ] * 1.9 + \ + hfr_scaled_moderator_list[GAS_PROTO_NITRATE] * 0.1 + \ + hfr_scaled_moderator_list[GAS_ANTINOBLIUM] * 10 + + if (selected_fuel) + energy_modifiers += hfr_scaled_fuel_list[selected_fuel.requirements[1]] + \ + hfr_scaled_fuel_list[selected_fuel.requirements[2]] + energy_modifiers -= hfr_scaled_fuel_list[selected_fuel.primary_products[1]] + + power_modifier += hfr_scaled_fuel_list[selected_fuel.requirements[2]] * 1.05 - \ + hfr_scaled_fuel_list[selected_fuel.primary_products[1]] * 0.55 + + heat_modifier += hfr_scaled_fuel_list[selected_fuel.requirements[1]] * 1.15 + \ + hfr_scaled_fuel_list[selected_fuel.primary_products[1]] * 1.05 + + radiation_modifier += hfr_scaled_fuel_list[selected_fuel.primary_products[1]] + + power_modifier = clamp(power_modifier, HFR_MODIFIER_CLAMP_MIN, HFR_MODIFIER_CLAMP_MAX) + heat_modifier = clamp(heat_modifier, HFR_MODIFIER_CLAMP_MIN, HFR_MODIFIER_CLAMP_MAX) + radiation_modifier = clamp(radiation_modifier, HFR_RADIATION_MODIFIER_MIN, HFR_RADIATION_MODIFIER_MAX) + + internal_power = 0 + efficiency = VOID_CONDUCTION * 1 + + // Внутренняя мощность: произведение по двум требованиям топлива (scaled*mod/100), площадь поперечника (радиусы H2/трит), и energy. Эффективность от первичного продукта. + if (selected_fuel) + internal_power = (hfr_scaled_fuel_list[selected_fuel.requirements[1]] * power_modifier / 100) * (hfr_scaled_fuel_list[selected_fuel.requirements[2]] * power_modifier / 100) * (PI * (2 * (hfr_scaled_fuel_list[selected_fuel.requirements[1]] * CALCULATED_H2RADIUS) * (hfr_scaled_fuel_list[selected_fuel.requirements[2]] * CALCULATED_TRITRADIUS))**2) * energy + + efficiency = VOID_CONDUCTION * clamp(hfr_scaled_fuel_list[selected_fuel.primary_products[1]], 1, 100) + + // Energy: модификаторы * c² * (темп * heat_mod/100), с масштабом чтобы не переполнить float. Дальше core_temperature, conduction, radiation, power_output. + energy = (energy_modifiers * LIGHT_SPEED_SQ_SCALED) * max(internal_fusion.return_temperature() * heat_modifier / 100, 1) * LIGHT_SPEED_SQ_SCALE + energy = energy / energy_concentration_multiplier + energy = clamp(energy, 0, HFR_ENERGY_CLAMP_MAX) + core_temperature = internal_power * power_modifier / HFR_CORE_TEMP_DIVISOR + core_temperature = max(TCMB, core_temperature) + delta_temperature = archived_heat - core_temperature + conduction = - delta_temperature * (magnetic_constrictor * HFR_CONDUCTION_MAGNETIC_FACTOR) + radiation = max(-(PLANCK_LIGHT_CONSTANT / HFR_PLANCK_RADIATION_DIVISOR) * radiation_modifier * delta_temperature, 0) + power_output = efficiency * (internal_power - conduction - radiation) + // Лимиты тепла: от уровня и heating_conductor. heat_output от нестабильности и power_output, ограничен min/max. + heat_limiter_modifier = HFR_HEAT_LIMITER_BASE * (10 ** power_level) * (heating_conductor * HFR_MAGNETIC_VOLUME_FRAC) + heat_output_min = - heat_limiter_modifier * HFR_COOLING_PER_TICK_FACTOR * negative_temperature_multiplier + heat_output_max = heat_limiter_modifier * positive_temperature_multiplier + heat_output = clamp(internal_instability * power_output * heat_modifier / HFR_HEAT_OUTPUT_DIVISOR, heat_output_min, heat_output_max) + + if (!check_fuel()) + return + + // Расход и производство за тик: consumption от уровня и fuel_injection_rate; production от heat_output и уровня (на 3/4 уровне по одному правилу, на остальных по другому). + var/fuel_consumption_rate = clamp(fuel_injection_rate * HFR_FUEL_CONSUMPTION_RATE_FACTOR * power_level, HFR_FUEL_CONSUMPTION_CLAMP_MIN, HFR_FUEL_CONSUMPTION_CLAMP_MAX) + var/consumption_amount = fuel_consumption_rate * seconds_per_tick + var/production_amount + switch(power_level) + if(3,4) + production_amount = clamp(heat_output / HFR_PRODUCTION_HEAT_DIVISOR, 0, fuel_consumption_rate) * seconds_per_tick + else + production_amount = clamp(heat_output * HFR_PRODUCTION_HEAT_MULT / 10 ** (power_level+1), 0, fuel_consumption_rate) * seconds_per_tick + + var/dirty_production_rate = hfr_scaled_fuel_list[selected_fuel.primary_products[1]] / fuel_injection_rate + + hfr_internal_output.clear() + moderator_fuel_process(seconds_per_tick, production_amount, consumption_amount, hfr_internal_output, hfr_moderator_list, selected_fuel, hfr_fuel_list) + + CHECK_TICK + + var/common_production_amount = production_amount * selected_fuel.gas_production_multiplier + moderator_common_process(seconds_per_tick, common_production_amount, hfr_internal_output, hfr_moderator_list, dirty_production_rate, heat_output, radiation_modifier) + +/// Топливо: вычитаем из fusion по requirements, добавляем primary_products. В модератор по уровням (tier) добавляем вторичные продукты. Коэффициенты по уровням захардкожены. +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/moderator_fuel_process(seconds_per_tick, production_amount, consumption_amount, datum/gas_mixture/internal_output, moderator_list, datum/hfr_fuel/fuel, fuel_list) + var/fuel_consumption = consumption_amount * HFR_FUEL_CONSUMPTION_MULT * selected_fuel.fuel_consumption_multiplier + var/scaled_production = production_amount * selected_fuel.gas_production_multiplier + + for(var/gas_id in fuel.requirements) + internal_fusion.adjust_moles(gas_id, -min(fuel_list[gas_id], fuel_consumption)) + for(var/gas_id in fuel.primary_products) + internal_fusion.adjust_moles(gas_id, fuel_consumption * HFR_PRIMARY_PRODUCTION_FRAC) + + var/list/tier = fuel.secondary_products + switch(power_level) + if(1) + moderator_internal.adjust_moles(tier[1], scaled_production * 0.95) + moderator_internal.adjust_moles(tier[2], scaled_production * 0.75) + if(2) + moderator_internal.adjust_moles(tier[1], scaled_production * 1.65) + moderator_internal.adjust_moles(tier[2], scaled_production) + if(moderator_list[GAS_PLASMA] > 50) + moderator_internal.adjust_moles(tier[3], scaled_production * 1.15) + if(3) + moderator_internal.adjust_moles(tier[2], scaled_production * 0.5) + moderator_internal.adjust_moles(tier[3], scaled_production * 0.45) + if(4) + moderator_internal.adjust_moles(tier[3], scaled_production * 1.65) + moderator_internal.adjust_moles(tier[4], scaled_production * 1.25) + if(5) + moderator_internal.adjust_moles(tier[4], scaled_production * 0.65) + moderator_internal.adjust_moles(tier[5], scaled_production) + moderator_internal.adjust_moles(tier[6], scaled_production * 0.75) + if(6) + moderator_internal.adjust_moles(tier[5], scaled_production * 0.35) + moderator_internal.adjust_moles(tier[6], scaled_production) + +/// Выход в output: по уровням 1–6 от количества модератора (BZ, plasma, proto_nitrate и т.д.) добавляем газы в internal_output, правим radiation/heat. Healium при proximity > порога уменьшает proximity и съедает GAS_HEALIUM. +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/moderator_common_process(seconds_per_tick, scaled_production, datum/gas_mixture/internal_output, moderator_list, dirty_production_rate, heat_output, radiation_modifier) + switch(power_level) + if(1) + if(moderator_list[GAS_PLASMA] > 100) + internal_output.adjust_moles(GAS_NITROUS, scaled_production * 0.5) + moderator_internal.adjust_moles(GAS_PLASMA, -min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 0.85)) + if(moderator_list[GAS_BZ] > 150) + internal_output.adjust_moles(GAS_HALON, scaled_production * 0.55) + moderator_internal.adjust_moles(GAS_BZ, -min(moderator_internal.get_moles(GAS_BZ), scaled_production * 0.95)) + if(2) + if(moderator_list[GAS_PLASMA] > 50) + internal_output.adjust_moles(GAS_BZ, scaled_production * 1.8) + moderator_internal.adjust_moles(GAS_PLASMA, -min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 1.75)) + if(moderator_list[GAS_PROTO_NITRATE] > 20) + radiation *= 1.55 + heat_output *= 1.025 + internal_output.adjust_moles(GAS_NITRIUM, scaled_production * 1.05) + moderator_internal.adjust_moles(GAS_PROTO_NITRATE, -min(moderator_internal.get_moles(GAS_PROTO_NITRATE), scaled_production * 1.35)) + if(3, 4) + if(moderator_list[GAS_PLASMA] > 10) + internal_output.adjust_moles(GAS_FREON, scaled_production * 0.15) + internal_output.adjust_moles(GAS_NITRIUM, scaled_production * 1.05) + moderator_internal.adjust_moles(GAS_PLASMA, -min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 0.45)) + if(moderator_list[GAS_FREON] > 50) + heat_output *= 0.9 + radiation *= 0.8 + if(moderator_list[GAS_PROTO_NITRATE] > 15) + internal_output.adjust_moles(GAS_NITRIUM, scaled_production * 1.25) + internal_output.adjust_moles(GAS_HALON, scaled_production * 1.15) + moderator_internal.adjust_moles(GAS_PROTO_NITRATE, -min(moderator_internal.get_moles(GAS_PROTO_NITRATE), scaled_production * 1.55)) + radiation *= 1.95 + heat_output *= 1.25 + if(moderator_list[GAS_BZ] > 100) + internal_output.adjust_moles(GAS_HEALIUM, scaled_production * 1.5) + internal_output.adjust_moles(GAS_PROTO_NITRATE, scaled_production * 1.5) + visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * seconds_per_tick) + + if(5) + if(moderator_list[GAS_PLASMA] > 15) + internal_output.adjust_moles(GAS_FREON, scaled_production * 0.25) + moderator_internal.adjust_moles(GAS_PLASMA, -min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 1.45)) + if(moderator_list[GAS_FREON] > 500) + heat_output *= 0.5 + radiation *= 0.2 + if(moderator_list[GAS_PROTO_NITRATE] > 50) + internal_output.adjust_moles(GAS_NITRIUM, scaled_production * 1.95) + internal_output.adjust_moles(GAS_PLUOXIUM, scaled_production) + moderator_internal.adjust_moles(GAS_PROTO_NITRATE, -min(moderator_internal.get_moles(GAS_PROTO_NITRATE), scaled_production * 1.35)) + radiation *= 1.95 + heat_output *= 1.25 + if(moderator_list[GAS_BZ] > 100) + internal_output.adjust_moles(GAS_HEALIUM, scaled_production) + visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * seconds_per_tick) + internal_output.adjust_moles(GAS_FREON, scaled_production * 1.15) + if(moderator_list[GAS_HEALIUM] > 100) + if(critical_threshold_proximity > HFR_HEALIUM_HEAL_PROXIMITY_THRESHOLD) + critical_threshold_proximity = max(critical_threshold_proximity - (moderator_list[GAS_HEALIUM] / 100) * HFR_HEALIUM_HEAL_RATE_FACTOR * melting_point * seconds_per_tick, 0) + moderator_internal.adjust_moles(GAS_HEALIUM, -min(moderator_internal.get_moles(GAS_HEALIUM), scaled_production * 20)) + if(moderator_internal.return_temperature() < HFR_ANTINOBLIUM_TEMP_THRESHOLD || (moderator_list[GAS_PLASMA] > 100 && moderator_list[GAS_BZ] > 50)) + internal_output.adjust_moles(GAS_ANTINOBLIUM, dirty_production_rate * 0.9 / 0.065 * seconds_per_tick) + if(6) + if(moderator_list[GAS_PLASMA] > 30) + internal_output.adjust_moles(GAS_BZ, scaled_production * 1.15) + moderator_internal.adjust_moles(GAS_PLASMA, -min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 1.45)) + if(moderator_list[GAS_PROTO_NITRATE]) + internal_output.adjust_moles(GAS_ZAUKER, scaled_production * 5.35) + internal_output.adjust_moles(GAS_NITRIUM, scaled_production * 2.15) + moderator_internal.adjust_moles(GAS_PROTO_NITRATE, -min(moderator_internal.get_moles(GAS_PROTO_NITRATE), scaled_production * 3.35)) + radiation *= 2 + heat_output *= 2.25 + if(moderator_list[GAS_BZ]) + visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * seconds_per_tick) + internal_output.adjust_moles(GAS_ANTINOBLIUM, clamp(dirty_production_rate / 0.045, 0, 10) * seconds_per_tick) + if(moderator_list[GAS_HEALIUM] > 100) + if(critical_threshold_proximity > HFR_HEALIUM_HEAL_PROXIMITY_THRESHOLD) + critical_threshold_proximity = max(critical_threshold_proximity - (moderator_list[GAS_HEALIUM] / 100) * HFR_HEALIUM_HEAL_RATE_FACTOR * melting_point * seconds_per_tick, 0) + moderator_internal.adjust_moles(GAS_HEALIUM, -min(moderator_internal.get_moles(GAS_HEALIUM), scaled_production * 20)) + internal_fusion.adjust_moles(GAS_ANTINOBLIUM, dirty_production_rate * 0.01 / 0.095 * seconds_per_tick) + + // Температура fusion: если не перегрев, добавляем heat_output за тик и clamp; иначе охлаждаем на heat_limiter_modifier за тик. + if(internal_fusion.return_temperature() <= FUSION_MAXIMUM_TEMPERATURE) + internal_fusion.set_temperature(clamp( + internal_fusion.return_temperature() + heat_output * seconds_per_tick, + TCMB, + FUSION_MAXIMUM_TEMPERATURE, + )) + else + internal_fusion.set_temperature(internal_fusion.return_temperature() - heat_limiter_modifier * HFR_COOLING_PER_TICK_FACTOR * seconds_per_tick) + + // Температура выхода: от модератора или от fusion. Мержим в linked_output и чистим кэш. + if(hfr_internal_output.total_moles() > 0) + if(moderator_internal.total_moles() > 0) + hfr_internal_output.set_temperature(moderator_internal.return_temperature() * HIGH_EFFICIENCY_CONDUCTIVITY) + else + hfr_internal_output.set_temperature(internal_fusion.return_temperature() * METALLIC_VOID_CONDUCTIVITY) + linked_output.airs[1].merge(hfr_internal_output) + hfr_internal_output.clear() + + evaporate_moderator(seconds_per_tick) + + check_nuclear_particles(hfr_moderator_list) + + check_lightning_arcs(hfr_moderator_list) + + // Хил железа кислородом: при достаточном O2 в модераторе убавляем iron_content и тратим O2 по константам. + if(hfr_moderator_list[GAS_O2] > HFR_IRON_HEAL_O2_THRESHOLD) + if(iron_content > 0) + var/max_iron_removable = IRON_OXYGEN_HEAL_PER_SECOND + var/iron_removed = min(max_iron_removable * seconds_per_tick, iron_content) + iron_content -= iron_removed + moderator_internal.adjust_moles(GAS_O2, -iron_removed * OXYGEN_MOLES_CONSUMED_PER_IRON_HEAL) + + check_gravity_pulse(seconds_per_tick) + + radiation_pulse(src, 500, HFR_LIGHTNING_RADIATION_RANGE) + +/// Удаляет из модератора долю молей за тик (экспонента от уровня). Чем выше уровень, тем быстрее испарение. +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/evaporate_moderator(seconds_per_tick) + if (!power_level) + return + if(moderator_internal.total_moles() > 0) + moderator_internal.remove(moderator_internal.total_moles() * (1 - (1 - HFR_EVAPORATE_RATE_BASE * power_level) ** seconds_per_tick)) + +/// Целостность (critical_threshold_proximity): урон от переполнения, температуры, железа, overmole; хил от малой массы, холодного куланта, кислорода. В конце cap по DAMAGE_CAP_MULTIPLIER. +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/process_damageheal(seconds_per_tick) + critical_threshold_proximity_archived = critical_threshold_proximity + + warning_damage_flags &= HYPERTORUS_FLAG_EMPED + + // Урон при переполнении (много молей и/или высокая темп) на высоком уровне. Плюс урон от лога температуры. + if(power_level >= HYPERTORUS_OVERFULL_MIN_POWER_LEVEL) + var/fusion_temp = internal_fusion.return_temperature() + var/overfull_damage_taken = HYPERTORUS_OVERFULL_MOLAR_SLOPE * internal_fusion.total_moles() + HYPERTORUS_OVERFULL_TEMPERATURE_SLOPE * fusion_temp + HYPERTORUS_OVERFULL_CONSTANT + critical_threshold_proximity = max(critical_threshold_proximity + max(overfull_damage_taken * seconds_per_tick, 0), 0) + warning_damage_flags |= HYPERTORUS_FLAG_HIGH_POWER_DAMAGE + // High fusion temperature damage: log10(fusion_temp) - 5 per tick (doc: 2 at 5e7K, 1 at 1e6K) + var/high_temp_damage = log(10, max(fusion_temp, 1)) - 5 + critical_threshold_proximity = max(critical_threshold_proximity + max(high_temp_damage * seconds_per_tick, 0), 0) + + // Хил при малой массе в смеси (ниже порога) на уровне не выше 4. + if(internal_fusion.total_moles() < HYPERTORUS_SUBCRITICAL_MOLES && power_level <= 4) + var/subcritical_heal_restore = (internal_fusion.total_moles() - HYPERTORUS_SUBCRITICAL_MOLES) / HYPERTORUS_SUBCRITICAL_SCALE + critical_threshold_proximity = max(critical_threshold_proximity + min(subcritical_heal_restore * seconds_per_tick, 0), 0) + + // Хил от холодного куланта: температура куланта ниже порога, лог даёт отрицательный restore, min(...,0) убавляет proximity. + if(internal_fusion.total_moles() > 0 && (airs[1].total_moles() && coolant_temperature < HYPERTORUS_COLD_COOLANT_THRESHOLD) && power_level <= 4) + var/cold_coolant_heal_restore = log(10, max(coolant_temperature, 1) * HYPERTORUS_COLD_COOLANT_SCALE) - (HYPERTORUS_COLD_COOLANT_MAX_RESTORE * 2) + critical_threshold_proximity = max(critical_threshold_proximity + min(cold_coolant_heal_restore * seconds_per_tick, 0), 0) + + // Урон от железа: (iron_content - 1) * коэффициент за тик. Потом общий cap роста за тик. + critical_threshold_proximity += max(round(iron_content) - 1, 0) * HFR_IRON_DAMAGE_PER_POINT * seconds_per_tick + if(round(iron_content) > 1) + warning_damage_flags |= HYPERTORUS_FLAG_IRON_CONTENT_DAMAGE + + critical_threshold_proximity = min(critical_threshold_proximity_archived + (seconds_per_tick * DAMAGE_CAP_MULTIPLIER * melting_point), critical_threshold_proximity) + + // Гиперкритический урон: молей выше порога, прирост ограничен HYPERTORUS_HYPERCRITICAL_MAX_DAMAGE за тик. + if(internal_fusion.total_moles() >= HYPERTORUS_HYPERCRITICAL_MOLES) + var/hypercritical_damage_taken = max((internal_fusion.total_moles() - HYPERTORUS_HYPERCRITICAL_MOLES) * HYPERTORUS_HYPERCRITICAL_SCALE, 0) + var/clamped_increment = min(hypercritical_damage_taken, HYPERTORUS_HYPERCRITICAL_MAX_DAMAGE) * seconds_per_tick + critical_threshold_proximity = max(critical_threshold_proximity + clamped_increment, 0) + warning_damage_flags |= HYPERTORUS_FLAG_HIGH_FUEL_MIX_MOLE + + // Over HFR_OVERMOLE_MOLES: lose HFR_OVERMOLE_DAMAGE_FRAC integrity every HFR_OVERMOLE_INTERVAL_DS ds, capped so large melting_point doesn't overshoot + if(internal_fusion.total_moles() > HFR_OVERMOLE_MOLES && (world.time - last_overmole_damage) >= HFR_OVERMOLE_INTERVAL_DS) + var/overmole_cap = 10 * seconds_per_tick * DAMAGE_CAP_MULTIPLIER * melting_point + critical_threshold_proximity += min(melting_point * HFR_OVERMOLE_DAMAGE_FRAC, overmole_cap, HYPERTORUS_OVERMOLE_MAX_ADD) + critical_threshold_proximity = min(critical_threshold_proximity_archived + overmole_cap, critical_threshold_proximity) + last_overmole_damage = world.time + + // Железо: на уровне >4 с вероятностью растёт; на уровне <=4 с вероятностью падает. Потом clamp в [0, max]. + if(power_level > 4 && prob(IRON_CHANCE_PER_FUSION_LEVEL * power_level)) + iron_content += IRON_ACCUMULATED_PER_SECOND * seconds_per_tick + warning_damage_flags |= HYPERTORUS_FLAG_IRON_CONTENT_INCREASE + if(iron_content > 0 && power_level <= 4 && prob(HFR_IRON_HEAL_CHANCE_DIVISOR / (power_level + 1))) + iron_content = max(iron_content - HFR_IRON_DECAY_RATE * seconds_per_tick, 0) + iron_content = clamp(iron_content, 0, HFR_IRON_CONTENT_MAX) + +/// При уровне >= 4 и достаточном BZ стреляет ядерной частицей из случайного угла в противоположную сторону. +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_nuclear_particles(moderator_list) + if(power_level < 4) + return + if(moderator_list[GAS_BZ] < (150 / power_level)) + return + var/obj/machinery/hypertorus/corner/picked_corner = pick(corners) + picked_corner.loc.fire_nuclear_particle(REVERSE_DIR(picked_corner.dir)) + +/// При уровне >= 4, достаточном Antinoblium или proximity запускает tesla_zap. Количество разрядов и флаги урона зависят от power_level и proximity. +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_lightning_arcs(moderator_list) + if(power_level < 4) + return + if(moderator_list[GAS_ANTINOBLIUM] <= HFR_LIGHTNING_ANTINOBLIUM_MIN && critical_threshold_proximity <= HFR_LIGHTNING_PROXIMITY_MAX) + return + var/zap_number = power_level - 2 + + if(critical_threshold_proximity > 650 && prob(20)) + zap_number += 1 + + var/flags = ZAP_SUPERMATTER_FLAGS + switch(power_level) + if(5) + flags |= (ZAP_MOB_DAMAGE) + if(6) + flags |= (ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE) + + playsound(loc, 'sound/weapons/emitter2.ogg', 100, TRUE, extrarange = 10) + for(var/i in 1 to zap_number) + tesla_zap(src, 5, power_level * 2.4e5, flags) + +/// С вероятностью от proximity тянет мобов в радиусе grav_range к реактору. Радиус от log(2.5, proximity). +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_gravity_pulse(seconds_per_tick) + if(SPT_PROB(100 - critical_threshold_proximity / 15, seconds_per_tick)) + return + var/grav_range = round(log(2.5, critical_threshold_proximity)) + for(var/mob/alive_mob in view(grav_range, src)) + if(alive_mob.mob_negates_gravity()) + continue + step_towards(alive_mob, loc) + +/// Сливает в output отфильтрованные газы модератора и часть He/Antinoblium из fusion. Вызывается при waste_remove и уровне < 6. +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/remove_waste(seconds_per_tick) + if(!waste_remove) + return + // Forcibly disabled at fusion power level 6 + if(power_level >= 6) + return + var/filtering_amount = moderator_scrubbing.len + for(var/gas_id in moderator_internal.get_gases() & moderator_scrubbing) + var/datum/gas_mixture/removed = moderator_internal.remove_specific(gas_id, (moderator_filtering_rate / filtering_amount) * seconds_per_tick, hfr_removed_waste) + if(removed) + linked_output.airs[1].merge(removed) + hfr_removed_waste.clear() + + // 50% of Fusion Mix Helium per second, 5% of Fusion Mix Anti-Noblium per second + if(internal_fusion.get_moles(GAS_HELIUM) > 0) + var/datum/gas_mixture/removed = internal_fusion.remove_specific(GAS_HELIUM, internal_fusion.get_moles(GAS_HELIUM) * (1 - (1 - 0.5) ** seconds_per_tick), hfr_removed_waste) + if(removed) + linked_output.airs[1].merge(removed) + hfr_removed_waste.clear() + if(internal_fusion.get_moles(GAS_ANTINOBLIUM) > 0) + var/datum/gas_mixture/removed = internal_fusion.remove_specific(GAS_ANTINOBLIUM, internal_fusion.get_moles(GAS_ANTINOBLIUM) * (1 - (1 - 0.05) ** seconds_per_tick), hfr_removed_waste) + if(removed) + linked_output.airs[1].merge(removed) + hfr_removed_waste.clear() + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/process_internal_cooling(seconds_per_tick) + if(moderator_internal.total_moles() > 0 && internal_fusion.total_moles() > 0) + var/fusion_temperature_delta = internal_fusion.return_temperature() - moderator_internal.return_temperature() + var/fusion_heat_amount = (1 - (1 - METALLIC_VOID_CONDUCTIVITY) ** seconds_per_tick) * fusion_temperature_delta * (internal_fusion.heat_capacity() * moderator_internal.heat_capacity() / (internal_fusion.heat_capacity() + moderator_internal.heat_capacity())) + internal_fusion.set_temperature(max(internal_fusion.return_temperature() - fusion_heat_amount / internal_fusion.heat_capacity(), TCMB)) + moderator_internal.set_temperature(max(moderator_internal.return_temperature() + fusion_heat_amount / moderator_internal.heat_capacity(), TCMB)) + + if(airs[1].total_moles() * 0.05 <= MINIMUM_MOLE_COUNT) + return + var/datum/gas_mixture/cooling_port = airs[1] + var/datum/gas_mixture/cooling_remove = cooling_port.remove(0.05 * cooling_port.total_moles()) + if(moderator_internal.total_moles() > 0) + var/coolant_temperature_delta = cooling_remove.return_temperature() - moderator_internal.return_temperature() + var/cooling_heat_amount = (1 - (1 - HIGH_EFFICIENCY_CONDUCTIVITY) ** seconds_per_tick) * coolant_temperature_delta * (cooling_remove.heat_capacity() * moderator_internal.heat_capacity() / (cooling_remove.heat_capacity() + moderator_internal.heat_capacity())) + cooling_remove.set_temperature(max(cooling_remove.return_temperature() - cooling_heat_amount / cooling_remove.heat_capacity(), TCMB)) + moderator_internal.set_temperature(max(moderator_internal.return_temperature() + cooling_heat_amount / moderator_internal.heat_capacity(), TCMB)) + + else if(internal_fusion.total_moles() > 0) + var/coolant_temperature_delta = cooling_remove.return_temperature() - internal_fusion.return_temperature() + var/cooling_heat_amount = (1 - (1 - METALLIC_VOID_CONDUCTIVITY) ** seconds_per_tick) * coolant_temperature_delta * (cooling_remove.heat_capacity() * internal_fusion.heat_capacity() / (cooling_remove.heat_capacity() + internal_fusion.heat_capacity())) + cooling_remove.set_temperature(max(cooling_remove.return_temperature() - cooling_heat_amount / cooling_remove.heat_capacity(), TCMB)) + internal_fusion.set_temperature(max(internal_fusion.return_temperature() + cooling_heat_amount / internal_fusion.heat_capacity(), TCMB)) + cooling_port.merge(cooling_remove) + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/inject_from_side_components(seconds_per_tick) + update_pipenets() + + var/datum/gas_mixture/moderator_port = linked_moderator.airs[1] + if(start_moderator && moderator_port.total_moles()) + moderator_internal.merge(moderator_port.remove(moderator_injection_rate * seconds_per_tick)) + linked_moderator.update_parents() + + if(!start_fuel || !selected_fuel || !check_gas_requirements()) + return + + var/datum/gas_mixture/fuel_port = linked_input.airs[1] + for(var/gas_type in selected_fuel.requirements) + var/datum/gas_mixture/removed = fuel_port.remove_specific(gas_type, fuel_injection_rate * seconds_per_tick / length(selected_fuel.requirements), hfr_removed_waste) + if(removed) + internal_fusion.merge(removed) + hfr_removed_waste.clear() + linked_input.update_parents() + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_deconstructable() + if(!active) + return + if(power_level > 0) + fusion_started = TRUE + linked_input.fusion_started = TRUE + linked_output.fusion_started = TRUE + linked_moderator.fusion_started = TRUE + linked_interface.fusion_started = TRUE + for(var/obj/machinery/hypertorus/corner/corner in corners) + corner.fusion_started = TRUE + else + fusion_started = FALSE + linked_input.fusion_started = FALSE + linked_output.fusion_started = FALSE + linked_moderator.fusion_started = FALSE + linked_interface.fusion_started = FALSE + for(var/obj/machinery/hypertorus/corner/corner in corners) + corner.fusion_started = FALSE diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm new file mode 100644 index 0000000000000..3531cddf17d36 --- /dev/null +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm @@ -0,0 +1,531 @@ +/** + * This file contain the eight parts surrounding the main core, those are: fuel input, moderator input, waste output, interface and the corners + * The file also contain the guicode of the machine + * BlueMoon: gas ID strings (GAS_O2, GAS_PLASMA, etc.), get_gases/get_moles, GLOB.gas_data. + */ +/obj/machinery/atmospherics/components/unary/hypertorus + icon = 'icons/obj/machines/atmospherics/hypertorus.dmi' + icon_state = "core_off" + + name = "thermomachine" + desc = "Heats or cools gas in connected pipes." + anchored = TRUE + density = TRUE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + layer = OBJ_LAYER + pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY + circuit = /obj/item/circuitboard/machine/thermomachine + ///Vars for the state of the icon of the object (open, off, active) + var/icon_state_open + var/icon_state_off + var/icon_state_active + ///Check if the machine has been activated + var/active = FALSE + ///Check if fusion has started + var/fusion_started = FALSE + ///Check if the machine is cracked open + var/cracked = FALSE + +/obj/machinery/atmospherics/components/unary/hypertorus/Initialize(mapload) + . = ..() + initialize_directions = dir + +/obj/machinery/atmospherics/components/unary/hypertorus/examine(mob/user) + . = ..() + . += span_notice("[src] can be rotated by first opening the panel with a screwdriver and then using a wrench on it.") + +/obj/machinery/atmospherics/components/unary/hypertorus/attackby(obj/item/I, mob/user, list/modifiers, list/attack_modifiers) + if(!fusion_started) + if(default_deconstruction_screwdriver(user, icon_state_open, icon_state_off, I)) + return + if(default_change_direction_wrench(user, I)) + return + return ..() + +/obj/machinery/atmospherics/components/unary/hypertorus/welder_act(mob/living/user, obj/item/tool) + if(!cracked) + return FALSE + if(user.combat_mode) + return FALSE + balloon_alert(user, "repairing...") + if(tool.use_tool(src, user, 10 SECONDS, volume=30)) + balloon_alert(user, "repaired") + cracked = FALSE + update_appearance(UPDATE_ICON) + +/obj/machinery/atmospherics/components/unary/hypertorus/crowbar_act(mob/living/user, obj/item/tool) + return default_deconstruction_crowbar(tool) + +/obj/machinery/atmospherics/components/unary/hypertorus/update_icon_state() + if(panel_open) + icon_state = icon_state_open + return ..() + if(active) + icon_state = icon_state_active + return ..() + icon_state = icon_state_off + return ..() + +/obj/machinery/atmospherics/components/unary/hypertorus/update_overlays() + . = ..() + if(!cracked) + return + var/image/crack = image(icon, icon_state = "crack") + crack.dir = dir + . += crack + +/obj/machinery/atmospherics/components/unary/hypertorus/update_layer() + return + +/obj/machinery/atmospherics/components/unary/hypertorus/fuel_input + name = "HFR fuel input port" + desc = "Input port for the Hypertorus Fusion Reactor, designed to take in fuels with the optimal fuel mix being a 50/50 split." + icon_state = "fuel_input_off" + icon_state_open = "fuel_input_open" + icon_state_off = "fuel_input_off" + icon_state_active = "fuel_input_active" + circuit = /obj/item/circuitboard/machine/HFR_fuel_input + +/obj/machinery/atmospherics/components/unary/hypertorus/waste_output + name = "HFR waste output port" + desc = "Waste port for the Hypertorus Fusion Reactor, designed to output the hot waste gases coming from the core of the machine." + icon_state = "waste_output_off" + icon_state_open = "waste_output_open" + icon_state_off = "waste_output_off" + icon_state_active = "waste_output_active" + circuit = /obj/item/circuitboard/machine/HFR_waste_output + +/obj/machinery/atmospherics/components/unary/hypertorus/moderator_input + name = "HFR moderator input port" + desc = "Moderator port for the Hypertorus Fusion Reactor, designed to move gases inside the machine to cool and control the flow of the reaction." + icon_state = "moderator_input_off" + icon_state_open = "moderator_input_open" + icon_state_off = "moderator_input_off" + icon_state_active = "moderator_input_active" + circuit = /obj/item/circuitboard/machine/HFR_moderator_input + +/* +* Interface and corners +*/ +/obj/machinery/hypertorus + name = "hypertorus_core" + desc = "hypertorus_core" + icon = 'icons/obj/machines/atmospherics/hypertorus.dmi' + icon_state = "core_off" + move_resist = INFINITY + anchored = TRUE + density = TRUE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + power_channel = AREA_USAGE_ENVIRON + var/active = FALSE + var/icon_state_open + var/icon_state_off + var/icon_state_active + var/fusion_started = FALSE + +/obj/machinery/hypertorus/examine(mob/user) + . = ..() + . += span_notice("[src] can be rotated by first opening the panel with a screwdriver and then using a wrench on it.") + +/obj/machinery/hypertorus/attackby(obj/item/I, mob/user, list/modifiers, list/attack_modifiers) + if(!fusion_started) + if(default_deconstruction_screwdriver(user, icon_state_open, icon_state_off, I)) + return + if(default_change_direction_wrench(user, I)) + return + if(default_deconstruction_crowbar(I)) + return + return ..() + +/obj/machinery/hypertorus/update_icon_state() + if(panel_open) + icon_state = icon_state_open + return ..() + if(active) + icon_state = icon_state_active + return ..() + icon_state = icon_state_off + return ..() + +/obj/machinery/hypertorus/interface + name = "HFR interface" + desc = "Interface for the HFR to control the flow of the reaction." + icon_state = "interface_off" + circuit = /obj/item/circuitboard/machine/HFR_interface + icon_state_off = "interface_off" + icon_state_open = "interface_open" + icon_state_active = "interface_active" + /// Have we been activated at least once? + var/activated = FALSE + /// Reference to the core of our machine + var/obj/machinery/atmospherics/components/unary/hypertorus/core/connected_core + +/obj/machinery/hypertorus/interface/Destroy() + if(connected_core) + connected_core = null + return ..() + +/obj/machinery/hypertorus/interface/multitool_act(mob/living/user, obj/item/I) + . = ..() + var/turf/T = get_step(src,REVERSE_DIR(dir)) + var/obj/machinery/atmospherics/components/unary/hypertorus/core/centre = locate() in T + + if(!centre || !centre.check_part_connectivity()) + to_chat(user, span_notice("Check all parts and then try again.")) + return TRUE + + connected_core = centre + connected_core.activate(user) + if(!activated) + new /obj/item/paper/guides/jobs/atmos/hypertorus(loc) + activated = TRUE + + return TRUE + +/obj/machinery/hypertorus/interface/ui_interact(mob/user, datum/tgui/ui) + if(active) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Hypertorus", name) + ui.open() + else + to_chat(user, span_notice("Activate the machine first by using a multitool on the interface.")) + if(ui) + ui.close() + +/// BlueMoon: requirements/primary_products/secondary_products are already gas id lists; return copy. +/obj/machinery/hypertorus/interface/proc/gas_list_to_gasid_list(list/gas_list) + return gas_list.Copy() + +/obj/machinery/hypertorus/interface/ui_static_data() + var/data = list() + data["base_max_temperature"] = FUSION_MAXIMUM_TEMPERATURE + data["selectable_fuel"] = list(list("name" = "Nothing", "id" = null)) + for(var/fuel_id in GLOB.hfr_fuels_list) + var/datum/hfr_fuel/recipe = GLOB.hfr_fuels_list[fuel_id] + data["selectable_fuel"] += list(list( + "name" = recipe.name, + "id" = recipe.id, + "requirements" = recipe.requirements, + "fusion_byproducts" = recipe.primary_products, + "product_gases" = recipe.secondary_products, + "recipe_cooling_multiplier" = recipe.negative_temperature_multiplier, + "recipe_heating_multiplier" = recipe.positive_temperature_multiplier, + "energy_loss_multiplier" = recipe.energy_concentration_multiplier, + "fuel_consumption_multiplier" = recipe.fuel_consumption_multiplier, + "gas_production_multiplier" = recipe.gas_production_multiplier, + "temperature_multiplier" = recipe.temperature_change_multiplier, + )) + return data + +/obj/machinery/hypertorus/interface/ui_data() + var/data = list() + if(!connected_core) + return data + + if(connected_core.selected_fuel) + data["selected"] = connected_core.selected_fuel.id + else + data["selected"] = null + + // Product gases (moderator output) for selected reaction + var/list/product_names = list() + if(connected_core.selected_fuel) + for(var/gas_id in connected_core.selected_fuel.secondary_products) + product_names += GLOB.gas_data.names[gas_id] + data["product_gases"] = product_names.len ? product_names.Join(", ") : "None" + + //Internal Fusion gases - BlueMoon: get_gases(), get_moles(gas_id) + var/list/fusion_gasdata = list() + if(connected_core.internal_fusion.total_moles()) + for(var/gas_id in connected_core.internal_fusion.get_gases()) + fusion_gasdata.Add(list(list( + "id" = gas_id, + "amount" = round(connected_core.internal_fusion.get_moles(gas_id), 0.01), + ))) + else + for(var/gas_id in GLOB.gas_data.ids) + fusion_gasdata.Add(list(list( + "id" = gas_id, + "amount" = 0, + ))) + //Moderator gases + var/list/moderator_gasdata = list() + if(connected_core.moderator_internal.total_moles()) + for(var/gas_id in connected_core.moderator_internal.get_gases()) + moderator_gasdata.Add(list(list( + "id" = gas_id, + "amount" = round(connected_core.moderator_internal.get_moles(gas_id), 0.01), + ))) + else + for(var/gas_id in GLOB.gas_data.ids) + moderator_gasdata.Add(list(list( + "id" = gas_id, + "amount" = 0, + ))) + + data["fusion_gases"] = fusion_gasdata + data["moderator_gases"] = moderator_gasdata + + data["energy_level"] = connected_core.energy + data["heat_limiter_modifier"] = connected_core.heat_limiter_modifier + data["heat_output_min"] = connected_core.heat_output_min + data["heat_output_max"] = connected_core.heat_output_max + data["heat_output"] = connected_core.heat_output + data["instability"] = connected_core.instability + + data["heating_conductor"] = connected_core.heating_conductor + data["magnetic_constrictor"] = connected_core.magnetic_constrictor + data["fuel_injection_rate"] = connected_core.fuel_injection_rate + data["moderator_injection_rate"] = connected_core.moderator_injection_rate + data["current_damper"] = connected_core.current_damper + + data["power_level"] = connected_core.power_level + data["apc_energy"] = connected_core.get_area_cell_percent() + data["iron_content"] = connected_core.iron_content + data["integrity"] = connected_core.get_integrity_percent() + + data["start_power"] = connected_core.start_power + data["start_cooling"] = connected_core.start_cooling + data["start_fuel"] = connected_core.start_fuel + data["start_moderator"] = connected_core.start_moderator + + data["internal_fusion_temperature"] = connected_core.fusion_temperature + data["moderator_internal_temperature"] = connected_core.moderator_temperature + data["internal_output_temperature"] = connected_core.output_temperature + data["internal_coolant_temperature"] = connected_core.coolant_temperature + + data["internal_fusion_temperature_archived"] = connected_core.fusion_temperature_archived + data["moderator_internal_temperature_archived"] = connected_core.moderator_temperature_archived + data["internal_output_temperature_archived"] = connected_core.output_temperature_archived + data["internal_coolant_temperature_archived"] = connected_core.coolant_temperature_archived + data["temperature_period"] = connected_core.temperature_period + + data["waste_remove"] = connected_core.waste_remove + data["filter_types"] = list() + for(var/id in GLOB.gas_data.ids) + data["filter_types"] += list(list("gas_id" = id, "gas_name" = GLOB.gas_data.names[id], "enabled" = (id in connected_core.moderator_scrubbing))) + + data["cooling_volume"] = connected_core.airs[1].return_volume() + data["mod_filtering_rate"] = connected_core.moderator_filtering_rate + + return data + +/obj/machinery/hypertorus/interface/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + if(!connected_core) + return + switch(action) + if("start_power") + connected_core.start_power = !connected_core.start_power + connected_core.use_power = connected_core.start_power ? ACTIVE_POWER_USE : IDLE_POWER_USE + . = TRUE + if("start_cooling") + connected_core.start_cooling = !connected_core.start_cooling + . = TRUE + if("start_fuel") + connected_core.start_fuel = !connected_core.start_fuel + . = TRUE + if("start_moderator") + connected_core.start_moderator = !connected_core.start_moderator + . = TRUE + if("heating_conductor") + var/heating_conductor = text2num(params["heating_conductor"]) + if(heating_conductor != null) + connected_core.heating_conductor = clamp(heating_conductor, 50, 500) + . = TRUE + if("magnetic_constrictor") + var/magnetic_constrictor = text2num(params["magnetic_constrictor"]) + if(magnetic_constrictor != null) + connected_core.magnetic_constrictor = clamp(magnetic_constrictor, 50, 1000) + . = TRUE + if("fuel_injection_rate") + var/fuel_injection_rate = text2num(params["fuel_injection_rate"]) + if(fuel_injection_rate != null) + connected_core.fuel_injection_rate = clamp(fuel_injection_rate, 5, 1500) + . = TRUE + if("moderator_injection_rate") + var/moderator_injection_rate = text2num(params["moderator_injection_rate"]) + if(moderator_injection_rate != null) + connected_core.moderator_injection_rate = clamp(moderator_injection_rate, 5, 1500) + . = TRUE + if("current_damper") + var/current_damper = text2num(params["current_damper"]) + if(current_damper != null) + connected_core.current_damper = clamp(current_damper, 0, 1000) + . = TRUE + if("waste_remove") + connected_core.waste_remove = !connected_core.waste_remove + . = TRUE + if("filter") + connected_core.moderator_scrubbing ^= params["mode"] + . = TRUE + if("mod_filtering_rate") + var/mod_filtering_rate = text2num(params["mod_filtering_rate"]) + if(mod_filtering_rate != null) + connected_core.moderator_filtering_rate = clamp(mod_filtering_rate, 5, 200) + . = TRUE + if("fuel") + connected_core.selected_fuel = null + var/fuel_mix = "nothing" + var/datum/hfr_fuel/fuel = null + if(params["mode"] != "") + fuel = GLOB.hfr_fuels_list[params["mode"]] + if(fuel) + connected_core.selected_fuel = fuel + fuel_mix = fuel.name + if(connected_core.internal_fusion.total_moles()) + connected_core.dump_gases() + connected_core.update_parents() + connected_core.linked_input.update_parents() + connected_core.linked_output.update_parents() + connected_core.linked_moderator.update_parents() + investigate_log("was set to recipe [fuel_mix ? fuel_mix : "null"] by [key_name(usr)]", INVESTIGATE_ATMOS) + . = TRUE + if("cooling_volume") + var/cooling_volume = text2num(params["cooling_volume"]) + if(cooling_volume != null) + connected_core.airs[1].set_volume(clamp(cooling_volume, 50, 2000)) + . = TRUE + +/obj/machinery/hypertorus/corner + name = "HFR corner" + desc = "Structural piece of the machine." + icon_state = "corner_off" + circuit = /obj/item/circuitboard/machine/HFR_corner + icon_state_off = "corner_off" + icon_state_open = "corner_open" + icon_state_active = "corner_active" + +/obj/item/paper/guides/jobs/atmos/hypertorus + name = "paper- 'Quick guide to safe handling of the HFR'" + default_raw_text = "How to safely(TM) operate the Hypertorus
\ + -Build the machine as its shown in the main guide.
\ + -Make a 50/50 gasmix of tritium and hydrogen totalling around 2000 moles.
\ + -Start the machine, fill up the cooling loop with plasma/hypernoblium and use space or freezers to cool it.
\ + -Connect the fuel mix into the fuel injector port, allow only 1000 moles into the machine to ease the kickstart of the reaction
\ + -Set the Heat conductor to 500 when starting the reaction, reset it to 100 when power level is higher than 1
\ + -In the event of a meltdown, set the heat conductor to max and set the current damper to max. Set the fuel injection to min. \ + If the heat output doesnt go negative, try changing the magnetic costrictors untill heat output goes negative. \ + Make the cooling stronger, put high heat capacity gases inside the moderator (hypernoblium will help dealing with the problem)

\ + Warnings:
\ + -You cannot dismantle the machine if the power level is over 0
\ + -You cannot power of the machine if the power level is over 0
\ + -You cannot dispose of waste gases if power level is over 5
\ + -You cannot remove gases from the fusion mix if they are not helium and antinoblium
\ + -Hypernoblium will decrease the power of the mix by a lot
\ + -Antinoblium will INCREASE the power of the mix by a lot more
\ + -High heat capacity gases are harder to heat/cool
\ + -Low heat capacity gases are easier to heat/cool
\ + -The machine consumes 50 KW per power level, reaching 350 KW at power level 6 so prepare the SM accordingly
\ + -In case of a power shortage, the fusion reaction will CONTINUE but the cooling will STOP

\ + The writer of the quick guide will not be held responsible for misuses and meltdown caused by the use of the guide, \ + use more advanced guides to understando how the various gases will act as moderators." + +/obj/item/hfr_box + name = "HFR box" + desc = "If you see this, call the police." + icon = 'icons/obj/machines/atmospherics/hypertorus.dmi' + icon_state = "error" + ///What kind of box are we handling? + var/box_type = "impossible" + ///What's the path of the machine we making + var/part_path + +/obj/item/hfr_box/corner + name = "HFR box corner" + desc = "Place this as the corner of your 3x3 multiblock fusion reactor" + icon_state = "box_corner" + box_type = "corner" + part_path = /obj/machinery/hypertorus/corner + +/obj/item/hfr_box/body + name = "HFR box body" + desc = "Place this on the sides of the core box of your 3x3 multiblock fusion reactor" + box_type = "body" + icon_state = "box_body" + +/obj/item/hfr_box/body/fuel_input + name = "HFR box fuel input" + icon_state = "box_fuel" + part_path = /obj/machinery/atmospherics/components/unary/hypertorus/fuel_input + +/obj/item/hfr_box/body/moderator_input + name = "HFR box moderator input" + icon_state = "box_moderator" + part_path = /obj/machinery/atmospherics/components/unary/hypertorus/moderator_input + +/obj/item/hfr_box/body/waste_output + name = "HFR box waste output" + icon_state = "box_waste" + part_path = /obj/machinery/atmospherics/components/unary/hypertorus/waste_output + +/obj/item/hfr_box/body/interface + name = "HFR box interface" + part_path = /obj/machinery/hypertorus/interface + +/obj/item/hfr_box/core + name = "HFR box core" + desc = "Activate this with a multitool to deploy the full machine after setting up the other boxes" + icon_state = "box_core" + box_type = "core" + part_path = /obj/machinery/atmospherics/components/unary/hypertorus/core + +/obj/item/hfr_box/core/multitool_act(mob/living/user, obj/item/I) + . = ..() + var/list/parts = list() + for(var/obj/item/hfr_box/box in orange(1,src)) + var/direction = get_dir(src, box) + if(box.box_type == "corner") + if(ISDIAGONALDIR(direction)) + switch(direction) + if(NORTHEAST) + direction = EAST + if(SOUTHEAST) + direction = SOUTH + if(SOUTHWEST) + direction = WEST + if(NORTHWEST) + direction = NORTH + box.dir = direction + parts |= box + continue + if(box.box_type == "body") + if(direction in GLOB.cardinals) + box.dir = direction + parts |= box + continue + if(parts.len == 8) + build_reactor(parts) + return + +/obj/item/hfr_box/core/proc/build_reactor(list/parts) + for(var/obj/item/hfr_box/box in parts) + if(box.box_type == "corner") + if(!box.part_path || !ispath(box.part_path, /obj/machinery/hypertorus/corner)) + qdel(box) + continue + var/obj/machinery/hypertorus/corner/corner = new box.part_path(box.loc) + corner.dir = box.dir + qdel(box) + continue + if(box.box_type == "body") + if(!box.part_path) + qdel(box) + continue + var/location = get_turf(box) + if(box.part_path != /obj/machinery/hypertorus/interface) + var/obj/machinery/atmospherics/components/unary/hypertorus/part = new box.part_path(location, TRUE, box.dir) + part.dir = box.dir + else + var/obj/machinery/hypertorus/interface/part = new box.part_path(location) + part.dir = box.dir + qdel(box) + continue + + new/obj/machinery/atmospherics/components/unary/hypertorus/core(loc, TRUE) + qdel(src) diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm new file mode 100644 index 0000000000000..f467aa98087f2 --- /dev/null +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm @@ -0,0 +1,469 @@ +/** + * HFR procs: build, destroy, control. BlueMoon: gas IDs, get_moles/set_moles. + */ +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_part_connectivity() + . = TRUE + if(!anchored || panel_open) + return FALSE + corners.Cut() + machine_parts.Cut() + + for(var/obj/machinery/hypertorus/object in orange(1,src)) + if(. == FALSE) + break + if(object.panel_open) + . = FALSE + if(istype(object,/obj/machinery/hypertorus/corner)) + var/dir = get_dir(src,object) + if(dir in GLOB.cardinals) + . = FALSE + switch(dir) + if(SOUTHEAST) + if(object.dir != SOUTH) + . = FALSE + if(SOUTHWEST) + if(object.dir != WEST) + . = FALSE + if(NORTHEAST) + if(object.dir != EAST) + . = FALSE + if(NORTHWEST) + if(object.dir != NORTH) + . = FALSE + corners |= object + continue + if(get_step(object,REVERSE_DIR(object.dir)) != loc) + . = FALSE + if(istype(object,/obj/machinery/hypertorus/interface)) + if(linked_interface && linked_interface != object) + . = FALSE + linked_interface = object + + for(var/obj/machinery/atmospherics/components/unary/hypertorus/object in orange(1,src)) + if(. == FALSE) + break + if(object.panel_open) + . = FALSE + if(get_step(object,REVERSE_DIR(object.dir)) != loc) + . = FALSE + if(istype(object,/obj/machinery/atmospherics/components/unary/hypertorus/fuel_input)) + if(linked_input && linked_input != object) + . = FALSE + linked_input = object + machine_parts |= object + if(istype(object,/obj/machinery/atmospherics/components/unary/hypertorus/waste_output)) + if(linked_output && linked_output != object) + . = FALSE + linked_output = object + machine_parts |= object + if(istype(object,/obj/machinery/atmospherics/components/unary/hypertorus/moderator_input)) + if(linked_moderator && linked_moderator != object) + . = FALSE + linked_moderator = object + machine_parts |= object + + if(!linked_interface || !linked_input || !linked_moderator || !linked_output || corners.len != 4) + . = FALSE + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/activate(mob/living/user) + if(active) + to_chat(user, span_notice("You already activated the machine.")) + return + to_chat(user, span_notice("You link all parts together.")) + active = TRUE + update_appearance(UPDATE_ICON) + linked_interface.active = TRUE + linked_interface.update_appearance(UPDATE_ICON) + RegisterSignal(linked_interface, COMSIG_PARENT_QDELETING, PROC_REF(unregister_signals)) + linked_input.active = TRUE + linked_input.update_appearance(UPDATE_ICON) + RegisterSignal(linked_input, COMSIG_PARENT_QDELETING, PROC_REF(unregister_signals)) + linked_output.active = TRUE + linked_output.update_appearance(UPDATE_ICON) + RegisterSignal(linked_output, COMSIG_PARENT_QDELETING, PROC_REF(unregister_signals)) + linked_moderator.active = TRUE + linked_moderator.update_appearance(UPDATE_ICON) + RegisterSignal(linked_moderator, COMSIG_PARENT_QDELETING, PROC_REF(unregister_signals)) + for(var/obj/machinery/hypertorus/corner/corner in corners) + corner.active = TRUE + corner.update_appearance(UPDATE_ICON) + RegisterSignal(corner, COMSIG_PARENT_QDELETING, PROC_REF(unregister_signals)) + soundloop = new(src, TRUE) + soundloop.volume = 5 + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/unregister_signals(only_signals = FALSE) + SIGNAL_HANDLER + if(linked_interface) + UnregisterSignal(linked_interface, COMSIG_PARENT_QDELETING) + if(linked_input) + UnregisterSignal(linked_input, COMSIG_PARENT_QDELETING) + if(linked_output) + UnregisterSignal(linked_output, COMSIG_PARENT_QDELETING) + if(linked_moderator) + UnregisterSignal(linked_moderator, COMSIG_PARENT_QDELETING) + for(var/obj/machinery/hypertorus/corner/corner in corners) + UnregisterSignal(corner, COMSIG_PARENT_QDELETING) + if(!only_signals) + deactivate() + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/deactivate() + if(!active) + return + active = FALSE + update_appearance(UPDATE_ICON) + if(linked_interface) + linked_interface.active = FALSE + linked_interface.update_appearance(UPDATE_ICON) + linked_interface = null + if(linked_input) + linked_input.active = FALSE + linked_input.update_appearance(UPDATE_ICON) + linked_input = null + if(linked_output) + linked_output.active = FALSE + linked_output.update_appearance(UPDATE_ICON) + linked_output = null + if(linked_moderator) + linked_moderator.active = FALSE + linked_moderator.update_appearance(UPDATE_ICON) + linked_moderator = null + if(corners.len) + for(var/obj/machinery/hypertorus/corner/corner in corners) + corner.active = FALSE + corner.update_appearance(UPDATE_ICON) + corners = list() + QDEL_NULL(soundloop) + +/// Removed: was iterating GLOB.gas_data.ids every tick and doing set_moles(get_moles(...)) — no-op and major perf sink. get_moles() already returns 0 for missing gases. +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/assert_gases() + return + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/update_pipenets() + update_parents() + linked_input.update_parents() + linked_output.update_parents() + linked_moderator.update_parents() + +/// Обновляет архивные темпы и выставляет power_level по температуре fusion (диапазоны 500, 1e3, 1e4, ... до 6). +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/update_temperature_status(seconds_per_tick) + fusion_temperature_archived = fusion_temperature + fusion_temperature = internal_fusion.return_temperature() + moderator_temperature_archived = moderator_temperature + moderator_temperature = moderator_internal.return_temperature() + coolant_temperature_archived = coolant_temperature + coolant_temperature = airs[1].return_temperature() + output_temperature_archived = output_temperature + output_temperature = linked_output.airs[1].return_temperature() + temperature_period = seconds_per_tick + switch(fusion_temperature) + if(-INFINITY to 500) + power_level = 0 + if(500 to 1e3) + power_level = 1 + if(1e3 to 1e4) + power_level = 2 + if(1e4 to 1e5) + power_level = 3 + if(1e5 to 1e6) + power_level = 4 + if(1e6 to 1e7) + power_level = 5 + else + power_level = 6 + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/play_ambience(seconds_per_tick) + if(last_accent_sound < world.time && SPT_PROB(10, seconds_per_tick)) + var/aggression = min(((critical_threshold_proximity / 800) * ((power_level) / 5)), 1.0) * 100 + if(critical_threshold_proximity >= 300) + playsound(src, SFX_HYPERTORUS_MELTING, max(50, aggression), FALSE, 40, 30, falloff_distance = 10) + else + playsound(src, SFX_HYPERTORUS_CALM, max(50, aggression), FALSE, 25, 25, falloff_distance = 10) + var/next_sound = round((100 - aggression) * 5) + 5 + last_accent_sound = world.time + max(HYPERTORUS_ACCENT_SOUND_MIN_COOLDOWN, next_sound) + var/ambient_hum = 1 + if(check_fuel()) + ambient_hum = power_level + 1 + soundloop.volume = clamp(ambient_hum * 8, 0, 50) + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_fuel() + if(!selected_fuel) + return FALSE + if(!internal_fusion.total_moles()) + return FALSE + for(var/gas_id in selected_fuel.requirements) + if(internal_fusion.get_moles(gas_id) < HFR_FUSION_MOLE_THRESHOLD) + return FALSE + return TRUE + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_power_use() + if(machine_stat & (NOPOWER|BROKEN)) + return FALSE + if(use_power == ACTIVE_POWER_USE) + use_power = ACTIVE_POWER_USE + active_power_usage = (power_level + 1) * MIN_POWER_USAGE + return TRUE + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_gas_requirements() + var/datum/gas_mixture/contents = linked_input.airs[1] + for(var/gas_id in selected_fuel.requirements) + if(contents.get_moles(gas_id) <= 0) + return FALSE + return TRUE + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/dump_gases() + var/datum/gas_mixture/remove = internal_fusion.remove(internal_fusion.total_moles()) + if(remove) + linked_output.airs[1].merge(remove) + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/get_status() + var/integrity = get_integrity_percent() + if(integrity < HYPERTORUS_MELTING_PERCENT) + return HYPERTORUS_MELTING + if(integrity < HYPERTORUS_EMERGENCY_PERCENT) + return HYPERTORUS_EMERGENCY + if(integrity < HYPERTORUS_DANGER_PERCENT) + return HYPERTORUS_DANGER + if(integrity < HYPERTORUS_WARNING_PERCENT) + return HYPERTORUS_WARNING + if(power_level > 0) + return HYPERTORUS_NOMINAL + return HYPERTORUS_INACTIVE + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/alarm() + switch(get_status()) + if(HYPERTORUS_MELTING) + playsound(src, 'sound/misc/bloblarm.ogg', 100, FALSE, 40, 30, falloff_distance = 10) + if(HYPERTORUS_EMERGENCY) + playsound(src, 'sound/machines/engine_alert1.ogg', 100, FALSE, 30, 30, falloff_distance = 10) + if(HYPERTORUS_DANGER) + playsound(src, 'sound/machines/engine_alert2.ogg', 100, FALSE, 30, 30, falloff_distance = 10) + if(HYPERTORUS_WARNING) + playsound(src, 'sound/machines/terminal_alert.ogg', 75) + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/get_integrity_percent() + var/integrity = critical_threshold_proximity / melting_point + integrity = round(100 - integrity * 100, 0.01) + integrity = integrity < 0 ? 0 : integrity + return integrity + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/get_area_cell_percent() + var/area/area = get_area(src) + if(!area) + return 0 + var/obj/machinery/power/apc/apc = area.get_apc() + if(!apc) + return 0 + var/obj/item/stock_parts/cell/cell = apc.cell + if(!cell) + return 0 + return cell.percent() + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_alert() + if(critical_threshold_proximity < warning_point) + return + if((REALTIMEOFDAY - lastwarning) / 10 >= WARNING_TIME_DELAY) + alarm() + if(critical_threshold_proximity > emergency_point) + radio.talk_into(src, "[emergency_alert] Integrity: [get_integrity_percent()]%", common_channel) + lastwarning = REALTIMEOFDAY + if(!has_reached_emergency) + investigate_log("has reached the emergency point for the first time.", INVESTIGATE_HYPERTORUS) + message_admins("[src] has reached the emergency point [ADMIN_JMP(src)].") + has_reached_emergency = TRUE + send_radio_explanation() + else if(critical_threshold_proximity >= critical_threshold_proximity_archived) + radio.talk_into(src, "[warning_alert] Integrity: [get_integrity_percent()]%", engineering_channel) + lastwarning = REALTIMEOFDAY - (WARNING_TIME_DELAY * 5) + send_radio_explanation() + else + radio.talk_into(src, "[safe_alert] Integrity: [get_integrity_percent()]%", engineering_channel) + lastwarning = REALTIMEOFDAY + if(critical_threshold_proximity >= melting_point) + countdown() + +/obj/machinery/atmospherics/components/unary/hypertorus/core/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + warning_damage_flags |= HYPERTORUS_FLAG_EMPED + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/send_radio_explanation() + if(warning_damage_flags & HYPERTORUS_FLAG_EMPED) + var/list/characters = list() + characters += GLOB.alphabet + for(var/c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ") + characters += c + for(var/c in "0123456789") + characters += c + characters += " " + characters += " " + var/message = random_string(rand(50,70), characters) + radio.talk_into(src, "[message]", engineering_channel) + return + if(warning_damage_flags & HYPERTORUS_FLAG_HIGH_POWER_DAMAGE) + radio.talk_into(src, "Warning! Shield destabilizing due to excessive power!", engineering_channel) + if(warning_damage_flags & HYPERTORUS_FLAG_IRON_CONTENT_DAMAGE) + radio.talk_into(src, "Warning! Iron shards are damaging the internal core shielding!", engineering_channel) + if(warning_damage_flags & HYPERTORUS_FLAG_HIGH_FUEL_MIX_MOLE) + radio.talk_into(src, "Warning! Fuel mix moles reaching critical levels!", engineering_channel) + if(warning_damage_flags & HYPERTORUS_FLAG_IRON_CONTENT_INCREASE) + radio.talk_into(src, "Warning! Iron amount inside the core is increasing!", engineering_channel) + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/countdown() + set waitfor = FALSE + if(final_countdown) + return + final_countdown = TRUE + var/critical = selected_fuel.meltdown_flags & HYPERTORUS_FLAG_CRITICAL_MELTDOWN + if(critical) + priority_announce("ВНИМАНИЕ! Взрыв ХФР, скорее всего, охватит большую часть станции, а грядущий ЭМИ уничтожит большую часть электроники. \ + Отойдите как можно дальше от реактора или найдите способ его остановить от расщепления.", "ВНИМАНИЕ", 'sound/announcer/notice/notice3.ogg') + var/speaking = "[emergency_alert] The Hypertorus fusion reactor has reached critical integrity failure. Emergency magnetic dampeners online." + radio.talk_into(src, speaking, common_channel) + notify_ghosts("The [src] has begun melting down!", 'sound/machines/warning-buzzer.ogg', FALSE, src, header = "Meltdown Incoming") + for(var/i in HYPERTORUS_COUNTDOWN_TIME to 0 step -10) + if(QDELETED(src)) + return + if(critical_threshold_proximity < melting_point) + radio.talk_into(src, "[safe_alert] Failsafe has been disengaged.", common_channel) + final_countdown = FALSE + return + else if((i % 50) != 0 && i > 50) + sleep(1 SECONDS) + continue + else if(i > 50) + if(i == 10 SECONDS && critical) + sound_to_playing_players('sound/machines/warning-buzzer.ogg') + speaking = "[DisplayTimeText(i, TRUE)] remain before total integrity failure." + else + speaking = "[i*0.1]..." + radio.talk_into(src, speaking, common_channel) + sleep(1 SECONDS) + meltdown() + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/meltdown() + var/flash_explosion = 0 + var/light_impact_explosion = 0 + var/heavy_impact_explosion = 0 + var/devastating_explosion = 0 + var/em_pulse = selected_fuel.meltdown_flags & HYPERTORUS_FLAG_EMP + var/rad_pulse = selected_fuel.meltdown_flags & HYPERTORUS_FLAG_RADIATION_PULSE + var/emp_heavy_size = 0 + var/rad_pulse_size = 0 + var/gas_spread = 0 + var/gas_pockets = 0 + var/critical = selected_fuel.meltdown_flags & HYPERTORUS_FLAG_CRITICAL_MELTDOWN + if(selected_fuel.meltdown_flags & HYPERTORUS_FLAG_BASE_EXPLOSION) + flash_explosion = power_level * 3 + light_impact_explosion = power_level * 2 + if(selected_fuel.meltdown_flags & HYPERTORUS_FLAG_MEDIUM_EXPLOSION) + flash_explosion = power_level * 6 + light_impact_explosion = power_level * 5 + heavy_impact_explosion = power_level * 0.5 + if(selected_fuel.meltdown_flags & HYPERTORUS_FLAG_DEVASTATING_EXPLOSION) + flash_explosion = power_level * 8 + light_impact_explosion = power_level * 7 + heavy_impact_explosion = power_level * 2 + devastating_explosion = power_level + if(selected_fuel.meltdown_flags & HYPERTORUS_FLAG_MINIMUM_SPREAD) + if(em_pulse) + emp_heavy_size = power_level * 1 + if(rad_pulse) + rad_pulse_size = 2 * power_level + 8 + gas_pockets = 5 + gas_spread = power_level * 2 + if(selected_fuel.meltdown_flags & HYPERTORUS_FLAG_MEDIUM_SPREAD) + if(em_pulse) + emp_heavy_size = power_level * 3 + if(rad_pulse) + rad_pulse_size = power_level + 24 + gas_pockets = 7 + gas_spread = power_level * 4 + if(selected_fuel.meltdown_flags & HYPERTORUS_FLAG_BIG_SPREAD) + if(em_pulse) + emp_heavy_size = power_level * 5 + if(rad_pulse) + rad_pulse_size = power_level + 34 + gas_pockets = 10 + gas_spread = power_level * 6 + if(selected_fuel.meltdown_flags & HYPERTORUS_FLAG_MASSIVE_SPREAD) + if(em_pulse) + emp_heavy_size = power_level * 7 + if(rad_pulse) + rad_pulse_size = power_level + 44 + gas_pockets = 15 + gas_spread = power_level * 8 + var/list/around_turfs = spiral_range_turfs(gas_spread, src) + var/list/turfs_to_remove = list() + for(var/turf/turf as anything in around_turfs) + if(isclosedturf(turf) || isspaceturf(turf)) + turfs_to_remove += turf + around_turfs -= turfs_to_remove + var/datum/gas_mixture/remove_fusion + if(internal_fusion.total_moles() > 0) + remove_fusion = internal_fusion.remove_ratio(0.2) + var/datum/gas_mixture/remove + for(var/i in 1 to gas_pockets) + remove = remove_fusion.remove_ratio(1/gas_pockets) + var/turf/local = pick(around_turfs) + local.assume_air(remove) + loc.assume_air(internal_fusion) + var/datum/gas_mixture/remove_moderator + if(moderator_internal.total_moles() > 0) + remove_moderator = moderator_internal.remove_ratio(0.2) + var/datum/gas_mixture/remove + for(var/i in 1 to gas_pockets) + remove = remove_moderator.remove_ratio(1/gas_pockets) + var/turf/local = pick(around_turfs) + local.assume_air(remove) + loc.assume_air(moderator_internal) + explosion(loc, critical ? devastating_explosion * 2 : devastating_explosion, critical ? heavy_impact_explosion * 2 : heavy_impact_explosion, light_impact_explosion, flash_explosion, TRUE, TRUE) + if(rad_pulse) + radiation_pulse(src, 3000, rad_pulse_size, TRUE) + if(em_pulse) + empulse_using_range(loc, critical ? emp_heavy_size * 2 : emp_heavy_size, TRUE) + qdel(src) + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_cracked_parts() + for(var/obj/machinery/atmospherics/components/unary/hypertorus/part in machine_parts) + if(part.cracked) + return part + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/create_crack() as /obj/machinery/atmospherics/components/unary/hypertorus + var/obj/machinery/atmospherics/components/unary/hypertorus/part = pick(machine_parts) + part.cracked = TRUE + part.update_appearance(UPDATE_ICON) + return part + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/spill_gases(obj/origin, datum/gas_mixture/target_mix, ratio) + var/datum/gas_mixture/remove_mixture = target_mix.remove_ratio(ratio) + var/turf/origin_turf = origin.loc + if(!origin_turf) + return + origin_turf.assume_air(remove_mixture) + +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/process_moderator_overflow(seconds_per_tick) + var/obj/machinery/atmospherics/components/unary/hypertorus/cracked_part = check_cracked_parts() + if(cracked_part) + var/leak_rate + if(moderator_internal.return_pressure() < HYPERTORUS_MEDIUM_SPILL_PRESSURE) + if(!prob(HYPERTORUS_WEAK_SPILL_CHANCE)) + return + leak_rate = HYPERTORUS_WEAK_SPILL_RATE + else if(moderator_internal.return_pressure() < HYPERTORUS_STRONG_SPILL_PRESSURE) + leak_rate = HYPERTORUS_MEDIUM_SPILL_RATE + else + leak_rate = HYPERTORUS_STRONG_SPILL_RATE + spill_gases(cracked_part, moderator_internal, ratio = 1 - (1 - leak_rate) ** seconds_per_tick) + return + if(moderator_internal.total_moles() < HYPERTORUS_HYPERCRITICAL_MOLES) + return + cracked_part = create_crack() + if(moderator_internal.return_pressure() < HYPERTORUS_MEDIUM_SPILL_PRESSURE) + return + if(moderator_internal.return_pressure() < HYPERTORUS_STRONG_SPILL_PRESSURE) + explosion(get_turf(cracked_part), 0, 0, 1, 3, TRUE, FALSE, 3) + spill_gases(cracked_part, moderator_internal, ratio = HYPERTORUS_MEDIUM_SPILL_INITIAL) + return + explosion(get_turf(cracked_part), 0, 1, 3, 5, TRUE, FALSE, 5) + spill_gases(cracked_part, moderator_internal, ratio = HYPERTORUS_STRONG_SPILL_INITIAL) diff --git a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm new file mode 100644 index 0000000000000..696739b550948 --- /dev/null +++ b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm @@ -0,0 +1,318 @@ +#define MIN_PROGRESS_AMOUNT 3 +#define MIN_DEVIATION_RATE 0.90 +#define MAX_DEVIATION_RATE 1.1 +#define HIGH_CONDUCTIVITY_RATIO 0.95 + +/obj/machinery/atmospherics/components/binary/crystallizer + icon = 'icons/obj/machines/atmospherics/machines.dmi' + icon_state = "crystallizer-off" + base_icon_state = "crystallizer" + name = "crystallizer" + desc = "Used to crystallize or solidify gases." + layer = ABOVE_MOB_LAYER + density = TRUE + max_integrity = 300 + armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 0, BIO = 100, RAD = 100, FIRE = 80, ACID = 30) + circuit = /obj/item/circuitboard/machine/crystallizer + pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY + + var/datum/gas_mixture/internal + var/gas_input = 0 + var/progress_bar = 0 + var/quality_loss = 0 + var/datum/gas_recipe/selected_recipe = null + var/total_recipe_moles = 0 + +/obj/machinery/atmospherics/components/binary/crystallizer/Initialize(mapload) + . = ..(mapload) + internal = new + register_context() + +/obj/machinery/atmospherics/components/binary/crystallizer/on_deconstruction(disassembled) + var/turf/local_turf = get_turf(loc) + if(internal.total_moles() > 0) + local_turf.assume_air(internal) + return ..() + +/obj/machinery/atmospherics/components/binary/crystallizer/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Turn [on ? "off" : "on"]" + if(!held_item) + return CONTEXTUAL_SCREENTIP_SET + switch(held_item.tool_behaviour) + if(TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel" + if(TOOL_WRENCH) + context[SCREENTIP_CONTEXT_RMB] = "Rotate" + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/atmospherics/components/binary/crystallizer/attackby(obj/item/I, mob/user, list/modifiers, list/attack_modifiers) + if(!on) + if(default_deconstruction_screwdriver(user, "[base_icon_state]-open", "[base_icon_state]-off", I)) + return + if(default_change_direction_wrench(user, I)) + return + return ..() + +/obj/machinery/atmospherics/components/binary/crystallizer/crowbar_act(mob/living/user, obj/item/tool) + if(internal.return_pressure() > 0) + say("WARNING - Internal pressure present, deconstruct with caution!") + return default_deconstruction_crowbar(tool) + +/obj/machinery/atmospherics/components/binary/crystallizer/update_overlays() + . = ..() + var/mutable_appearance/pipe_appearance1 = mutable_appearance('icons/obj/pipes_n_cables/pipe_underlays.dmi', "intact_[dir]_[piping_layer]", layer = GAS_SCRUBBER_LAYER) + pipe_appearance1.color = COLOR_LIME + var/mutable_appearance/pipe_appearance2 = mutable_appearance('icons/obj/pipes_n_cables/pipe_underlays.dmi', "intact_[REVERSE_DIR(dir)]_[piping_layer]", layer = GAS_SCRUBBER_LAYER) + pipe_appearance2.color = COLOR_MOSTLY_PURE_RED + . += pipe_appearance1 + . += pipe_appearance2 + +/obj/machinery/atmospherics/components/binary/crystallizer/update_icon_state() + . = ..() + if(panel_open) + icon_state = "[base_icon_state]-open" + else if(on && is_operational) + icon_state = "[base_icon_state]-on" + else + icon_state = "[base_icon_state]-off" + +/obj/machinery/atmospherics/components/binary/crystallizer/CtrlClick(mob/user) + if(!can_interact(user)) + return + if(!is_operational) + return + if(panel_open) + balloon_alert(user, "close panel!") + return + on = !on + balloon_alert(user, "turned [on ? "on" : "off"]") + investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) + update_icon() + +/obj/machinery/atmospherics/components/binary/crystallizer/proc/check_temp_requirements() + if(internal.return_temperature() >= selected_recipe.min_temp * MIN_DEVIATION_RATE && internal.return_temperature() <= selected_recipe.max_temp * MAX_DEVIATION_RATE) + return TRUE + return FALSE + +/obj/machinery/atmospherics/components/binary/crystallizer/proc/inject_gases() + var/datum/gas_mixture/contents = airs[2] + for(var/gas_id in selected_recipe.requirements) + var/moles_in = contents.get_moles(gas_id) + if(moles_in <= 0) + continue + var/moles_internal = internal.get_moles(gas_id) + if(moles_internal >= selected_recipe.requirements[gas_id] * 2) + continue + var/amount = moles_in * gas_input + var/datum/gas_mixture/removed = contents.remove_specific(gas_id, amount) + if(removed) + internal.merge(removed) + +/obj/machinery/atmospherics/components/binary/crystallizer/proc/internal_check() + var/gas_check = 0 + for(var/gas_id in selected_recipe.requirements) + var/moles = internal.get_moles(gas_id) + if(moles <= 0) + return FALSE + if(moles >= selected_recipe.requirements[gas_id]) + gas_check++ + if(gas_check == selected_recipe.requirements.len) + return TRUE + return FALSE + +/// Качество: у краёв диапазона темпов (min/max рецепта) quality_loss растёт; у медианы падает. Потом внутренняя темп сдвигается на energy_release с учётом теплоёмкости. +/obj/machinery/atmospherics/components/binary/crystallizer/proc/heat_calculations() + var/quality_rate = MIN_PROGRESS_AMOUNT * 0.5 * clamp(total_recipe_moles / 20, 0.1, 5) + var/internal_temp = internal.return_temperature() + if((internal_temp >= (selected_recipe.min_temp * MIN_DEVIATION_RATE) && internal_temp <= selected_recipe.min_temp) || \ + (internal_temp >= selected_recipe.max_temp && internal_temp <= (selected_recipe.max_temp * MAX_DEVIATION_RATE))) + quality_loss = min(quality_loss + quality_rate, 100) + + var/median_temperature = (selected_recipe.max_temp + selected_recipe.min_temp) / 2 + if(internal_temp >= (median_temperature * MIN_DEVIATION_RATE) && internal_temp <= (median_temperature * MAX_DEVIATION_RATE)) + quality_loss = max(quality_loss - quality_rate, -85) + + var/heat_cap = max(internal.heat_capacity(), 1e-10) + internal.set_temperature(max(internal_temp + (selected_recipe.energy_release / heat_cap), TCMB)) + +/// Теплообмен между портом охлаждения (airs[1]) и внутренней смесью. Количество тепла по разности температур и теплоёмкостям, коэффициент HIGH_CONDUCTIVITY_RATIO. +/obj/machinery/atmospherics/components/binary/crystallizer/proc/heat_conduction() + var/datum/gas_mixture/cooling_port = airs[1] + if(cooling_port.total_moles() > MINIMUM_MOLE_COUNT) + if(internal.total_moles() > 0) + var/cooling_heat_capacity = cooling_port.heat_capacity() + var/internal_heat_capacity = internal.heat_capacity() + if(cooling_heat_capacity <= 0 || internal_heat_capacity <= 0) + return + var/coolant_temperature_delta = cooling_port.return_temperature() - internal.return_temperature() + var/cooling_heat_amount = HIGH_CONDUCTIVITY_RATIO * coolant_temperature_delta * (cooling_heat_capacity * internal_heat_capacity / (cooling_heat_capacity + internal_heat_capacity)) + cooling_port.set_temperature(max(cooling_port.return_temperature() - cooling_heat_amount / cooling_heat_capacity, TCMB)) + internal.set_temperature(max(internal.return_temperature() + cooling_heat_amount / internal_heat_capacity, TCMB)) + +/obj/machinery/atmospherics/components/binary/crystallizer/proc/moles_calculations() + var/amounts = 0 + for(var/gas_id in selected_recipe.requirements) + amounts += selected_recipe.requirements[gas_id] + total_recipe_moles = amounts + +/obj/machinery/atmospherics/components/binary/crystallizer/proc/dump_gases() + var/datum/gas_mixture/remove = internal.remove(internal.total_moles()) + airs[2].merge(remove) + internal.clear() + +/// За тик: подкачка газов из входа, теплообмен, при выполнении требований рецепта и темпы считаем качество и прогресс. При progress_bar == 100 потребляем газы и выдаём предметы. +/obj/machinery/atmospherics/components/binary/crystallizer/process_atmos() + if(!on || !is_operational || selected_recipe == null) + return + + inject_gases() + + if(!internal.total_moles()) + return + + heat_conduction() + + if(internal_check()) + if(check_temp_requirements()) + heat_calculations() + var/progress_step = MIN_PROGRESS_AMOUNT * 0.5 * clamp(total_recipe_moles / 20, 0.5, 2) + progress_bar = min(progress_bar + progress_step, 100) + else + quality_loss = min(quality_loss + 0.5, 100) + progress_bar = max(progress_bar - 1, 0) + if(progress_bar != 100) + update_parents() + return + progress_bar = 0 + + for(var/gas_id in selected_recipe.requirements) + var/required_gas_moles = selected_recipe.requirements[gas_id] + var/amount_consumed = required_gas_moles + (required_gas_moles * (quality_loss * 0.01)) + if(internal.get_moles(gas_id) < amount_consumed) + quality_loss = min(quality_loss + 10, 100) + internal.remove_specific(gas_id, amount_consumed) + + var/total_quality = clamp(50 - quality_loss, 0, 100) + var/quality_control + switch(total_quality) + if(100) + quality_control = "Masterwork" + if(95 to 99) + quality_control = "Supreme" + if(75 to 94) + quality_control = "Good" + if(65 to 74) + quality_control = "Decent" + if(55 to 64) + quality_control = "Average" + if(35 to 54) + quality_control = "Ok" + if(15 to 34) + quality_control = "Poor" + if(5 to 14) + quality_control = "Ugly" + if(1 to 4) + quality_control = "Cracked" + if(0) + quality_control = "Oh God why" + + for(var/path in selected_recipe.products) + var/amount_produced = selected_recipe.products[path] + for(var/i in 1 to amount_produced) + var/obj/creation = new path(get_step(src, SOUTH)) + creation.name = "[quality_control] [creation.name]" + if(selected_recipe.dangerous) + investigate_log("[creation.name] has been created in the crystallizer.", INVESTIGATE_ATMOS) + message_admins("[creation.name] has been created in the crystallizer [ADMIN_JMP(src)].") + + quality_loss = 0 + update_parents() + +/obj/machinery/atmospherics/components/binary/crystallizer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Crystallizer", name) + ui.open() + +/obj/machinery/atmospherics/components/binary/crystallizer/ui_static_data() + var/data = list() + data["selected_recipes"] = list(list("name" = "Nothing", "id" = "")) + for(var/id in GLOB.gas_recipe_meta) + var/datum/gas_recipe/recipe = GLOB.gas_recipe_meta[id] + if(recipe.machine_type != "Crystallizer") + continue + data["selected_recipes"] += list(list("name" = recipe.name, "id" = recipe.id)) + return data + +/obj/machinery/atmospherics/components/binary/crystallizer/ui_data() + var/data = list() + data["on"] = on + + if(selected_recipe) + data["selected"] = selected_recipe.id + else + data["selected"] = "" + + var/list/internal_gas_data = list() + for(var/gas_id in internal.get_gases()) + internal_gas_data.Add(list(list( + "name" = GLOB.gas_data.names[gas_id], + "id" = gas_id, + "amount" = round(internal.get_moles(gas_id), 0.01), + ))) + data["internal_gas_data"] = internal_gas_data + + var/list/requirements + if(!selected_recipe) + requirements = list("Select a recipe to see the requirements") + else + requirements = list("To create [selected_recipe.name] you will need:") + for(var/gas_id in selected_recipe.requirements) + var/amount_consumed = selected_recipe.requirements[gas_id] + requirements += "-[amount_consumed] moles of [GLOB.gas_data.names[gas_id]]" + requirements += "In a temperature range between [selected_recipe.min_temp] K and [selected_recipe.max_temp] K" + requirements += "The crystallization reaction will be [selected_recipe.energy_release ? (selected_recipe.energy_release > 0 ? "exothermic" : "endothermic") : "thermally neutral"]" + data["requirements"] = requirements.Join("\n") + + data["internal_temperature"] = internal.total_moles() ? internal.return_temperature() : 0 + data["progress_bar"] = progress_bar + data["gas_input"] = gas_input + return data + +/obj/machinery/atmospherics/components/binary/crystallizer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + switch(action) + if("power") + on = !on + investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS) + . = TRUE + if("recipe") + selected_recipe = null + var/recipe_name = "nothing" + var/datum/gas_recipe/recipe = GLOB.gas_recipe_meta[params["mode"]] + if(internal.total_moles() > 0) + dump_gases() + quality_loss = 0 + progress_bar = 0 + if(recipe && recipe.id != "") + selected_recipe = recipe + recipe_name = recipe.name + update_parents() + moles_calculations() + investigate_log("was set to recipe [recipe_name ? recipe_name : "null"] by [key_name(usr)]", INVESTIGATE_ATMOS) + . = TRUE + if("gas_input") + var/_gas_input = params["gas_input"] + gas_input = clamp(_gas_input, 0, 250) + update_icon() + +/obj/machinery/atmospherics/components/binary/crystallizer/update_layer() + return + +#undef MIN_PROGRESS_AMOUNT +#undef MIN_DEVIATION_RATE +#undef MAX_DEVIATION_RATE +#undef HIGH_CONDUCTIVITY_RATIO diff --git a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_extra_items.dm b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_extra_items.dm new file mode 100644 index 0000000000000..bb96235e4b02b --- /dev/null +++ b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_extra_items.dm @@ -0,0 +1,239 @@ +/// Crystallizer recipes: gas crystal grenades, fuel pellets, stacks (from WhiteMoon) +/// Sprites: copy from WhiteMoon icons/obj/weapons/grenade.dmi, exploration.dmi, stack_objects.dmi, mineral sheets + +// === Gas crystal grenades (base + types) === +/obj/item/grenade/gas_crystal + desc = "A crystal from the crystallizer." + name = "Gas Crystal" + icon = 'icons/obj/crystallizer_grenades.dmi' + icon_state = "bluefrag" + item_state = "flashbang" + resistance_flags = FIRE_PROOF + +/obj/item/grenade/gas_crystal/prime(mob/living/lanced_by) + ..() + update_mob() + +/obj/item/grenade/gas_crystal/healium_crystal + name = "Healium crystal" + desc = "A crystal made from Healium gas, cold to the touch." + icon_state = "healium_crystal" + var/fix_range = 7 + /// Healium moles released per turf (scaled by distance) + var/healium_per_turf = 50 + +/obj/item/grenade/gas_crystal/healium_crystal/prime(mob/living/lanced_by) + ..() + playsound(src, 'sound/effects/spray2.ogg', 100, TRUE) + for(var/turf/open/T in range(fix_range, src)) + var/dist = max(get_dist(T, src), 1) + T.air.adjust_moles(GAS_HEALIUM, healium_per_turf / dist) + T.air.adjust_moles(GAS_O2, MOLES_O2STANDARD * 0.5 / dist) + T.air.adjust_moles(GAS_N2, MOLES_N2STANDARD * 0.5 / dist) + T.air.set_temperature(T20C) + qdel(src) + +/obj/item/grenade/gas_crystal/proto_nitrate_crystal + name = "Proto Nitrate crystal" + desc = "A crystal made from Proto Nitrate gas." + icon_state = "proto_nitrate_crystal" + var/refill_range = 5 + var/n2_gas_amount = 80 + var/o2_gas_amount = 30 + /// Proto nitrate moles released per turf (scaled by distance) + var/proto_nitrate_amount = 40 + +/obj/item/grenade/gas_crystal/proto_nitrate_crystal/prime(mob/living/lanced_by) + ..() + playsound(src, 'sound/effects/spray2.ogg', 100, TRUE) + for(var/turf/open/T in view(refill_range, src)) + var/dist = max(get_dist(T, src), 1) + T.air.adjust_moles(GAS_PROTO_NITRATE, proto_nitrate_amount / dist) + T.air.adjust_moles(GAS_N2, n2_gas_amount / dist) + T.air.adjust_moles(GAS_O2, o2_gas_amount / dist) + qdel(src) + +/obj/item/grenade/gas_crystal/nitrous_oxide_crystal + name = "N2O crystal" + desc = "A crystal made from N2O gas." + icon_state = "n2o_crystal" + var/fill_range = 3 + var/n2o_gas_amount = 50 + +/obj/item/grenade/gas_crystal/nitrous_oxide_crystal/prime(mob/living/lanced_by) + ..() + playsound(src, 'sound/effects/spray2.ogg', 100, TRUE) + for(var/turf/open/T in range(fill_range, src)) + var/dist = max(get_dist(T, src), 1) + T.air.adjust_moles(GAS_NITROUS, n2o_gas_amount / dist) + qdel(src) + +/obj/item/grenade/gas_crystal/crystal_foam + name = "crystal foam" + desc = "A crystal with a foggy inside." + icon_state = "crystal_foam" + var/breach_range = 7 + +/obj/item/grenade/gas_crystal/crystal_foam/prime(mob/living/lanced_by) + ..() + var/datum/reagents/first_batch = new(75) + var/datum/reagents/second_batch = new(50) + first_batch.add_reagent(/datum/reagent/aluminium, 75) + second_batch.add_reagent(/datum/reagent/smart_foaming_agent, 25) + second_batch.add_reagent(/datum/reagent/toxin/acid/fluacid, 25) + chem_splash(get_turf(src), breach_range, list(first_batch, second_batch)) + playsound(src, 'sound/effects/spray2.ogg', 100, TRUE) + update_mob() + qdel(src) + +// === Fuel pellets === +/obj/item/fuel_pellet + name = "standard fuel pellet" + desc = "A compressed fuel pellet." + icon = 'icons/obj/crystallizer_exploration.dmi' + icon_state = "fuel_basic" + w_class = WEIGHT_CLASS_SMALL + var/uses = 5 + +/obj/item/fuel_pellet/advanced + name = "advanced fuel pellet" + icon_state = "fuel_advanced" + +/obj/item/fuel_pellet/exotic + name = "exotic fuel pellet" + icon_state = "fuel_exotic" + +// === Stacks (crystallizer products) === +/obj/item/stack/ammonia_crystals + name = "ammonia crystals" + singular_name = "ammonia crystal" + icon = 'icons/obj/crystallizer_sheets.dmi' + icon_state = "ammonia_crystal" + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + max_amount = 50 + grind_results = list(/datum/reagent/ammonia = 10) + merge_type = /obj/item/stack/ammonia_crystals + +/obj/item/stack/sheet/mineral/metal_hydrogen + name = "metallic hydrogen" + singular_name = "metallic hydrogen sheet" + icon = 'icons/obj/crystallizer_sheets.dmi' + icon_state = "sheet-metalhydrogen" + merge_type = /obj/item/stack/sheet/mineral/metal_hydrogen + +/obj/item/stack/sheet/mineral/zaukerite + name = "zaukerite" + singular_name = "zaukerite crystal" + icon = 'icons/obj/crystallizer_sheets.dmi' + icon_state = "zaukerite" + merge_type = /obj/item/stack/sheet/mineral/zaukerite + +// === Metallic hydrogen crafts === +/obj/item/metallic_hydrogen_rod + name = "metallic hydrogen rod" + desc = "A rod reinforced with metallic hydrogen. Extremely dense; useful as a tool or improvised weapon." + icon = 'icons/obj/crystallizer_sheets.dmi' + icon_state = "sheet-metalhydrogen" + item_state = "rods" + force = 10 + throwforce = 12 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("bludgeoned", "hit", "struck") + +/obj/item/metallic_hydrogen_cooling_pack + name = "metallic hydrogen cooling pack" + desc = "Stabilized metallic hydrogen wrapped in cloth. Stays very cold; use to cool down." + icon = 'icons/obj/crystallizer_sheets.dmi' + icon_state = "sheet-metalhydrogen" + w_class = WEIGHT_CLASS_SMALL + +/obj/item/metallic_hydrogen_cooling_pack/attack_self(mob/user) + . = ..() + if(.) + return + to_chat(user, "You press the pack to your skin; it feels intensely cold.") + if(isliving(user)) + var/mob/living/L = user + L.adjust_bodytemperature(-80) + +/obj/item/stack/sheet/hot_ice + name = "hot ice" + singular_name = "hot ice sheet" + icon = 'icons/obj/hot_ice.dmi' + icon_state = "hot-ice" + merge_type = /obj/item/stack/sheet/hot_ice + grind_results = list(/datum/reagent/hot_ice_slush = 25) + +/obj/item/stack/sheet/hot_ice/proc/melt_release() + var/turf/open/T = get_turf(src) + if(!istype(T) || !T.air) + return + var/plasma_moles = amount * 150 + var/release_temp = 20 * amount + 300 + T.atmos_spawn_air("plasma=[plasma_moles];TEMP=[release_temp]") + qdel(src) + +/obj/item/stack/sheet/hot_ice/fire_act(exposed_temperature, exposed_volume) + melt_release() + +/obj/item/stack/sheet/hot_ice/welder_act(mob/living/user, obj/item/I) + if(I.use_tool(src, user, 0, volume = 50)) + melt_release() + return TRUE + return FALSE + +// === Hot ice cooling pack (craftable from crystallizer hot ice) === +/obj/item/hot_ice_pack + name = "hot ice cooling pack" + desc = "A pack of stabilized hot ice wrapped in cloth. Stays cold for a long time; use to cool down." + icon = 'icons/obj/hot_ice.dmi' + icon_state = "hot-ice" + w_class = WEIGHT_CLASS_SMALL + +/obj/item/hot_ice_pack/attack_self(mob/user) + . = ..() + if(.) + return + to_chat(user, "You press the pack to your skin; it feels pleasantly cool.") + if(isliving(user)) + var/mob/living/L = user + L.adjust_bodytemperature(-50) + +// === Ammonia pack (craftable from ammonia crystals) === +/obj/item/ammonia_pack + name = "ammonia pack" + desc = "Crystals of ammonia wrapped in cloth. Can be broken to release a small cloud of ammonia." + icon = 'icons/obj/crystallizer_sheets.dmi' + icon_state = "ammonia_crystal" + w_class = WEIGHT_CLASS_SMALL + +/obj/item/ammonia_pack/attack_self(mob/user) + . = ..() + if(.) + return + var/turf/T = get_turf(src) + if(T) + var/datum/reagents/R = new(10) + R.add_reagent(/datum/reagent/ammonia, 10) + var/datum/effect_system/smoke_spread/chem/smoke = new + smoke.set_up(R, 1, T, TRUE) + smoke.start() + to_chat(user, "You crush the pack; a sharp smell of ammonia fills the air.") + qdel(src) + +// === Zaukerite bolts (craftable from crystallizer sheets) === +/obj/item/zaukerite_bolt + name = "zaukerite bolt" + desc = "A rod tipped with crystallized Zauker. Deadly when thrown." + icon = 'icons/obj/crystallizer_exploration.dmi' + icon_state = "zaukerite_bolt" + item_state = "bolt" + force = 8 + throwforce = 15 + throw_speed = 4 + embedding = list("embedded_pain_multiplier" = 2, "embed_chance" = 40, "embedded_fall_chance" = 10) + w_class = WEIGHT_CLASS_SMALL + sharpness = SHARP_POINTY + attack_verb = list("stabbed", "pierced", "stuck") + hitsound = 'sound/weapons/bladeslice.ogg' diff --git a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm new file mode 100644 index 0000000000000..7bef3be3616ea --- /dev/null +++ b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm @@ -0,0 +1,50 @@ +/obj/item/hypernoblium_crystal + name = "Hypernoblium Crystal" + desc = "Crystallized oxygen and hypernoblium stored in a bottle to pressure-proof your clothes or stop reactions occurring in portable atmospheric devices." + icon = 'icons/obj/pipes_n_cables/atmos.dmi' + icon_state = "hypernoblium_crystal" + var/uses = 1 + +/obj/item/hypernoblium_crystal/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(!proximity_flag || !uses) + return + var/obj/item/clothing/worn_item = target + if(istype(target, /obj/machinery/portable_atmospherics)) + to_chat(user, span_notice("You could insert the crystal into [target], but this device does not support hypernoblium crystals.")) + return + 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 <= 0) + qdel(src) + return + to_chat(user, span_warning("The crystal can only be used on clothing!")) + +/obj/item/nitrium_crystal + desc = "A weird brown crystal, it smokes when broken" + name = "nitrium crystal" + icon = 'icons/obj/pipes_n_cables/atmos.dmi' + icon_state = "nitrium_crystal" + var/cloud_size = 1 + +/obj/item/nitrium_crystal/attack_self(mob/user) + . = ..() + var/datum/effect_system/smoke_spread/smoke = new + var/turf/location = get_turf(src) + smoke.set_up(cloud_size, location) + smoke.attach(location) + smoke.start() + qdel(src) diff --git a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_recipes.dm b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_recipes.dm new file mode 100644 index 0000000000000..a874f25f2eff9 --- /dev/null +++ b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_recipes.dm @@ -0,0 +1,171 @@ +/// Global list of recipes for atmospheric machines (id -> recipe) +GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list()) + +/proc/gas_recipes_list() + . = list() + for(var/recipe_path in subtypesof(/datum/gas_recipe)) + var/datum/gas_recipe/recipe = new recipe_path() + if(recipe.id != "") + .[recipe.id] = recipe + +/datum/gas_recipe + var/id = "" + var/machine_type = "" + var/name = "" + var/min_temp = TCMB + var/max_temp = INFINITY + var/energy_release = 0 + var/dangerous = FALSE + /// Gas ID -> moles required (e.g. list(GAS_O2 = 1000, GAS_HYPERNOB = 85)) + var/list/requirements + /// path -> count (e.g. list(/obj/item/hypernoblium_crystal = 1)) + var/list/products + +/datum/gas_recipe/crystallizer + machine_type = "Crystallizer" + +/datum/gas_recipe/crystallizer/hypern_crystalium + id = "hyper_crystalium" + name = "Hypernoblium Crystal" + min_temp = 3 + max_temp = 250 + energy_release = -250000 + requirements = list(GAS_O2 = 1000, GAS_HYPERNOB = 85) + products = list(/obj/item/hypernoblium_crystal = 1) + +/datum/gas_recipe/crystallizer/diamond + id = "diamond" + name = "Diamond" + min_temp = 10000 + max_temp = 30000 + energy_release = 9500000 + requirements = list(GAS_CO2 = 1500) + products = list(/obj/item/stack/sheet/mineral/diamond = 1) + +/datum/gas_recipe/crystallizer/plasma_sheet + id = "plasma_sheet" + name = "Plasma sheet" + min_temp = 10 + max_temp = 20 + energy_release = 3500000 + requirements = list(GAS_PLASMA = 450) + products = list(/obj/item/stack/sheet/mineral/plasma = 1) + +/datum/gas_recipe/crystallizer/crystallized_nitrium + id = "crystallized_nitrium" + name = "Nitrium crystal" + min_temp = 10 + max_temp = 25 + energy_release = -45000 + requirements = list(GAS_NITRIUM = 150, GAS_O2 = 70, GAS_BZ = 50) + products = list(/obj/item/nitrium_crystal = 1) + +/datum/gas_recipe/crystallizer/metallic_hydrogen + id = "metal_h" + name = "Metallic hydrogen" + min_temp = 10000 // H2 + BZ catalyst at high heat and pressure (around or above 10,000 K) + max_temp = 150000 + energy_release = -2500000 + requirements = list(GAS_HYDROGEN = 300, GAS_BZ = 50) + products = list(/obj/item/stack/sheet/mineral/metal_hydrogen = 1) + +/datum/gas_recipe/crystallizer/healium_grenade + id = "healium_g" + name = "Healium crystal" + min_temp = 200 + max_temp = 400 + energy_release = -2000000 + requirements = list(GAS_HEALIUM = 100, GAS_O2 = 120, GAS_PLASMA = 50) + products = list(/obj/item/grenade/gas_crystal/healium_crystal = 1) + +/datum/gas_recipe/crystallizer/proto_nitrate_grenade + id = "proto_nitrate_g" + name = "Proto nitrate crystal" + min_temp = 200 + max_temp = 400 + energy_release = 1500000 + requirements = list(GAS_PROTO_NITRATE = 100, GAS_N2 = 80, GAS_O2 = 80) + products = list(/obj/item/grenade/gas_crystal/proto_nitrate_crystal = 1) + +/datum/gas_recipe/crystallizer/hot_ice + id = "hot_ice" + name = "Hot ice" + min_temp = 15 + max_temp = 35 + energy_release = -3000000 + requirements = list(GAS_FREON = 60, GAS_PLASMA = 160, GAS_O2 = 80) + products = list(/obj/item/stack/sheet/hot_ice = 1) + +/datum/gas_recipe/crystallizer/ammonia_crystal + id = "ammonia_crystal" + name = "Ammonia crystal" + min_temp = 200 + max_temp = 240 + energy_release = 950000 + requirements = list(GAS_HYDROGEN = 50, GAS_N2 = 40) + products = list(/obj/item/stack/ammonia_crystals = 2) + +/datum/gas_recipe/crystallizer/shard + id = "crystal_shard" + name = "Supermatter crystal shard" + min_temp = 10 + max_temp = 20 + energy_release = 3500000 + dangerous = TRUE + requirements = list(GAS_HYPERNOB = 250, GAS_ANTINOBLIUM = 250, GAS_BZ = 200, GAS_PLASMA = 5000, GAS_O2 = 4500) + products = list(/obj/machinery/power/supermatter_crystal/shard = 1) + +/datum/gas_recipe/crystallizer/n2o_crystal + id = "n2o_crystal" + name = "Nitrous oxide crystal" + min_temp = 50 + max_temp = 350 + energy_release = 3500000 + requirements = list(GAS_NITROUS = 150, GAS_BZ = 30) + products = list(/obj/item/grenade/gas_crystal/nitrous_oxide_crystal = 1) + +/datum/gas_recipe/crystallizer/crystal_cell + id = "crystal_cell" + name = "Crystal Cell" + min_temp = 50 + max_temp = 90 + energy_release = -800000 + requirements = list(GAS_PLASMA = 800, GAS_HELIUM = 100, GAS_BZ = 50) + products = list(/obj/item/stock_parts/cell/crystal_cell = 1) + +/datum/gas_recipe/crystallizer/zaukerite + id = "zaukerite" + name = "Zaukerite sheet" + min_temp = 5 + max_temp = 20 + energy_release = 2900000 + requirements = list(GAS_ANTINOBLIUM = 5, GAS_ZAUKER = 20, GAS_BZ = 8) + products = list(/obj/item/stack/sheet/mineral/zaukerite = 2) + +/datum/gas_recipe/crystallizer/fuel_pellet + id = "fuel_basic" + name = "standard fuel pellet" + energy_release = -6000000 + requirements = list(GAS_O2 = 50, GAS_PLASMA = 100) + products = list(/obj/item/fuel_pellet = 1) + +/datum/gas_recipe/crystallizer/fuel_pellet_advanced + id = "fuel_advanced" + name = "advanced fuel pellet" + energy_release = -6000000 + requirements = list(GAS_TRITIUM = 100, GAS_HYDROGEN = 100) + products = list(/obj/item/fuel_pellet/advanced = 1) + +/datum/gas_recipe/crystallizer/fuel_pellet_exotic + id = "fuel_exotic" + name = "exotic fuel pellet" + energy_release = -6000000 + requirements = list(GAS_HYPERNOB = 100, GAS_NITRIUM = 100) + products = list(/obj/item/fuel_pellet/exotic = 1) + +/datum/gas_recipe/crystallizer/crystal_foam + id = "crystal_foam" + name = "Crystal foam grenade" + energy_release = 140000 + requirements = list(GAS_CO2 = 150, GAS_NITROUS = 100, GAS_H2O = 25) + products = list(/obj/item/grenade/gas_crystal/crystal_foam = 1) diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 0cec7b565913c..443ad85945433 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -55,12 +55,19 @@ "water vapor" = /obj/machinery/portable_atmospherics/canister/water_vapor, "tritium" = /obj/machinery/portable_atmospherics/canister/tritium, "hyper-noblium" = /obj/machinery/portable_atmospherics/canister/nob, - "stimulum" = /obj/machinery/portable_atmospherics/canister/stimulum, "pluoxium" = /obj/machinery/portable_atmospherics/canister/pluoxium, "caution" = /obj/machinery/portable_atmospherics/canister, "miasma" = /obj/machinery/portable_atmospherics/canister/miasma, "methane" = /obj/machinery/portable_atmospherics/canister/methane, - "methyl bromide" = /obj/machinery/portable_atmospherics/canister/methyl_bromide + "hydrogen" = /obj/machinery/portable_atmospherics/canister/hydrogen, + "helium" = /obj/machinery/portable_atmospherics/canister/helium, + "freon" = /obj/machinery/portable_atmospherics/canister/freon, + "halon" = /obj/machinery/portable_atmospherics/canister/halon, + "antinoblium" = /obj/machinery/portable_atmospherics/canister/antinoblium, + "proto nitrate" = /obj/machinery/portable_atmospherics/canister/proto_nitrate, + "zauker" = /obj/machinery/portable_atmospherics/canister/zauker, + "healium" = /obj/machinery/portable_atmospherics/canister/healium, + "nitrium" = /obj/machinery/portable_atmospherics/canister/nitrium ) /obj/machinery/portable_atmospherics/canister/interact(mob/user) @@ -129,6 +136,7 @@ icon_state = "brown" gas_type = GAS_NITRYL +// Убраны из label2types (не заказываются), но оставлены для совместимости с картами (Academy, ihategordon, undergroundoutpost45) /obj/machinery/portable_atmospherics/canister/stimulum name = "stimulum canister" desc = "Stimulum. High energy gas, high energy people." @@ -161,12 +169,67 @@ icon_state = "greyblackred" gas_type = GAS_METHANE +// Убраны из label2types (не заказываются), оставлены для совместимости с картами (undergroundoutpost45) /obj/machinery/portable_atmospherics/canister/methyl_bromide name = "methyl bromide canister" desc = "Methyl bromide. A potent toxin to most, essential for the Kharmaan to live." icon_state = "purplecyan" gas_type = GAS_METHYL_BROMIDE +/obj/machinery/portable_atmospherics/canister/hydrogen + name = "hydrogen canister" + desc = "Hydrogen. Flammable and used in fusion." + icon_state = "green" + gas_type = GAS_HYDROGEN + +/obj/machinery/portable_atmospherics/canister/helium + name = "helium canister" + desc = "Helium. Inert gas, byproduct of fusion." + icon_state = "grey" + gas_type = GAS_HELIUM + +/obj/machinery/portable_atmospherics/canister/freon + name = "freon canister" + desc = "Freon. Coolant gas. Breathing causes burn damage and slowdown." + icon_state = "darkblue" + gas_type = GAS_FREON + +/obj/machinery/portable_atmospherics/canister/halon + name = "halon canister" + desc = "Halon. Fire suppressant. Heavy slowdown and heat proof when inhaled." + icon_state = "purple" + gas_type = GAS_HALON + +/obj/machinery/portable_atmospherics/canister/antinoblium + name = "antinoblium canister" + desc = "Antinoblium. Rare fuel for fusion, replicates by consuming other gases." + icon_state = "darkpurple" + gas_type = GAS_ANTINOBLIUM + +/obj/machinery/portable_atmospherics/canister/proto_nitrate + name = "proto nitrate canister" + desc = "Proto nitrate. Highly reactive gas, catalyst for many reactions." + icon_state = "brown" + gas_type = GAS_PROTO_NITRATE + +/obj/machinery/portable_atmospherics/canister/zauker + name = "zauker canister" + desc = "Zauker. Incredibly deadly if inhaled." + icon_state = "black" + gas_type = GAS_ZAUKER + +/obj/machinery/portable_atmospherics/canister/healium + name = "healium canister" + desc = "Healium. Healing gas, stronger sleeping agent than N2O." + icon_state = "red" + gas_type = GAS_HEALIUM + +/obj/machinery/portable_atmospherics/canister/nitrium + name = "nitrium canister" + desc = "Nitrium. Gaseous stimulant, enhances speed and endurance." + icon_state = "orange" + gas_type = GAS_NITRIUM + /obj/machinery/portable_atmospherics/canister/proc/get_time_left() if(timing) . = round(max(0, valve_timer - world.time) / 10, 1) diff --git a/code/modules/cargo/exports/large_objects.dm b/code/modules/cargo/exports/large_objects.dm index 5d9164041f073..a09eba7a6b591 100644 --- a/code/modules/cargo/exports/large_objects.dm +++ b/code/modules/cargo/exports/large_objects.dm @@ -170,13 +170,21 @@ cost = 10 //Base cost of canister. You get more for nice gases inside. unit_name = "Gas Canister" export_types = list(/obj/machinery/portable_atmospherics/canister) + /// Above this many moles in the canister, gas value is scaled down (TG-style overflow penalty) + var/canister_mole_threshold = 2000 + var/canister_overflow_mult = 0.5 /datum/export/large/gas_canister/get_cost(obj/O) var/obj/machinery/portable_atmospherics/canister/C = O var/worth = 10 var/list/gas_prices = GLOB.gas_data.prices + var/total_moles = C.air_contents.total_moles() for(var/gas in C.air_contents.get_gases()) - worth += C.air_contents.get_moles(gas)*gas_prices[gas] + var/moles = C.air_contents.get_moles(gas) + var/gas_worth = moles * (gas_prices[gas] || 0) + if(total_moles > canister_mole_threshold) + gas_worth *= canister_overflow_mult + worth += gas_worth return worth diff --git a/code/modules/cargo/exports/sheets.dm b/code/modules/cargo/exports/sheets.dm index d0e3cbf7c8267..98d7a8c445e23 100644 --- a/code/modules/cargo/exports/sheets.dm +++ b/code/modules/cargo/exports/sheets.dm @@ -169,3 +169,78 @@ message = "credits" export_types = list(/obj/item/stack/telecrystal) +// Crystallizer products (prices from Whitemoon-station) +/datum/export/stack/hot_ice + cost = CARGO_CRATE_VALUE * 0.8 + unit_name = "hot ice sheet" + message = "of Hot Ice" + export_types = list(/obj/item/stack/sheet/hot_ice) + +/datum/export/stack/metal_hydrogen + cost = CARGO_CRATE_VALUE * 1.05 + unit_name = "metallic hydrogen sheet" + message = "of metallic hydrogen" + export_types = list(/obj/item/stack/sheet/mineral/metal_hydrogen) + +/datum/export/stack/ammonia_crystals + cost = CARGO_CRATE_VALUE * 0.125 + unit_name = "ammonia crystal" + message = "of ammonia crystals" + export_types = list(/obj/item/stack/ammonia_crystals) + +/datum/export/stack/zaukerite + cost = CARGO_CRATE_VALUE * 2 + unit_name = "zaukerite sheet" + message = "of zaukerite" + export_types = list(/obj/item/stack/sheet/mineral/zaukerite) + +// Crystallizer single-item exports (sellable at cargo) +/datum/export/crystallizer_item + unit_name = "item" + include_subtypes = FALSE + +/datum/export/crystallizer_item/nitrium_crystal + cost = CARGO_CRATE_VALUE * 2.5 + unit_name = "nitrium crystal" + export_types = list(/obj/item/nitrium_crystal) + +/datum/export/crystallizer_item/hypernoblium_crystal + cost = CARGO_CRATE_VALUE * 4 + unit_name = "hypernoblium crystal" + export_types = list(/obj/item/hypernoblium_crystal) + +/datum/export/crystallizer_item/fuel_pellet + cost = CARGO_CRATE_VALUE * 0.25 + unit_name = "fuel pellet" + export_types = list(/obj/item/fuel_pellet) + +/datum/export/crystallizer_item/fuel_pellet/advanced + cost = CARGO_CRATE_VALUE * 0.5 + unit_name = "advanced fuel pellet" + export_types = list(/obj/item/fuel_pellet/advanced) + +/datum/export/crystallizer_item/fuel_pellet/exotic + cost = CARGO_CRATE_VALUE * 0.75 + unit_name = "exotic fuel pellet" + export_types = list(/obj/item/fuel_pellet/exotic) + +/datum/export/crystallizer_item/gas_crystal_healium + cost = CARGO_CRATE_VALUE * 1.5 + unit_name = "healium crystal" + export_types = list(/obj/item/grenade/gas_crystal/healium_crystal) + +/datum/export/crystallizer_item/gas_crystal_proto_nitrate + cost = CARGO_CRATE_VALUE * 1 + unit_name = "proto nitrate crystal" + export_types = list(/obj/item/grenade/gas_crystal/proto_nitrate_crystal) + +/datum/export/crystallizer_item/gas_crystal_n2o + cost = CARGO_CRATE_VALUE * 0.5 + unit_name = "N2O crystal" + export_types = list(/obj/item/grenade/gas_crystal/nitrous_oxide_crystal) + +/datum/export/crystallizer_item/gas_crystal_foam + cost = CARGO_CRATE_VALUE * 1 + unit_name = "crystal foam" + export_types = list(/obj/item/grenade/gas_crystal/crystal_foam) + diff --git a/code/modules/cargo/packs/engine.dm b/code/modules/cargo/packs/engine.dm index 10b520dbce1d5..3a9950e7438ce 100644 --- a/code/modules/cargo/packs/engine.dm +++ b/code/modules/cargo/packs/engine.dm @@ -196,6 +196,31 @@ dangerous = TRUE contraband = TRUE +/datum/supply_pack/engine/crystallizer + name = "Crystallizer Board" + desc = "Плата для сборки кристаллярия — машины для кристаллизации и отверждения газов. Компоненты и трубы в комплект не входят." + cost = 4000 + contains = list(/obj/item/circuitboard/machine/crystallizer) + crate_name = "crystallizer board crate" + +/datum/supply_pack/engine/hfr + name = "Hypertorus Fusion Reactor Kit" + desc = "Набор для сборки реактора термоядерного синтеза (ХФР). Содержит ядро, четыре угловых блока, порты подачи топлива и модулятора, отвода отходов и интерфейс. Разместите боксы в сетке 3x3 и активируйте мультитулом на ядре. Требуется доступ СЕ." + cost = 15000 + access = ACCESS_CE + contains = list(/obj/item/hfr_box/core, + /obj/item/hfr_box/corner, + /obj/item/hfr_box/corner, + /obj/item/hfr_box/corner, + /obj/item/hfr_box/corner, + /obj/item/hfr_box/body/fuel_input, + /obj/item/hfr_box/body/moderator_input, + /obj/item/hfr_box/body/waste_output, + /obj/item/hfr_box/body/interface) + crate_name = "Hypertorus Fusion Reactor Kit" + crate_type = /obj/structure/closet/crate/secure/engineering + dangerous = TRUE + /datum/supply_pack/engine/reactor name = "RMBK Nuclear Reactor Kit" // (not) a toy desc = "Содержит реакторный маяк и 3 реакторных пульта. Урановые стержни в комплект не входят." diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm index e78a2f8fbd54e..ad00cc93cffd9 100644 --- a/code/modules/clothing/head/helmet.dm +++ b/code/modules/clothing/head/helmet.dm @@ -672,3 +672,22 @@ icon_state = "policehelm" dynamic_hair_suffix = "" flags_inv = HIDEEARS + +// Elder Atmosian — шлем легендарного атмос-теха (спрайты из WhiteMoon modular_zubbers) +/obj/item/clothing/head/helmet/elder_atmosian + name = "\improper Elder Atmosian Helmet" + desc = "Вершина атмос-экипировки: огнезащитный шлем, усиленный металлическим водородом. Полная защита головы от огня и газов." + icon = 'modular_bluemoon/icons/obj/clothing/head/helmet.dmi' + mob_overlay_icon = 'modular_bluemoon/icons/mob/clothing/head/helmet.dmi' + anthro_mob_worn_overlay = 'modular_bluemoon/icons/mob/clothing/head/helmet_teshari.dmi' + icon_state = "h2helmet" + item_state = "h2_helmet" + armor = list(MELEE = 50, BULLET = 45, LASER = 55, ENERGY = 55, BOMB = 95, BIO = 100, RAD = 100, FIRE = 100, ACID = 90, WOUND = 30) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + cold_protection = HEAD + heat_protection = HEAD + min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL | BLOCK_GAS_SMOKE_EFFECT + resistance_flags = FIRE_PROOF diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index 0a4691bd9997a..ea57d19b66057 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -354,7 +354,7 @@ body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS allowed = list(/obj/item/gun/energy, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) clothing_flags = THICKMATERIAL - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS @@ -371,12 +371,12 @@ clothing_flags = THICKMATERIAL body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS slowdown = 3 - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK armor = list(MELEE = 80, BULLET = 80, LASER = 50, ENERGY = 50, BOMB = 100, BIO = 100, RAD = 100, FIRE = 90, ACID = 90, WOUND = 50) /obj/item/clothing/suit/armor/tdome body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR|ALLOWS_BACK_TANK clothing_flags = THICKMATERIAL cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS @@ -403,7 +403,7 @@ item_state = "knight_green" armor = list(MELEE = 80, BULLET = 40, LASER = 10, ENERGY = 10, BOMB = 10, BIO = 0, RAD = 0, FIRE = 80, ACID = 80, WOUND = 30) slowdown = 0.5 - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK /obj/item/clothing/suit/armor/riot/knight/yellow icon_state = "knight_yellow" @@ -478,3 +478,36 @@ icon_state = "sov_offcoat" item_state = "sov_offcoat" //armor = list(MELEE = 25, BULLET = 20, LASER = 20, ENERGY = 10, BOMB = 20, BIO = 50, RAD = 50, FIRE = -10, ACID = 50, WOUND = 10) + +// Elder Atmosian — риг легендарного атмос-теха +/obj/item/clothing/suit/armor/elder_atmosian + name = "\improper Elder Atmosian Armor" + desc = "Вершина атмос-экипировки: дорогая огнезащитная броня, усиленная металлическим водородом. Полная защита от огня и газов без тяжёлого замедления. В слот костюма можно повесить металл-водородный топор." + icon = 'modular_bluemoon/icons/obj/clothing/suits/armor.dmi' + mob_overlay_icon = 'modular_bluemoon/icons/mob/clothing/suits/armor.dmi' + anthro_mob_worn_overlay = 'modular_bluemoon/icons/mob/clothing/suits/armor_digi.dmi' + taur_mob_worn_overlay = 'modular_bluemoon/icons/mob/clothing/suits/armor_teshari.dmi' + icon_state = "h2armor" + item_state = null + material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS + armor = list(MELEE = 50, BULLET = 45, LASER = 55, ENERGY = 55, BOMB = 95, BIO = 100, RAD = 100, FIRE = 100, ACID = 90, WOUND = 30) + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR|ALLOWS_BACK_TANK + clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL + strip_delay = 60 + equip_delay_other = 60 + resistance_flags = FIRE_PROOF + +/obj/item/clothing/suit/armor/elder_atmosian/Initialize(mapload) + . = ..() + allowed = islist(allowed) ? allowed.Copy() : list() + allowed += list( + /obj/item/fireaxe, + /obj/item/tank, + ) diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm index ff077d7fcde0b..7ffb3b8a04ff9 100644 --- a/code/modules/clothing/suits/jobs.dm +++ b/code/modules/clothing/suits/jobs.dm @@ -20,7 +20,7 @@ icon_state = "captunic" item_state = "bio_suit" body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDEJUMPSUIT + flags_inv = HIDEJUMPSUIT|ALLOWS_BACK_TANK allowed = list(/obj/item/disk, /obj/item/stamp, /obj/item/reagent_containers/food/drinks/flask, /obj/item/melee, /obj/item/storage/lockbox/medal, /obj/item/assembly/flash/handheld, /obj/item/storage/box/matches, /obj/item/lighter, /obj/item/clothing/mask/cigarette, /obj/item/storage/fancy/cigarettes, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) //Chaplain @@ -38,7 +38,7 @@ //icon_state = "nun" //item_state = "nun" body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS - flags_inv = HIDEJUMPSUIT + flags_inv = HIDEJUMPSUIT|ALLOWS_BACK_TANK mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON /obj/item/clothing/suit/chaplain/studentuni @@ -70,7 +70,7 @@ icon_state = "holidaypriest" item_state = "w_suit" body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDEJUMPSUIT + flags_inv = HIDEJUMPSUIT|ALLOWS_BACK_TANK mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm index 32f5a6c0a893e..e37835d1dc960 100644 --- a/code/modules/clothing/suits/miscellaneous.dm +++ b/code/modules/clothing/suits/miscellaneous.dm @@ -72,7 +72,7 @@ flags_1 = CONDUCT_1 fire_resist = T0C+5200 body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS|FEET - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK /obj/item/clothing/suit/justice @@ -81,7 +81,7 @@ icon_state = "justice" item_state = "justice" body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS|FEET - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK /obj/item/clothing/suit/judgerobe @@ -91,7 +91,7 @@ item_state = "judge" body_parts_covered = CHEST|GROIN|LEGS|ARMS allowed = list(/obj/item/storage/fancy/cigarettes, /obj/item/stack/spacecash) - flags_inv = HIDEJUMPSUIT + flags_inv = HIDEJUMPSUIT|ALLOWS_BACK_TANK mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON /obj/item/clothing/suit/tailcoat @@ -137,7 +137,7 @@ desc = "A plastic replica of the Syndicate space suit. You'll look just like a real murderous Syndicate agent in this! This is a toy, it is not made for use in space!" body_parts_covered = CHEST|ARMS|GROIN|LEGS|FEET|HANDS allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK resistance_flags = NONE /obj/item/clothing/suit/hastur @@ -146,7 +146,7 @@ icon_state = "hastur" item_state = "hastur" body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK mutantrace_variation = NONE /obj/item/clothing/suit/imperium_monk @@ -155,7 +155,7 @@ icon_state = "imperium_monk" item_state = "imperium_monk" body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK allowed = list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen) mutantrace_variation = NONE @@ -165,7 +165,7 @@ icon_state = "chickensuit" item_state = "chickensuit" body_parts_covered = CHEST|ARMS|GROIN|LEGS|FEET - flags_inv = HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK /obj/item/clothing/suit/monkeysuit @@ -174,7 +174,7 @@ icon_state = "monkeysuit" item_state = "monkeysuit" body_parts_covered = CHEST|ARMS|GROIN|LEGS|FEET|HANDS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK /obj/item/clothing/suit/toggle/owlwings name = "owl cloak" @@ -201,7 +201,7 @@ icon_state = "cardborg" item_state = "cardborg" body_parts_covered = CHEST|GROIN - flags_inv = HIDEJUMPSUIT + flags_inv = HIDEJUMPSUIT|ALLOWS_BACK_TANK dog_fashion = /datum/dog_fashion/back /obj/item/clothing/suit/cardborg/equipped(mob/living/user, slot) @@ -230,7 +230,7 @@ icon_state = "snowman" item_state = "snowman" body_parts_covered = CHEST|GROIN - flags_inv = HIDEJUMPSUIT + flags_inv = HIDEJUMPSUIT|ALLOWS_BACK_TANK mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON /obj/item/clothing/suit/poncho @@ -269,7 +269,7 @@ icon_state = "white_dress" item_state = "w_suit" body_parts_covered = CHEST|GROIN|LEGS|FEET - flags_inv = HIDEJUMPSUIT|HIDESHOES + flags_inv = HIDEJUMPSUIT|HIDESHOES|ALLOWS_BACK_TANK mutantrace_variation = NONE /obj/item/clothing/suit/hooded/carp_costume @@ -395,7 +395,7 @@ strip_delay = 120 slowdown = 0.5 obj_flags = IMMUTABLE_SLOW - flags_inv = HIDESHOES|HIDEJUMPSUIT|HIDETAUR + flags_inv = HIDESHOES|HIDEJUMPSUIT|HIDETAUR|ALLOWS_BACK_TANK /obj/item/clothing/suit/ran name = "shikigami costume" @@ -403,7 +403,7 @@ icon_state = "ran_suit" item_state = "ran_suit" body_parts_covered = CHEST|GROIN|LEGS - flags_inv = HIDEJUMPSUIT|HIDETAUR + flags_inv = HIDEJUMPSUIT|HIDETAUR|ALLOWS_BACK_TANK cold_protection = CHEST|GROIN|LEGS //fluffy tails! min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT //Bleh, same as winter coat heat_protection = CHEST|GROIN|LEGS @@ -427,7 +427,7 @@ icon_state = "straight_jacket" item_state = "straight_jacket" body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK equip_delay_self = 50 strip_delay = 60 breakouttime = 3000 @@ -664,7 +664,7 @@ icon_state = "xenos" item_state = "xenos_helm" body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK allowed = list(/obj/item/clothing/mask/facehugger/toy) /obj/item/clothing/suit/caution @@ -1208,7 +1208,7 @@ item_state = "assu_suit" blood_overlay_type = "armor" body_parts_covered = CHEST|GROIN|ARMS|LEGS - flags_inv = HIDEJUMPSUIT + flags_inv = HIDEJUMPSUIT|ALLOWS_BACK_TANK resistance_flags = NONE /obj/item/clothing/suit/hooded/wintercoat/christmascoatr diff --git a/code/modules/clothing/suits/utility.dm b/code/modules/clothing/suits/utility.dm index 9da2692fad442..bcbe48bb2c685 100644 --- a/code/modules/clothing/suits/utility.dm +++ b/code/modules/clothing/suits/utility.dm @@ -22,7 +22,7 @@ allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/extinguisher, /obj/item/crowbar, /obj/item/tank/internals/oxygen, /obj/item/tank/internals/air, /obj/item/tank/internals/generic) slowdown = 1 armor = list(MELEE = 15, BULLET = 5, LASER = 20, ENERGY = 10, BOMB = 20, BIO = 10, RAD = 20, FIRE = 100, ACID = 50) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR|ALLOWS_BACK_TANK clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT @@ -36,7 +36,7 @@ icon_state = "firesuit" item_state = "firefighter" tail_state = "atmos" - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR|ALLOWS_BACK_TANK mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC @@ -58,7 +58,7 @@ cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR|ALLOWS_BACK_TANK mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC /* diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index 3ec5e373de67f..ce1a79da7486d 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -78,7 +78,7 @@ body_parts_covered = CHEST|ARMS|LEGS armor = list(MELEE = 30, BULLET = 20, LASER = 20, ENERGY = 20, BOMB = 20, BIO = 20, RAD = 20, FIRE = 100, ACID = 100, WOUND = 20) allowed = list(/obj/item/teleportation_scroll) - flags_inv = HIDEJUMPSUIT + flags_inv = HIDEJUMPSUIT|ALLOWS_BACK_TANK strip_delay = 50 equip_delay_other = 50 resistance_flags = FIRE_PROOF | ACID_PROOF diff --git a/code/modules/holiday/easter.dm b/code/modules/holiday/easter.dm index dc0bc66b1d106..20c9a27dba27e 100644 --- a/code/modules/holiday/easter.dm +++ b/code/modules/holiday/easter.dm @@ -104,7 +104,7 @@ item_state = "bunnysuit" slowdown = -1 body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|ALLOWS_BACK_TANK //Egg prizes and egg spawns! /obj/item/reagent_containers/food/snacks/egg diff --git a/code/modules/mining/equipment/explorer_gear.dm b/code/modules/mining/equipment/explorer_gear.dm index cc13de651716a..ecb7ca9b7a254 100644 --- a/code/modules/mining/equipment/explorer_gear.dm +++ b/code/modules/mining/equipment/explorer_gear.dm @@ -11,7 +11,7 @@ min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT hoodtype = /obj/item/clothing/head/hooded/explorer armor = list(MELEE = 30, BULLET = 20, LASER = 20, ENERGY = 20, BOMB = 50, BIO = 100, RAD = 50, FIRE = 50, ACID = 50, WOUND = 15) - flags_inv = HIDEJUMPSUIT|HIDETAUR + flags_inv = HIDEJUMPSUIT|HIDETAUR|ALLOWS_BACK_TANK allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator, /obj/item/pickaxe, /obj/item/device/cooler/lavaland) // BLUEMOON ADD - добавлен лавалендовский ПОУ resistance_flags = FIRE_PROOF mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 719ec2bfa584d..cb3b5f097ac2b 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -385,6 +385,9 @@ if(wear_suit.flags_inv & HIDESHOES) LAZYOR(., ITEM_SLOT_FEET) LAZYOR(., ITEM_SLOT_SOCKS) + // Full-body suits HIDEJUMPSUIT normally obscure back; ALLOWS_BACK_TANK or no HIDEBACK allows tank on back + if((wear_suit.flags_inv & HIDEBACK) || ((wear_suit.flags_inv & HIDEJUMPSUIT) && !(wear_suit.flags_inv & ALLOWS_BACK_TANK))) + LAZYOR(., ITEM_SLOT_BACK) if(w_uniform) if(underwear_hidden()) LAZYOR(., ITEM_SLOT_UNDERWEAR) diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index a03c3b6059049..aadd42b1b53ec 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -261,6 +261,16 @@ name = "pulse pistol power cell" maxcharge = 2000 +/obj/item/stock_parts/cell/crystal_cell + name = "crystal power cell" + desc = "A very high power cell made from crystallized plasma." + icon_state = "crystal_cell" + maxcharge = 50000 + chargerate = 0 + has_charge_overlay = FALSE + custom_materials = null + grind_results = null + /obj/item/stock_parts/cell/high name = "high-capacity power cell" icon_state = "hcell" diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index cbf858b4a72b9..62b789933e1c2 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -1697,6 +1697,51 @@ L.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/nitryl) ..() +/datum/reagent/freon + name = "Freon" + description = "A coolant gas. Breathing it causes burn damage and heavy slowdown." + reagent_state = GAS + gas = GAS_FREON + metabolization_rate = REAGENTS_METABOLISM + color = "#66ccff" + taste_description = "cold burn" + value = REAGENT_VALUE_UNCOMMON + +/datum/reagent/freon/on_mob_metabolize(mob/living/L) + ..() + L.add_movespeed_modifier(/datum/movespeed_modifier/reagent/freon) + +/datum/reagent/freon/on_mob_end_metabolize(mob/living/L) + L.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/freon) + ..() + +/datum/reagent/halon + name = "Halon" + description = "A fire suppressant gas. Heavy slowdown when inhaled, but makes you heat proof." + reagent_state = GAS + gas = GAS_HALON + metabolization_rate = REAGENTS_METABOLISM + color = "#44cc44" + taste_description = "chemical stagnation" + value = REAGENT_VALUE_UNCOMMON + +/datum/reagent/halon/on_mob_metabolize(mob/living/L) + ..() + L.add_movespeed_modifier(/datum/movespeed_modifier/reagent/halon) + ADD_TRAIT(L, TRAIT_RESISTHEAT, type) + +/datum/reagent/halon/on_mob_end_metabolize(mob/living/L) + L.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/halon) + REMOVE_TRAIT(L, TRAIT_RESISTHEAT, type) + ..() + +/datum/reagent/hot_ice_slush + name = "Hot Ice Slush" + description = "A slush of hot ice. Holds a great amount of power inside." + color = "#66ccff" + taste_description = "cold burn" + value = REAGENT_VALUE_VERY_RARE + /////////////////////////Coloured Crayon Powder//////////////////////////// //For colouring in /proc/mix_color_from_reagents diff --git a/code/modules/research/designs/machine_desings/machine_designs_all_misc.dm b/code/modules/research/designs/machine_desings/machine_designs_all_misc.dm index 8da5aba0578b1..4532a0a384310 100644 --- a/code/modules/research/designs/machine_desings/machine_designs_all_misc.dm +++ b/code/modules/research/designs/machine_desings/machine_designs_all_misc.dm @@ -77,6 +77,14 @@ category = list ("Engineering Machinery") departmental_flags = DEPARTMENTAL_FLAG_ALL +/datum/design/board/electrolyzer + name = "Machine Design (Electrolyzer Board)" + desc = "The circuit board for a space electrolyzer." + id = "electrolyzer" + build_path = /obj/item/circuitboard/machine/electrolyzer + category = list ("Engineering Machinery") + departmental_flags = DEPARTMENTAL_FLAG_ALL + /datum/design/board/reagentgrinder name = "Machine Design (All-In-One Grinder)" desc = "The circuit board for an All-In-One Grinder." diff --git a/code/modules/research/techweb/_techweb_node.dm b/code/modules/research/techweb/_techweb_node.dm index 61a0a27ae443f..ea1eed155610c 100644 --- a/code/modules/research/techweb/_techweb_node.dm +++ b/code/modules/research/techweb/_techweb_node.dm @@ -104,7 +104,7 @@ // Default research tech, prevents bricking design_ids = list("basic_matter_bin", "basic_cell", "basic_scanning", "basic_capacitor", "basic_micro_laser", "micro_mani", "desttagger", "handlabel", "packagewrap", "destructive_analyzer", "circuit_imprinter", "circuit_imprinter_science", "circuit_imprinter_robotic", "experimentor", "rdconsole", "bepis", "design_disk", "tech_disk", "rdserver", "rdservercontrol", "mechfab", "paystand", - "space_heater", "beaker", "large_beaker", "xlarge_beaker", "bucket", "hypovial", "large_hypovial", "syringe", "pillbottle", + "space_heater", "electrolyzer", "beaker", "large_beaker", "xlarge_beaker", "bucket", "hypovial", "large_hypovial", "syringe", "pillbottle", "sec_beanbag", "sec_rshot", "sec_bshot", "sec_slug", "sec_islug", "sec_dart", "sec_38", "sec_38lethal", "rglass","plasteel","plastitanium","plasmaglass","plasmareinforcedglass","titaniumglass","plastitaniumglass", "salestagger", "cooler_mining", "cooler") diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index b92a9dbe39cb4..35bbcc7c59e9e 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -57,7 +57,7 @@ var/SA_para_min = 1 //nitrous values var/SA_sleep_min = 5 var/BZ_trip_balls_min = 0.1 //BZ gas - var/BZ_brain_damage_min = 1 + var/BZ_brain_damage_min = 10 // partial pressure over 10: 33% per tick for 3 brain damage, max 150 var/gas_stimulation_min = 0.002 //Nitryl and Stimulum var/cold_message = "your face freezing and an icicle forming" @@ -309,6 +309,17 @@ breath.adjust_moles(GAS_NITRYL, -gas_breathed) + // Freon — burn damage + slowdown (reagent from breath_reagent) + gas_breathed = breath.get_moles(GAS_FREON) + if (gas_breathed > 0.0001) + var/freon_pp = PP(breath, GAS_FREON) + H.adjustFireLoss(clamp(round(freon_pp * 0.5), 0, 5)) + + // Nitrium — at high concentrations causes lung damage + var/nitrium_pp = PP(breath, GAS_NITRIUM) + if (nitrium_pp > 20) + applyOrganDamage(min(round((nitrium_pp - 20) * 0.5), 15)) + // Stimulum gas_breathed = PP(breath,GAS_STIMULUM) if (gas_breathed > gas_stimulation_min) @@ -316,6 +327,17 @@ H.reagents.add_reagent(/datum/reagent/stimulum, max(0, 5 - existing)) breath.adjust_moles(GAS_STIMULUM, -gas_breathed) + // Healium — лечит брутал и берн при дыхании (не breath_reagent: иначе газ вычитается до этого блока и лечение не срабатывает) + gas_breathed = breath.get_moles(GAS_HEALIUM) + if (gas_breathed > 0.0001) + var/healium_pp = PP(breath, GAS_HEALIUM) + var/heal_amount = clamp(round(healium_pp * 6), 3, 18) + H.adjustBruteLoss(-heal_amount) + H.adjustFireLoss(-heal_amount) + H.adjustOxyLoss(-max(round(heal_amount * 0.5), 1)) + H.adjustToxLoss(-max(round(heal_amount * 0.3), 1)) + breath.adjust_moles(GAS_HEALIUM, -gas_breathed) + // Miasma if (breath.get_moles(GAS_MIASMA)) var/miasma_pp = PP(breath,GAS_MIASMA) diff --git a/icons/obj/crystallizer_exploration.dmi b/icons/obj/crystallizer_exploration.dmi new file mode 100644 index 0000000000000..b7224d2df84d9 Binary files /dev/null and b/icons/obj/crystallizer_exploration.dmi differ diff --git a/icons/obj/crystallizer_grenades.dmi b/icons/obj/crystallizer_grenades.dmi new file mode 100644 index 0000000000000..415166f9c4074 Binary files /dev/null and b/icons/obj/crystallizer_grenades.dmi differ diff --git a/icons/obj/crystallizer_sheets.dmi b/icons/obj/crystallizer_sheets.dmi new file mode 100644 index 0000000000000..a10d56851ada4 Binary files /dev/null and b/icons/obj/crystallizer_sheets.dmi differ diff --git a/icons/obj/exploration.dmi b/icons/obj/exploration.dmi new file mode 100644 index 0000000000000..b7224d2df84d9 Binary files /dev/null and b/icons/obj/exploration.dmi differ diff --git a/icons/obj/hot_ice.dmi b/icons/obj/hot_ice.dmi new file mode 100644 index 0000000000000..a10d56851ada4 Binary files /dev/null and b/icons/obj/hot_ice.dmi differ diff --git a/icons/obj/machines/atmospherics/binary_devices.dmi b/icons/obj/machines/atmospherics/binary_devices.dmi new file mode 100644 index 0000000000000..7855922700cdb Binary files /dev/null and b/icons/obj/machines/atmospherics/binary_devices.dmi differ diff --git a/icons/obj/machines/atmospherics/hypertorus.dmi b/icons/obj/machines/atmospherics/hypertorus.dmi new file mode 100644 index 0000000000000..1c7107cf59bcd Binary files /dev/null and b/icons/obj/machines/atmospherics/hypertorus.dmi differ diff --git a/icons/obj/machines/atmospherics/machines.dmi b/icons/obj/machines/atmospherics/machines.dmi new file mode 100644 index 0000000000000..baf01a3e9cb85 Binary files /dev/null and b/icons/obj/machines/atmospherics/machines.dmi differ diff --git a/icons/obj/machines/atmospherics/miners.dmi b/icons/obj/machines/atmospherics/miners.dmi new file mode 100644 index 0000000000000..db47b47feeadb Binary files /dev/null and b/icons/obj/machines/atmospherics/miners.dmi differ diff --git a/icons/obj/machines/atmospherics/thermomachine.dmi b/icons/obj/machines/atmospherics/thermomachine.dmi new file mode 100644 index 0000000000000..bcc36f4242f15 Binary files /dev/null and b/icons/obj/machines/atmospherics/thermomachine.dmi differ diff --git a/icons/obj/machines/atmospherics/trinary_devices.dmi b/icons/obj/machines/atmospherics/trinary_devices.dmi new file mode 100644 index 0000000000000..3f65aa419aaa2 Binary files /dev/null and b/icons/obj/machines/atmospherics/trinary_devices.dmi differ diff --git a/icons/obj/machines/atmospherics/unary_devices.dmi b/icons/obj/machines/atmospherics/unary_devices.dmi new file mode 100644 index 0000000000000..73da54768b1cb Binary files /dev/null and b/icons/obj/machines/atmospherics/unary_devices.dmi differ diff --git a/icons/obj/pipes_n_cables/atmos.dmi b/icons/obj/pipes_n_cables/atmos.dmi new file mode 100644 index 0000000000000..e7401411a290e Binary files /dev/null and b/icons/obj/pipes_n_cables/atmos.dmi differ diff --git a/icons/obj/pipes_n_cables/canisters.dmi b/icons/obj/pipes_n_cables/canisters.dmi new file mode 100644 index 0000000000000..d7761182d8a52 Binary files /dev/null and b/icons/obj/pipes_n_cables/canisters.dmi differ diff --git a/icons/obj/pipes_n_cables/pipe_underlays.dmi b/icons/obj/pipes_n_cables/pipe_underlays.dmi new file mode 100644 index 0000000000000..1a74cc3740190 Binary files /dev/null and b/icons/obj/pipes_n_cables/pipe_underlays.dmi differ diff --git a/icons/obj/pipes_n_cables/stationary_canisters_misc.dmi b/icons/obj/pipes_n_cables/stationary_canisters_misc.dmi new file mode 100644 index 0000000000000..857b5a816b40c Binary files /dev/null and b/icons/obj/pipes_n_cables/stationary_canisters_misc.dmi differ diff --git a/icons/obj/weapons/fireaxe.dmi b/icons/obj/weapons/fireaxe.dmi new file mode 100644 index 0000000000000..a4743f680ad52 Binary files /dev/null and b/icons/obj/weapons/fireaxe.dmi differ diff --git a/icons/obj/weapons/grenade.dmi b/icons/obj/weapons/grenade.dmi new file mode 100644 index 0000000000000..415166f9c4074 Binary files /dev/null and b/icons/obj/weapons/grenade.dmi differ diff --git a/modular_bluemoon/icons/mob/clothing/head/helmet.dmi b/modular_bluemoon/icons/mob/clothing/head/helmet.dmi new file mode 100644 index 0000000000000..7f91a43759184 Binary files /dev/null and b/modular_bluemoon/icons/mob/clothing/head/helmet.dmi differ diff --git a/modular_bluemoon/icons/mob/clothing/head/helmet_teshari.dmi b/modular_bluemoon/icons/mob/clothing/head/helmet_teshari.dmi new file mode 100644 index 0000000000000..04238deebd186 Binary files /dev/null and b/modular_bluemoon/icons/mob/clothing/head/helmet_teshari.dmi differ diff --git a/modular_bluemoon/icons/mob/clothing/suits/armor.dmi b/modular_bluemoon/icons/mob/clothing/suits/armor.dmi new file mode 100644 index 0000000000000..1acfbc0fabf28 Binary files /dev/null and b/modular_bluemoon/icons/mob/clothing/suits/armor.dmi differ diff --git a/modular_bluemoon/icons/mob/clothing/suits/armor_digi.dmi b/modular_bluemoon/icons/mob/clothing/suits/armor_digi.dmi new file mode 100644 index 0000000000000..7e13766d0415a Binary files /dev/null and b/modular_bluemoon/icons/mob/clothing/suits/armor_digi.dmi differ diff --git a/modular_bluemoon/icons/mob/clothing/suits/armor_teshari.dmi b/modular_bluemoon/icons/mob/clothing/suits/armor_teshari.dmi new file mode 100644 index 0000000000000..35cc34ca2a6e2 Binary files /dev/null and b/modular_bluemoon/icons/mob/clothing/suits/armor_teshari.dmi differ diff --git a/modular_bluemoon/icons/obj/clothing/head/helmet.dmi b/modular_bluemoon/icons/obj/clothing/head/helmet.dmi new file mode 100644 index 0000000000000..f45d4b8634f02 Binary files /dev/null and b/modular_bluemoon/icons/obj/clothing/head/helmet.dmi differ diff --git a/modular_bluemoon/icons/obj/clothing/suits/armor.dmi b/modular_bluemoon/icons/obj/clothing/suits/armor.dmi new file mode 100644 index 0000000000000..ebff7f2777c76 Binary files /dev/null and b/modular_bluemoon/icons/obj/clothing/suits/armor.dmi differ diff --git a/sound/announcer/notice/notice3.ogg b/sound/announcer/notice/notice3.ogg new file mode 100644 index 0000000000000..77ae19af552ca Binary files /dev/null and b/sound/announcer/notice/notice3.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 3a3d844a55f84..3d4492e766ab7 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -776,6 +776,7 @@ #include "code\datums\components\crafting\recipes.dm" #include "code\datums\components\crafting\glassware\glassware.dm" #include "code\datums\components\crafting\glassware\lens_crafting.dm" +#include "code\datums\components\crafting\recipes\recipes_atmospheric_crystals.dm" #include "code\datums\components\crafting\recipes\recipes_clothing.dm" #include "code\datums\components\crafting\recipes\recipes_misc.dm" #include "code\datums\components\crafting\recipes\recipes_primal.dm" @@ -2203,6 +2204,18 @@ #include "code\modules\atmospherics\machinery\components\binary_devices\relief_valve.dm" #include "code\modules\atmospherics\machinery\components\binary_devices\valve.dm" #include "code\modules\atmospherics\machinery\components\binary_devices\volume_pump.dm" +#include "code\modules\atmospherics\machinery\components\electrolyzer\electrolyzer.dm" +#include "code\modules\atmospherics\machinery\components\electrolyzer\electrolyzer_reactions.dm" +#include "code\modules\atmospherics\machinery\components\fusion\_hfr_defines.dm" +#include "code\modules\atmospherics\machinery\components\fusion\hfr_core.dm" +#include "code\modules\atmospherics\machinery\components\fusion\hfr_fuel_datums.dm" +#include "code\modules\atmospherics\machinery\components\fusion\hfr_main_processes.dm" +#include "code\modules\atmospherics\machinery\components\fusion\hfr_parts.dm" +#include "code\modules\atmospherics\machinery\components\fusion\hfr_procs.dm" +#include "code\modules\atmospherics\machinery\components\gas_recipe_machines\crystallizer.dm" +#include "code\modules\atmospherics\machinery\components\gas_recipe_machines\crystallizer_extra_items.dm" +#include "code\modules\atmospherics\machinery\components\gas_recipe_machines\crystallizer_items.dm" +#include "code\modules\atmospherics\machinery\components\gas_recipe_machines\crystallizer_recipes.dm" #include "code\modules\atmospherics\machinery\components\trinary_devices\filter.dm" #include "code\modules\atmospherics\machinery\components\trinary_devices\mixer.dm" #include "code\modules\atmospherics\machinery\components\trinary_devices\trinary_devices.dm" @@ -4386,9 +4399,9 @@ #include "modular_bluemoon\code\game\objects\items\medals.dm" #include "modular_bluemoon\code\game\objects\items\miscellaneous.dm" #include "modular_bluemoon\code\game\objects\items\mop.dm" -#include "modular_bluemoon\code\game\objects\items\pet_bowl.dm" #include "modular_bluemoon\code\game\objects\items\noose.dm" #include "modular_bluemoon\code\game\objects\items\nyamagotchi.dm" +#include "modular_bluemoon\code\game\objects\items\pet_bowl.dm" #include "modular_bluemoon\code\game\objects\items\pinpointer.dm" #include "modular_bluemoon\code\game\objects\items\plushes.dm" #include "modular_bluemoon\code\game\objects\items\qareen_chalk.dm" diff --git a/tgui/packages/tgui/interfaces/Hypertorus.js b/tgui/packages/tgui/interfaces/Hypertorus.js index ab3a196922ac9..b7b17b94cff13 100644 --- a/tgui/packages/tgui/interfaces/Hypertorus.js +++ b/tgui/packages/tgui/interfaces/Hypertorus.js @@ -11,7 +11,7 @@ import { Window } from '../layouts'; export const Hypertorus = (props, context) => { const { act, data } = useBackend(context); const filterTypes = data.filter_types || []; - const selectedFuels = data.selected_fuel || []; + const selectableFuels = data.selectable_fuel || []; const { energy_level, core_temperature, @@ -19,7 +19,6 @@ export const Hypertorus = (props, context) => { power_output, heat_limiter_modifier, heat_output, - heat_output_bool, heating_conductor, magnetic_constrictor, fuel_injection_rate, @@ -89,14 +88,15 @@ export const Hypertorus = (props, context) => { -
+
- {selectedFuels.map(recipe => ( + {selectableFuels.map(recipe => (