diff --git a/_maps/shuttles/emergency_supermatter.dmm b/_maps/shuttles/emergency_supermatter.dmm index fb420049b6f..05e4d018cff 100644 --- a/_maps/shuttles/emergency_supermatter.dmm +++ b/_maps/shuttles/emergency_supermatter.dmm @@ -100,7 +100,7 @@ /turf/open/floor/plating, /area/shuttle/escape) "aB" = ( -/obj/machinery/power/supermatter_crystal/shard/hugbox/fakecrystal, +/obj/machinery/power/supermatter_crystal/hugbox, /turf/open/floor/plating, /area/shuttle/escape) "aD" = ( diff --git a/code/__DEFINES/supermatter.dm b/code/__DEFINES/supermatter.dm index 6b9d52d665e..c5df44c4ead 100644 --- a/code/__DEFINES/supermatter.dm +++ b/code/__DEFINES/supermatter.dm @@ -14,24 +14,19 @@ /// How much the amount of CO2 consumed per tick scales with gasmix power ratio. Higher values means gasmix has a greater effect on the CO2 consumed. #define CO2_GASMIX_SCALING (0.1) -#define MOLE_PENALTY_THRESHOLD 1800 //Above this value we can get lord singulo and independent mol damage, below it we can heal damage -#define MOLE_HEAT_PENALTY 350 //Heat damage scales around this. Too hot setups with this amount of moles do regular damage, anything above and below is scaled -//Along with damage_penalty_point, makes flux anomalies. -/// The cutoff for the minimum amount of power required to trigger the crystal invasion delamination event. -#define EVENT_POWER_PENALTY_THRESHOLD 4500 +#define MOLE_PENALTY_THRESHOLD 1800 //Above this value we can get lord singulo and independent mol damage, below it we can heal damage #define POWER_PENALTY_THRESHOLD 5000 //The cutoff on power properly doing damage, pulling shit around, and delamming into a tesla. Low chance of pyro anomalies, +2 bolts of electricity #define SEVERE_POWER_PENALTY_THRESHOLD 7000 //+1 bolt of electricity, allows for gravitational anomalies, and higher chances of pyro anomalies #define CRITICAL_POWER_PENALTY_THRESHOLD 9000 //+1 bolt of electricity. #define HEAT_PENALTY_THRESHOLD 40 //Higher == Crystal safe operational temperature is higher. -#define DAMAGE_HARDCAP 0.002 -#define DAMAGE_INCREASE_MULTIPLIER 0.25 +#define DAMAGE_HARDCAP 1.8 #define THERMAL_RELEASE_MODIFIER 4 //Higher == less heat released during reaction, not to be confused with the above values #define PLASMA_RELEASE_MODIFIER 650 //Higher == less plasma released by reaction #define OXYGEN_RELEASE_MODIFIER 340 //Higher == less oxygen released at high temperature/power -#define REACTION_POWER_MODIFIER 0.65 //Higher == more overall power +#define REACTION_POWER_MODIFIER 0.65 //Higher == more waste gas #define MATTER_POWER_CONVERSION 10 //Crystal converts 1/this value of stored matter into energy. @@ -43,8 +38,7 @@ #define SUPERMATTER_WARNING_DELAY 60 SECONDS -#define HALLUCINATION_RANGE(P) (min(7, round(P ** 0.25))) - +#define SM_HALLUCINATION_RANGE(P) (min(7, round(P ** 0.25))) #define GRAVITATIONAL_ANOMALY "gravitational_anomaly" #define FLUX_ANOMALY "flux_anomaly" @@ -63,16 +57,12 @@ #define SLIGHTLY_CHARGED_ZAP_ICON_STATE "sm_arc_supercharged" #define OVER_9000_ZAP_ICON_STATE "sm_arc_dbz_referance" //Witty I know -#define MAX_SPACE_EXPOSURE_DAMAGE 10 - #define SUPERMATTER_CASCADE_PERCENT 80 /// The divisor scaling value for cubic power loss. #define POWERLOSS_CUBIC_DIVISOR 500 /// The rate at which the linear power loss function scales with power. #define POWERLOSS_LINEAR_RATE 0.83 -/// How much a psychologist can reduce power loss. -#define PSYCHOLOGIST_POWERLOSS_REDUCTION 0.2 /// Means it's not forced, sm decides itself by checking the [/datum/sm_delam/proc/can_select] #define SM_DELAM_PRIO_NONE 0 @@ -102,3 +92,64 @@ #define SUPERMATTER_EMERGENCY 5 /// Currently counting down to delamination. True [/obj/machinery/power/supermatter_crystal/var/final_countdown] #define SUPERMATTER_DELAMINATING 6 + +// SUPERMATTER FACTORS DEFINES +// This might feel obvious, but mentioning it doesnt hurt anyone: +// While we separate these factors into neat little boxes, order still matters in the code. +// There are a few factors that depends on other factors. So pay close attention on which values are interlinked. + +/// How much energy we get from external factors that are applied immediately. +#define SM_POWER_EXTERNAL_IMMEDIATE "External Power Gain" +/// How much energy we get from external factors that are applied over time. +#define SM_POWER_EXTERNAL_TRICKLE "External Power Trickle" +/// How much energy is gained from the temperature. Enabled by gas. +#define SM_POWER_HEAT "Gas Heat Power Gain" +/// How much energy the SM loses. Happens over time. +/// Order matters here. We depend on current power + power gained from the factors above for the loss calc. +#define SM_POWER_POWERLOSS "Internal Power Decay" +/// How much of the energy the SM loses is recouped. From gas factors here. +/// Order matters here. We depend on the powerloss amount. +#define SM_POWER_POWERLOSS_GAS "Gas Power Decay Negation" +/// How much of the energy the SM loses is recouped. From the psychologist this time. +/// Order matters here. We depend on the powerloss amount. +#define SM_POWER_POWERLOSS_SOOTHED "Psychologist Power Decay Negation" + +// These four internal damage factors, heat, power, moles, and heal heat dont depend on each other, but they are interlinked. +// They are going to be scaled to have a maximum damage hardcap of 1.8 per tick. +/// How many damage we take from heat. +#define SM_DAMAGE_HEAT "Heating Damage" +/// How many damage we take from too much internal energy. +#define SM_DAMAGE_POWER "Charge Damage" +/// How many damage we take from too much moles around us. +#define SM_DAMAGE_MOLES "Molar Damage" +/// How many we healed. Happens when temp is low. +#define SM_DAMAGE_HEAL_HEAT "Low Temperature Healing" +/// How many damage do we take from external factors. +/// This one is discrete. +#define SM_DAMAGE_EXTERNAL "External Damage" +/// How many damage do we take from space exposure. Here to prevent people from spacing SM chambers. +/// Also discrete. +#define SM_DAMAGE_SPACED "Space Exposure Damage" + +/// How much waste multiplier we get just from existing. +#define SM_WASTE_BASE "Base Waste Multiplier" +/// How much waste multiplier we get because of the gases around us. +#define SM_WASTE_GAS "Gas Waste Multiplier" +/// How much waste multiplier we (don't) get because there is a psychologist. +#define SM_WASTE_SOOTHED "Psychologist Waste Multiplier" + +/// How many kelvins we get before taking damage, Given by god. +#define SM_TEMP_LIMIT_BASE "Base Heat Resistance" +/// How many extra kelvins we get before taking damage, this time from gases. +/// Order matters, depends on base resistance. +#define SM_TEMP_LIMIT_GAS "Gas Heat Resistance" +/// How many extra kelvins we get before taking damage, this time from psychologist. +#define SM_TEMP_LIMIT_SOOTHED "Psychologist Heat Resistance" +/// How many extra kelvins we get before taking damage because our moles are low. I know, this is fucking hilarious. +/// Order matters, depends on base resistance. +#define SM_TEMP_LIMIT_LOW_MOLES "Low Moles Heat Resistance" + +/// How much we are multiplying our zap energy. +#define SM_ZAP_BASE "Base Zap Multiplier" +/// How much we are multiplying our zap energy because of gas factors. +#define SM_ZAP_GAS "Gas Zap Multiplier" diff --git a/code/modules/modular_computers/file_system/programs/sm_monitor.dm b/code/modules/modular_computers/file_system/programs/sm_monitor.dm index 20d0200128c..3f74ae0273c 100644 --- a/code/modules/modular_computers/file_system/programs/sm_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/sm_monitor.dm @@ -8,18 +8,105 @@ requires_ntnet = TRUE transfer_access = list(ACCESS_CONSTRUCTION) size = 5 - tgui_id = "NtosSupermatterMonitor" + tgui_id = "NtosSupermatter" program_icon = "radiation" alert_able = TRUE var/last_status = SUPERMATTER_INACTIVE - var/list/supermatters - var/obj/machinery/power/supermatter_crystal/active // Currently selected supermatter crystal. + /// List of supermatters that we are going to send the data of. + var/list/obj/machinery/power/supermatter_crystal/supermatters = list() + /// The supermatter which will send a notification to us if it's delamming. + var/obj/machinery/power/supermatter_crystal/focused_supermatter -/datum/computer_file/program/supermatter_monitor/Destroy() - clear_signals() - active = null +/datum/computer_file/program/supermatter_monitor/on_start(mob/living/user) + . = ..() + refresh() + +/// Apparently destroy calls this [/datum/computer_file/Destroy]. Here just to clean our references. +/datum/computer_file/program/supermatter_monitor/kill_program(forced = FALSE) + for(var/supermatter in supermatters) + clear_supermatter(supermatter) return ..() +/// Refreshes list of active supermatter crystals +/datum/computer_file/program/supermatter_monitor/proc/refresh() + for(var/supermatter in supermatters) + clear_supermatter(supermatter) + var/turf/user_turf = get_turf(ui_host()) + if(!user_turf) + return + for(var/obj/machinery/power/supermatter_crystal/sm in GLOB.machines) + //Exclude Syndicate owned, Delaminating, not within coverage, not on a tile. + if (!sm.include_in_cims || !isturf(sm.loc) || !(is_station_level(sm.z) || is_mining_level(sm.z) || sm.z == user_turf.z)) + continue + supermatters += sm + RegisterSignal(sm, COMSIG_PARENT_QDELETING, .proc/clear_supermatter) + +/datum/computer_file/program/supermatter_monitor/ui_static_data(mob/user) + var/list/data = list() + data["gas_metadata"] = sm_gas_data() + return data + +/datum/computer_file/program/supermatter_monitor/ui_data(mob/user) + var/list/data = get_header_data() + data["sm_data"] = list() + for (var/obj/machinery/power/supermatter_crystal/sm as anything in supermatters) + data["sm_data"] += list(sm.sm_ui_data()) + data["focus_uid"] = focused_supermatter?.uid + return data + +/datum/computer_file/program/supermatter_monitor/ui_act(action, params) + . = ..() + if(.) + return + + switch(action) + if("PRG_refresh") + refresh() + return TRUE + if("PRG_focus") + for (var/obj/machinery/power/supermatter_crystal/sm in supermatters) + if(sm.uid == params["focus_uid"]) + if(focused_supermatter == sm) + unfocus_supermatter(sm) + else + focus_supermatter(sm) + return TRUE + +/// Sends an SM delam alert to the computer if our focused supermatter is delaminating. +/// [var/obj/machinery/power/supermatter_crystal/focused_supermatter]. +/datum/computer_file/program/supermatter_monitor/proc/send_alert() + SIGNAL_HANDLER + if(!computer.get_ntnet_status()) + return + computer.alert_call(src, "Crystal delamination in progress!") + alert_pending = TRUE + +/datum/computer_file/program/supermatter_monitor/proc/clear_supermatter(obj/machinery/power/supermatter_crystal/sm) + SIGNAL_HANDLER + supermatters -= sm + if(focused_supermatter == sm) + unfocus_supermatter() + UnregisterSignal(sm, COMSIG_PARENT_QDELETING) + +/datum/computer_file/program/supermatter_monitor/proc/focus_supermatter(obj/machinery/power/supermatter_crystal/sm) + if(sm == focused_supermatter) + return + if(focused_supermatter) + unfocus_supermatter() + RegisterSignal(sm, COMSIG_SUPERMATTER_DELAM_ALARM, .proc/send_alert) + focused_supermatter = sm + +/datum/computer_file/program/supermatter_monitor/proc/unfocus_supermatter() + if(!focused_supermatter) + return + UnregisterSignal(focused_supermatter, COMSIG_SUPERMATTER_DELAM_ALARM) + focused_supermatter = null + +/datum/computer_file/program/supermatter_monitor/proc/get_status() + . = SUPERMATTER_INACTIVE + for(var/obj/machinery/power/supermatter_crystal/S in supermatters) + . = max(., S.get_status()) + /datum/computer_file/program/supermatter_monitor/process_tick(delta_time) ..() var/new_status = get_status() @@ -29,153 +116,3 @@ program_icon_state = "smmon_[last_status]" if(istype(computer)) computer.update_appearance() - -/datum/computer_file/program/supermatter_monitor/on_start(mob/living/user) - . = ..(user) - if(!(active in GLOB.machines)) - active = null - refresh() - -/datum/computer_file/program/supermatter_monitor/kill_program(forced = FALSE) - for(var/supermatter in supermatters) - clear_supermatter(supermatter) - supermatters = null - ..() - -// Refreshes list of active supermatter crystals -/datum/computer_file/program/supermatter_monitor/proc/refresh() - for(var/supermatter in supermatters) - clear_supermatter(supermatter) - supermatters = list() - var/turf/user_turf = get_turf(ui_host()) - if(!user_turf) - return - for(var/obj/machinery/power/supermatter_crystal/crystal in GLOB.machines) - //Exclude Syndicate owned, Delaminating, not within coverage, not on a tile. - if (!crystal.include_in_cims || !isturf(crystal.loc) || !(is_station_level(crystal.z) || is_mining_level(crystal.z) || crystal.z == user_turf.z)) - continue - supermatters.Add(crystal) - RegisterSignal(crystal, COMSIG_PARENT_QDELETING, .proc/react_to_del) - -/datum/computer_file/program/supermatter_monitor/proc/get_status() - . = SUPERMATTER_INACTIVE - for(var/obj/machinery/power/supermatter_crystal/S in supermatters) - . = max(., S.get_status()) - -/** - * Sets up the signal listener for Supermatter delaminations. - * - * Unregisters any old listners for SM delams, and then registers one for the SM refered - * to in the `active` variable. This proc is also used with no active SM to simply clear - * the signal and exit. - */ -/datum/computer_file/program/supermatter_monitor/proc/set_signals() - if(active) - RegisterSignal(active, COMSIG_SUPERMATTER_DELAM_ALARM, .proc/send_alert, override = TRUE) - RegisterSignal(active, COMSIG_SUPERMATTER_DELAM_START_ALARM, .proc/send_start_alert, override = TRUE) - -/** - * Removes the signal listener for Supermatter delaminations from the selected supermatter. - * - * Pretty much does what it says. - */ -/datum/computer_file/program/supermatter_monitor/proc/clear_signals() - if(active) - UnregisterSignal(active, COMSIG_SUPERMATTER_DELAM_ALARM) - UnregisterSignal(active, COMSIG_SUPERMATTER_DELAM_START_ALARM) - -/** - * Sends an SM delam alert to the computer. - * - * Triggered by a signal from the selected supermatter, this proc sends a notification - * to the computer if the program is either closed or minimized. We do not send these - * notifications to the comptuer if we're the active program, because engineers fixing - * the supermatter probably don't need constant beeping to distract them. - */ -/datum/computer_file/program/supermatter_monitor/proc/send_alert() - SIGNAL_HANDLER - if(!computer.get_ntnet_status()) - return - if(computer.active_program != src) - computer.alert_call(src, "Crystal delamination in progress!") - alert_pending = TRUE - -/** - * Sends an SM delam start alert to the computer. - * - * Triggered by a signal from the selected supermatter at the start of a delamination, - * this proc sends a notification to the computer if this program is the active one. - * We do this so that people carrying a tablet with NT CIMS open but with the NTOS window - * closed will still get one audio alert. This is not sent to computers with the program - * minimized or closed to avoid double-notifications. - */ -/datum/computer_file/program/supermatter_monitor/proc/send_start_alert() - SIGNAL_HANDLER - if(!computer.get_ntnet_status()) - return - if(computer.active_program == src) - computer.alert_call(src, "Crystal delamination in progress!") - -/datum/computer_file/program/supermatter_monitor/ui_data(mob/user) - var/list/data = get_header_data() - - if(istype(active)) - var/turf/T = get_turf(active) - if(!T) - active = null - refresh() - return - var/datum/gas_mixture/air = T.return_air() - if(!air) - active = null - return - - data += active.ui_data() - data["singlecrystal"] = FALSE - - else - var/list/SMS = list() - for(var/obj/machinery/power/supermatter_crystal/S in supermatters) - var/area/A = get_area(S) - if(A) - SMS.Add(list(list( - "area_name" = A.name, - "integrity" = S.get_integrity_percent(), - "uid" = S.uid - ))) - - data["active"] = FALSE - data["supermatters"] = SMS - - return data - -/datum/computer_file/program/supermatter_monitor/ui_act(action, params) - . = ..() - if(.) - return - - switch(action) - if("PRG_clear") - clear_signals() - active = null - return TRUE - if("PRG_refresh") - refresh() - return TRUE - if("PRG_set") - var/newuid = text2num(params["target"]) - for(var/obj/machinery/power/supermatter_crystal/S in supermatters) - if(S.uid == newuid) - active = S - set_signals() - return TRUE - -/datum/computer_file/program/supermatter_monitor/proc/react_to_del(datum/source) - SIGNAL_HANDLER - clear_supermatter(source) - -/datum/computer_file/program/supermatter_monitor/proc/clear_supermatter(matter) - supermatters -= matter - if(matter == active) - active = null - UnregisterSignal(matter, COMSIG_PARENT_QDELETING) diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 7b33fee08e4..84aa3f77760 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -18,7 +18,6 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) name = "supermatter crystal" desc = "A strangely translucent and iridescent crystal." icon = 'icons/obj/engine/supermatter.dmi' - icon_state = "darkmatter" density = TRUE anchored = TRUE layer = MOB_LAYER @@ -26,7 +25,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) light_range = 4 resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF critical_machine = TRUE - base_icon_state = "darkmatter" + base_icon_state = "sm" + icon_state = "sm" ///The id of our supermatter var/uid = 1 @@ -35,14 +35,33 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) ///Tracks the bolt color we are using var/zap_icon = DEFAULT_ZAP_ICON_STATE - ///Are we exploding? - var/final_countdown = FALSE + ///The portion of the gasmix we're on that we should remove + var/absorption_ratio = 0.15 + /// The gasmix we just recently absorbed. Tile's air multiplied by absorption_ratio + var/datum/gas_mixture/absorbed_gasmix - ///The amount of damage we have currently + ///Refered to as EER on the monitor. This value effects gas output, damage, and power generation. + var/internal_energy = 0 + var/list/internal_energy_factors + + ///The amount of damage we have currently. var/damage = 0 /// The damage we had before this cycle. - /// Used to limit the damage we can take each cycle, and to check if we are currently taking damage or healing. + /// Used to check if we are currently taking damage or healing. var/damage_archived = 0 + var/list/damage_factors + + /// How much extra power does the main zap generate. + var/zap_multiplier = 1 + var/list/zap_factors + + /// The temperature at which we start taking damage + var/temp_limit = T0C + HEAT_PENALTY_THRESHOLD + var/list/temp_limit_factors + + /// Multiplies our waste gas amount and temperature. + var/waste_multiplier = 0 + var/list/waste_multiplier_factors ///The point at which we consider the supermatter to be [SUPERMATTER_STATUS_WARNING] var/warning_point = 50 @@ -55,35 +74,38 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) var/emergency_channel = null // Need null to actually broadcast, lol. ///The point at which we delam [SUPERMATTER_STATUS_DELAMINATING] var/explosion_point = 900 - + ///Are we exploding? + var/final_countdown = FALSE ///A scaling value that affects the severity of explosions. var/explosion_power = 35 ///Time in 1/10th of seconds since the last sent warning var/lastwarning = 0 - ///Refered to as eer on the moniter. This value effects gas output, heat, damage, and radiation. - var/power = 0 - ///The list of gases mapped against their current comp. We use this to calculate different values the supermatter uses, like power or heat resistance. Ranges from 0 to 1 + + /// The list of gases mapped against their current comp. + /// We use this to calculate different values the supermatter uses, like power or heat resistance. + /// Ranges from 0 to 1 var/list/gas_percentage - ///The last air sample's total molar count, will always be above or equal to 0 - var/combined_gas = 0 - ///Total mole count of the environment we are in - var/environment_total_moles = 0 - ///Affects the power gain the sm experiances from heat - var/gasmix_power_ratio = 0 - ///Affects the amount of o2 and plasma the sm outputs, along with the heat it makes. - var/dynamic_heat_modifier = 1 - ///Affects the amount of damage and minimum point at which the sm takes heat damage - var/dynamic_heat_resistance = 1 - ///Uses powerloss_dynamic_scaling and combined_gas to lessen the effects of our powerloss functions - var/powerloss_inhibitor = 1 - ///Based on co2 percentage, slowly moves between 0 and 1. We use it to calc the powerloss_inhibitor - var/powerloss_dynamic_scaling= 0 - ///Affects the amount of radiation the sm makes. We multiply this with power to find the zap power. - var/power_transmission_bonus = 0 - ///Used to increase or lessen the amount of damage the sm takes from heat based on molar counts. - var/mole_heat_penalty = 0 - ///Takes the energy throwing things into the sm generates and slowly turns it into actual power - var/matter_power = 0 + + /// Affects the heat our SM makes. + var/gas_heat_modifier = 0 + /// Affects the minimum point at which the SM takes heat damage + var/gas_heat_resistance = 0 + /// How much power decay is negated. Complete power decay negation at 1. + var/gas_powerloss_inhibition = 0 + /// Affects the amount of power the main SM zap makes. + var/gas_power_transmission = 0 + /// Affects the power gain the SM experiances from heat. + var/gas_heat_power_generation = 0 + + /// External power that are added over time instead of immediately. + var/external_power_trickle = 0 + /// External power that are added to the sm on next [/obj/machinery/power/supermatter_crystal/process_atmos] call. + var/external_power_immediate = 0 + + /// External damage that are added to the sm on next [/obj/machinery/power/supermatter_crystal/process_atmos] call. + /// SM will not take damage if it's health is lower than emergency point. + var/external_damage_immediate = 0 + ///The cutoff for a bolt jumping, grows with heat, lowers with higher mol count, var/zap_cutoff = 1500 ///How much the bullets damage should be multiplied by when it is added to the internal variables @@ -91,27 +113,6 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) ///How much hallucination should we produce per unit of power? var/hallucination_power = 0.1 - ///Pressure bonus constants - ///If the SM is operating in sufficiently low pressure, increase power output. - ///This needs both a small amount of gas and a strong cooling system to keep temperature low in a low heat capacity environment. - - ///These constants are used to derive the values in the pressure bonus equation from human-meaningful values - ///If you're varediting these, call update_constants() to update the derived values - - ///What is the maximum multiplier reachable from having low pressure? - var/pressure_bonus_max_multiplier = 0.5 - ///At what environmental pressure, in kPa, should we start giving a pressure bonus? - var/pressure_bonus_max_pressure = 100 - ///How steeply angled is the pressure bonus curve? Higher values means more of the bonus is available at higher pressures. - ///Note that very low values can keep the bonus very close to 1 until it's nearly a vaccuum. Higher values can introduce diminishing returns on lower pressure. - var/pressure_bonus_curve_angle = 1.8 - - ///These values are calculated from the above in update_constants() and immediately overwritten - ///The default values will always result in a no-op 1x modifier, in case something breaks. - var/pressure_bonus_derived_constant = 1 - var/pressure_bonus_derived_steepness = 0 - - ///Our internal radio var/obj/item/radio/radio ///The key our internal radio uses @@ -132,22 +133,21 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) ///cooldown tracker for accent sounds var/last_accent_sound = 0 - ///Var that increases from 0 to 1 when a psycologist is nearby, and decreases in the same way - var/psyCoeff = 0 - ///Should we check the psy overlay? - var/psy_overlay = FALSE - ///A pinkish overlay used to denote the presance of a psycologist. We fade in and out of this depending on the amount of time they've spent near the crystal - var/obj/overlay/psy/psyOverlay = /obj/overlay/psy + ///Var that increases from 0 to 1 when a psychologist is nearby, and decreases in the same way + var/psy_coeff = 0 + + /// Disables all methods of taking damage. + var/disable_damage = FALSE + /// Disables the calculation of gas effects and production of waste. + /// SM still "breathes" though, still takes gas and spits it out. Nothing is done on them though. + /// Cleaner code this way. Get rid of if it's too wasteful. + var/disable_gas = FALSE + /// Disables power changes. + var/disable_power_change = FALSE + /// Disables the SM's proccessing totally. + /// Make sure absorbed_gasmix and gas_percentage isnt null if this is on. + var/disable_process = FALSE - //For making hugbox supermatters - ///Disables all methods of taking damage - var/takes_damage = TRUE - ///Disables the production of gas, and pretty much any handling of it we do. - var/produces_gas = TRUE - ///Disables power changes - var/power_changes = TRUE - ///Disables the sm's proccessing totally. - var/processes = TRUE ///Stores the time of when the last zap occurred var/last_power_zap = 0 ///Do we show this crystal in the CIMS modular program @@ -162,11 +162,6 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) ///The offset of the linear powerloss function set so the transition is differentiable. var/powerloss_linear_offset = 0 - ///The portion of the gasmix we're on that we should remove - var/absorption_ratio = 0.15 - /// The gasmix we just recently absorbed. Tile's air multiplied by absorption_ratio - var/datum/gas_mixture/absorbed_gasmix - /// How we are delaminating. var/datum/sm_delam/delamination_strategy /// Whether the sm is forced in a specific delamination_strategy or not. All truthy values means it's forced. @@ -175,6 +170,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) /obj/machinery/power/supermatter_crystal/Initialize(mapload) . = ..() + gas_percentage = list() + absorbed_gasmix = new() uid = gl_uid++ set_delam(SM_DELAM_PRIO_NONE, /datum/sm_delam/explosive) SSair.start_processing_machine(src) @@ -200,16 +197,13 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) AddComponent(/datum/component/supermatter_crystal, CALLBACK(src, .proc/wrench_act_callback), CALLBACK(src, .proc/consume_callback)) soundloop = new(src, TRUE) - if(ispath(psyOverlay)) - psyOverlay = new psyOverlay() - else - stack_trace("Supermatter created with non-path psyOverlay variable. This can break things, please fix.") - psyOverlay = new() if (!moveable) move_resist = MOVE_FORCE_OVERPOWERING // Avoid being moved by statues or other memes - update_constants() + // Damn math nerds + powerloss_linear_threshold = sqrt(POWERLOSS_LINEAR_RATE / 3 * POWERLOSS_CUBIC_DIVISOR ** 3) + powerloss_linear_offset = -1 * powerloss_linear_threshold * POWERLOSS_LINEAR_RATE + (powerloss_linear_threshold / POWERLOSS_CUBIC_DIVISOR) ** 3 /obj/machinery/power/supermatter_crystal/Destroy() if(warp) @@ -217,13 +211,12 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) QDEL_NULL(warp) investigate_log("has been destroyed.", INVESTIGATE_ENGINE) SSair.stop_processing_machine(src) + absorbed_gasmix = null QDEL_NULL(radio) QDEL_NULL(countdown) if(is_main_engine && GLOB.main_supermatter_engine == src) GLOB.main_supermatter_engine = null QDEL_NULL(soundloop) - if(psyOverlay) - QDEL_NULL(psyOverlay) return ..() /obj/machinery/power/supermatter_crystal/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents) @@ -233,20 +226,94 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) if(warp) SET_PLANE_EXPLICIT(warp, PLANE_TO_TRUE(warp.plane), src) -/obj/machinery/power/supermatter_crystal/proc/update_constants() - pressure_bonus_derived_steepness = (1 - 1 / pressure_bonus_max_multiplier) / (pressure_bonus_max_pressure ** pressure_bonus_curve_angle) - pressure_bonus_derived_constant = 1 / pressure_bonus_max_multiplier - pressure_bonus_derived_steepness - powerloss_linear_threshold = sqrt(POWERLOSS_LINEAR_RATE / 3 * POWERLOSS_CUBIC_DIVISOR ** 3) - powerloss_linear_offset = -1 * powerloss_linear_threshold * POWERLOSS_LINEAR_RATE + (powerloss_linear_threshold / POWERLOSS_CUBIC_DIVISOR) ** 3 - /obj/machinery/power/supermatter_crystal/examine(mob/user) . = ..() var/immune = HAS_TRAIT(user, TRAIT_MADNESS_IMMUNE) || (user.mind && HAS_TRAIT(user.mind, TRAIT_MADNESS_IMMUNE)) - if(isliving(user) && !immune && (get_dist(user, src) < HALLUCINATION_RANGE(power))) + if(isliving(user) && !immune && (get_dist(user, src) < SM_HALLUCINATION_RANGE(internal_energy))) . += span_danger("You get headaches just from looking at it.") . += delamination_strategy.examine(src) return . +/obj/machinery/power/supermatter_crystal/process_atmos() + // PART 1: PRELIMINARIES + if(disable_process) + return + + var/turf/local_turf = loc + if(!istype(local_turf))//We are in a crate or somewhere that isn't turf, if we return to turf resume processing but for now. + return //Yeah just stop. + if(isclosedturf(local_turf)) + var/turf/did_it_melt = local_turf.Melt() + if(!isclosedturf(did_it_melt)) //In case some joker finds way to place these on indestructible walls + visible_message(span_warning("[src] melts through [local_turf]!")) + return + + // PART 2: GAS PROCESSING + var/datum/gas_mixture/env = local_turf.return_air() + absorbed_gasmix = env?.remove_ratio(absorption_ratio) || new() + absorbed_gasmix.volume = (env?.volume || CELL_VOLUME) * absorption_ratio // To match the pressure. + calculate_gases() + // Extra effects should always fire after the compositions are all finished + // Some extra effects like [/datum/sm_gas/carbon_dioxide/extra_effects] + // needs more than one gas and rely on a fully parsed gas_percentage. + for (var/gas_path in absorbed_gasmix.gases) + var/datum/sm_gas/sm_gas = GLOB.sm_gas_behavior[gas_path] + sm_gas.extra_effects(src) + + // PART 3: POWER PROCESSING + internal_energy_factors = calculate_internal_energy() + zap_factors = calculate_zap_multiplier() + if(internal_energy && (last_power_zap + 4 SECONDS - (internal_energy * 0.001)) < world.time) + playsound(src, 'sound/weapons/emitter2.ogg', 70, TRUE) + hue_angle_shift = clamp(903 * log(10, (internal_energy + 8000)) - 3590, -50, 240) + var/zap_color = color_matrix_rotate_hue(hue_angle_shift) + supermatter_zap( + zapstart = src, + range = 3, + zap_str = 5 * internal_energy * zap_multiplier, + zap_flags = ZAP_SUPERMATTER_FLAGS, + zap_cutoff = 300, + power_level = internal_energy, + color = zap_color, + ) + last_power_zap = world.time + + // PART 4: DAMAGE PROCESSING + temp_limit_factors = calculate_temp_limit() + damage_factors = calculate_damage() + if(damage == 0) // Clear any in game forced delams if on full health. + set_delam(SM_DELAM_PRIO_IN_GAME, SM_DELAM_STRATEGY_PURGE) + else + set_delam(SM_DELAM_PRIO_NONE, SM_DELAM_STRATEGY_PURGE) // This one cant clear any forced delams. + delamination_strategy.delam_progress(src) + if(damage > explosion_point && !final_countdown) + count_down() + + // PART 5: WASTE GAS PROCESSING + waste_multiplier_factors = calculate_waste_multiplier() + var/device_energy = internal_energy * REACTION_POWER_MODIFIER + + /// Do waste on another gasmix so we can keep a copy of the gasmix we use for processing. + var/datum/gas_mixture/merged_gasmix = absorbed_gasmix.copy() + merged_gasmix.temperature += device_energy * waste_multiplier / THERMAL_RELEASE_MODIFIER + merged_gasmix.temperature = clamp(merged_gasmix.temperature, TCMB, 2500 * waste_multiplier) + merged_gasmix.assert_gases(/datum/gas/plasma, /datum/gas/oxygen) + merged_gasmix.gases[/datum/gas/plasma][MOLES] += max(device_energy * waste_multiplier / PLASMA_RELEASE_MODIFIER, 0) + merged_gasmix.gases[/datum/gas/oxygen][MOLES] += max(((device_energy + merged_gasmix.temperature * waste_multiplier) - T0C) / OXYGEN_RELEASE_MODIFIER, 0) + merged_gasmix.garbage_collect() + env.merge(merged_gasmix) + air_update_turf(FALSE, FALSE) + + // PART 6: EXTRA BEHAVIOUR + emit_radiation() + processing_sound() + handle_high_power() + psychological_examination() + if(prob(15)) + supermatter_pull(loc, min(internal_energy/850, 3))//850, 1700, 2550 + update_appearance() + return TRUE + // SupermatterMonitor UI for ghosts only. Inherited attack_ghost will call this. /obj/machinery/power/supermatter_crystal/ui_interact(mob/user, datum/tgui/ui) if(!isobserver(user)) @@ -254,55 +321,87 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) . = ..() ui = SStgui.try_update_ui(user, src, ui) if (!ui) - ui = new(user, src, "SupermatterMonitor") + ui = new(user, src, "Supermatter") ui.open() +/obj/machinery/power/supermatter_crystal/ui_static_data(mob/user) + var/list/data = list() + data["gas_metadata"] = sm_gas_data() + return data + +/// Returns data that are exclusively about this sm. +/obj/machinery/power/supermatter_crystal/proc/sm_ui_data() + var/list/data = list() + data["uid"] = uid + data["area_name"] = get_area_name(src) + + data["integrity"] = get_integrity_percent() + data["integrity_factors"] = list() + for (var/factor in damage_factors) + var/amount = round(damage_factors[factor], 0.01) + if(!amount) + continue + data["integrity_factors"] += list(list( + "name" = factor, + "amount" = amount * -1 + )) + data["internal_energy"] = internal_energy + data["internal_energy_factors"] = list() + for (var/factor in internal_energy_factors) + var/amount = round(internal_energy_factors[factor], 0.01) + if(!amount) + continue + data["internal_energy_factors"] += list(list( + "name" = factor, + "amount" = amount + )) + data["temp_limit"] = temp_limit + data["temp_limit_factors"] = list() + for (var/factor in temp_limit_factors) + var/amount = round(temp_limit_factors[factor], 0.01) + if(!amount) + continue + data["temp_limit_factors"] += list(list( + "name" = factor, + "amount" = amount + )) + data["waste_multiplier"] = waste_multiplier + data["waste_multiplier_factors"] = list() + for (var/factor in waste_multiplier_factors) + var/amount = round(waste_multiplier_factors[factor], 0.01) + if(!amount) + continue + data["waste_multiplier_factors"] += list(list( + "name" = factor, + "amount" = amount + )) + data["zap_multiplier"] = zap_multiplier + data["zap_multiplier_factors"] = list() + for (var/factor in zap_factors) + var/amount = round(zap_factors[factor], 0.01) + if(!amount) + continue + data["zap_multiplier_factors"] += list(list( + "name" = factor, + "amount" = amount + )) + data["absorbed_ratio"] = absorption_ratio + var/list/formatted_gas_percentage = list() + for (var/datum/gas/gas_path as anything in subtypesof(/datum/gas)) + formatted_gas_percentage[gas_path] = gas_percentage?[gas_path] || 0 + data["gas_composition"] = formatted_gas_percentage + data["gas_temperature"] = absorbed_gasmix.temperature + data["gas_total_moles"] = absorbed_gasmix.total_moles() + return data + /obj/machinery/power/supermatter_crystal/ui_data(mob/user) var/list/data = list() - - var/turf/local_turf = get_turf(src) - - var/datum/gas_mixture/air = local_turf.return_air() - - // singlecrystal set to true eliminates the back sign on the gases breakdown. - data["singlecrystal"] = TRUE - data["active"] = TRUE - data["SM_integrity"] = get_integrity_percent() - data["SM_power"] = power - data["SM_ambienttemp"] = air.temperature - data["SM_ambientpressure"] = air.return_pressure() - data["SM_bad_moles_amount"] = MOLE_PENALTY_THRESHOLD / absorption_ratio - data["SM_moles"] = 0 - data["SM_uid"] = uid - var/area/active_supermatter_area = get_area(src) - data["SM_area_name"] = active_supermatter_area.name - - var/list/gasdata = list() - - if(air.total_moles()) - data["SM_moles"] = air.total_moles() - for(var/gasid in air.gases) - gasdata.Add(list(list( - "name"= air.gases[gasid][GAS_META][META_GAS_NAME], - "amount" = round(100*air.gases[gasid][MOLES]/air.total_moles(),0.01)))) - - else - for(var/gasid in air.gases) - gasdata.Add(list(list( - "name"= air.gases[gasid][GAS_META][META_GAS_NAME], - "amount" = 0))) - - data["gases"] = gasdata - + data["sm_data"] = list(sm_ui_data()) return data /// Encodes the current state of the supermatter. /obj/machinery/power/supermatter_crystal/proc/get_status() - var/turf/local_turf = get_turf(src) - if(!local_turf) - return SUPERMATTER_ERROR - var/datum/gas_mixture/air = local_turf.return_air() - if(!air) + if(!absorbed_gasmix) return SUPERMATTER_ERROR if(final_countdown) return SUPERMATTER_DELAMINATING @@ -312,9 +411,9 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) return SUPERMATTER_DANGER if(damage >= warning_point) return SUPERMATTER_WARNING - if(absorbed_gasmix.temperature > (T0C + HEAT_PENALTY_THRESHOLD) * 0.8) + if(absorbed_gasmix.temperature > temp_limit * 0.8) return SUPERMATTER_NOTIFY - if(power) + if(internal_energy) return SUPERMATTER_NORMAL return SUPERMATTER_INACTIVE @@ -326,28 +425,26 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) /obj/machinery/power/supermatter_crystal/update_overlays() . = ..() + if(psy_coeff > 0) + var/mutable_appearance/psy_overlay = mutable_appearance(icon, "[base_icon_state]-psy", FLOAT_LAYER - 1) + psy_overlay.alpha = psy_coeff * 255 + . += psy_overlay if(delamination_strategy) . += delamination_strategy.overlays(src) + return . + +/obj/machinery/power/supermatter_crystal/update_icon(updates) + . = ..() + if(gas_heat_power_generation > 0.8) + icon_state = "[base_icon_state]-glow" + else + icon_state = base_icon_state /obj/machinery/power/supermatter_crystal/proc/force_delam() SIGNAL_HANDLER investigate_log("was forcefully delaminated", INVESTIGATE_ENGINE) INVOKE_ASYNC(delamination_strategy, /datum/sm_delam/proc/delaminate, src) -/obj/machinery/power/supermatter_crystal/proc/supermatter_pull(turf/center, pull_range = 3) - playsound(center, 'sound/weapons/marauder.ogg', 100, TRUE, extrarange = pull_range - world.view) - for(var/atom/movable/movable_atom in orange(pull_range,center)) - if((movable_atom.anchored || movable_atom.move_resist >= MOVE_FORCE_EXTREMELY_STRONG)) //move resist memes. - if(istype(movable_atom, /obj/structure/closet)) - var/obj/structure/closet/closet = movable_atom - closet.open(force = TRUE) - continue - if(ismob(movable_atom)) - var/mob/pulled_mob = movable_atom - if(pulled_mob.mob_negates_gravity()) - continue //You can't pull someone nailed to the deck - step_towards(movable_atom,center) - /** * Count down, spout some messages, and then execute the delam itself. * We guard for last second delam strat changes here, mostly because some have diff messages. @@ -364,7 +461,6 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) return final_countdown = TRUE - update_appearance() var/datum/sm_delam/last_delamination_strategy = delamination_strategy var/list/count_down_messages = delamination_strategy.count_down_messages() @@ -398,32 +494,258 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) if(healed) final_countdown = FALSE - update_appearance() return // delam averted sleep(1 SECONDS) delamination_strategy.delaminate(src) -/proc/supermatter_anomaly_gen(turf/anomalycenter, type = FLUX_ANOMALY, anomalyrange = 5, has_changed_lifespan = TRUE) - var/turf/local_turf = pick(orange(anomalyrange, anomalycenter)) - if(!local_turf) +// All the calculate procs should only update variables. +// Move the actual real-world effects to [/obj/machinery/power/supermatter_crystal/process_atmos]. + +/** + * Perform calculation for variables that depend on gases. + * Description of each factors can be found in the defines. + * + * Updates: + * [/obj/machinery/power/supermatter_crystal/var/list/gas_percentage] + * [/obj/machinery/power/supermatter_crystal/var/gas_power_transmission] + * [/obj/machinery/power/supermatter_crystal/var/gas_heat_modifier] + * [/obj/machinery/power/supermatter_crystal/var/gas_heat_resistance] + * [/obj/machinery/power/supermatter_crystal/var/gas_heat_power_generation] + * [/obj/machinery/power/supermatter_crystal/var/gas_powerloss_inhibition] + * + * Returns: null + */ +/obj/machinery/power/supermatter_crystal/proc/calculate_gases() + gas_percentage = list() + gas_power_transmission = 0 + gas_heat_modifier = 0 + gas_heat_resistance = 0 + gas_heat_power_generation = 0 + gas_powerloss_inhibition = 0 + if(disable_gas) return - switch(type) - if(FLUX_ANOMALY) - var/explosive = has_changed_lifespan ? FLUX_NO_EXPLOSION : FLUX_LOW_EXPLOSIVE - new /obj/effect/anomaly/flux(local_turf, has_changed_lifespan ? rand(250, 350) : null, FALSE, explosive) - if(GRAVITATIONAL_ANOMALY) - new /obj/effect/anomaly/grav(local_turf, has_changed_lifespan ? rand(200, 300) : null, FALSE) - if(PYRO_ANOMALY) - new /obj/effect/anomaly/pyro(local_turf, has_changed_lifespan ? rand(150, 250) : null, FALSE) - if(HALLUCINATION_ANOMALY) - new /obj/effect/anomaly/hallucination(local_turf, has_changed_lifespan ? rand(150, 250) : null, FALSE) - if(VORTEX_ANOMALY) - new /obj/effect/anomaly/bhole(local_turf, 20, FALSE) - if(BIOSCRAMBLER_ANOMALY) - new /obj/effect/anomaly/bioscrambler(local_turf, null, FALSE) - if(DIMENSIONAL_ANOMALY) - new /obj/effect/anomaly/dimensional(local_turf, null, FALSE) + + var/total_moles = absorbed_gasmix.total_moles() + + for (var/gas_path in absorbed_gasmix.gases) + gas_percentage[gas_path] = absorbed_gasmix.gases[gas_path][MOLES] / total_moles + var/datum/sm_gas/sm_gas = GLOB.sm_gas_behavior[gas_path] + if(!sm_gas) + continue + gas_power_transmission += sm_gas.power_transmission * gas_percentage[gas_path] + gas_heat_modifier += sm_gas.heat_modifier * gas_percentage[gas_path] + gas_heat_resistance += sm_gas.heat_resistance * gas_percentage[gas_path] + gas_heat_power_generation += sm_gas.heat_power_generation * gas_percentage[gas_path] + gas_powerloss_inhibition += sm_gas.powerloss_inhibition * gas_percentage[gas_path] + + gas_heat_power_generation = clamp(gas_heat_power_generation, 0, 1) + gas_powerloss_inhibition = clamp(gas_powerloss_inhibition, 0, 1) + +/** + * Perform calculation for power lost and gained this tick. + * Description of each factors can be found in the defines. + * + * Updates: + * [/obj/machinery/power/supermatter_crystal/var/internal_energy] + * [/obj/machinery/power/supermatter_crystal/var/external_power_trickle] + * [/obj/machinery/power/supermatter_crystal/var/external_power_immediate] + * + * Returns: The factors that have influenced the calculation. list[FACTOR_DEFINE] = number + */ +/obj/machinery/power/supermatter_crystal/proc/calculate_internal_energy() + if(disable_power_change) + return + var/list/additive_power = list() + + /// If we have a small amount of external_power_trickle we just round it up to 40. + additive_power[SM_POWER_EXTERNAL_TRICKLE] = external_power_trickle ? max(external_power_trickle/MATTER_POWER_CONVERSION, 40) : 0 + external_power_trickle -= min(additive_power[SM_POWER_EXTERNAL_TRICKLE], external_power_trickle) + additive_power[SM_POWER_EXTERNAL_IMMEDIATE] = external_power_immediate + external_power_immediate = 0 + additive_power[SM_POWER_HEAT] = gas_heat_power_generation * absorbed_gasmix.temperature / 6 + + // I'm sorry for this, but we need to calculate power lost immediately after power gain. + // Helps us prevent cases when someone dumps superhothotgas into the SM and shoots the power to the moon for one tick. + /// Power if we dont have decay. Used for powerloss calc. + var/momentary_power = internal_energy + for(var/powergain_type in additive_power) + momentary_power += additive_power[powergain_type] + if(internal_energy < powerloss_linear_threshold) // Negative numbers + additive_power[SM_POWER_POWERLOSS] = -1 * (momentary_power / POWERLOSS_CUBIC_DIVISOR) ** 3 + else + additive_power[SM_POWER_POWERLOSS] = -1 * (momentary_power * POWERLOSS_LINEAR_RATE + powerloss_linear_offset) + // Positive number + additive_power[SM_POWER_POWERLOSS_GAS] = -1 * gas_powerloss_inhibition * additive_power[SM_POWER_POWERLOSS] + additive_power[SM_POWER_POWERLOSS_SOOTHED] = -1 * min(1-gas_powerloss_inhibition , 0.2 * psy_coeff) * additive_power[SM_POWER_POWERLOSS] + + for(var/powergain_types in additive_power) + internal_energy += additive_power[powergain_types] + internal_energy = max(internal_energy, 0) + return additive_power + +/** + * Perform calculation for the main zap power multiplier. + * Description of each factors can be found in the defines. + * + * Updates: + * [/obj/machinery/power/supermatter_crystal/var/zap_multiplier] + * + * Returns: The factors that have influenced the calculation. list[FACTOR_DEFINE] = number + */ +/obj/machinery/power/supermatter_crystal/proc/calculate_zap_multiplier() + var/list/additive_transmission = list() + additive_transmission[SM_ZAP_BASE] = 1 + additive_transmission[SM_ZAP_GAS] = gas_power_transmission + + zap_multiplier = 0 + for (var/transmission_types in additive_transmission) + zap_multiplier += additive_transmission[transmission_types] + zap_multiplier = max(zap_multiplier, 0) + return additive_transmission + +/** + * Perform calculation for the waste multiplier. + * This number affects the temperature, plasma, and oxygen of the waste gas. + * Multiplier is applied to energy for plasma and temperature but temperature for oxygen. + * + * Description of each factors can be found in the defines. + * + * Updates: + * [/obj/machinery/power/supermatter_crystal/var/waste_multiplier] + * + * Returns: The factors that have influenced the calculation. list[FACTOR_DEFINE] = number + */ +/obj/machinery/power/supermatter_crystal/proc/calculate_waste_multiplier() + waste_multiplier = 0 + if(disable_gas) + return + /// Tell people the heat output in energy. More informative than telling them the heat multiplier. + var/additive_waste_multiplier = list() + additive_waste_multiplier[SM_WASTE_BASE] = 1 + additive_waste_multiplier[SM_WASTE_GAS] = gas_heat_modifier + additive_waste_multiplier[SM_WASTE_SOOTHED] = -0.2 * psy_coeff + + for (var/waste_type in additive_waste_multiplier) + waste_multiplier += additive_waste_multiplier[waste_type] + waste_multiplier = clamp(waste_multiplier, 0.5, INFINITY) + return additive_waste_multiplier + +/** + * Calculate at which temperature the sm starts taking damage. + * heat limit is given by: (T0C+40) * (1 + gas heat res + psy_coeff) + * + * Description of each factors can be found in the defines. + * + * Updates: + * [/obj/machinery/power/supermatter_crystal/var/temp_limit] + * + * Returns: The factors that have influenced the calculation. list[FACTOR_DEFINE] = number + */ +/obj/machinery/power/supermatter_crystal/proc/calculate_temp_limit() + var/list/additive_temp_limit = list() + additive_temp_limit[SM_TEMP_LIMIT_BASE] = T0C + HEAT_PENALTY_THRESHOLD + additive_temp_limit[SM_TEMP_LIMIT_GAS] = gas_heat_resistance * (T0C + HEAT_PENALTY_THRESHOLD) + additive_temp_limit[SM_TEMP_LIMIT_SOOTHED] = psy_coeff * 45 + additive_temp_limit[SM_TEMP_LIMIT_LOW_MOLES] = clamp(2 - absorbed_gasmix.total_moles() / 100, 0, 1) * (T0C + HEAT_PENALTY_THRESHOLD) + + temp_limit = 0 + for (var/resistance_type in additive_temp_limit) + temp_limit += additive_temp_limit[resistance_type] + temp_limit = max(temp_limit, TCMB) + + return additive_temp_limit + +/** + * Perform calculation for the damage taken or healed. + * Description of each factors can be found in the defines. + * + * Updates: + * [/obj/machinery/power/supermatter_crystal/var/damage] + * + * Returns: The factors that have influenced the calculation. list[FACTOR_DEFINE] = number + */ +/obj/machinery/power/supermatter_crystal/proc/calculate_damage() + if(disable_damage) + return + /// Multiplier to our mole and power damage limits. + var/list/additive_damage = list() + var/mole_power_damage_limit = delamination_strategy.mole_power_damage_limit() + var/total_moles = absorbed_gasmix.total_moles() + additive_damage[SM_DAMAGE_HEAT] = max((absorbed_gasmix.temperature - temp_limit) * max(total_moles / 350, 0.25) / 600, 0) + additive_damage[SM_DAMAGE_POWER] = max((internal_energy - POWER_PENALTY_THRESHOLD * mole_power_damage_limit) / 2000, 0) + additive_damage[SM_DAMAGE_MOLES] = max((total_moles - MOLE_PENALTY_THRESHOLD * mole_power_damage_limit) / 320, 0) + if(total_moles) + additive_damage[SM_DAMAGE_HEAL_HEAT] = clamp((absorbed_gasmix.temperature - temp_limit) / 300, -2, 0) + + /// Damage from internal factors. + var/total_internal_damage = 0 + for (var/damage_type in additive_damage) + total_internal_damage += additive_damage[damage_type] + if(total_internal_damage > DAMAGE_HARDCAP) + // Scale each of the damages according to the hardcap. + // This means we lie a bit about each of the factors, but the final addition is honest. + for (var/damage_type in additive_damage) + // shorter way of writing damage = damage / total_internal_damage * damage_hardcap. + additive_damage[damage_type] *= DAMAGE_HARDCAP / total_internal_damage + total_internal_damage = DAMAGE_HARDCAP + + // We dont let external factors deal more damage than the emergency point. + // Evaluates the current damage and not the damage + internal damage though. + // Helps us decouple this and make it discrete. + additive_damage[SM_DAMAGE_EXTERNAL] = external_damage_immediate * clamp((emergency_point - damage) / emergency_point, 0, 1) + external_damage_immediate = 0 + + for(var/turf/open/space/turf_to_check in RANGE_TURFS(1, loc)) + if(LAZYLEN(turf_to_check.atmos_adjacent_turfs)) + additive_damage[SM_DAMAGE_SPACED] = clamp(internal_energy * 0.00125, 0, 10) + additive_damage[SM_DAMAGE_HEAL_HEAT] = 0 + break + + var/total_damage = 0 + for (var/damage_type in additive_damage) + total_damage += additive_damage[damage_type] + + damage_archived = damage + damage += total_damage + damage = max(damage, 0) + return additive_damage + +/** + * Sets the delam of our sm. + * + * Arguments: + * * priority: Truthy values means a forced delam. If current forced_delam is higher than priority we dont run. + * Set to a number higher than [SM_DELAM_PRIO_IN_GAME] to fully force an admin delam. + * * delam_path: Typepath of a [/datum/sm_delam]. [SM_DELAM_STRATEGY_PURGE] means reset and put prio back to zero. + * + * Returns: Not used for anything, just returns true on succesful set, manual and automatic. Helps admins check stuffs. + */ +/obj/machinery/power/supermatter_crystal/proc/set_delam(priority = SM_DELAM_PRIO_NONE, manual_delam_path = SM_DELAM_STRATEGY_PURGE) + if(priority < delam_priority) + return FALSE + var/datum/sm_delam/new_delam = null + + if(manual_delam_path == SM_DELAM_STRATEGY_PURGE) + for (var/delam_path in GLOB.sm_delam_list) + var/datum/sm_delam/delam = GLOB.sm_delam_list[delam_path] + if(!delam.can_select(src)) + continue + if(delam == delamination_strategy) + return FALSE + new_delam = delam + break + delam_priority = SM_DELAM_PRIO_NONE + else + new_delam = GLOB.sm_delam_list[manual_delam_path] + delam_priority = priority + + if(!new_delam) + return FALSE + delamination_strategy?.on_deselect(src) + delamination_strategy = new_delam + delamination_strategy.on_select(src) + return TRUE /obj/machinery/proc/supermatter_zap(atom/zapstart = src, range = 5, zap_str = 4000, zap_flags = ZAP_SUPERMATTER_FLAGS, list/targets_hit = list(), zap_cutoff = 1500, power_level = 0, zap_icon = DEFAULT_ZAP_ICON_STATE, color = null) if(QDELETED(zapstart)) @@ -564,60 +886,6 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) child_targets_hit = targets_hit.Copy() //Pass by ref begone supermatter_zap(target, new_range, zap_str, zap_flags, child_targets_hit, zap_cutoff, power_level, zap_icon, color) -/obj/machinery/power/supermatter_crystal/engine - is_main_engine = TRUE - -/obj/machinery/power/supermatter_crystal/shard - name = "supermatter shard" - desc = "A strangely translucent and iridescent crystal that looks like it used to be part of a larger structure." - base_icon_state = "darkmatter_shard" - icon_state = "darkmatter_shard" - anchored = FALSE - absorption_ratio = 0.125 - explosion_power = 12 - layer = ABOVE_MOB_LAYER - plane = GAME_PLANE_UPPER - moveable = TRUE - psyOverlay = /obj/overlay/psy/shard - -/obj/machinery/power/supermatter_crystal/shard/engine - name = "anchored supermatter shard" - is_main_engine = TRUE - anchored = TRUE - moveable = FALSE - -// When you wanna make a supermatter shard for the dramatic effect, but -// don't want it exploding suddenly -/obj/machinery/power/supermatter_crystal/shard/hugbox - name = "anchored supermatter shard" - takes_damage = FALSE - produces_gas = FALSE - power_changes = FALSE - processes = FALSE //SHUT IT DOWN - moveable = FALSE - anchored = TRUE - -/obj/machinery/power/supermatter_crystal/shard/hugbox/fakecrystal //Hugbox shard with crystal visuals, used in the Supermatter/Hyperfractal shuttle - name = "supermatter crystal" - base_icon_state = "darkmatter" - icon_state = "darkmatter" - -/obj/overlay/psy - icon = 'icons/obj/engine/supermatter.dmi' - icon_state = "psy" - layer = FLOAT_LAYER - 1 - -/obj/overlay/psy/shard - icon_state = "psy_shard" - -/atom/movable/supermatter_warp_effect - plane = GRAVITY_PULSE_PLANE - appearance_flags = PIXEL_SCALE // no tile bound so you can see it around corners and so - icon = 'icons/effects/light_overlays/light_352.dmi' - icon_state = "light" - pixel_x = -176 - pixel_y = -176 - #undef BIKE #undef COIL #undef ROD diff --git a/code/modules/power/supermatter/supermatter_delamination/_sm_delam.dm b/code/modules/power/supermatter/supermatter_delamination/_sm_delam.dm index f61672dbd92..0c6e0d73b60 100644 --- a/code/modules/power/supermatter/supermatter_delamination/_sm_delam.dm +++ b/code/modules/power/supermatter/supermatter_delamination/_sm_delam.dm @@ -67,8 +67,6 @@ GLOBAL_LIST_INIT(sm_delam_list, list( sm.lastwarning = REALTIMEOFDAY - (SUPERMATTER_WARNING_DELAY / 2) // Cut the time to next announcement in half. else // Taking damage, in warning sm.radio.talk_into(sm, "Danger! Crystal hyperstructure integrity faltering! Integrity: [sm.get_integrity_percent()]%", sm.warning_channel) - if(sm.damage_archived < sm.warning_point) - SEND_SIGNAL(sm, COMSIG_SUPERMATTER_DELAM_START_ALARM) SEND_SIGNAL(sm, COMSIG_SUPERMATTER_DELAM_ALARM) return TRUE @@ -95,11 +93,6 @@ GLOBAL_LIST_INIT(sm_delam_list, list( return list(mutable_appearance(sm.icon, "causality_field")) return list() -/// Modifies the damage dealt to the sm. -/// [/obj/machinery/power/supermatter_crystal/proc/deal_damage] -/datum/sm_delam/proc/damage_multiplier(obj/machinery/power/supermatter_crystal/sm) - return 1 - /// Returns a set of messages to be spouted during delams /// First message is start of count down, second message is quitting of count down (if sm healed), third is 5 second intervals /datum/sm_delam/proc/count_down_messages(obj/machinery/power/supermatter_crystal/sm) @@ -108,3 +101,7 @@ GLOBAL_LIST_INIT(sm_delam_list, list( messages += "Crystalline hyperstructure returning to safe operating parameters. Failsafe has been disengaged." messages += "remain before causality stabilization." return messages + +/// Adjusts the damage and mole limit at which we start taking damage. +/datum/sm_delam/proc/mole_power_damage_limit(obj/machinery/power/supermatter_crystal/sm) + return 1 diff --git a/code/modules/power/supermatter/supermatter_delamination/cascade_delam.dm b/code/modules/power/supermatter/supermatter_delamination/cascade_delam.dm index 68a827ddc7c..1a096a25707 100644 --- a/code/modules/power/supermatter/supermatter_delamination/cascade_delam.dm +++ b/code/modules/power/supermatter/supermatter_delamination/cascade_delam.dm @@ -7,7 +7,7 @@ if(total_moles < MOLE_PENALTY_THRESHOLD * sm.absorption_ratio) return FALSE for (var/gas_path in list(/datum/gas/antinoblium, /datum/gas/hypernoblium)) - var/percent = sm.absorbed_gasmix.gases[gas_path]?[MOLES] / total_moles + var/percent = sm.gas_percentage[gas_path] if(!percent || percent < 0.4) return FALSE return TRUE @@ -74,9 +74,6 @@ /datum/sm_delam/cascade/overlays(obj/machinery/power/supermatter_crystal/sm) return list() -/datum/sm_delam/cascade/damage_multiplier(obj/machinery/power/supermatter_crystal/sm) - return 0.25 - /datum/sm_delam/cascade/count_down_messages(obj/machinery/power/supermatter_crystal/sm) var/list/messages = list() messages += "CRYSTAL DELAMINATION IMMINENT. The supermatter has reached critical integrity failure. Harmonic frequency limits exceeded. Causality destabilization field could not be engaged." @@ -84,6 +81,9 @@ messages += "remain before resonance-induced stabilization." return messages +/datum/sm_delam/cascade/mole_power_damage_limit(obj/machinery/power/supermatter_crystal/sm) + return 0.25 + /datum/sm_delam/cascade/proc/announce_cascade(obj/machinery/power/supermatter_crystal/sm) if(QDELETED(sm)) return FALSE diff --git a/code/modules/power/supermatter/supermatter_delamination/common_delams.dm b/code/modules/power/supermatter/supermatter_delamination/common_delams.dm index c46aaf998fc..eeed35d6eb5 100644 --- a/code/modules/power/supermatter/supermatter_delamination/common_delams.dm +++ b/code/modules/power/supermatter/supermatter_delamination/common_delams.dm @@ -32,7 +32,7 @@ /datum/sm_delam/tesla /datum/sm_delam/tesla/can_select(obj/machinery/power/supermatter_crystal/sm) - return (sm.power > POWER_PENALTY_THRESHOLD) + return (sm.internal_energy > POWER_PENALTY_THRESHOLD) /datum/sm_delam/tesla/delam_progress(obj/machinery/power/supermatter_crystal/sm) . = ..() diff --git a/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm b/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm index 07b5142b93b..39b49657ef9 100644 --- a/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm +++ b/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm @@ -7,7 +7,7 @@ /// Just the mobs apparently. /datum/sm_delam/proc/effect_irradiate(obj/machinery/power/supermatter_crystal/sm) var/turf/sm_turf = get_turf(sm) - for (var/mob/living/victim in range(20, sm)) + for (var/mob/living/victim in range(DETONATION_RADIATION_RANGE, sm)) if(!is_valid_z_level(get_turf(victim), sm_turf)) continue if(victim.z == 0) @@ -70,7 +70,7 @@ /// Explodes /datum/sm_delam/proc/effect_explosion(obj/machinery/power/supermatter_crystal/sm) var/explosion_power = sm.explosion_power - var/power_scaling = sm.gasmix_power_ratio + var/power_scaling = sm.gas_heat_power_generation var/turf/sm_turf = get_turf(sm) //Dear mappers, balance the sm max explosion radius to 17.5, 37, 39, 41 explosion(origin = sm_turf, diff --git a/code/modules/power/supermatter/supermatter_extra_effects.dm b/code/modules/power/supermatter/supermatter_extra_effects.dm new file mode 100644 index 00000000000..62a66f11b8c --- /dev/null +++ b/code/modules/power/supermatter/supermatter_extra_effects.dm @@ -0,0 +1,183 @@ +// Any power past this number will be clamped down +#define MAX_ACCEPTED_POWER_OUTPUT 5000 + +// At the highest power output, assuming no integrity changes, the threshold will be 0. +#define THRESHOLD_EQUATION_SLOPE (-1 / MAX_ACCEPTED_POWER_OUTPUT) + +// The higher this number, the faster low integrity will drop threshold +// I would've named this "power", but y'know. :P +#define INTEGRITY_EXPONENTIAL_DEGREE 2 + +// At INTEGRITY_MIN_NUDGABLE_AMOUNT, the power will be treated as, at most, INTEGRITY_MAX_POWER_NUDGE. +// Any lower integrity will result in INTEGRITY_MAX_POWER_NUDGE. +#define INTEGRITY_MAX_POWER_NUDGE 1500 +#define INTEGRITY_MIN_NUDGABLE_AMOUNT 0.7 + +#define RADIATION_CHANCE_AT_FULL_INTEGRITY 0.03 +#define RADIATION_CHANCE_AT_ZERO_INTEGRITY 0.4 +#define CHANCE_EQUATION_SLOPE (RADIATION_CHANCE_AT_ZERO_INTEGRITY - RADIATION_CHANCE_AT_FULL_INTEGRITY) + +/obj/machinery/power/supermatter_crystal/proc/emit_radiation() + // As power goes up, rads go up. + // A standard N2 SM seems to produce a value of around 1,500. + var/power_factor = min(internal_energy, MAX_ACCEPTED_POWER_OUTPUT) + + var/integrity = 1 - CLAMP01(damage / explosion_point) + + // When integrity goes down, the threshold (from an observable point of view, rads) go up. + // However, the power factor must go up as well, otherwise turning off the emitters + // on a delaminating SM would stop radiation from escaping. + // To fix this, lower integrities raise the power factor to a minimum. + var/integrity_power_nudge = LERP(INTEGRITY_MAX_POWER_NUDGE, 0, CLAMP01((integrity - INTEGRITY_MIN_NUDGABLE_AMOUNT) / (1 - INTEGRITY_MIN_NUDGABLE_AMOUNT))) + + power_factor = max(power_factor, integrity_power_nudge) + + // At the "normal" N2 power output (with max integrity), this is 0.7, which is enough to be stopped + // by the walls or the radation shutters. + // As integrity does down, rads go up. + var/threshold + switch(integrity) + if(0) + threshold = power_factor ? 0 : 1 + if(1) + threshold = (THRESHOLD_EQUATION_SLOPE * power_factor + 1) + else + threshold = (THRESHOLD_EQUATION_SLOPE * power_factor + 1) ** ((1 / integrity) ** INTEGRITY_EXPONENTIAL_DEGREE) + + // Calculating chance is done entirely on integrity, so that actively delaminating SMs feel more dangerous + var/chance = (CHANCE_EQUATION_SLOPE * (1 - integrity)) + RADIATION_CHANCE_AT_FULL_INTEGRITY + + radiation_pulse( + src, + max_range = 8, + threshold = threshold, + chance = chance * 100, + ) + +/obj/machinery/power/supermatter_crystal/proc/processing_sound() + if(internal_energy) + soundloop.volume = clamp((50 + (internal_energy / 50)), 50, 100) + if(damage >= 300) + soundloop.mid_sounds = list('sound/machines/sm/loops/delamming.ogg' = 1) + else + soundloop.mid_sounds = list('sound/machines/sm/loops/calm.ogg' = 1) + + //We play delam/neutral sounds at a rate determined by power and damage + if(last_accent_sound >= world.time || !prob(20)) + return + var/aggression = min(((damage / 800) * (internal_energy / 2500)), 1.0) * 100 + if(damage >= 300) + playsound(src, SFX_SM_DELAM, max(50, aggression), FALSE, 40, 30, falloff_distance = 10) + else + playsound(src, SFX_SM_CALM, max(50, aggression), FALSE, 25, 25, falloff_distance = 10) + var/next_sound = round((100 - aggression) * 5) + last_accent_sound = world.time + max(SUPERMATTER_ACCENT_SOUND_MIN_COOLDOWN, next_sound) + +/obj/machinery/power/supermatter_crystal/proc/psychological_examination() + // Defaults to a value less than 1. Over time the psy_coeff goes to 0 if + // no supermatter soothers are nearby. + var/psy_coeff_diff = -0.05 + for(var/mob/living/carbon/human/seen_by_sm in view(src, SM_HALLUCINATION_RANGE(internal_energy))) + // Someone (generally a Psychologist), when looking at the SM within hallucination range makes it easier to manage. + if(HAS_TRAIT(seen_by_sm, TRAIT_SUPERMATTER_SOOTHER) || (seen_by_sm.mind && HAS_TRAIT(seen_by_sm.mind, TRAIT_SUPERMATTER_SOOTHER))) + psy_coeff_diff = 0.05 + visible_hallucination_pulse( + center = src, + radius = SM_HALLUCINATION_RANGE(internal_energy), + hallucination_duration = internal_energy * hallucination_power, + hallucination_max_duration = 400 SECONDS, + ) + psy_coeff = clamp(psy_coeff + psy_coeff_diff, 0, 1) + +/obj/machinery/power/supermatter_crystal/proc/handle_high_power() + if(internal_energy <= POWER_PENALTY_THRESHOLD && damage <= danger_point) //If the power is above 5000 or if the damage is above 550 + return + var/range = 4 + zap_cutoff = 1500 + var/total_moles = absorbed_gasmix.total_moles() + var/pressure = absorbed_gasmix.return_pressure() + var/temp = absorbed_gasmix.temperature + if(pressure > 0 && temp > 0) + //You may be able to freeze the zapstate of the engine with good planning, we'll see + zap_cutoff = clamp(3000 - (internal_energy * total_moles / 10) / temp, 350, 3000)//If the core is cold, it's easier to jump, ditto if there are a lot of mols + //We should always be able to zap our way out of the default enclosure + //See supermatter_zap() for more details + range = clamp(internal_energy / pressure * 10, 2, 7) + var/flags = ZAP_SUPERMATTER_FLAGS + var/zap_count = 0 + //Deal with power zaps + switch(internal_energy) + if(POWER_PENALTY_THRESHOLD to SEVERE_POWER_PENALTY_THRESHOLD) + zap_icon = DEFAULT_ZAP_ICON_STATE + zap_count = 2 + if(SEVERE_POWER_PENALTY_THRESHOLD to CRITICAL_POWER_PENALTY_THRESHOLD) + zap_icon = SLIGHTLY_CHARGED_ZAP_ICON_STATE + //Uncaps the zap damage, it's maxed by the input power + //Objects take damage now + flags |= (ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE) + zap_count = 3 + if(CRITICAL_POWER_PENALTY_THRESHOLD to INFINITY) + zap_icon = OVER_9000_ZAP_ICON_STATE + //It'll stun more now, and damage will hit harder, gloves are no garentee. + //Machines go boom + flags |= (ZAP_MOB_STUN | ZAP_MACHINE_EXPLOSIVE | ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE) + zap_count = 4 + //Now we deal with damage shit + if (damage > danger_point && prob(20)) + zap_count += 1 + + if(zap_count >= 1) + playsound(loc, 'sound/weapons/emitter2.ogg', 100, TRUE, extrarange = 10) + for(var/i in 1 to zap_count) + supermatter_zap(src, range, clamp(internal_energy*2, 4000, 20000), flags, zap_cutoff = src.zap_cutoff, power_level = internal_energy, zap_icon = src.zap_icon) + + if(prob(5)) + supermatter_anomaly_gen(src, FLUX_ANOMALY, rand(5, 10)) + if(prob(5)) + supermatter_anomaly_gen(src, HALLUCINATION_ANOMALY, rand(5, 10)) + if(internal_energy > SEVERE_POWER_PENALTY_THRESHOLD && prob(5) || prob(1)) + supermatter_anomaly_gen(src, GRAVITATIONAL_ANOMALY, rand(5, 10)) + if((internal_energy > SEVERE_POWER_PENALTY_THRESHOLD && prob(2)) || (prob(0.3) && internal_energy > POWER_PENALTY_THRESHOLD)) + supermatter_anomaly_gen(src, PYRO_ANOMALY, rand(5, 10)) + +/obj/machinery/power/supermatter_crystal/proc/supermatter_pull(turf/center, pull_range = 3) + playsound(center, 'sound/weapons/marauder.ogg', 100, TRUE, extrarange = pull_range - world.view) + for(var/atom/movable/movable_atom in orange(pull_range,center)) + if((movable_atom.anchored || movable_atom.move_resist >= MOVE_FORCE_EXTREMELY_STRONG)) //move resist memes. + if(istype(movable_atom, /obj/structure/closet)) + var/obj/structure/closet/closet = movable_atom + closet.open(force = TRUE) + continue + if(ismob(movable_atom)) + var/mob/pulled_mob = movable_atom + if(pulled_mob.mob_negates_gravity()) + continue //You can't pull someone nailed to the deck + step_towards(movable_atom,center) + +/proc/supermatter_anomaly_gen(turf/anomalycenter, type = FLUX_ANOMALY, anomalyrange = 5, has_changed_lifespan = TRUE) + var/turf/local_turf = pick(orange(anomalyrange, anomalycenter)) + if(!local_turf) + return + switch(type) + if(FLUX_ANOMALY) + var/explosive = has_changed_lifespan ? FLUX_NO_EXPLOSION : FLUX_LOW_EXPLOSIVE + new /obj/effect/anomaly/flux(local_turf, has_changed_lifespan ? rand(250, 350) : null, FALSE, explosive) + if(GRAVITATIONAL_ANOMALY) + new /obj/effect/anomaly/grav(local_turf, has_changed_lifespan ? rand(200, 300) : null, FALSE) + if(PYRO_ANOMALY) + new /obj/effect/anomaly/pyro(local_turf, has_changed_lifespan ? rand(150, 250) : null, FALSE) + if(HALLUCINATION_ANOMALY) + new /obj/effect/anomaly/hallucination(local_turf, has_changed_lifespan ? rand(150, 250) : null, FALSE) + if(VORTEX_ANOMALY) + new /obj/effect/anomaly/bhole(local_turf, 20, FALSE) + if(BIOSCRAMBLER_ANOMALY) + new /obj/effect/anomaly/bioscrambler(local_turf, null, FALSE) + +#undef CHANCE_EQUATION_SLOPE +#undef INTEGRITY_EXPONENTIAL_DEGREE +#undef INTEGRITY_MAX_POWER_NUDGE +#undef INTEGRITY_MIN_NUDGABLE_AMOUNT +#undef MAX_ACCEPTED_POWER_OUTPUT +#undef RADIATION_CHANCE_AT_FULL_INTEGRITY +#undef RADIATION_CHANCE_AT_ZERO_INTEGRITY +#undef THRESHOLD_EQUATION_SLOPE diff --git a/code/modules/power/supermatter/supermatter_gas.dm b/code/modules/power/supermatter/supermatter_gas.dm index 23db8587e48..738715969bb 100644 --- a/code/modules/power/supermatter/supermatter_gas.dm +++ b/code/modules/power/supermatter/supermatter_gas.dm @@ -5,187 +5,225 @@ gas_list[sm_gas.gas_path] = sm_gas return gas_list +/// Return a list info of the SM gases. +/// Can only run after init_sm_gas +/proc/sm_gas_data() + var/list/data = list() + for (var/gas_path in GLOB.sm_gas_behavior) + var/datum/sm_gas/sm_gas = GLOB.sm_gas_behavior[gas_path] + var/list/singular_gas_data = list() + singular_gas_data["desc"] = sm_gas.desc + + // Positive is true if more of the amount is a good thing. + var/list/numeric_data = list() + if(sm_gas.power_transmission) + numeric_data += list(list( + "name" = "Power Transmission", + "amount" = sm_gas.power_transmission, + "positive" = TRUE, + )) + if(sm_gas.heat_modifier) + numeric_data += list(list( + "name" = "Waste Multiplier", + "amount" = sm_gas.heat_modifier, + "positive" = FALSE, + )) + if(sm_gas.heat_resistance) + numeric_data += list(list( + "name" = "Heat Resistance", + "amount" = sm_gas.heat_resistance, + "positive" = TRUE, + )) + if(sm_gas.heat_power_generation) + numeric_data += list(list( + "name" = "Heat Power Gain", + "amount" = sm_gas.heat_power_generation, + "positive" = TRUE, + )) + if(sm_gas.powerloss_inhibition) + numeric_data += list(list( + "name" = "Power Decay Negation", + "amount" = sm_gas.powerloss_inhibition, + "positive" = TRUE, + )) + singular_gas_data["numeric_data"] = numeric_data + data[gas_path] = singular_gas_data + return data + /// Assoc of sm_gas_behavior[/datum/gas (path)] = datum/sm_gas (instance) GLOBAL_LIST_INIT(sm_gas_behavior, init_sm_gas()) /// Contains effects of gases when absorbed by the sm. +/// If the gas has no effects you do not need to add another sm_gas subtype, +/// We already guard for nulls in [/obj/machinery/power/supermatter_crystal/proc/calculate_gases] /datum/sm_gas /// Path of the [/datum/gas] involved with this interaction. var/gas_path /// Influences zap power without interfering with the crystal's own energy. - var/transmit_modifier = 0 + var/power_transmission = 0 /// How much more waste heat and gas the SM generates. - var/heat_penalty = 0 + var/heat_modifier = 0 /// How extra hot the SM can run before taking damage var/heat_resistance = 0 /// Lets the sm generate extra power from heat. Yeah... - var/powermix = 0 + var/heat_power_generation = 0 /// How much powerloss do we get rid of. var/powerloss_inhibition = 0 + /// Give a short description of the gas if needed. If the gas have extra effects describe it here. + var/desc -/datum/sm_gas/proc/extra_effects(obj/machinery/power/supermatter_crystal/sm, datum/gas_mixture/env) +/datum/sm_gas/proc/extra_effects(obj/machinery/power/supermatter_crystal/sm) return /datum/sm_gas/oxygen gas_path = /datum/gas/oxygen - heat_penalty = 1 - transmit_modifier = 1.5 - powermix = 1 + power_transmission = 0.15 + heat_power_generation = 1 /datum/sm_gas/nitrogen gas_path = /datum/gas/nitrogen - heat_penalty = -1.5 - powermix = -1 + heat_modifier = -2.5 + heat_power_generation = -1 /datum/sm_gas/carbon_dioxide gas_path = /datum/gas/carbon_dioxide - heat_penalty = 2 - powermix = 1 + heat_modifier = 1 + heat_power_generation = 1 powerloss_inhibition = 1 + desc = "When absorbed by the Supermatter and exposed to oxygen, Pluoxium will be generated." /// Can be on Oxygen or CO2, but better lump it here since CO2 is rarer. -/datum/sm_gas/carbon_dioxide/extra_effects(obj/machinery/power/supermatter_crystal/sm, datum/gas_mixture/env) - if(!(sm.gas_percentage[/datum/gas/carbon_dioxide] && sm.gas_percentage[/datum/gas/oxygen])) +/datum/sm_gas/carbon_dioxide/extra_effects(obj/machinery/power/supermatter_crystal/sm) + if(!sm.gas_percentage[/datum/gas/carbon_dioxide] || !sm.gas_percentage[/datum/gas/oxygen]) return - var/co2_pp = env.return_pressure() * sm.gas_percentage[/datum/gas/carbon_dioxide] - /// Our consumption ratio, not the actual ratio in SM we already have that. - /// This var is a fucking lie, we only consume half of it. - var/co2_ratio = (co2_pp - CO2_CONSUMPTION_PP) / (co2_pp + CO2_PRESSURE_SCALING) - co2_ratio = clamp(co2_ratio, 0, 1) + var/co2_pp = sm.absorbed_gasmix.return_pressure() * sm.gas_percentage[/datum/gas/carbon_dioxide] + var/co2_ratio = clamp((1/2 * (co2_pp - CO2_CONSUMPTION_PP) / (co2_pp + CO2_PRESSURE_SCALING)), 0, 1) var/consumed_co2 = sm.absorbed_gasmix.gases[/datum/gas/carbon_dioxide][MOLES] * co2_ratio consumed_co2 = min( consumed_co2, - sm.absorbed_gasmix.gases[/datum/gas/carbon_dioxide][MOLES] * INVERSE(0.5), - sm.absorbed_gasmix.gases[/datum/gas/oxygen][MOLES] * INVERSE(0.5) + sm.absorbed_gasmix.gases[/datum/gas/carbon_dioxide][MOLES], + sm.absorbed_gasmix.gases[/datum/gas/oxygen][MOLES] ) if(!consumed_co2) return - sm.absorbed_gasmix.gases[/datum/gas/carbon_dioxide][MOLES] -= consumed_co2 * 0.5 - sm.absorbed_gasmix.gases[/datum/gas/oxygen][MOLES] -= consumed_co2 * 0.5 + sm.absorbed_gasmix.gases[/datum/gas/carbon_dioxide][MOLES] -= consumed_co2 + sm.absorbed_gasmix.gases[/datum/gas/oxygen][MOLES] -= consumed_co2 ASSERT_GAS(/datum/gas/pluoxium, sm.absorbed_gasmix) - sm.absorbed_gasmix.gases[/datum/gas/pluoxium][MOLES] += consumed_co2 * 0.25 + sm.absorbed_gasmix.gases[/datum/gas/pluoxium][MOLES] += consumed_co2 /datum/sm_gas/plasma gas_path = /datum/gas/plasma - heat_penalty = 15 - transmit_modifier = 4 - powermix = 1 + heat_modifier = 14 + power_transmission = 0.4 + heat_power_generation = 1 /datum/sm_gas/water_vapor gas_path = /datum/gas/water_vapor - heat_penalty = 12 - transmit_modifier = -2.5 - powermix = 1 + heat_modifier = 11 + power_transmission = -0.25 + heat_power_generation = 1 /datum/sm_gas/hypernoblium gas_path = /datum/gas/hypernoblium - heat_penalty = -13 - transmit_modifier = 3 - powermix = -1 + heat_modifier = -14 + power_transmission = 0.3 + heat_power_generation = -1 /datum/sm_gas/nitrous_oxide gas_path = /datum/gas/nitrous_oxide - heat_resistance = 6 - -/datum/sm_gas/nitrium - gas_path = /datum/gas/nitrium + heat_resistance = 5 /datum/sm_gas/tritium gas_path = /datum/gas/tritium - heat_penalty = 10 - transmit_modifier = 30 - powermix = 1 + heat_modifier = 9 + power_transmission = 3 + heat_power_generation = 1 /datum/sm_gas/bz gas_path = /datum/gas/bz - heat_penalty = 5 - transmit_modifier = -2 - powermix = 1 + heat_modifier = 4 + power_transmission = -0.2 + heat_power_generation = 1 + desc = "Will emit nuclear particles at compositions above 40%" /// Start to emit radballs at a maximum of 30% chance per tick -/datum/sm_gas/bz/extra_effects(obj/machinery/power/supermatter_crystal/sm, datum/gas_mixture/env) - if(sm.gas_percentage[/datum/gas/bz] >= 0.4 && prob(30 * sm.gas_percentage[/datum/gas/bz])) +/datum/sm_gas/bz/extra_effects(obj/machinery/power/supermatter_crystal/sm) + if(sm.gas_percentage[/datum/gas/bz] > 0.4 && prob(30 * sm.gas_percentage[/datum/gas/bz])) sm.fire_nuclear_particle() /datum/sm_gas/pluoxium gas_path = /datum/gas/pluoxium - heat_penalty = -0.5 - transmit_modifier = -5 - powermix = 1 + heat_modifier = -1.5 + power_transmission = -0.5 + heat_power_generation = 1 /datum/sm_gas/miasma gas_path = /datum/gas/miasma - powermix = 0.5 + heat_power_generation = 0.5 + desc = "Will be consumed by the Supermatter to generate power." ///Miasma is really just microscopic particulate. It gets consumed like anything else that touches the crystal. -/datum/sm_gas/miasma/extra_effects(obj/machinery/power/supermatter_crystal/sm, datum/gas_mixture/env) +/datum/sm_gas/miasma/extra_effects(obj/machinery/power/supermatter_crystal/sm) if(!sm.gas_percentage[/datum/gas/miasma]) return - var/miasma_pp = env.return_pressure() * sm.gas_percentage[/datum/gas/miasma] - /// Our consumption ratio, not the actual ratio in SM we already have that. - var/miasma_ratio = ((miasma_pp - MIASMA_CONSUMPTION_PP) / (miasma_pp + MIASMA_PRESSURE_SCALING)) * (1 + (sm.gasmix_power_ratio * MIASMA_GASMIX_SCALING)) - miasma_ratio = clamp(miasma_ratio, 0, 1) + var/miasma_pp = sm.absorbed_gasmix.return_pressure() * sm.gas_percentage[/datum/gas/miasma] + var/miasma_ratio = clamp(((miasma_pp - MIASMA_CONSUMPTION_PP) / (miasma_pp + MIASMA_PRESSURE_SCALING)) * (1 + (sm.gas_heat_power_generation * MIASMA_GASMIX_SCALING)), 0, 1) var/consumed_miasma = sm.absorbed_gasmix.gases[/datum/gas/miasma][MOLES] * miasma_ratio if(!consumed_miasma) return sm.absorbed_gasmix.gases[/datum/gas/miasma][MOLES] -= consumed_miasma - sm.matter_power += consumed_miasma * MIASMA_POWER_GAIN + sm.external_power_trickle += consumed_miasma * MIASMA_POWER_GAIN /datum/sm_gas/freon gas_path = /datum/gas/freon - heat_penalty = -10 - transmit_modifier = -30 - powermix = 1 + heat_modifier = -9 + power_transmission = -3 + heat_power_generation = 1 /datum/sm_gas/hydrogen gas_path = /datum/gas/hydrogen - heat_penalty = 10 - transmit_modifier = 25 - heat_resistance = 2 - powermix = 1 + heat_modifier = 9 + power_transmission = 2.5 + heat_resistance = 1 + heat_power_generation = 1 /datum/sm_gas/healium gas_path = /datum/gas/healium - heat_penalty = 4 - transmit_modifier = 2.4 - powermix = 1 + heat_modifier = 3 + power_transmission = 0.24 + heat_power_generation = 1 /datum/sm_gas/proto_nitrate gas_path = /datum/gas/proto_nitrate - heat_penalty = -3 - transmit_modifier = 15 - heat_resistance = 5 - powermix = 1 + heat_modifier = -4 + power_transmission = 1.5 + heat_resistance = 4 + heat_power_generation = 1 /datum/sm_gas/zauker gas_path = /datum/gas/zauker - heat_penalty = 8 - transmit_modifier = 20 - powermix = 1 + heat_modifier = 7 + power_transmission = 2 + heat_power_generation = 1 + desc = "Will generate electrical zaps." -/datum/sm_gas/zauker/extra_effects(obj/machinery/power/supermatter_crystal/sm, datum/gas_mixture/env) +/datum/sm_gas/zauker/extra_effects(obj/machinery/power/supermatter_crystal/sm) if(!prob(sm.gas_percentage[/datum/gas/zauker])) return playsound(sm.loc, 'sound/weapons/emitter2.ogg', 100, TRUE, extrarange = 10) sm.supermatter_zap( - sm, - range = 6, - zap_str = clamp(sm.power * 2, 4000, 20000), - zap_flags = ZAP_MOB_STUN, - zap_cutoff = sm.zap_cutoff, - power_level = sm.power, + sm, + range = 6, + zap_str = clamp(sm.internal_energy * 2, 4000, 20000), + zap_flags = ZAP_MOB_STUN, + zap_cutoff = sm.zap_cutoff, + power_level = sm.internal_energy, zap_icon = sm.zap_icon ) -/datum/sm_gas/halon - gas_path = /datum/gas/halon - -/datum/sm_gas/helium - gas_path = /datum/gas/helium - /datum/sm_gas/antinoblium gas_path = /datum/gas/antinoblium - transmit_modifier = -5 - heat_penalty = 15 - powermix = 1 + heat_modifier = 14 + power_transmission = -0.5 + heat_power_generation = 1 diff --git a/code/modules/power/supermatter/supermatter_hit_procs.dm b/code/modules/power/supermatter/supermatter_hit_procs.dm index 9a269fc9965..c99279013ed 100644 --- a/code/modules/power/supermatter/supermatter_hit_procs.dm +++ b/code/modules/power/supermatter/supermatter_hit_procs.dm @@ -15,31 +15,32 @@ kiss_power = 20000 if(!istype(local_turf)) return FALSE - if(!istype(projectile.firer, /obj/machinery/power/emitter) && power_changes) + if(!istype(projectile.firer, /obj/machinery/power/emitter)) investigate_log("has been hit by [projectile] fired by [key_name(projectile.firer)]", INVESTIGATE_ENGINE) if(projectile.armor_flag != BULLET || kiss_power) if(kiss_power) - psyCoeff = 1 - psy_overlay = TRUE - if(power_changes) //This needs to be here I swear - power += projectile.damage * bullet_energy + kiss_power - if(!has_been_powered) - var/fired_from_str = projectile.fired_from ? " with [projectile.fired_from]" : "" - investigate_log( - projectile.firer \ - ? "has been powered for the first time by [key_name(projectile.firer)][fired_from_str]." \ - : "has been powered for the first time.", - INVESTIGATE_ENGINE - ) - message_admins( - projectile.firer \ - ? "[src] [ADMIN_JMP(src)] has been powered for the first time by [ADMIN_FULLMONTY(projectile.firer)][fired_from_str]." \ - : "[src] [ADMIN_JMP(src)] has been powered for the first time." - ) - has_been_powered = TRUE - else if(takes_damage) - damage += (projectile.damage * bullet_energy) * clamp((emergency_point - damage) / emergency_point, 0, 1) - if(damage > danger_point) + psy_coeff = 1 + external_power_immediate += projectile.damage * bullet_energy + kiss_power + if(!has_been_powered) + var/fired_from_str = projectile.fired_from ? " with [projectile.fired_from]" : "" + investigate_log( + projectile.firer \ + ? "has been powered for the first time by [key_name(projectile.firer)][fired_from_str]." \ + : "has been powered for the first time.", + INVESTIGATE_ENGINE + ) + message_admins( + projectile.firer \ + ? "[src] [ADMIN_JMP(src)] has been powered for the first time by [ADMIN_FULLMONTY(projectile.firer)][fired_from_str]." \ + : "[src] [ADMIN_JMP(src)] has been powered for the first time." + ) + has_been_powered = TRUE + else + external_damage_immediate += projectile.damage * bullet_energy + // Stop taking damage at emergency point, yell to players at danger point. + // This isn't clean and we are repeating [/obj/machinery/power/supermatter_crystal/proc/calculate_damage], sorry for this. + var/damage_to_be = damage + external_damage_immediate * clamp((emergency_point - damage) / emergency_point, 0, 1) + if(damage_to_be > danger_point) visible_message(span_notice("[src] compresses under stress, resisting further impacts!")) return BULLET_ACT_HIT @@ -78,7 +79,7 @@ if (scalpel.usesLeft) to_chat(user, span_danger("You extract a sliver from \the [src]. \The [src] begins to react violently!")) new /obj/item/nuke_core/supermatter_sliver(src.drop_location()) - matter_power += 800 + external_power_trickle += 800 scalpel.usesLeft-- if (!scalpel.usesLeft) to_chat(user, span_notice("A tiny piece of \the [scalpel] falls off, rendering it useless!")) @@ -104,8 +105,8 @@ investigate_log("[key_name(user)] attached [destabilizing_crystal] to a supermatter crystal.", INVESTIGATE_ENGINE) to_chat(user, span_danger("\The [destabilizing_crystal] snaps onto \the [src].")) set_delam(SM_DELAM_PRIO_IN_GAME, /datum/sm_delam/cascade) - damage += 100 - matter_power += 500 + external_damage_immediate += 100 + external_power_trickle += 500 qdel(destabilizing_crystal) return @@ -120,8 +121,5 @@ default_unfasten_wrench(user, tool) /obj/machinery/power/supermatter_crystal/proc/consume_callback(matter_increase, damage_increase) - if(matter_increase && power_changes) - matter_power += matter_increase - if(damage_increase && takes_damage) - damage += damage_increase - damage = max(damage, 0) + external_power_trickle += matter_increase + external_damage_immediate += damage_increase diff --git a/code/modules/power/supermatter/supermatter_process.dm b/code/modules/power/supermatter/supermatter_process.dm deleted file mode 100644 index 4a360557b7a..00000000000 --- a/code/modules/power/supermatter/supermatter_process.dm +++ /dev/null @@ -1,355 +0,0 @@ -/obj/machinery/power/supermatter_crystal/process_atmos() - if(!processes) //Just fuck me up bro - return - var/turf/local_turf = loc - - if(isnull(local_turf))// We have a null turf...something is wrong, stop processing this entity. - return PROCESS_KILL - - if(!istype(local_turf))//We are in a crate or somewhere that isn't turf, if we return to turf resume processing but for now. - return //Yeah just stop. - - if(isclosedturf(local_turf)) - var/turf/did_it_melt = local_turf.Melt() - if(!isclosedturf(did_it_melt)) //In case some joker finds way to place these on indestructible walls - visible_message(span_warning("[src] melts through [local_turf]!")) - return - - handle_crystal_sounds() - - //Ok, get the air from the turf - var/datum/gas_mixture/env = local_turf.return_air() - environment_total_moles = env.total_moles() - if(produces_gas) - //Remove gas from surrounding area - absorbed_gasmix = env.remove_ratio(absorption_ratio) - else - // Pass all the gas related code an empty gas container - absorbed_gasmix = new() - - overlays -= psyOverlay - if(psy_overlay) - overlays -= psyOverlay - if(psyCoeff > 0) - psyOverlay.alpha = psyCoeff * 255 - overlays += psyOverlay - else - psy_overlay = FALSE - damage_archived = damage - if(!absorbed_gasmix || !absorbed_gasmix.total_moles() || isspaceturf(local_turf)) //we're in space or there is no gas to process - if(takes_damage) - damage += max((power / 1000) * DAMAGE_INCREASE_MULTIPLIER, 0.1) // always does at least some damage - if(!istype(env, /datum/gas_mixture/immutable) && produces_gas && power) //There is no gas to process, but we are not in a space turf. Lets make them. - //Power * 0.55 * a value between 1 and 0.8 - var/device_energy = power * REACTION_POWER_MODIFIER * (1 - (psyCoeff * 0.2)) - //Can't do stuff if it's null, so lets make a new gasmix. - absorbed_gasmix = new() - //Since there is no gas to process, we will produce as if heat penalty is 1 and temperature at TCMB. - absorbed_gasmix.assert_gases(/datum/gas/plasma, /datum/gas/oxygen) - absorbed_gasmix.temperature = ((device_energy) / THERMAL_RELEASE_MODIFIER) - absorbed_gasmix.temperature = max(TCMB, min(absorbed_gasmix.temperature, 2500)) - absorbed_gasmix.gases[/datum/gas/plasma][MOLES] = max((device_energy) / PLASMA_RELEASE_MODIFIER, 0) - absorbed_gasmix.gases[/datum/gas/oxygen][MOLES] = max(((device_energy + TCMB) - T0C) / OXYGEN_RELEASE_MODIFIER, 0) - absorbed_gasmix.garbage_collect() - env.merge(absorbed_gasmix) - air_update_turf(FALSE, FALSE) - else - combined_gas = absorbed_gasmix.total_moles() - gas_percentage = list() - - power_transmission_bonus = 0 - dynamic_heat_modifier = 0 - dynamic_heat_resistance = 0 - gasmix_power_ratio = 0 - powerloss_dynamic_scaling = 0 - - for (var/gas_path in absorbed_gasmix.gases) - var/datum/sm_gas/sm_gas = GLOB.sm_gas_behavior[gas_path] - gas_percentage[gas_path] = absorbed_gasmix.gases[gas_path][MOLES] / combined_gas - power_transmission_bonus += sm_gas.transmit_modifier * gas_percentage[gas_path] - dynamic_heat_modifier += sm_gas.heat_penalty * gas_percentage[gas_path] - dynamic_heat_resistance += sm_gas.heat_resistance * gas_percentage[gas_path] - gasmix_power_ratio += sm_gas.powermix * gas_percentage[gas_path] - powerloss_dynamic_scaling += sm_gas.powerloss_inhibition * gas_percentage[gas_path] - - gasmix_power_ratio = clamp(gasmix_power_ratio, 0, 1) - dynamic_heat_modifier = max(dynamic_heat_modifier, 0.5) - dynamic_heat_resistance = max(dynamic_heat_resistance, 1) - - // Extra effects should always fire after the compositions are all finished - // Some extra effects like [/datum/sm_gas/carbon_dioxide/extra_effects] needs more than one gas. - for (var/gas_path in absorbed_gasmix.gases) - var/datum/sm_gas/sm_gas = GLOB.sm_gas_behavior[gas_path] - sm_gas.extra_effects(src, env) - - if(takes_damage) - //causing damage - deal_damage(absorbed_gasmix) - - //main power calculations proc - power_calculations(env, absorbed_gasmix) - //irradiate at this point - emit_radiation() - //handles temperature increase and gases made by the crystal - temperature_gas_production(env, absorbed_gasmix) - - //handles hallucinations and the presence of a psychiatrist - psychological_examination() - - //Transitions between one function and another, one we use for the fast inital startup, the other is used to prevent errors with fusion temperatures. - //Use of the second function improves the power gain imparted by using co2 - if(power_changes) - ///The power that is getting lost this tick. - var/power_loss = power < powerloss_linear_threshold ? ((power / POWERLOSS_CUBIC_DIVISOR) ** 3) : (power * POWERLOSS_LINEAR_RATE + powerloss_linear_offset) - power_loss *= powerloss_inhibitor * (1 - (PSYCHOLOGIST_POWERLOSS_REDUCTION * psyCoeff)) - power = max(power - power_loss, 0) - //After this point power is lowered - //This wraps around to the begining of the function - //Handle high power zaps/anomaly generation - handle_high_power(absorbed_gasmix) - - if(prob(15)) - supermatter_pull(loc, min(power/850, 3))//850, 1700, 2550 - - if(damage == 0) // Clear any in game forced delams if on full health. - set_delam(SM_DELAM_PRIO_IN_GAME, SM_DELAM_STRATEGY_PURGE) - else - set_delam() // This one cant clear any forced delams. - delamination_strategy.delam_progress(src) - - if(damage > explosion_point && !final_countdown) - count_down() - - return TRUE - -/obj/machinery/power/supermatter_crystal/proc/handle_crystal_sounds() - //We vary volume by power, and handle OH FUCK FUSION IN COOLING LOOP noises. - if(power) - soundloop.volume = clamp((50 + (power / 50)), 50, 100) - if(damage >= 300) - soundloop.mid_sounds = list('sound/machines/sm/loops/delamming.ogg' = 1) - else - soundloop.mid_sounds = list('sound/machines/sm/loops/calm.ogg' = 1) - - //We play delam/neutral sounds at a rate determined by power and damage - if(last_accent_sound >= world.time || !prob(20)) - return - var/aggression = min(((damage / 800) * (power / 2500)), 1.0) * 100 - if(damage >= 300) - playsound(src, SFX_SM_DELAM, max(50, aggression), FALSE, 40, 30, falloff_distance = 10) - else - playsound(src, SFX_SM_CALM, max(50, aggression), FALSE, 25, 25, falloff_distance = 10) - var/next_sound = round((100 - aggression) * 5) - last_accent_sound = world.time + max(SUPERMATTER_ACCENT_SOUND_MIN_COOLDOWN, next_sound) - -/obj/machinery/power/supermatter_crystal/proc/deal_damage(datum/gas_mixture/removed) - var/has_holes = FALSE - //Check for holes in the SM inner chamber - for(var/turf/open/space/turf_to_check in RANGE_TURFS(1, loc)) - if(LAZYLEN(turf_to_check.atmos_adjacent_turfs)) - damage += clamp((power * 0.005) * DAMAGE_INCREASE_MULTIPLIER, 0, MAX_SPACE_EXPOSURE_DAMAGE) - power += 250 - has_holes = TRUE - break - - var/delam_damage_multipler = delamination_strategy.damage_multiplier(src) - //more moles of gases are harder to heat than fewer, so let's scale heat damage around them - mole_heat_penalty = max(combined_gas / MOLE_HEAT_PENALTY, 0.25) - - //Due to DAMAGE_INCREASE_MULTIPLIER, we only deal one 4th of the damage the statements otherwise would cause - //((((some value between 0.5 and 1 * temp - ((273.15 + 40) * some values between 1 and 10)) * some number between 0.25 and knock your socks off / 150) * 0.25 - //Heat and mols account for each other, a lot of hot mols are more damaging then a few - //Mols start to have a positive effect on damage after 350 - damage = max(damage + (max(clamp(removed.total_moles() / 200, 0.5, 1) * removed.temperature - ((T0C + HEAT_PENALTY_THRESHOLD)*dynamic_heat_resistance), 0) * mole_heat_penalty / 150 ) * DAMAGE_INCREASE_MULTIPLIER, 0) - //Power only starts affecting damage when it is above 5000 (1250 when a cascade is occurring) - damage = max(damage + (max(power - (POWER_PENALTY_THRESHOLD * delam_damage_multipler), 0)/500) * DAMAGE_INCREASE_MULTIPLIER, 0) - //Molar count only starts affecting damage when it is above 1800 (450 when a cascade is occurring) - damage = max(damage + (max(combined_gas - (MOLE_PENALTY_THRESHOLD * delam_damage_multipler), 0)/80) * DAMAGE_INCREASE_MULTIPLIER, 0) - - //There might be a way to integrate healing and hurting via heat - //healing damage - if(combined_gas < MOLE_PENALTY_THRESHOLD && !has_holes) - //Only has a net positive effect when the temp is below 313.15, heals up to 2 damage. Psycologists increase this temp min by up to 45 - damage = max(damage + (min(removed.temperature - ((T0C + HEAT_PENALTY_THRESHOLD) + (45 * psyCoeff)), 0) / 150 ), 0) - - //caps damage rate - //Takes the lower number between archived damage + (1.8) and damage - //This means we can only deal 1.8 damage per function call - damage = min(damage_archived + (DAMAGE_HARDCAP * explosion_point),damage) - -/obj/machinery/power/supermatter_crystal/proc/power_calculations(datum/gas_mixture/env, datum/gas_mixture/removed) - //Ranges from 0 to 1(1-(value between 0 and 1 * ranges from 1 to 1.5(mol / 500))) - //We take the mol count, and scale it to be our inhibitor - powerloss_inhibitor = clamp(1-powerloss_dynamic_scaling, 0, 1) - - //Releases stored power into the general pool - //We get this by consuming shit or being scalpeled - if(matter_power && power_changes) - //We base our removed power off one 10th of the matter_power. - var/removed_matter = max(matter_power/MATTER_POWER_CONVERSION, 40) - //Adds at least 40 power - power = max(power + removed_matter, 0) - //Removes at least 40 matter power - matter_power = max(matter_power - removed_matter, 0) - - var/temp_factor = 50 - if(gasmix_power_ratio > 0.8) - //with a perfect gas mix, make the power more based on heat - icon_state = "[base_icon_state]_glow" - else - //in normal mode, power is less effected by heat - temp_factor = 30 - icon_state = base_icon_state - - //if there is more pluox and n2 then anything else, we receive no power increase from heat - if(power_changes) - power = max((removed.temperature * temp_factor / T0C) * gasmix_power_ratio + power, 0) - - //Zaps around 2.5 seconds at 1500 MeV, limited to 0.5 from 4000 MeV and up - if(power && (last_power_zap + 4 SECONDS - (power * 0.001)) < world.time) - playsound(src, 'sound/weapons/emitter2.ogg', 70, TRUE) - var/power_multiplier = max(0, 1 + power_transmission_bonus / 10) - var/pressure_multiplier = max((1 / ((env.return_pressure() ** pressure_bonus_curve_angle) + 1) * pressure_bonus_derived_steepness) + pressure_bonus_derived_constant, 1) - hue_angle_shift = clamp(903 * log(10, (power + 8000)) - 3590, -50, 240) - var/zap_color = color_matrix_rotate_hue(hue_angle_shift) - supermatter_zap( - zapstart = src, - range = 3, - zap_str = 2.5 * power * power_multiplier * pressure_multiplier, - zap_flags = ZAP_SUPERMATTER_FLAGS, - zap_cutoff = 300, - power_level = power, - color = zap_color, - ) - last_power_zap = world.time - -/obj/machinery/power/supermatter_crystal/proc/temperature_gas_production(datum/gas_mixture/env, datum/gas_mixture/removed) - //Power * 0.55 * a value between 1 and 0.8 - var/device_energy = power * REACTION_POWER_MODIFIER * (1 - (psyCoeff * 0.2)) - - //To figure out how much temperature to add each tick, consider that at one atmosphere's worth - //of pure oxygen, with all four lasers firing at standard energy and no N2 present, at room temperature - //that the device energy is around 2140. At that stage, we don't want too much heat to be put out - //Since the core is effectively "cold" - - //Also keep in mind we are only adding this temperature to (efficiency)% of the one tile the rock - //is on. An increase of 4*C @ 25% efficiency here results in an increase of 1*C / (#tilesincore) overall. - //Power * 0.55 * (some value between 1.5 and 23) / 5 - removed.temperature += ((device_energy * dynamic_heat_modifier) / THERMAL_RELEASE_MODIFIER) - //We can only emit so much heat, that being 57500 - removed.temperature = max(TCMB, min(removed.temperature, 2500 * dynamic_heat_modifier)) - - //Calculate how much gas to release - //Varies based on power and gas content - - absorbed_gasmix.assert_gases(/datum/gas/plasma, /datum/gas/oxygen) - removed.gases[/datum/gas/plasma][MOLES] += max((device_energy * dynamic_heat_modifier) / PLASMA_RELEASE_MODIFIER, 0) - //Varies based on power, gas content, and heat - removed.gases[/datum/gas/oxygen][MOLES] += max(((device_energy + removed.temperature * dynamic_heat_modifier) - T0C) / OXYGEN_RELEASE_MODIFIER, 0) - - if(produces_gas) - removed.garbage_collect() - env.merge(removed) - air_update_turf(FALSE, FALSE) - -/obj/machinery/power/supermatter_crystal/proc/psychological_examination() - // Defaults to a value less than 1. Over time the psyCoeff goes to 0 if - // no supermatter soothers are nearby. - var/psy_coeff_diff = -0.05 - for(var/mob/living/carbon/human/seen_by_sm in view(src, HALLUCINATION_RANGE(power))) - // Someone (generally a Psychologist), when looking at the SM within hallucination range makes it easier to manage. - if(HAS_TRAIT(seen_by_sm, TRAIT_SUPERMATTER_SOOTHER) || (seen_by_sm.mind && HAS_TRAIT(seen_by_sm.mind, TRAIT_SUPERMATTER_SOOTHER))) - psy_coeff_diff = 0.05 - psy_overlay = TRUE - - visible_hallucination_pulse( - center = src, - radius = HALLUCINATION_RANGE(power), - hallucination_duration = power * hallucination_power, - hallucination_max_duration = 400 SECONDS, - ) - psyCoeff = clamp(psyCoeff + psy_coeff_diff, 0, 1) - -/obj/machinery/power/supermatter_crystal/proc/handle_high_power(datum/gas_mixture/removed) - if(power <= POWER_PENALTY_THRESHOLD && damage <= danger_point) //If the power is above 5000 or if the damage is above 550 - return - var/range = 4 - zap_cutoff = 1500 - if(removed && removed.return_pressure() > 0 && removed.return_temperature() > 0) - //You may be able to freeze the zapstate of the engine with good planning, we'll see - zap_cutoff = clamp(3000 - (power * (removed.total_moles()) / 10) / removed.return_temperature(), 350, 3000)//If the core is cold, it's easier to jump, ditto if there are a lot of mols - //We should always be able to zap our way out of the default enclosure - //See supermatter_zap() for more details - range = clamp(power / removed.return_pressure() * 10, 2, 7) - var/flags = ZAP_SUPERMATTER_FLAGS - var/zap_count = 0 - //Deal with power zaps - switch(power) - if(POWER_PENALTY_THRESHOLD to SEVERE_POWER_PENALTY_THRESHOLD) - zap_icon = DEFAULT_ZAP_ICON_STATE - zap_count = 2 - if(SEVERE_POWER_PENALTY_THRESHOLD to CRITICAL_POWER_PENALTY_THRESHOLD) - zap_icon = SLIGHTLY_CHARGED_ZAP_ICON_STATE - //Uncaps the zap damage, it's maxed by the input power - //Objects take damage now - flags |= (ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE) - zap_count = 3 - if(CRITICAL_POWER_PENALTY_THRESHOLD to INFINITY) - zap_icon = OVER_9000_ZAP_ICON_STATE - //It'll stun more now, and damage will hit harder, gloves are no garentee. - //Machines go boom - flags |= (ZAP_MOB_STUN | ZAP_MACHINE_EXPLOSIVE | ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE) - zap_count = 4 - //Now we deal with damage shit - if (damage > danger_point && prob(20)) - zap_count += 1 - - if(zap_count >= 1) - playsound(loc, 'sound/weapons/emitter2.ogg', 100, TRUE, extrarange = 10) - for(var/i in 1 to zap_count) - supermatter_zap(src, range, clamp(power*2, 4000, 20000), flags, zap_cutoff = src.zap_cutoff, power_level = power, zap_icon = src.zap_icon) - - if(prob(5)) - supermatter_anomaly_gen(src, FLUX_ANOMALY, rand(5, 10)) - if(prob(5)) - supermatter_anomaly_gen(src, HALLUCINATION_ANOMALY, rand(5, 10)) - if(power > SEVERE_POWER_PENALTY_THRESHOLD && prob(5) || prob(1)) - supermatter_anomaly_gen(src, GRAVITATIONAL_ANOMALY, rand(5, 10)) - if((power > SEVERE_POWER_PENALTY_THRESHOLD && prob(2)) || (prob(0.3) && power > POWER_PENALTY_THRESHOLD)) - supermatter_anomaly_gen(src, PYRO_ANOMALY, rand(5, 10)) - -/** - * Sets the delam of our sm. - * - * Arguments: - * * priority: Truthy values means a forced delam. If current forced_delam is higher than priority we dont run. - * Set to a number higher than [SM_DELAM_PRIO_IN_GAME] to fully force an admin delam. - * * delam_path: Typepath of a [/datum/sm_delam]. [SM_DELAM_STRATEGY_PURGE] means reset and put prio back to zero. - * - * Returns: Not used for anything, just returns true on succesful set, manual and automatic. Helps admins check stuffs. - */ -/obj/machinery/power/supermatter_crystal/proc/set_delam(priority = SM_DELAM_PRIO_NONE, manual_delam_path = SM_DELAM_STRATEGY_PURGE) - if(priority < delam_priority) - return FALSE - var/datum/sm_delam/new_delam = null - - if(manual_delam_path == SM_DELAM_STRATEGY_PURGE) - for (var/delam_path in GLOB.sm_delam_list) - var/datum/sm_delam/delam = GLOB.sm_delam_list[delam_path] - if(!delam.can_select(src)) - continue - if(delam == delamination_strategy) - return FALSE - new_delam = delam - break - delam_priority = SM_DELAM_PRIO_NONE - else - new_delam = GLOB.sm_delam_list[manual_delam_path] - delam_priority = priority - - if(!new_delam) - return FALSE - delamination_strategy?.on_deselect(src) - delamination_strategy = new_delam - delamination_strategy.on_select(src) - return TRUE diff --git a/code/modules/power/supermatter/supermatter_radiation.dm b/code/modules/power/supermatter/supermatter_radiation.dm deleted file mode 100644 index 90e4aef001d..00000000000 --- a/code/modules/power/supermatter/supermatter_radiation.dm +++ /dev/null @@ -1,64 +0,0 @@ -// Any power past this number will be clamped down -#define MAX_ACCEPTED_POWER_OUTPUT 5000 - -// At the highest power output, assuming no integrity changes, the threshold will be 0. -#define THRESHOLD_EQUATION_SLOPE (-1 / MAX_ACCEPTED_POWER_OUTPUT) - -// The higher this number, the faster low integrity will drop threshold -// I would've named this "power", but y'know. :P -#define INTEGRITY_EXPONENTIAL_DEGREE 2 - -// At INTEGRITY_MIN_NUDGABLE_AMOUNT, the power will be treated as, at most, INTEGRITY_MAX_POWER_NUDGE. -// Any lower integrity will result in INTEGRITY_MAX_POWER_NUDGE. -#define INTEGRITY_MAX_POWER_NUDGE 1500 -#define INTEGRITY_MIN_NUDGABLE_AMOUNT 0.7 - -#define RADIATION_CHANCE_AT_FULL_INTEGRITY 0.03 -#define RADIATION_CHANCE_AT_ZERO_INTEGRITY 0.4 -#define CHANCE_EQUATION_SLOPE (RADIATION_CHANCE_AT_ZERO_INTEGRITY - RADIATION_CHANCE_AT_FULL_INTEGRITY) - -/obj/machinery/power/supermatter_crystal/proc/emit_radiation() - // As power goes up, rads go up. - // A standard N2 SM seems to produce a value of around 1,500. - var/power_factor = min(power, MAX_ACCEPTED_POWER_OUTPUT) - - var/integrity = 1 - CLAMP01(damage / explosion_point) - - // When integrity goes down, the threshold (from an observable point of view, rads) go up. - // However, the power factor must go up as well, otherwise turning off the emitters - // on a delaminating SM would stop radiation from escaping. - // To fix this, lower integrities raise the power factor to a minimum. - var/integrity_power_nudge = LERP(INTEGRITY_MAX_POWER_NUDGE, 0, CLAMP01((integrity - INTEGRITY_MIN_NUDGABLE_AMOUNT) / (1 - INTEGRITY_MIN_NUDGABLE_AMOUNT))) - - power_factor = max(power_factor, integrity_power_nudge) - - // At the "normal" N2 power output (with max integrity), this is 0.7, which is enough to be stopped - // by the walls or the radation shutters. - // As integrity does down, rads go up. - var/threshold - switch(integrity) - if(0) - threshold = power_factor ? 0 : 1 - if(1) - threshold = (THRESHOLD_EQUATION_SLOPE * power_factor + 1) - else - threshold = (THRESHOLD_EQUATION_SLOPE * power_factor + 1) ** ((1 / integrity) ** INTEGRITY_EXPONENTIAL_DEGREE) - - // Calculating chance is done entirely on integrity, so that actively delaminating SMs feel more dangerous - var/chance = (CHANCE_EQUATION_SLOPE * (1 - integrity)) + RADIATION_CHANCE_AT_FULL_INTEGRITY - - radiation_pulse( - src, - max_range = 8, - threshold = threshold, - chance = chance * 100, - ) - -#undef CHANCE_EQUATION_SLOPE -#undef INTEGRITY_EXPONENTIAL_DEGREE -#undef INTEGRITY_MAX_POWER_NUDGE -#undef INTEGRITY_MIN_NUDGABLE_AMOUNT -#undef MAX_ACCEPTED_POWER_OUTPUT -#undef RADIATION_CHANCE_AT_FULL_INTEGRITY -#undef RADIATION_CHANCE_AT_ZERO_INTEGRITY -#undef THRESHOLD_EQUATION_SLOPE diff --git a/code/modules/power/supermatter/supermatter_variants.dm b/code/modules/power/supermatter/supermatter_variants.dm new file mode 100644 index 00000000000..bcdf5ea802c --- /dev/null +++ b/code/modules/power/supermatter/supermatter_variants.dm @@ -0,0 +1,48 @@ +/// Normal SM with it's processing disabled. +/obj/machinery/power/supermatter_crystal/hugbox + disable_damage = TRUE + disable_gas = TRUE + disable_power_change = TRUE + disable_process = TRUE + +/// Normal SM designated as main engine. +/obj/machinery/power/supermatter_crystal/engine + is_main_engine = TRUE + +/// Shard SM. +/obj/machinery/power/supermatter_crystal/shard + name = "supermatter shard" + desc = "A strangely translucent and iridescent crystal that looks like it used to be part of a larger structure." + base_icon_state = "sm_shard" + icon_state = "sm_shard" + anchored = FALSE + absorption_ratio = 0.125 + explosion_power = 12 + layer = ABOVE_MOB_LAYER + plane = GAME_PLANE_UPPER + moveable = TRUE + +/// Shard SM with it's processing disabled. +/obj/machinery/power/supermatter_crystal/shard/hugbox + name = "anchored supermatter shard" + disable_damage = TRUE + disable_gas = TRUE + disable_power_change = TRUE + disable_process = TRUE + moveable = FALSE + anchored = TRUE + +/// Shard SM designated as the main engine. +/obj/machinery/power/supermatter_crystal/shard/engine + name = "anchored supermatter shard" + is_main_engine = TRUE + anchored = TRUE + moveable = FALSE + +/atom/movable/supermatter_warp_effect + plane = GRAVITY_PULSE_PLANE + appearance_flags = PIXEL_SCALE // no tile bound so you can see it around corners and so + icon = 'icons/effects/light_overlays/light_352.dmi' + icon_state = "light" + pixel_x = -176 + pixel_y = -176 diff --git a/icons/obj/engine/supermatter.dmi b/icons/obj/engine/supermatter.dmi index dd006d56de8..874773ef085 100644 Binary files a/icons/obj/engine/supermatter.dmi and b/icons/obj/engine/supermatter.dmi differ diff --git a/tgstation.dme b/tgstation.dme index ab05dd921c6..bd4123e92a1 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4071,10 +4071,10 @@ #include "code\modules\power\singularity\narsie.dm" #include "code\modules\power\singularity\singularity.dm" #include "code\modules\power\supermatter\supermatter.dm" +#include "code\modules\power\supermatter\supermatter_extra_effects.dm" #include "code\modules\power\supermatter\supermatter_gas.dm" #include "code\modules\power\supermatter\supermatter_hit_procs.dm" -#include "code\modules\power\supermatter\supermatter_process.dm" -#include "code\modules\power\supermatter\supermatter_radiation.dm" +#include "code\modules\power\supermatter\supermatter_variants.dm" #include "code\modules\power\supermatter\supermatter_delamination\_sm_delam.dm" #include "code\modules\power\supermatter\supermatter_delamination\cascade_delam.dm" #include "code\modules\power\supermatter\supermatter_delamination\cascade_delam_objects.dm" diff --git a/tgui/packages/tgui/constants.js b/tgui/packages/tgui/constants.js index 2cf76cb7a04..21bfd49cab3 100644 --- a/tgui/packages/tgui/constants.js +++ b/tgui/packages/tgui/constants.js @@ -142,120 +142,140 @@ export const RADIO_CHANNELS = [ const GASES = [ { id: 'o2', + path: '/datum/gas/oxygen', name: 'Oxygen', label: 'O₂', color: 'blue', }, { id: 'n2', + path: '/datum/gas/nitrogen', name: 'Nitrogen', label: 'N₂', color: 'red', }, { id: 'co2', + path: '/datum/gas/carbon_dioxide', name: 'Carbon Dioxide', label: 'CO₂', color: 'grey', }, { id: 'plasma', + path: '/datum/gas/plasma', name: 'Plasma', label: 'Plasma', color: 'pink', }, { id: 'water_vapor', + path: '/datum/gas/water_vapor', name: 'Water Vapor', label: 'H₂O', color: 'lightsteelblue', }, { id: 'nob', + path: '/datum/gas/hypernoblium', name: 'Hyper-noblium', label: 'Hyper-nob', color: 'teal', }, { id: 'n2o', + path: '/datum/gas/nitrous_oxide', name: 'Nitrous Oxide', label: 'N₂O', color: 'bisque', }, { id: 'no2', + path: '/datum/gas/nitrium', name: 'Nitrium', label: 'Nitrium', color: 'brown', }, { id: 'tritium', + path: '/datum/gas/tritium', name: 'Tritium', label: 'Tritium', color: 'limegreen', }, { id: 'bz', + path: '/datum/gas/bz', name: 'BZ', label: 'BZ', color: 'mediumpurple', }, { id: 'pluox', + path: '/datum/gas/pluoxium', name: 'Pluoxium', label: 'Pluoxium', color: 'mediumslateblue', }, { id: 'miasma', + path: '/datum/gas/miasma', name: 'Miasma', label: 'Miasma', color: 'olive', }, { id: 'Freon', + path: '/datum/gas/freon', name: 'Freon', label: 'Freon', color: 'paleturquoise', }, { id: 'hydrogen', + path: '/datum/gas/hydrogen', name: 'Hydrogen', label: 'H₂', color: 'white', }, { id: 'healium', + path: '/datum/gas/healium', name: 'Healium', label: 'Healium', color: 'salmon', }, { id: 'proto_nitrate', + path: '/datum/gas/proto_nitrate', name: 'Proto Nitrate', label: 'Proto-Nitrate', color: 'greenyellow', }, { id: 'zauker', + path: '/datum/gas/zauker', name: 'Zauker', label: 'Zauker', color: 'darkgreen', }, { id: 'halon', + path: '/datum/gas/halon', name: 'Halon', label: 'Halon', color: 'purple', }, { id: 'helium', + path: '/datum/gas/helium', name: 'Helium', label: 'He', color: 'aliceblue', }, { id: 'antinoblium', + path: '/datum/gas/antinoblium', name: 'Antinoblium', label: 'Anti-Noblium', color: 'maroon', @@ -281,3 +301,18 @@ export const getGasColor = (gasId) => { )); return gas && gas.color; }; + +export const getGasFromId = (gasId) => { + const gasSearchString = String(gasId).toLowerCase(); + // prettier-ignore + const gas = GASES.find((gas) => ( + gas.id === gasSearchString + || gas.name.toLowerCase() === gasSearchString + )); + return gas; +}; + +// Paths need to be exact matches so we dont need to lowercase stuffs. +export const getGasFromPath = (gasPath) => { + return GASES.find((gas) => gasPath === gas.path); +}; diff --git a/tgui/packages/tgui/interfaces/NtosSupermatter.tsx b/tgui/packages/tgui/interfaces/NtosSupermatter.tsx new file mode 100644 index 00000000000..4b89123a21e --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosSupermatter.tsx @@ -0,0 +1,74 @@ +import { useBackend, useLocalState } from '../backend'; +import { NtosWindow } from '../layouts'; +import { SupermatterContent, SupermatterData } from './Supermatter'; +import { Button, ProgressBar, Section, Table } from '../components'; + +type NtosSupermatterData = SupermatterData & { focus_uid?: number }; + +export const NtosSupermatter = (props, context) => { + const { act, data } = useBackend(context); + const { sm_data, gas_metadata, focus_uid } = data; + const [activeUID, setActiveUID] = useLocalState(context, 'activeUID', 0); + const activeSM = sm_data.find((sm) => sm.uid === activeUID); + return ( + + + {activeSM ? ( + setActiveUID(0)}> + Back + + } + /> + ) : ( +
act('PRG_refresh')} + /> + }> + + {sm_data.map((sm) => ( + + {sm.uid + '. ' + sm.area_name} + + Integrity: + + + + + +
+
+ )} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosSupermatterMonitor.tsx b/tgui/packages/tgui/interfaces/NtosSupermatterMonitor.tsx deleted file mode 100644 index c7c40bc6307..00000000000 --- a/tgui/packages/tgui/interfaces/NtosSupermatterMonitor.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { NtosWindow } from '../layouts'; -import { SupermatterMonitorContent } from './SupermatterMonitor'; - -export const NtosSupermatterMonitor = () => { - return ( - - - - - - ); -}; diff --git a/tgui/packages/tgui/interfaces/Supermatter.tsx b/tgui/packages/tgui/interfaces/Supermatter.tsx new file mode 100644 index 00000000000..4c66b5975c5 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Supermatter.tsx @@ -0,0 +1,391 @@ +import { filter, sortBy } from 'common/collections'; +import { flow } from 'common/fp'; +import { toFixed } from 'common/math'; +import { BooleanLike } from 'common/react'; +import { InfernoNode } from 'inferno'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, LabeledList, ProgressBar, Section, Stack } from '../components'; +import { getGasFromPath } from '../constants'; +import { Window } from '../layouts'; + +const logScale = (value) => Math.log2(16 + Math.max(0, value)) - 4; + +type SMGasMetadata = { + [key: string]: { + desc?: string; + numeric_data: { + name: string; + amount: number; + positive: BooleanLike; + }[]; + }; +}; + +type SupermatterProps = { + sectionButton?: InfernoNode; + uid: number; + area_name: string; + integrity: number; + integrity_factors: { name: string; amount: number }[]; + internal_energy: number; + internal_energy_factors: { name: string; amount: number }[]; + zap_multiplier: number; + zap_multiplier_factors: { name: string; amount: number }[]; + temp_limit: number; + temp_limit_factors: { name: string; amount: number }[]; + waste_multiplier: number; + waste_multiplier_factors: { name: string; amount: number }[]; + absorbed_ratio: number; + gas_composition: { [gas_path: string]: number }; + gas_temperature: number; + gas_total_moles: number; + gas_metadata: SMGasMetadata; +}; + +// LabeledList but stack and with a chevron dropdown. +type SupermatterEntryProps = { + title: string; + content: InfernoNode; + detail?: InfernoNode; + alwaysShowChevron?: boolean; +}; +const SupermatterEntry = (props: SupermatterEntryProps, context) => { + const { title, content, detail, alwaysShowChevron } = props; + if (!alwaysShowChevron && !detail) { + return ( + + + + {title + ':'} + + {content} + + + ); + } + const [activeDetail, setActiveDetail] = useLocalState(context, title, false); + return ( + <> + + + + {title + ':'} + + {content} + + + }> + + {gas_composition.map(([gas_path, amount]) => ( + + {toFixed(amount * 100, 2) + '%'} + + } + detail={ + gas_metadata[gas_path] ? ( + <> + {gas_metadata[gas_path].desc &&
} + {gas_metadata[gas_path].numeric_data.length ? ( + <> + + At 100% Composition gives: + + + {gas_metadata[gas_path].numeric_data.map( + (effect) => + effect.amount !== 0 && ( + 0 + ? 'green' + : 'red' + : effect.amount < 0 + ? 'green' + : 'red' + }> + {effect.amount > 0 + ? '+' + effect.amount * 100 + '%' + : effect.amount * 100 + '%'} + + ) + )} + + + ) : ( + 'Has no composition effects' + )} + + ) : ( + 'Has no effects' + ) + } + /> + ))} +
+ +
+
+ ); +}; + +export type SupermatterData = { + sm_data: Omit[]; + gas_metadata: SMGasMetadata; +}; + +export const Supermatter = (props, context) => { + const { act, data } = useBackend(context); + const { sm_data, gas_metadata } = data; + return ( + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/SupermatterMonitor.js b/tgui/packages/tgui/interfaces/SupermatterMonitor.js deleted file mode 100644 index 6115c31a886..00000000000 --- a/tgui/packages/tgui/interfaces/SupermatterMonitor.js +++ /dev/null @@ -1,195 +0,0 @@ -import { sortBy } from 'common/collections'; -import { flow } from 'common/fp'; -import { toFixed } from 'common/math'; -import { useBackend } from '../backend'; -import { Button, LabeledList, ProgressBar, Section, Stack, Table } from '../components'; -import { getGasColor, getGasLabel } from '../constants'; -import { Window } from '../layouts'; - -const logScale = (value) => Math.log2(16 + Math.max(0, value)) - 4; - -export const SupermatterMonitor = () => { - return ( - - - - - - ); -}; - -export const SupermatterMonitorContent = (props, context) => { - const { act, data } = useBackend(context); - const { - active, - singlecrystal, - SM_uid, - SM_area_name, - SM_integrity, - SM_power, - SM_ambienttemp, - SM_ambientpressure, - SM_moles, - SM_bad_moles_amount, - } = data; - if (!active) { - return ; - } - const gases = flow([ - (gases) => gases.filter((gas) => gas.amount >= 0.01), - sortBy((gas) => -gas.amount), - ])(data.gases || []); - const gasMaxAmount = Math.max(1, ...gases.map((gas) => gas.amount)); - return ( -
act('PRG_clear')} - /> - ) - }> - - -
- - - - - - - {toFixed(SM_power) + ' MeV/cm3'} - - - - - {toFixed(SM_ambienttemp) + ' K'} - - - - - {toFixed(SM_moles) + ' moles'} - - - - - {toFixed(SM_ambientpressure) + ' kPa'} - - - -
-
- -
- - {gases.map((gas) => ( - - - {toFixed(gas.amount, 2) + '%'} - - - ))} - -
-
-
-
- ); -}; - -const SupermatterList = (props, context) => { - const { act, data } = useBackend(context); - const { supermatters = [] } = data; - return ( -
act('PRG_refresh')} - /> - }> - - {supermatters.map((sm) => ( - - {sm.uid + '. ' + sm.area_name} - - Integrity: - - - - - -
-
- ); -};