MILLA phase 2 (#27659)
* MILLA phase 2 * Lint. * Build Rust library * Assorted bugfixes and tweaks * Simplify fire mechanics and make hotspot sending to DM more reliable. * Assorted tweaks, fixed an issue in the core engine and removed the softcap it made necesary. * SM fixes, slower plasmafire, less overpowered pyro anomalies, and air alarm improvements. * Review fixes. * Build Rust library * Unbalanced icon. * Rebalance immovable rods. * Unbalanced has alerts, can fight airflow (but get slowed) * Build Rust library * Stronger space cooling, slower temperature flow, faster burns, burnt floors, and a hotspot display fix. * Build Rust library * Tweaks to avoid merge conflict * Oops. * Build Rust library * Rebalance wind. * Rebalance temperature flow and space cooling. * Fix gas flamethrower. * Build Rust library * Make air push slowdown directional, so you don't get slowed while moving with the air. * Variable name cleanup. * Reduce the speed of wind pushes. * Prevent wind pushes from breaking your pull. * Prevent swaps during wind push. * Made supermatter crytals reliably run last in atmos machinery. * Sped up plasmafire burning, but dropped the minimum burn amount. * Rebalanced SM heat output. * Rebalanced pyro anomaly. * Build Rust library * Lint * Build Rust library * Uncap fuel burnt readout. * Added Custom air alarm mode, dropped Refill cap to ONE_ATMOSPHERE. * Updated air alarm modes to use pressure settings instead of ONE_ATMOSPHERE * Added a list of areas not in Filtering to atmos alert computer. * Increase pressure gradient and vent output, especially at low distro pressure. * Changed Immovable Rod from Moderate to Major. * Lint * Build Rust library * More lint! * Build Rust library * Magboots, scaled slowdown, and nukie borg immunity * Wind image * Wind v2 * Wind v3 * pngcrush * pngcrush again * Moved hotspot removal into SSair, add wind effect. * Lint * Build Rust library * Fix hotspots. * Hotspot visual based on gas burnt * Build Rust library * Scaled wind to gas amount, stopped first wind push while moving. * Made Rust panic logging safer. * Made MILLA full tick and sleep timers more honest. * Pressure overlay, ghost mode, atmos goggles. * Build Rust library * Lint * Build Rust library * More lint-y stuff. * Build Rust library * Repair pressure overlay if it loses its loc. * Bind pressure overlays to their tile better. * Build Rust library * Make the pressure overlay work on z=1 and not proliferate effects. * Don't block the supply shuttle. * Don't fine for special effects. * Maybe don't fine for ghosts, either. * Build Rust library * Make pressure overlay play nice with shuttles. * Build Rust library * Pressure scanning for borgs. * Build Rust library * Lint * Build Rust library * Made explosions not generate so much wind. * Removed an old and non-functional proc. * Clientless mobs can get pushed again. * Build Rust library * cargo fmt * Build Rust library * Don't divide by zero. * Plasmafire generator for the Shadow * Update shadow to use plasmafire generators * Fix shadow's plasmafire generators going out on depart. * Prevent heat2color from runtiming at absolute zero. * Better nanofrost * Build Rust library * Singularity immunity * Build Rust library * Add back meson mode to atmospheric scanner goggles, so they don't stare deeply into the SM * Build Rust library * Dump panic outputs into data/ instead. * Build Rust library * Apply suggestions from code review Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com> Signed-off-by: Charlie Nolan <funnyman3595@gmail.com> * Saner handling of MILLA crash. * Build Rust library --------- Signed-off-by: Charlie Nolan <funnyman3595@gmail.com> Co-authored-by: paradisess13[bot] <165046124+paradisess13[bot]@users.noreply.github.com> Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
@@ -154,7 +154,8 @@
|
||||
/obj/machinery/atmospherics/pipe/simple/hidden{
|
||||
dir = 5
|
||||
},
|
||||
/turf/simulated/floor/engine/plasma,
|
||||
/obj/structure/plasmafire_generator/shadow,
|
||||
/turf/simulated/floor/engine/airless,
|
||||
/area/shuttle/escape)
|
||||
"ge" = (
|
||||
/obj/structure/table/glass/reinforced/plastitanium,
|
||||
@@ -230,9 +231,6 @@
|
||||
},
|
||||
/turf/simulated/floor/catwalk,
|
||||
/area/shuttle/escape)
|
||||
"jR" = (
|
||||
/turf/simulated/floor/engine/o2,
|
||||
/area/shuttle/escape)
|
||||
"kx" = (
|
||||
/obj/structure/chair/comfy/shuttle,
|
||||
/turf/simulated/floor/mineral/plastitanium/red/brig,
|
||||
@@ -354,7 +352,8 @@
|
||||
/obj/machinery/atmospherics/pipe/manifold/hidden{
|
||||
dir = 4
|
||||
},
|
||||
/turf/simulated/floor/engine/plasma,
|
||||
/obj/structure/plasmafire_generator/shadow,
|
||||
/turf/simulated/floor/engine/airless,
|
||||
/area/shuttle/escape)
|
||||
"pi" = (
|
||||
/obj/effect/turf_decal/stripes/white/line{
|
||||
@@ -565,7 +564,8 @@
|
||||
},
|
||||
/area/shuttle/escape)
|
||||
"zj" = (
|
||||
/turf/simulated/floor/engine/plasma,
|
||||
/obj/structure/plasmafire_generator/shadow,
|
||||
/turf/simulated/floor/engine/airless,
|
||||
/area/shuttle/escape)
|
||||
"zs" = (
|
||||
/obj/structure/reagent_dispensers/fueltank,
|
||||
@@ -639,7 +639,8 @@
|
||||
/area/template_noop)
|
||||
"CD" = (
|
||||
/obj/machinery/igniter/shadow,
|
||||
/turf/simulated/floor/engine/plasma,
|
||||
/obj/structure/plasmafire_generator/shadow,
|
||||
/turf/simulated/floor/engine/airless,
|
||||
/area/shuttle/escape)
|
||||
"CK" = (
|
||||
/obj/structure/shuttle/engine/propulsion/burst{
|
||||
@@ -680,7 +681,8 @@
|
||||
/area/shuttle/escape)
|
||||
"DY" = (
|
||||
/obj/machinery/atmospherics/pipe/simple/hidden/universal,
|
||||
/turf/simulated/floor/engine/plasma,
|
||||
/obj/structure/plasmafire_generator/shadow,
|
||||
/turf/simulated/floor/engine/airless,
|
||||
/area/shuttle/escape)
|
||||
"DZ" = (
|
||||
/obj/effect/turf_decal/stripes/red/corner{
|
||||
@@ -739,7 +741,8 @@
|
||||
dir = 4;
|
||||
invisibility = 99
|
||||
},
|
||||
/turf/simulated/floor/engine/plasma,
|
||||
/obj/structure/plasmafire_generator/shadow,
|
||||
/turf/simulated/floor/engine/airless,
|
||||
/area/shuttle/escape)
|
||||
"Gx" = (
|
||||
/obj/structure/extinguisher_cabinet{
|
||||
@@ -1151,7 +1154,8 @@
|
||||
dir = 9
|
||||
},
|
||||
/obj/machinery/atmospherics/meter,
|
||||
/turf/simulated/floor/engine/plasma,
|
||||
/obj/structure/plasmafire_generator/shadow,
|
||||
/turf/simulated/floor/engine/airless,
|
||||
/area/shuttle/escape)
|
||||
"Xp" = (
|
||||
/obj/structure/marker_beacon/dock_marker/collision,
|
||||
@@ -1357,9 +1361,9 @@ Lv
|
||||
OL
|
||||
Bu
|
||||
tb
|
||||
jR
|
||||
zj
|
||||
CD
|
||||
jR
|
||||
zj
|
||||
cX
|
||||
wF
|
||||
cH
|
||||
|
||||
@@ -170,6 +170,12 @@
|
||||
/// Cold environment: Normal atmosphere, -93 C.
|
||||
#define ENVIRONMENT_COLD "cold"
|
||||
|
||||
/// How far away should we load the pressure HUD data from MILLA?
|
||||
#define PRESSURE_HUD_LOAD_RADIUS 15
|
||||
|
||||
/// How far away should we send the pressure HUD to the player?
|
||||
#define PRESSURE_HUD_RADIUS 12
|
||||
|
||||
// Vent pump modes
|
||||
/// Don't go over the external pressure
|
||||
#define ONLY_CHECK_EXT_PRESSURE 1
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#define DIAG_AIRLOCK_HUD "22" // Airlock shock overlay
|
||||
#define GLAND_HUD "23"//Gland indicators for abductors
|
||||
#define JANI_HUD "24" // Sign overlay over cleanable decals
|
||||
#define PRESSURE_HUD "25" // Pressure coloring for tiles
|
||||
|
||||
//by default everything in the hud_list of an atom is an image
|
||||
//a value in hud_list with one of these will change that behavior
|
||||
@@ -42,22 +43,23 @@
|
||||
#define DATA_HUD_DIAGNOSTIC_ADVANCED 6
|
||||
#define DATA_HUD_HYDROPONIC 7
|
||||
#define DATA_HUD_JANITOR 8
|
||||
#define DATA_HUD_PRESSURE 9
|
||||
//antag HUD defines
|
||||
#define ANTAG_HUD_CULT 9
|
||||
#define ANTAG_HUD_REV 10
|
||||
#define ANTAG_HUD_OPS 11
|
||||
#define ANTAG_HUD_WIZ 12
|
||||
#define ANTAG_HUD_SHADOW 13
|
||||
#define ANTAG_HUD_TRAITOR 14
|
||||
#define ANTAG_HUD_NINJA 15
|
||||
#define ANTAG_HUD_CHANGELING 16
|
||||
#define ANTAG_HUD_VAMPIRE 17
|
||||
#define ANTAG_HUD_ABDUCTOR 18
|
||||
#define DATA_HUD_ABDUCTOR 19
|
||||
#define ANTAG_HUD_EVENTMISC 20
|
||||
#define ANTAG_HUD_BLOB 21
|
||||
#define ANTAG_HUD_ZOMBIE 22
|
||||
#define ANTAG_HUD_MIND_FLAYER 23
|
||||
#define ANTAG_HUD_CULT 10
|
||||
#define ANTAG_HUD_REV 11
|
||||
#define ANTAG_HUD_OPS 12
|
||||
#define ANTAG_HUD_WIZ 13
|
||||
#define ANTAG_HUD_SHADOW 14
|
||||
#define ANTAG_HUD_TRAITOR 15
|
||||
#define ANTAG_HUD_NINJA 16
|
||||
#define ANTAG_HUD_CHANGELING 17
|
||||
#define ANTAG_HUD_VAMPIRE 18
|
||||
#define ANTAG_HUD_ABDUCTOR 19
|
||||
#define DATA_HUD_ABDUCTOR 20
|
||||
#define ANTAG_HUD_EVENTMISC 21
|
||||
#define ANTAG_HUD_BLOB 22
|
||||
#define ANTAG_HUD_ZOMBIE 23
|
||||
#define ANTAG_HUD_MIND_FLAYER 24
|
||||
|
||||
// Notification action types
|
||||
#define NOTIFY_JUMP "jump"
|
||||
|
||||
@@ -84,15 +84,16 @@
|
||||
/**
|
||||
* Air alarm modes
|
||||
*/
|
||||
#define AALARM_MODE_SCRUBBING 1
|
||||
#define AALARM_MODE_VENTING 2 //makes draught
|
||||
#define AALARM_MODE_FILTERING 1
|
||||
#define AALARM_MODE_DRAUGHT 2 //makes draught
|
||||
#define AALARM_MODE_PANIC 3 //like siphon, but stronger (enables widenet)
|
||||
#define AALARM_MODE_REPLACEMENT 4 //sucks off all air, then refill and swithes to scrubbing
|
||||
#define AALARM_MODE_CYCLE 4 //sucks off all air, then refill and swithes to scrubbing
|
||||
#define AALARM_MODE_SIPHON 5 //Scrubbers suck air
|
||||
#define AALARM_MODE_CONTAMINATED 6 //Turns on all filtering and widenet scrubbing.
|
||||
#define AALARM_MODE_REFILL 7 //just like normal, but with triple the air output
|
||||
#define AALARM_MODE_REFILL 7 //just like normal, but disables low pressure check until normalized, then switches to normal
|
||||
#define AALARM_MODE_OFF 8
|
||||
#define AALARM_MODE_FLOOD 9 //Emagged mode; turns off scrubbers and pressure checks on vents
|
||||
#define AALARM_MODE_CUSTOM 10
|
||||
|
||||
#define NUKE_STATUS_INTACT 0
|
||||
#define NUKE_CORE_MISSING 1
|
||||
|
||||
@@ -53,14 +53,15 @@
|
||||
// This needs to go BELOW the above define, otherwise the BYOND compiler can make the above immediate call disappear
|
||||
#undef RUSTLIBS_SUFFIX
|
||||
|
||||
/// Exists by default in 516, but needs to be defined for 515 or byondapi-rs doesn't like it.
|
||||
/proc/byondapi_stack_trace(err)
|
||||
CRASH(err)
|
||||
|
||||
/proc/milla_init_z(z)
|
||||
return RUSTLIB_CALL(milla_initialize, z)
|
||||
|
||||
/proc/is_milla_synchronous(tick)
|
||||
return RUSTLIB_CALL(milla_is_synchronous, tick)
|
||||
|
||||
/proc/set_tile_atmos(turf/T, airtight_north, airtight_east, airtight_south, airtight_west, atmos_mode, environment_id, oxygen, carbon_dioxide, nitrogen, toxins, sleeping_agent, agent_b, temperature, innate_heat_capacity)
|
||||
return RUSTLIB_CALL(milla_set_tile, T, airtight_north, airtight_east, airtight_south, airtight_west, atmos_mode, environment_id, oxygen, carbon_dioxide, nitrogen, toxins, sleeping_agent, agent_b, temperature, innate_heat_capacity)
|
||||
/proc/set_tile_atmos(turf/T, airtight_north, airtight_east, airtight_south, airtight_west, atmos_mode, environment_id, oxygen, carbon_dioxide, nitrogen, toxins, sleeping_agent, agent_b, temperature, innate_heat_capacity, hotspot_temperature, hotspot_volume)
|
||||
return RUSTLIB_CALL(milla_set_tile, T, airtight_north, airtight_east, airtight_south, airtight_west, atmos_mode, environment_id, oxygen, carbon_dioxide, nitrogen, toxins, sleeping_agent, agent_b, temperature, innate_heat_capacity, hotspot_temperature, hotspot_volume)
|
||||
|
||||
/proc/get_tile_atmos(turf/T, list/L)
|
||||
return RUSTLIB_CALL(milla_get_tile, T, L)
|
||||
@@ -74,6 +75,9 @@
|
||||
/proc/get_interesting_atmos_tiles()
|
||||
return RUSTLIB_CALL(milla_get_interesting_tiles)
|
||||
|
||||
/proc/get_tracked_pressure_tiles()
|
||||
return RUSTLIB_CALL(milla_get_tracked_pressure_tiles)
|
||||
|
||||
/proc/reduce_superconductivity(turf/T, list/superconductivity)
|
||||
var/north = superconductivity[1]
|
||||
var/east = superconductivity[2]
|
||||
@@ -93,6 +97,14 @@
|
||||
|
||||
return RUSTLIB_CALL(milla_set_tile_airtight, T, north, east, south, west)
|
||||
|
||||
/proc/create_hotspot(turf/T, hotspot_temperature, hotspot_volume)
|
||||
return RUSTLIB_CALL(milla_create_hotspot, T, hotspot_temperature, hotspot_volume)
|
||||
|
||||
/proc/track_pressure_tiles(atom/A, radius)
|
||||
var/turf/T = get_turf(A)
|
||||
if(istype(T))
|
||||
return RUSTLIB_CALL(milla_track_pressure_tiles, T, radius)
|
||||
|
||||
/proc/get_random_interesting_tile()
|
||||
return RUSTLIB_CALL(milla_get_random_interesting_tile)
|
||||
|
||||
@@ -122,15 +134,20 @@
|
||||
#define MILLA_INDEX_SUPERCONDUCTIVITY_WEST 13
|
||||
#define MILLA_INDEX_INNATE_HEAT_CAPACITY 14
|
||||
#define MILLA_INDEX_TEMPERATURE 15
|
||||
#define MILLA_INDEX_HOTSPOT_TEMPERATURE 16
|
||||
#define MILLA_INDEX_HOTSPOT_VOLUME 17
|
||||
#define MILLA_INDEX_WIND_X 18
|
||||
#define MILLA_INDEX_WIND_Y 19
|
||||
#define MILLA_INDEX_FUEL_BURNT 20
|
||||
|
||||
/// The number of values per tile.
|
||||
#define MILLA_TILE_SIZE MILLA_INDEX_TEMPERATURE
|
||||
#define MILLA_TILE_SIZE MILLA_INDEX_FUEL_BURNT
|
||||
|
||||
// These are only for InterestingTiles.
|
||||
#define MILLA_INDEX_TURF 16
|
||||
#define MILLA_INDEX_INTERESTING_REASONS 17
|
||||
#define MILLA_INDEX_AIRFLOW_X 18
|
||||
#define MILLA_INDEX_AIRFLOW_Y 19
|
||||
#define MILLA_INDEX_TURF 21
|
||||
#define MILLA_INDEX_INTERESTING_REASONS 22
|
||||
#define MILLA_INDEX_AIRFLOW_X 23
|
||||
#define MILLA_INDEX_AIRFLOW_Y 24
|
||||
|
||||
/// The number of values per interesting tile.
|
||||
#define MILLA_INTERESTING_TILE_SIZE MILLA_INDEX_AIRFLOW_Y
|
||||
|
||||
@@ -136,6 +136,8 @@
|
||||
|
||||
#define STATUS_EFFECT_REVERSED_HIGH_PRIESTESS /datum/status_effect/reversed_high_priestess //Bubblegum will chase the person hit by the effect, grabbing people at random. This can and WILL include the caster.
|
||||
|
||||
#define STATUS_EFFECT_UNBALANCED /datum/status_effect/unbalanced // Prevents you from automatically grabbing walls to stop moving in space.
|
||||
|
||||
#define STATUS_EFFECT_C_FOAMED /datum/status_effect/c_foamed
|
||||
|
||||
#define STATUS_EFFECT_TEMPORAL_SLASH /datum/status_effect/temporal_slash
|
||||
@@ -165,6 +167,7 @@
|
||||
#define STATUS_EFFECT_IMMOBILIZED /datum/status_effect/incapacitating/immobilized
|
||||
#define STATUS_EFFECT_SLEEPING /datum/status_effect/incapacitating/sleeping
|
||||
#define STATUS_EFFECT_SLOWED /datum/status_effect/incapacitating/slowed
|
||||
#define STATUS_EFFECT_DIRECTIONAL_SLOW /datum/status_effect/incapacitating/directional_slow
|
||||
#define STATUS_EFFECT_PARALYZED /datum/status_effect/incapacitating/paralyzed
|
||||
#define STATUS_EFFECT_FLOORED /datum/status_effect/incapacitating/floored
|
||||
#define STATUS_EFFECT_ARMBAR /datum/status_effect/judo_armbar
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#define WIRE_BOLT_LIGHT "Bolt Lights"
|
||||
|
||||
// Air alarm
|
||||
#define WIRE_SYPHON "Siphon"
|
||||
#define WIRE_SIPHON "Siphon"
|
||||
#define WIRE_AALARM "Atmospherics Alarm"
|
||||
|
||||
// Camera
|
||||
|
||||
@@ -180,6 +180,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
|
||||
#define TRAIT_DWARF "dwarf"
|
||||
#define TRAIT_SILENT_FOOTSTEPS "silent_footsteps" //makes your footsteps completely silent
|
||||
#define TRAIT_MESON_VISION "meson_vision"
|
||||
#define TRAIT_PRESSURE_VISION "pressure_vision"
|
||||
#define TRAIT_FLASH_PROTECTION "flash_protection"
|
||||
#define TRAIT_NIGHT_VISION "night_vision"
|
||||
#define TRAIT_EMOTE_MUTE "emote_mute"
|
||||
|
||||
@@ -249,7 +249,7 @@
|
||||
/proc/heat2color_g(temp)
|
||||
temp /= 100
|
||||
if(temp <= 66)
|
||||
. = max(0, min(255, 99.4708025861 * log(temp) - 161.1195681661))
|
||||
. = max(0, min(255, 99.4708025861 * log(max(temp, 1)) - 161.1195681661))
|
||||
else
|
||||
. = max(0, min(255, 288.1221695283 * ((temp - 60) ** -0.0755148492)))
|
||||
|
||||
@@ -261,7 +261,7 @@
|
||||
if(temp <= 16)
|
||||
. = 0
|
||||
else
|
||||
. = max(0, min(255, 138.5177312231 * log(temp - 10) - 305.0447927307))
|
||||
. = max(0, min(255, 138.5177312231 * log(max(temp - 10, 1)) - 305.0447927307))
|
||||
|
||||
//Argument: Give this a space-separated string consisting of 6 numbers. Returns null if you don't
|
||||
/proc/text2matrix(matrixtext)
|
||||
|
||||
@@ -49,6 +49,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
|
||||
"TRAIT_SILENT_FOOTSTEPS" = TRAIT_SILENT_FOOTSTEPS,
|
||||
"TRAIT_ALCOHOL_TOLERANCE" = TRAIT_ALCOHOL_TOLERANCE,
|
||||
"TRAIT_MESON_VISION" = TRAIT_MESON_VISION,
|
||||
"TRAIT_PRESSURE_VISION" = TRAIT_PRESSURE_VISION,
|
||||
"TRAIT_FLASH_PROTECTION" = TRAIT_FLASH_PROTECTION,
|
||||
"TRAIT_NIGHT_VISION" = TRAIT_NIGHT_VISION,
|
||||
"TRAIT_DRASK_SUPERCOOL" = TRAIT_DRASK_SUPERCOOL,
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
if(get_dist(T, target_turf) > P.shock_range)
|
||||
to_chat(user, "<span class='warning'>The target is too far away.</span>")
|
||||
return FALSE
|
||||
target_turf.hotspot_expose(2000, 400)
|
||||
target_turf.hotspot_expose(2000, 1)
|
||||
playsound(user.loc, 'sound/effects/eleczap.ogg', 40, 1)
|
||||
|
||||
var/atom/beam_from = user
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
#define SSAIR_ATMOSMACHINERY 3
|
||||
#define SSAIR_INTERESTING_TILES 4
|
||||
#define SSAIR_HOTSPOTS 5
|
||||
#define SSAIR_BOUND_MIXTURES 6
|
||||
#define SSAIR_MILLA_TICK 7
|
||||
#define SSAIR_WINDY_TILES 6
|
||||
#define SSAIR_BOUND_MIXTURES 7
|
||||
#define SSAIR_PRESSURE_OVERLAY 8
|
||||
#define SSAIR_MILLA_TICK 9
|
||||
|
||||
SUBSYSTEM_DEF(air)
|
||||
name = "Atmospherics"
|
||||
init_order = INIT_ORDER_AIR
|
||||
priority = FIRE_PRIORITY_AIR
|
||||
wait = 5
|
||||
wait = 2
|
||||
flags = SS_BACKGROUND
|
||||
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
|
||||
offline_implications = "Turfs will no longer process atmos, and all atmospheric machines (including cryotubes) will no longer function. Shuttle call recommended."
|
||||
@@ -28,12 +30,16 @@ SUBSYSTEM_DEF(air)
|
||||
var/datum/resumable_cost_counter/cost_interesting_tiles = new()
|
||||
/// The cost of a pass through hotspots, shown in SS Info's C block as HS.
|
||||
var/datum/resumable_cost_counter/cost_hotspots = new()
|
||||
/// The cost of a pass through windy tiles, shown in SS Info's C block as WT.
|
||||
var/datum/resumable_cost_counter/cost_windy_tiles = new()
|
||||
/// The cost of a pass through pipenets, shown in SS Info's C block as PN.
|
||||
var/datum/resumable_cost_counter/cost_pipenets = new()
|
||||
/// The cost of a pass through building pipenets, shown in SS Info's C block as DPN.
|
||||
var/datum/resumable_cost_counter/cost_pipenets_to_build = new()
|
||||
/// The cost of a pass through atmos machinery, shown in SS Info's C block as AM.
|
||||
var/datum/resumable_cost_counter/cost_atmos_machinery = new()
|
||||
/// The cost of a pass through loading pressure tiles, shown in SS Info's C block as PT.
|
||||
var/datum/resumable_cost_counter/cost_pressure_tiles = new()
|
||||
|
||||
/// The set of current bound mixtures. Shown in SS Info as BM:x+y, where x is the length at the start of the most recent processing, and y is the number of mixtures that have been added during processing.
|
||||
var/list/bound_mixtures = list()
|
||||
@@ -45,14 +51,24 @@ SUBSYSTEM_DEF(air)
|
||||
var/added_bound_mixtures = 0
|
||||
/// The length of the most recent interesting tiles list, shown in SS Info as IT.
|
||||
var/interesting_tile_count = 0
|
||||
/// The set of current active hotspots. Length shown in SS Info as HS.
|
||||
var/list/hotspots = list()
|
||||
/// The length of the most recent hotspot list, shown in SS Info as HS.
|
||||
var/hotspot_count = 0
|
||||
/// The length of the most recent windy tiles list, shown in SS Info as WT.
|
||||
var/windy_tile_count = 0
|
||||
/// The set of pipenets that need to be built. Length shown in SS Info as PTB.
|
||||
var/list/pipenets_to_build = list()
|
||||
/// The set of active pipenets. Length shown in SS Info as PN.
|
||||
var/list/pipenets = list()
|
||||
/// The set of active atmos machinery. Length shown in SS Info as AM.
|
||||
var/list/atmos_machinery = list()
|
||||
/// The set of tiles that are currently on fire.
|
||||
var/list/hotspots
|
||||
/// The set of tiles that are still on fire after this tick.
|
||||
var/list/new_hotspots
|
||||
/// The set of tiles that are currently experiencing wind.
|
||||
var/list/windy_tiles
|
||||
/// The set of tiles that are still experiencing wind after this tick.
|
||||
var/list/new_windy_tiles
|
||||
|
||||
/// A list of atmos machinery to set up in Initialize.
|
||||
var/list/machinery_to_construct = list()
|
||||
@@ -69,18 +85,20 @@ SUBSYSTEM_DEF(air)
|
||||
/// Are we currently running in a MILLA-safe context, i.e. is is_synchronous *guaranteed* to be TRUE. Nothing outside of this file should change this.
|
||||
VAR_PRIVATE/in_milla_safe_code = FALSE
|
||||
|
||||
/// When did we start the last MILLA tick?
|
||||
var/milla_tick_start = null
|
||||
|
||||
/// Is MILLA (and hence SSair) reliably healthy?
|
||||
var/healthy = TRUE
|
||||
|
||||
/// When was MILLA last seen unhealthy?
|
||||
var/last_unhealthy = 0
|
||||
|
||||
/// A list of callbacks waiting for MILLA to finish its tick and enter synchronous mode.
|
||||
var/list/waiting_for_sync = list()
|
||||
|
||||
/// The coordinates of the pressure image we're currently loading.
|
||||
var/pressure_x = 0
|
||||
var/pressure_y = 0
|
||||
var/pressure_z = 0
|
||||
|
||||
/// The people who were using the pressure HUD last tick.
|
||||
var/list/pressure_hud_users = list()
|
||||
|
||||
/// What alpha to use for the pressure overlay. Applies to everyone using the overlay.
|
||||
var/pressure_overlay_alpha = 50
|
||||
|
||||
/// A cost counter for resumable, repeating processes.
|
||||
/datum/resumable_cost_counter
|
||||
var/last_complete_ms = 0
|
||||
@@ -112,13 +130,16 @@ SUBSYSTEM_DEF(air)
|
||||
msg += "MT:[round(cost_milla_tick,1)]|"
|
||||
msg += "IT:[cost_interesting_tiles.to_string()]|"
|
||||
msg += "HS:[cost_hotspots.to_string()]|"
|
||||
msg += "WT:[cost_windy_tiles.to_string()]|"
|
||||
msg += "PN:[cost_pipenets.to_string()]|"
|
||||
msg += "PTB:[cost_pipenets_to_build.to_string()]|"
|
||||
msg += "AM:[cost_atmos_machinery.to_string()]"
|
||||
msg += "AM:[cost_atmos_machinery.to_string()]|"
|
||||
msg += "PT:[cost_pressure_tiles.to_string()]"
|
||||
msg += "} "
|
||||
msg += "BM:[original_bound_mixtures]+[added_bound_mixtures]|"
|
||||
msg += "IT:[interesting_tile_count]|"
|
||||
msg += "HS:[length(hotspots)]|"
|
||||
msg += "HS:[hotspot_count]|"
|
||||
msg += "WT:[windy_tile_count]|"
|
||||
msg += "PN:[length(pipenets)]|"
|
||||
msg += "AM:[length(atmos_machinery)]|"
|
||||
return msg.Join("")
|
||||
@@ -126,8 +147,9 @@ SUBSYSTEM_DEF(air)
|
||||
/datum/controller/subsystem/air/get_metrics()
|
||||
. = ..()
|
||||
var/list/cust = list()
|
||||
cust["hotspots"] = length(hotspots)
|
||||
cust["interesting turfs"] = interesting_tile_count
|
||||
cust["hotspots"] = hotspot_count
|
||||
cust["windy turfs"] = windy_tile_count
|
||||
.["cost"] = cost_full.last_complete_ms
|
||||
.["custom"] = cust
|
||||
|
||||
@@ -145,7 +167,6 @@ SUBSYSTEM_DEF(air)
|
||||
in_milla_safe_code = FALSE
|
||||
|
||||
/datum/controller/subsystem/air/Recover()
|
||||
hotspots = SSair.hotspots
|
||||
pipenets_to_build = SSair.pipenets_to_build
|
||||
pipenets = SSair.pipenets
|
||||
atmos_machinery = SSair.atmos_machinery
|
||||
@@ -154,23 +175,23 @@ SUBSYSTEM_DEF(air)
|
||||
currentpart = SSair.currentpart
|
||||
is_synchronous = SSair.is_synchronous
|
||||
|
||||
#define SLEEPABLE_TIMER (world.time + world.tick_usage * world.tick_lag / 100)
|
||||
/datum/controller/subsystem/air/fire(resumed = 0)
|
||||
// All atmos stuff assumes MILLA is synchronous. Ensure it actually is.
|
||||
if(!is_synchronous)
|
||||
var/timer = TICK_USAGE_REAL
|
||||
var/timer = SLEEPABLE_TIMER
|
||||
|
||||
while(!is_synchronous)
|
||||
// Sleep for 1ms.
|
||||
sleep(0.01)
|
||||
if(MC_TICK_CHECK)
|
||||
time_slept.record_progress(TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer), FALSE)
|
||||
cost_full.record_progress(TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer), FALSE)
|
||||
time_slept.record_progress((SLEEPABLE_TIMER - timer) * 100, FALSE)
|
||||
return
|
||||
|
||||
cost_full.record_progress(TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer), FALSE)
|
||||
time_slept.record_progress(TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer), TRUE)
|
||||
time_slept.record_progress((SLEEPABLE_TIMER - timer) * 100, TRUE)
|
||||
|
||||
fire_sleepless(resumed)
|
||||
#undef SLEEPABLE_TIMER
|
||||
|
||||
/datum/controller/subsystem/air/proc/fire_sleepless(resumed)
|
||||
// Any proc that wants MILLA to be synchronous should not sleep.
|
||||
@@ -242,6 +263,19 @@ SUBSYSTEM_DEF(air)
|
||||
in_milla_safe_code = FALSE
|
||||
return
|
||||
resumed = 0
|
||||
currentpart = SSAIR_WINDY_TILES
|
||||
|
||||
if(currentpart == SSAIR_WINDY_TILES)
|
||||
timer = TICK_USAGE_REAL
|
||||
|
||||
process_windy_tiles(resumed)
|
||||
|
||||
cost_windy_tiles.record_progress(TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer), state != SS_PAUSED && state != SS_PAUSING)
|
||||
cost_full.record_progress(TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer), FALSE)
|
||||
if(state == SS_PAUSED || state == SS_PAUSING)
|
||||
in_milla_safe_code = FALSE
|
||||
return
|
||||
resumed = 0
|
||||
currentpart = SSAIR_BOUND_MIXTURES
|
||||
|
||||
if(currentpart == SSAIR_BOUND_MIXTURES)
|
||||
@@ -255,6 +289,19 @@ SUBSYSTEM_DEF(air)
|
||||
in_milla_safe_code = FALSE
|
||||
return
|
||||
resumed = 0
|
||||
currentpart = SSAIR_PRESSURE_OVERLAY
|
||||
|
||||
if(currentpart == SSAIR_PRESSURE_OVERLAY)
|
||||
timer = TICK_USAGE_REAL
|
||||
|
||||
process_pressure_overlay(resumed)
|
||||
|
||||
cost_pressure_tiles.record_progress(TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer), state != SS_PAUSED && state != SS_PAUSING)
|
||||
cost_full.record_progress(TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer), FALSE)
|
||||
if(state == SS_PAUSED || state == SS_PAUSING)
|
||||
in_milla_safe_code = FALSE
|
||||
return
|
||||
resumed = 0
|
||||
currentpart = SSAIR_MILLA_TICK
|
||||
|
||||
if(currentpart == SSAIR_MILLA_TICK)
|
||||
@@ -309,27 +356,26 @@ SUBSYSTEM_DEF(air)
|
||||
src.currentrun = atmos_machinery.Copy()
|
||||
//cache for sanic speed (lists are references anyways)
|
||||
var/list/currentrun = src.currentrun
|
||||
var/list/supermatters = list()
|
||||
while(length(currentrun))
|
||||
var/obj/machinery/atmospherics/M = currentrun[length(currentrun)]
|
||||
currentrun.len--
|
||||
if(isnull(M) || (M.process_atmos(seconds) == PROCESS_KILL))
|
||||
if(istype(M, /obj/machinery/atmospherics/supermatter_crystal))
|
||||
supermatters += M
|
||||
else if(isnull(M) || (M.process_atmos(seconds) == PROCESS_KILL))
|
||||
atmos_machinery -= M
|
||||
if(MC_TICK_CHECK)
|
||||
for(var/SM in supermatters)
|
||||
currentrun += SM
|
||||
return
|
||||
|
||||
/datum/controller/subsystem/air/proc/process_hotspots(resumed = 0)
|
||||
if(!resumed)
|
||||
src.currentrun = hotspots.Copy()
|
||||
//cache for sanic speed (lists are references anyways)
|
||||
var/list/currentrun = src.currentrun
|
||||
while(length(currentrun))
|
||||
var/obj/effect/hotspot/H = currentrun[length(currentrun)]
|
||||
currentrun.len--
|
||||
if(H)
|
||||
H.process()
|
||||
else
|
||||
hotspots -= H
|
||||
while(length(supermatters))
|
||||
var/obj/machinery/atmospherics/supermatter_crystal/SM = supermatters[length(supermatters)]
|
||||
supermatters.len--
|
||||
if(isnull(SM) || (SM.process_atmos(seconds) == PROCESS_KILL))
|
||||
atmos_machinery -= SM
|
||||
if(MC_TICK_CHECK)
|
||||
for(var/other_sm in supermatters)
|
||||
currentrun += other_sm
|
||||
return
|
||||
|
||||
/datum/controller/subsystem/air/proc/process_interesting_tiles(resumed = 0)
|
||||
@@ -371,18 +417,183 @@ SUBSYSTEM_DEF(air)
|
||||
S.update_visuals()
|
||||
|
||||
if(reasons & MILLA_INTERESTING_REASON_HOT)
|
||||
var/datum/gas_mixture/air = T.get_readonly_air()
|
||||
T.hotspot_expose(air.temperature(), CELL_VOLUME)
|
||||
for(var/atom/movable/item in T)
|
||||
item.temperature_expose(air, air.temperature(), CELL_VOLUME)
|
||||
T.temperature_expose(air, air.temperature(), CELL_VOLUME)
|
||||
var/turf/simulated/S = T
|
||||
if(istype(S))
|
||||
if(isnull(S.active_hotspot))
|
||||
// Wasn't an active hotspot before, add it.
|
||||
hotspots += S
|
||||
|
||||
var/datum/gas_mixture/air = T.get_readonly_air()
|
||||
T.temperature_expose(air.temperature())
|
||||
for(var/atom/movable/item in T)
|
||||
item.temperature_expose(air, air.temperature(), CELL_VOLUME)
|
||||
T.temperature_expose(air, air.temperature(), CELL_VOLUME)
|
||||
|
||||
if(reasons & MILLA_INTERESTING_REASON_WIND)
|
||||
var/turf/simulated/S = T
|
||||
if(istype(S))
|
||||
if(isnull(S.wind_tick))
|
||||
// Didn't have wind before, add it.
|
||||
windy_tiles += S
|
||||
S.wind_tick = times_fired
|
||||
S.wind_x = x_flow
|
||||
S.wind_y = y_flow
|
||||
T.high_pressure_movements(x_flow, y_flow)
|
||||
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
/datum/controller/subsystem/air/proc/process_hotspots(resumed = 0)
|
||||
if(!resumed)
|
||||
src.currentrun = hotspots
|
||||
new_hotspots = list()
|
||||
hotspot_count = length(src.currentrun)
|
||||
//cache for sanic speed (lists are references anyways)
|
||||
var/list/currentrun = src.currentrun
|
||||
while(length(currentrun))
|
||||
var/turf/simulated/S = currentrun[length(currentrun)]
|
||||
currentrun.len--
|
||||
|
||||
if(!istype(S))
|
||||
continue
|
||||
|
||||
if(S.update_hotspot())
|
||||
// Is still a hotspot, keep it.
|
||||
new_hotspots += S
|
||||
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
hotspots = new_hotspots
|
||||
|
||||
/datum/controller/subsystem/air/proc/process_windy_tiles(resumed = 0)
|
||||
if(!resumed)
|
||||
src.currentrun = windy_tiles
|
||||
new_windy_tiles = list()
|
||||
//cache for sanic speed (lists are references anyways)
|
||||
var/list/currentrun = src.currentrun
|
||||
while(length(currentrun))
|
||||
var/turf/simulated/S = currentrun[length(currentrun)]
|
||||
currentrun.len--
|
||||
|
||||
if(!istype(S))
|
||||
continue
|
||||
|
||||
if(S.update_wind())
|
||||
// Still has wind, keep it.
|
||||
new_windy_tiles += S
|
||||
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
windy_tiles = new_windy_tiles
|
||||
|
||||
/datum/controller/subsystem/air/proc/process_pressure_overlay(resumed = 0)
|
||||
if(!resumed)
|
||||
currentrun = list(get_tracked_pressure_tiles(), pressure_hud_users)
|
||||
pressure_hud_users = list()
|
||||
|
||||
//cache for sanic speed (lists are references anyways)
|
||||
var/list/tracked_tiles = currentrun[1]
|
||||
while(length(tracked_tiles))
|
||||
var/x = tracked_tiles[length(tracked_tiles) - 3]
|
||||
var/y = tracked_tiles[length(tracked_tiles) - 2]
|
||||
var/z = tracked_tiles[length(tracked_tiles) - 1]
|
||||
var/pressure = tracked_tiles[length(tracked_tiles) - 0]
|
||||
tracked_tiles.len -= 4
|
||||
|
||||
var/turf/tile = locate(x, y, z)
|
||||
if(istype(tile))
|
||||
var/obj/effect/pressure_overlay/pressure_overlay = tile.ensure_pressure_overlay()
|
||||
var/ratio = pressure / ONE_ATMOSPHERE
|
||||
pressure_overlay.overlay.color = rgb((1 - ratio) * 255, 0, ratio * 255)
|
||||
pressure_overlay.overlay.alpha = pressure_overlay_alpha
|
||||
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
var/datum/atom_hud/data/pressure/hud = GLOB.huds[DATA_HUD_PRESSURE]
|
||||
|
||||
//cache for sanic speed (lists are references anyways)
|
||||
var/list/users = currentrun[2]
|
||||
while(length(users))
|
||||
var/mob/user = users[length(users)]
|
||||
var/turf/oldloc = users[user]
|
||||
users.len--
|
||||
|
||||
if(!istype(user) || QDELETED(user) || isnull(user.client) || !istype(oldloc))
|
||||
continue
|
||||
|
||||
if(user in hud.hudusers)
|
||||
var/turf/newloc = get_turf(user)
|
||||
if(oldloc == newloc)
|
||||
// Ya ain't moved, son. Ya get the same tiles ya already had.
|
||||
continue
|
||||
clear_pressure_hud(user, oldloc, FALSE)
|
||||
add_pressure_hud(user, oldloc, FALSE)
|
||||
pressure_hud_users[user] = newloc
|
||||
else
|
||||
clear_pressure_hud(user, oldloc, TRUE)
|
||||
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
for(var/mob/user in hud.hudusers)
|
||||
if(isnull(user.client))
|
||||
continue
|
||||
track_pressure_tiles(user, PRESSURE_HUD_LOAD_RADIUS)
|
||||
if(!(user in pressure_hud_users))
|
||||
pressure_hud_users[user] = get_turf(user)
|
||||
|
||||
// Clean up currentrun so we don't confuse the next step.
|
||||
currentrun = list()
|
||||
|
||||
/datum/controller/subsystem/air/proc/clear_pressure_hud(mob/user, turf/oldloc, full_clear)
|
||||
if(!istype(oldloc) || isnull(user.client))
|
||||
return
|
||||
var/turf/newloc = get_turf(user)
|
||||
if(!istype(newloc))
|
||||
full_clear = TRUE
|
||||
else if(oldloc.z != newloc.z)
|
||||
full_clear = TRUE
|
||||
|
||||
for(var/x in oldloc.x - PRESSURE_HUD_RADIUS to oldloc.x + PRESSURE_HUD_RADIUS)
|
||||
if(x < 1 || x > world.maxx)
|
||||
continue
|
||||
|
||||
for(var/y in oldloc.y - PRESSURE_HUD_RADIUS to oldloc.y + PRESSURE_HUD_RADIUS)
|
||||
if(y < 1 || y > world.maxy)
|
||||
continue
|
||||
if(!full_clear && abs(newloc.x - x) <= PRESSURE_HUD_RADIUS && abs(newloc.y - y) <= PRESSURE_HUD_RADIUS)
|
||||
continue
|
||||
|
||||
var/turf/tile = locate(x, y, oldloc.z)
|
||||
var/obj/effect/pressure_overlay/pressure_overlay = tile.ensure_pressure_overlay()
|
||||
user.client.images -= pressure_overlay.overlay
|
||||
|
||||
/datum/controller/subsystem/air/proc/add_pressure_hud(mob/user, turf/oldloc, full_send)
|
||||
var/turf/newloc = get_turf(user)
|
||||
if(!istype(newloc) || isnull(user.client))
|
||||
return
|
||||
if(!istype(oldloc))
|
||||
full_send = TRUE
|
||||
else if(oldloc.z != newloc.z)
|
||||
full_send = TRUE
|
||||
|
||||
for(var/x in newloc.x - PRESSURE_HUD_RADIUS to newloc.x + PRESSURE_HUD_RADIUS)
|
||||
if(x < 1 || x > world.maxx)
|
||||
continue
|
||||
|
||||
for(var/y in newloc.y - PRESSURE_HUD_RADIUS to newloc.y + PRESSURE_HUD_RADIUS)
|
||||
if(y < 1 || y > world.maxy)
|
||||
continue
|
||||
if(!full_send && abs(oldloc.x - x) <= PRESSURE_HUD_RADIUS && abs(oldloc.y - y) <= PRESSURE_HUD_RADIUS)
|
||||
continue
|
||||
|
||||
var/turf/tile = locate(x, y, newloc.z)
|
||||
var/obj/effect/pressure_overlay/pressure_overlay = tile.ensure_pressure_overlay()
|
||||
user.client.images += pressure_overlay.overlay
|
||||
|
||||
/datum/controller/subsystem/air/proc/process_bound_mixtures(resumed = 0)
|
||||
if(!resumed)
|
||||
original_bound_mixtures = length(bound_mixtures)
|
||||
@@ -531,6 +742,21 @@ SUBSYSTEM_DEF(air)
|
||||
|
||||
SSair.on_milla_tick_finished()
|
||||
|
||||
/proc/milla_tick_error(err)
|
||||
// Any proc that wants MILLA to be synchronous should not sleep.
|
||||
SHOULD_NOT_SLEEP(TRUE)
|
||||
|
||||
log_debug(err)
|
||||
var/msg = "MILLA has crashed. SSair will stop running, and all atmospherics will stop functioning. Every turf will report as full of breathable air, and all fires will be extinguished. Shuttle call highly recommended."
|
||||
to_chat(GLOB.admins, "<span class='boldannounceooc'>[msg]</span>")
|
||||
log_world(msg)
|
||||
|
||||
// Disable firing.
|
||||
SSair.flags |= SS_NO_FIRE
|
||||
// Disable fire, too.
|
||||
for(var/turf/simulated/S in SSair.hotspots)
|
||||
QDEL_NULL(S.active_hotspot)
|
||||
|
||||
/// Create a subclass of this and implement `on_run` to manipulate tile air safely.
|
||||
/datum/milla_safe
|
||||
var/run_args = list()
|
||||
@@ -589,5 +815,7 @@ SUBSYSTEM_DEF(air)
|
||||
#undef SSAIR_ATMOSMACHINERY
|
||||
#undef SSAIR_INTERESTING_TILES
|
||||
#undef SSAIR_HOTSPOTS
|
||||
#undef SSAIR_WINDY_TILES
|
||||
#undef SSAIR_BOUND_MIXTURES
|
||||
#undef SSAIR_PRESSURE_OVERLAY
|
||||
#undef SSAIR_MILLA_TICK
|
||||
|
||||
@@ -15,7 +15,7 @@ SUBSYSTEM_DEF(debugview)
|
||||
// Generate debug text
|
||||
var/list/entries = list()
|
||||
entries += "CPU: [round(world.cpu, 1)] | MCPU: [round(world.map_cpu, 1)] | FPS/TPS: [world.fps] | Clients: [length(GLOB.clients)] | BYOND: [world.byond_version].[world.byond_build]"
|
||||
entries += "\[Air] Cost: [SSair.get_cost()]ms | MT: [round(SSair.cost_milla_tick, 1)]ms | IT: [SSair.interesting_tile_count]"
|
||||
entries += "\[Air] Cost: [SSair.get_cost()]ms | MT: [round(SSair.cost_milla_tick, 1)]ms | IT: [SSair.interesting_tile_count] | HS: [SSair.hotspot_count] | WT: [SSair.windy_tile_count]"
|
||||
entries += "\[Debug] Cost: [round(SSdebugview.cost, 1)]ms | P: [length(SSdebugview.processing)]" // meta af (tbf we need to know how much were using)
|
||||
entries += "\[FP] Cost: [round(SSfastprocess.cost, 1)]ms | P: [length(SSfastprocess.processing)]"
|
||||
// Snowflakery for SSgarbage
|
||||
|
||||
@@ -11,6 +11,7 @@ GLOBAL_LIST_INIT(huds, list(
|
||||
DATA_HUD_DIAGNOSTIC_ADVANCED = new/datum/atom_hud/data/diagnostic/advanced(),
|
||||
DATA_HUD_HYDROPONIC = new/datum/atom_hud/data/hydroponic(),
|
||||
DATA_HUD_JANITOR = new/datum/atom_hud/data/janitor(),
|
||||
DATA_HUD_PRESSURE = new/datum/atom_hud/data/pressure(),
|
||||
ANTAG_HUD_CULT = new/datum/atom_hud/antag(),
|
||||
ANTAG_HUD_REV = new/datum/atom_hud/antag(),
|
||||
ANTAG_HUD_OPS = new/datum/atom_hud/antag(),
|
||||
|
||||
@@ -696,6 +696,18 @@
|
||||
if(isnum(_slowdown_value))
|
||||
slowdown_value = _slowdown_value
|
||||
|
||||
// Directional slow - Like slowed, but only if you're moving in a certain direction.
|
||||
/datum/status_effect/incapacitating/directional_slow
|
||||
id = "directional_slow"
|
||||
var/direction
|
||||
var/slowdown_value = 10 // defaults to this value if none is specified
|
||||
|
||||
/datum/status_effect/incapacitating/directional_slow/on_creation(mob/living/new_owner, set_duration, _direction, _slowdown_value)
|
||||
. = ..()
|
||||
direction = _direction
|
||||
if(isnum(_slowdown_value))
|
||||
slowdown_value = _slowdown_value
|
||||
|
||||
/datum/status_effect/transient/silence
|
||||
id = "silenced"
|
||||
|
||||
@@ -1355,6 +1367,18 @@
|
||||
new /obj/effect/abstract/bubblegum_rend_helper(get_turf(src), null, 10)
|
||||
qdel(src)
|
||||
|
||||
/// The mob has been pushed by airflow recently, and won't automatically grab nearby objects to stop drifting.
|
||||
/datum/status_effect/unbalanced
|
||||
id = "unbalanced"
|
||||
duration = 1 SECONDS
|
||||
status_type = STATUS_EFFECT_REFRESH
|
||||
alert_type = /atom/movable/screen/alert/status_effect/unbalanced
|
||||
|
||||
/atom/movable/screen/alert/status_effect/unbalanced
|
||||
name = "Unbalanced"
|
||||
desc = "You're being shoved around by airflow! You can resist this by moving, but moving against the wind will be slow."
|
||||
icon_state = "unbalanced"
|
||||
|
||||
/datum/status_effect/c_foamed
|
||||
id = "c_foamed up"
|
||||
duration = 1 MINUTES
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
/datum/wires/alarm/New(atom/_holder)
|
||||
wires = list(
|
||||
WIRE_IDSCAN , WIRE_MAIN_POWER1 , WIRE_SYPHON,
|
||||
WIRE_IDSCAN , WIRE_MAIN_POWER1 , WIRE_SIPHON,
|
||||
WIRE_AI_CONTROL, WIRE_AALARM
|
||||
)
|
||||
return ..()
|
||||
@@ -39,9 +39,9 @@
|
||||
if(WIRE_AI_CONTROL)
|
||||
A.aidisabled = !mend
|
||||
|
||||
if(WIRE_SYPHON)
|
||||
if(WIRE_SIPHON)
|
||||
if(!mend)
|
||||
A.mode = 3 // AALARM_MODE_PANIC
|
||||
A.mode = AALARM_MODE_PANIC
|
||||
A.apply_mode()
|
||||
|
||||
if(WIRE_AALARM)
|
||||
@@ -68,11 +68,11 @@
|
||||
addtimer(CALLBACK(A, TYPE_PROC_REF(/obj/machinery/alarm, enable_ai_control_callback)), 10 SECONDS)
|
||||
|
||||
|
||||
if(WIRE_SYPHON)
|
||||
if(A.mode == 1) // AALARM_MODE_SCRUB
|
||||
A.mode = 3 // AALARM_MODE_PANIC
|
||||
if(WIRE_SIPHON)
|
||||
if(A.mode == AALARM_MODE_FILTERING)
|
||||
A.mode = AALARM_MODE_PANIC
|
||||
else
|
||||
A.mode = 1 // AALARM_MODE_SCRUB
|
||||
A.mode = AALARM_MODE_FILTERING
|
||||
A.apply_mode()
|
||||
|
||||
if(WIRE_AALARM)
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
// Atmos
|
||||
var/pressure_resistance = 10
|
||||
var/last_high_pressure_movement_air_cycle = 0
|
||||
var/last_high_pressure_movement_time = 0
|
||||
|
||||
/// UID for the atom which the current atom is orbiting
|
||||
var/orbiting_uid = null
|
||||
|
||||
@@ -60,6 +60,14 @@
|
||||
/datum/atom_hud/data/janitor
|
||||
hud_icons = list(JANI_HUD)
|
||||
|
||||
/datum/atom_hud/data/pressure
|
||||
hud_icons = list(PRESSURE_HUD)
|
||||
|
||||
/// Pressure hud is special, because it doesn't use hudatoms. SSair manages its images, so tell SSair to add the initial set.
|
||||
/datum/atom_hud/data/pressure/add_hud_to(mob/user)
|
||||
..()
|
||||
SSair.add_pressure_hud(user)
|
||||
|
||||
/* MED/SEC/DIAG HUD HOOKS */
|
||||
|
||||
/*
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
if(prob(30))
|
||||
do_sparks(2, 1, T)
|
||||
T.ex_act(EXPLODE_LIGHT)
|
||||
T.hotspot_expose(1000,500,1)
|
||||
T.hotspot_expose(1000,500)
|
||||
|
||||
/obj/machinery/computer/holodeck_control/proc/loadProgram(area/A)
|
||||
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
/obj/machinery/computer/atmos_alert/Initialize(mapload)
|
||||
. = ..()
|
||||
alarm_cache = list()
|
||||
alarm_cache["minor"] = list()
|
||||
alarm_cache["priority"] = list()
|
||||
alarm_cache["minor"] = list()
|
||||
alarm_cache["mode"] = list()
|
||||
var/area/machine_area = get_area(src)
|
||||
parent_area_type = machine_area.get_top_parent_type()
|
||||
|
||||
@@ -34,6 +35,10 @@
|
||||
else
|
||||
alarm_cache["priority"] -= air_alarm.alarm_area.name
|
||||
alarm_cache["minor"] -= air_alarm.alarm_area.name
|
||||
if(air_alarm.mode == AALARM_MODE_FILTERING)
|
||||
alarm_cache["mode"] -= air_alarm.alarm_area.name
|
||||
else
|
||||
alarm_cache["mode"][air_alarm.alarm_area.name] = GLOB.aalarm_modes["[air_alarm.mode]"]
|
||||
|
||||
update_icon()
|
||||
|
||||
|
||||
@@ -1284,6 +1284,7 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays)
|
||||
return TRUE
|
||||
SEND_SIGNAL(src, COMSIG_AIRLOCK_OPEN)
|
||||
operating = DOOR_OPENING
|
||||
recalculate_atmos_connectivity()
|
||||
update_icon(AIRLOCK_OPENING, 1)
|
||||
sleep(1)
|
||||
set_opacity(0)
|
||||
@@ -1294,7 +1295,6 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays)
|
||||
density = FALSE
|
||||
if(width > 1)
|
||||
set_fillers_density(FALSE)
|
||||
recalculate_atmos_connectivity()
|
||||
sleep(1)
|
||||
layer = OPEN_DOOR_LAYER
|
||||
update_icon(AIRLOCK_OPEN, 1)
|
||||
@@ -1336,7 +1336,6 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays)
|
||||
density = TRUE
|
||||
if(width > 1)
|
||||
set_fillers_density(TRUE)
|
||||
recalculate_atmos_connectivity()
|
||||
if(!override)
|
||||
sleep(4)
|
||||
if(!safe)
|
||||
@@ -1349,6 +1348,7 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays)
|
||||
sleep(1)
|
||||
update_icon(AIRLOCK_CLOSED, 1)
|
||||
operating = NONE
|
||||
recalculate_atmos_connectivity()
|
||||
if(safe)
|
||||
CheckForMobs()
|
||||
return TRUE
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
return !density
|
||||
|
||||
/obj/machinery/door/CanAtmosPass(direction)
|
||||
return !density
|
||||
return operating || !density
|
||||
|
||||
/obj/machinery/door/get_superconductivity(direction)
|
||||
if(!density)
|
||||
@@ -373,6 +373,7 @@
|
||||
return
|
||||
SEND_SIGNAL(src, COMSIG_DOOR_OPEN)
|
||||
operating = DOOR_OPENING
|
||||
recalculate_atmos_connectivity()
|
||||
do_animate("opening")
|
||||
set_opacity(0)
|
||||
if(width > 1)
|
||||
@@ -388,7 +389,6 @@
|
||||
if(width > 1)
|
||||
set_fillers_opacity(0)
|
||||
operating = NONE
|
||||
recalculate_atmos_connectivity()
|
||||
update_freelook_sight()
|
||||
if(autoclose)
|
||||
autoclose_in(normalspeed ? auto_close_time : auto_close_time_dangerous)
|
||||
|
||||
@@ -204,6 +204,7 @@
|
||||
return 0
|
||||
if(!operating) //in case of emag
|
||||
operating = DOOR_OPENING
|
||||
recalculate_atmos_connectivity()
|
||||
do_animate("opening")
|
||||
set_opacity(FALSE)
|
||||
playsound(loc, 'sound/machines/windowdoor.ogg', 100, 1)
|
||||
@@ -235,11 +236,11 @@
|
||||
density = TRUE
|
||||
if(polarized_on)
|
||||
set_opacity(TRUE)
|
||||
recalculate_atmos_connectivity()
|
||||
update_freelook_sight()
|
||||
sleep(10)
|
||||
|
||||
operating = NONE
|
||||
recalculate_atmos_connectivity()
|
||||
return 1
|
||||
|
||||
/obj/machinery/door/window/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
if(on && !(stat & NOPOWER))
|
||||
var/turf/location = get_turf(src)
|
||||
if(isturf(location))
|
||||
location.hotspot_expose(1000, 500, 1)
|
||||
location.hotspot_expose(1000, 1)
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/igniter/Initialize(mapload)
|
||||
@@ -132,7 +132,7 @@
|
||||
|
||||
var/turf/location = get_turf(src)
|
||||
if(isturf(location))
|
||||
location.hotspot_expose(1000, 500, 1)
|
||||
location.hotspot_expose(1000, 500)
|
||||
|
||||
return TRUE
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
category = VENDOR_TYPE_DEPARTMENTAL
|
||||
req_one_access = list(ACCESS_ENGINE_EQUIP, ACCESS_ATMOSPHERICS)
|
||||
products = list(/obj/item/clothing/glasses/meson/engine = 2,
|
||||
/obj/item/clothing/glasses/meson/engine/tray = 4,
|
||||
/obj/item/clothing/glasses/meson/engine/atmos = 4,
|
||||
/obj/item/multitool = 4,
|
||||
/obj/item/geiger_counter = 5,
|
||||
/obj/item/airlock_electronics = 10,
|
||||
|
||||
@@ -341,7 +341,6 @@
|
||||
/obj/effect/anomaly/pyro
|
||||
name = "pyroclastic anomaly"
|
||||
icon_state = "mustard"
|
||||
var/ticks = 0
|
||||
var/produces_slime = TRUE
|
||||
aSignal = /obj/item/assembly/signaler/anomaly/pyro
|
||||
|
||||
@@ -351,16 +350,11 @@
|
||||
|
||||
/obj/effect/anomaly/pyro/anomalyEffect()
|
||||
..()
|
||||
ticks++
|
||||
for(var/mob/living/M in hearers(4, src))
|
||||
if(prob(50))
|
||||
M.adjust_fire_stacks(4)
|
||||
M.IgniteMob()
|
||||
|
||||
if(ticks < 4)
|
||||
return
|
||||
else
|
||||
ticks = 0
|
||||
var/turf/simulated/T = get_turf(src)
|
||||
if(istype(T))
|
||||
var/datum/gas_mixture/air = new()
|
||||
@@ -382,8 +376,8 @@
|
||||
//Make it hot and burny for the new slime
|
||||
var/datum/gas_mixture/air = new()
|
||||
air.set_temperature(1000)
|
||||
air.set_toxins(500)
|
||||
air.set_oxygen(500)
|
||||
air.set_toxins(125)
|
||||
air.set_oxygen(125)
|
||||
T.blind_release_air(air)
|
||||
var/new_colour = pick("red", "orange")
|
||||
var/mob/living/simple_animal/slime/S = new(T, new_colour)
|
||||
|
||||
@@ -175,53 +175,71 @@
|
||||
name = "nanofrost smoke"
|
||||
color = "#B2FFFF"
|
||||
opacity = FALSE
|
||||
var/spread_range = 6
|
||||
lifetime = 0.5 SECONDS_TO_LIFE_CYCLES
|
||||
|
||||
/datum/effect_system/smoke_spread/freezing
|
||||
effect_type = /obj/effect/particle_effect/smoke/freezing
|
||||
var/blast = FALSE
|
||||
|
||||
/datum/effect_system/smoke_spread/freezing/proc/Chilled(atom/A)
|
||||
if(issimulatedturf(A))
|
||||
var/turf/simulated/T = A
|
||||
if(!T.blocks_air)
|
||||
var/datum/milla_safe/smoke_spread_chill/milla = new()
|
||||
milla.invoke_async(src, T)
|
||||
for(var/obj/machinery/atmospherics/unary/vent_pump/V in T)
|
||||
if(!isnull(V.welded) && !V.welded) //must be an unwelded vent pump.
|
||||
V.welded = TRUE
|
||||
V.update_icon()
|
||||
V.visible_message("<span class='danger'>[V] was frozen shut!</span>")
|
||||
for(var/obj/machinery/atmospherics/unary/vent_scrubber/U in T)
|
||||
if(!isnull(U.welded) && !U.welded) //must be an unwelded vent scrubber.
|
||||
U.welded = TRUE
|
||||
U.update_icon()
|
||||
U.visible_message("<span class='danger'>[U] was frozen shut!</span>")
|
||||
for(var/mob/living/L in T)
|
||||
L.ExtinguishMob()
|
||||
for(var/obj/item/Item in T)
|
||||
Item.extinguish()
|
||||
/obj/effect/particle_effect/smoke/freezing/process()
|
||||
. = ..()
|
||||
var/turf/T = get_turf(src)
|
||||
if(!istype(T))
|
||||
return
|
||||
|
||||
/datum/milla_safe/smoke_spread_chill
|
||||
for(var/obj/machinery/atmospherics/unary/vent_pump/V in T)
|
||||
if(!isnull(V.welded) && !V.welded) //must be an unwelded vent pump.
|
||||
V.welded = TRUE
|
||||
V.update_icon()
|
||||
V.visible_message("<span class='danger'>[V] was frozen shut!</span>")
|
||||
for(var/obj/machinery/atmospherics/unary/vent_scrubber/U in T)
|
||||
if(!isnull(U.welded) && !U.welded) //must be an unwelded vent scrubber.
|
||||
U.welded = TRUE
|
||||
U.update_icon()
|
||||
U.visible_message("<span class='danger'>[U] was frozen shut!</span>")
|
||||
for(var/mob/living/L in T)
|
||||
L.ExtinguishMob()
|
||||
for(var/obj/item/Item in T)
|
||||
Item.extinguish()
|
||||
|
||||
/datum/milla_safe/smoke_spread_chill/on_run(datum/effect_system/smoke_spread/smoke, turf/T)
|
||||
var/datum/milla_safe/nanofrost_extinguish/milla = new()
|
||||
milla.invoke_async(src, T)
|
||||
|
||||
/datum/milla_safe/nanofrost_extinguish/on_run(obj/effect/particle_effect/smoke/freezing/smoke, turf/T)
|
||||
if(!istype(T))
|
||||
return
|
||||
var/datum/gas_mixture/env = get_turf_air(T)
|
||||
if(get_dist(T, smoke) < 2) // Otherwise we'll get silliness like people using Nanofrost to kill people through walls with cold air
|
||||
env.set_temperature(2)
|
||||
for(var/obj/effect/hotspot/H in T)
|
||||
for(var/obj/effect/hotspot/fake/H in T)
|
||||
qdel(H)
|
||||
if(env.toxins())
|
||||
env.set_nitrogen(env.nitrogen() + env.toxins())
|
||||
env.set_toxins(0)
|
||||
|
||||
/datum/effect_system/smoke_spread/freezing/set_up(amount = 5, only_cardinals = FALSE, source, desired_direction, datum/reagents/chemicals, blasting = FALSE)
|
||||
..()
|
||||
blast = blasting
|
||||
if(env.fuel_burnt() == 0)
|
||||
return
|
||||
|
||||
/datum/effect_system/smoke_spread/freezing/start()
|
||||
if(blast)
|
||||
for(var/turf/T in RANGE_TURFS(2, location))
|
||||
Chilled(T)
|
||||
..()
|
||||
env.set_temperature(2)
|
||||
env.set_nitrogen(env.nitrogen() + env.toxins())
|
||||
env.set_toxins(0)
|
||||
|
||||
if(smoke.spread_range <= 0 || smoke.lifetime > 0)
|
||||
return
|
||||
|
||||
// Spread the smoke, up to spread_range.
|
||||
for(var/direction in list(NORTH, SOUTH, EAST, WEST))
|
||||
var/turf/neighbor = get_step(T, direction)
|
||||
if(isnull(neighbor))
|
||||
continue
|
||||
if(neighbor.density)
|
||||
continue
|
||||
var/has_nanofrost = FALSE
|
||||
for(var/datum/effect_system/smoke_spread/freezing in neighbor)
|
||||
has_nanofrost = TRUE
|
||||
break
|
||||
if(has_nanofrost)
|
||||
continue
|
||||
if(prob(50))
|
||||
continue
|
||||
|
||||
var/obj/effect/particle_effect/smoke/freezing/new_smoke = new(neighbor)
|
||||
new_smoke.spread_range = smoke.spread_range - 1
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// Sleep smoke
|
||||
|
||||
@@ -26,20 +26,20 @@
|
||||
playsound(src, "sparks", 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
|
||||
var/turf/T = loc
|
||||
if(isturf(T))
|
||||
T.hotspot_expose(hotspottemp, 100)
|
||||
T.hotspot_expose(hotspottemp, 1)
|
||||
QDEL_IN(src, 20)
|
||||
|
||||
/obj/effect/particle_effect/sparks/Destroy()
|
||||
var/turf/T = loc
|
||||
if(isturf(T))
|
||||
T.hotspot_expose(hotspottemp,100)
|
||||
T.hotspot_expose(hotspottemp,1)
|
||||
return ..()
|
||||
|
||||
/obj/effect/particle_effect/sparks/Move()
|
||||
..()
|
||||
var/turf/T = loc
|
||||
if(isturf(T))
|
||||
T.hotspot_expose(hotspottemp,100)
|
||||
T.hotspot_expose(hotspottemp,1)
|
||||
|
||||
/datum/effect_system/spark_spread
|
||||
effect_type = /obj/effect/particle_effect/sparks
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
qdel(src)
|
||||
if(isturf(loc)) //start a fire if possible
|
||||
var/turf/T = loc
|
||||
T.hotspot_expose(700, 5)
|
||||
T.hotspot_expose(700, 1)
|
||||
|
||||
/obj/item/candle/proc/unlight()
|
||||
if(lit)
|
||||
|
||||
@@ -188,7 +188,7 @@
|
||||
/obj/item/flashlight/flare/process()
|
||||
var/turf/pos = get_turf(src)
|
||||
if(pos && produce_heat)
|
||||
pos.hotspot_expose(produce_heat, 5)
|
||||
pos.hotspot_expose(produce_heat, 1)
|
||||
fuel = max(fuel - 1, 0)
|
||||
if(!fuel || !on)
|
||||
turn_off()
|
||||
|
||||
@@ -625,18 +625,17 @@ SLIME SCANNER
|
||||
* Used in chat-based gas scans.
|
||||
*/
|
||||
/proc/atmos_scan(mob/user, atom/target, silent = FALSE, print = TRUE, milla_turf_details = FALSE)
|
||||
var/mixture
|
||||
var/datum/gas_mixture/air
|
||||
var/list/milla = null
|
||||
if(milla_turf_details)
|
||||
if(milla_turf_details && istype(target, /turf))
|
||||
milla = new/list(MILLA_TILE_SIZE)
|
||||
get_tile_atmos(target, milla)
|
||||
var/datum/gas_mixture/GM = new()
|
||||
GM.copy_from_milla(milla)
|
||||
mixture = GM
|
||||
air = new()
|
||||
air.copy_from_milla(milla)
|
||||
else
|
||||
mixture = target.return_analyzable_air()
|
||||
if(!mixture)
|
||||
return FALSE
|
||||
air = target.return_analyzable_air()
|
||||
if(!air)
|
||||
return FALSE
|
||||
|
||||
var/list/message = list()
|
||||
if(!silent && isliving(user))
|
||||
@@ -646,64 +645,53 @@ SLIME SCANNER
|
||||
if(!print)
|
||||
return TRUE
|
||||
|
||||
var/list/airs = islist(mixture) ? mixture : list(mixture)
|
||||
for(var/datum/gas_mixture/air as anything in airs)
|
||||
var/mix_name = capitalize(lowertext(target.name))
|
||||
if(length(air) > 1) //not a unary gas mixture
|
||||
var/mix_number = airs.Find(air)
|
||||
message += "<span class='boldnotice'>Node [mix_number]</span>"
|
||||
mix_name += " - Node [mix_number]"
|
||||
var/total_moles = air.total_moles()
|
||||
var/pressure = air.return_pressure()
|
||||
var/volume = air.return_volume() //could just do mixture.volume... but safety, I guess?
|
||||
var/heat_capacity = air.heat_capacity()
|
||||
var/thermal_energy = air.thermal_energy()
|
||||
|
||||
var/total_moles = air.total_moles()
|
||||
var/pressure = air.return_pressure()
|
||||
var/volume = air.return_volume() //could just do mixture.volume... but safety, I guess?
|
||||
var/heat_capacity = air.heat_capacity()
|
||||
var/thermal_energy = air.thermal_energy()
|
||||
if(total_moles)
|
||||
message += "<span class='notice'>Total: [round(total_moles, 0.01)] moles</span>"
|
||||
if(air.oxygen() && (milla_turf_details || air.oxygen() / total_moles > 0.01))
|
||||
message += " <span class='oxygen'>Oxygen: [round(air.oxygen(), 0.01)] moles ([round(air.oxygen() / total_moles * 100, 0.01)] %)</span>"
|
||||
if(air.nitrogen() && (milla_turf_details || air.nitrogen() / total_moles > 0.01))
|
||||
message += " <span class='nitrogen'>Nitrogen: [round(air.nitrogen(), 0.01)] moles ([round(air.nitrogen() / total_moles * 100, 0.01)] %)</span>"
|
||||
if(air.carbon_dioxide() && (milla_turf_details || air.carbon_dioxide() / total_moles > 0.01))
|
||||
message += " <span class='carbon_dioxide'>Carbon Dioxide: [round(air.carbon_dioxide(), 0.01)] moles ([round(air.carbon_dioxide() / total_moles * 100, 0.01)] %)</span>"
|
||||
if(air.toxins() && (milla_turf_details || air.toxins() / total_moles > 0.01))
|
||||
message += " <span class='plasma'>Plasma: [round(air.toxins(), 0.01)] moles ([round(air.toxins() / total_moles * 100, 0.01)] %)</span>"
|
||||
if(air.sleeping_agent() && (milla_turf_details || air.sleeping_agent() / total_moles > 0.01))
|
||||
message += " <span class='sleeping_agent'>Nitrous Oxide: [round(air.sleeping_agent(), 0.01)] moles ([round(air.sleeping_agent() / total_moles * 100, 0.01)] %)</span>"
|
||||
if(air.agent_b() && (milla_turf_details || air.agent_b() / total_moles > 0.01))
|
||||
message += " <span class='agent_b'>Agent B: [round(air.agent_b(), 0.01)] moles ([round(air.agent_b() / total_moles * 100, 0.01)] %)</span>"
|
||||
message += "<span class='notice'>Temperature: [round(air.temperature()-T0C)] °C ([round(air.temperature())] K)</span>"
|
||||
message += "<span class='notice'>Volume: [round(volume)] Liters</span>"
|
||||
message += "<span class='notice'>Pressure: [round(pressure, 0.1)] kPa</span>"
|
||||
message += "<span class='notice'>Heat Capacity: [DisplayJoules(heat_capacity)] / K</span>"
|
||||
message += "<span class='notice'>Thermal Energy: [DisplayJoules(thermal_energy)]</span>"
|
||||
else
|
||||
message += "<span class='notice'>[target] is empty!</span>"
|
||||
message += "<span class='notice'>Volume: [round(volume)] Liters</span>" // don't want to change the order volume appears in, suck it
|
||||
|
||||
if(total_moles)
|
||||
message += "<span class='notice'>Total: [round(total_moles, 0.01)] moles</span>"
|
||||
if(air.oxygen() && (milla_turf_details || air.oxygen() / total_moles > 0.01))
|
||||
message += " <span class='oxygen'>Oxygen: [round(air.oxygen(), 0.01)] moles ([round(air.oxygen() / total_moles * 100, 0.01)] %)</span>"
|
||||
if(air.nitrogen() && (milla_turf_details || air.nitrogen() / total_moles > 0.01))
|
||||
message += " <span class='nitrogen'>Nitrogen: [round(air.nitrogen(), 0.01)] moles ([round(air.nitrogen() / total_moles * 100, 0.01)] %)</span>"
|
||||
if(air.carbon_dioxide() && (milla_turf_details || air.carbon_dioxide() / total_moles > 0.01))
|
||||
message += " <span class='carbon_dioxide'>Carbon Dioxide: [round(air.carbon_dioxide(), 0.01)] moles ([round(air.carbon_dioxide() / total_moles * 100, 0.01)] %)</span>"
|
||||
if(air.toxins() && (milla_turf_details || air.toxins() / total_moles > 0.01))
|
||||
message += " <span class='plasma'>Plasma: [round(air.toxins(), 0.01)] moles ([round(air.toxins() / total_moles * 100, 0.01)] %)</span>"
|
||||
if(air.sleeping_agent() && (milla_turf_details || air.sleeping_agent() / total_moles > 0.01))
|
||||
message += " <span class='sleeping_agent'>Nitrous Oxide: [round(air.sleeping_agent(), 0.01)] moles ([round(air.sleeping_agent() / total_moles * 100, 0.01)] %)</span>"
|
||||
if(air.agent_b() && (milla_turf_details || air.agent_b() / total_moles > 0.01))
|
||||
message += " <span class='agent_b'>Agent B: [round(air.agent_b(), 0.01)] moles ([round(air.agent_b() / total_moles * 100, 0.01)] %)</span>"
|
||||
message += "<span class='notice'>Temperature: [round(air.temperature()-T0C)] °C ([round(air.temperature())] K)</span>"
|
||||
message += "<span class='notice'>Volume: [round(volume)] Liters</span>"
|
||||
message += "<span class='notice'>Pressure: [round(pressure, 0.1)] kPa</span>"
|
||||
message += "<span class='notice'>Heat Capacity: [DisplayJoules(heat_capacity)] / K</span>"
|
||||
message += "<span class='notice'>Thermal Energy: [DisplayJoules(thermal_energy)]</span>"
|
||||
else
|
||||
message += length(airs) > 1 ? "<span class='notice'>This node is empty!</span>" : "<span class='notice'>[target] is empty!</span>"
|
||||
message += "<span class='notice'>Volume: [round(volume)] Liters</span>" // don't want to change the order volume appears in, suck it
|
||||
|
||||
if(milla)
|
||||
// Values from milla/src/lib.rs, +1 due to array indexing difference.
|
||||
message += "<span class='notice'>Airtight North: [(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_NORTH) ? "yes" : "no"]</span>"
|
||||
message += "<span class='notice'>Airtight East: [(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_EAST) ? "yes" : "no"]</span>"
|
||||
message += "<span class='notice'>Airtight South: [(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_SOUTH) ? "yes" : "no"]</span>"
|
||||
message += "<span class='notice'>Airtight West: [(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_WEST) ? "yes" : "no"]</span>"
|
||||
switch(milla[MILLA_INDEX_ATMOS_MODE])
|
||||
// These are enum values, so they don't get increased.
|
||||
if(0)
|
||||
message += "<span class='notice'>Atmos Mode: Space</span>"
|
||||
if(1)
|
||||
message += "<span class='notice'>Atmos Mode: Sealed</span>"
|
||||
if(2)
|
||||
message += "<span class='notice'>Atmos Mode: Exposed to Environment (ID: [milla[MILLA_INDEX_ENVIRONMENT_ID]])</span>"
|
||||
else
|
||||
message += "<span class='notice'>Atmos Mode: Unknown ([milla[MILLA_INDEX_ATMOS_MODE]]), contact a coder.</span>"
|
||||
message += "<span class='notice'>Superconductivity North: [milla[MILLA_INDEX_SUPERCONDUCTIVITY_NORTH]]</span>"
|
||||
message += "<span class='notice'>Superconductivity East: [milla[MILLA_INDEX_SUPERCONDUCTIVITY_EAST]]</span>"
|
||||
message += "<span class='notice'>Superconductivity South: [milla[MILLA_INDEX_SUPERCONDUCTIVITY_SOUTH]]</span>"
|
||||
message += "<span class='notice'>Superconductivity West: [milla[MILLA_INDEX_SUPERCONDUCTIVITY_WEST]]</span>"
|
||||
message += "<span class='notice'>Turf's Innate Heat Capacity: [milla[MILLA_INDEX_INNATE_HEAT_CAPACITY]]</span>"
|
||||
if(milla)
|
||||
// Values from milla/src/lib.rs, +1 due to array indexing difference.
|
||||
message += "<span class='notice'>Airtight N/E/S/W: [(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_NORTH) ? "yes" : "no"]/[(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_EAST) ? "yes" : "no"]/[(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_SOUTH) ? "yes" : "no"]/[(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_WEST) ? "yes" : "no"]</span>"
|
||||
switch(milla[MILLA_INDEX_ATMOS_MODE])
|
||||
// These are enum values, so they don't get increased.
|
||||
if(0)
|
||||
message += "<span class='notice'>Atmos Mode: Space</span>"
|
||||
if(1)
|
||||
message += "<span class='notice'>Atmos Mode: Sealed</span>"
|
||||
if(2)
|
||||
message += "<span class='notice'>Atmos Mode: Exposed to Environment (ID: [milla[MILLA_INDEX_ENVIRONMENT_ID]])</span>"
|
||||
else
|
||||
message += "<span class='notice'>Atmos Mode: Unknown ([milla[MILLA_INDEX_ATMOS_MODE]]), contact a coder.</span>"
|
||||
message += "<span class='notice'>Superconductivity N/E/S/W: [milla[MILLA_INDEX_SUPERCONDUCTIVITY_NORTH]]/[milla[MILLA_INDEX_SUPERCONDUCTIVITY_EAST]]/[milla[MILLA_INDEX_SUPERCONDUCTIVITY_SOUTH]]/[milla[MILLA_INDEX_SUPERCONDUCTIVITY_WEST]]</span>"
|
||||
message += "<span class='notice'>Turf's Innate Heat Capacity: [milla[MILLA_INDEX_INNATE_HEAT_CAPACITY]]</span>"
|
||||
message += "<span class='notice'>Hotspot: [floor(milla[MILLA_INDEX_HOTSPOT_TEMPERATURE]-T0C)] °C ([floor(milla[MILLA_INDEX_HOTSPOT_TEMPERATURE])] K), [round(milla[MILLA_INDEX_HOTSPOT_VOLUME] * CELL_VOLUME, 1)] Liters ([milla[MILLA_INDEX_HOTSPOT_VOLUME]]x)</span>"
|
||||
message += "<span class='notice'>Wind: ([round(milla[MILLA_INDEX_WIND_X], 0.001)], [round(milla[MILLA_INDEX_WIND_Y], 0.001)])</span>"
|
||||
message += "<span class='notice'>Fuel burnt last tick: [milla[MILLA_INDEX_FUEL_BURNT]] moles</span>"
|
||||
|
||||
to_chat(user, chat_box_examine(message.Join("\n")))
|
||||
return TRUE
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
if(tool_enabled)
|
||||
var/turf/T = get_turf(src)
|
||||
if(T) // Implants for instance won't find a turf
|
||||
T.hotspot_expose(2500, 5)
|
||||
T.hotspot_expose(2500, 1)
|
||||
if(prob(5))
|
||||
remove_fuel(1)
|
||||
if(refills_over_time)
|
||||
|
||||
@@ -269,7 +269,7 @@ LIGHTERS ARE IN LIGHTERS.DM
|
||||
// There used to be a species check here, but synthetics can smoke now
|
||||
is_being_smoked = TRUE
|
||||
if(location)
|
||||
location.hotspot_expose(700, 5)
|
||||
location.hotspot_expose(700, 1)
|
||||
if(reagents && reagents.total_volume) // check if it has any reagents at all
|
||||
if(is_being_smoked) // if it's being smoked, transfer reagents to the mob
|
||||
var/mob/living/carbon/C = loc
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
/obj/item/lighter/process()
|
||||
var/turf/location = get_turf(src)
|
||||
if(location)
|
||||
location.hotspot_expose(700, 5)
|
||||
location.hotspot_expose(700, 1)
|
||||
return
|
||||
|
||||
/obj/item/lighter/update_icon_state()
|
||||
@@ -263,7 +263,7 @@
|
||||
if(smoketime < 1)
|
||||
matchburnout()
|
||||
if(location)
|
||||
location.hotspot_expose(700, 5)
|
||||
location.hotspot_expose(700, 1)
|
||||
return
|
||||
|
||||
/obj/item/match/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE)
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
/obj/item/bio_chip_implanter/shock, // 50TC
|
||||
/obj/item/dnainjector/telemut/darkbundle, // 0TC
|
||||
/obj/item/clothing/suit/hooded/chaplain_hoodie, // 0TC
|
||||
/obj/item/clothing/glasses/meson/engine/tray, // 0TC
|
||||
/obj/item/clothing/glasses/meson/engine/atmos, // 0TC
|
||||
/obj/item/clothing/mask/chameleon/voice_change, // 10TC
|
||||
/obj/item/card/id/syndicate, // 10TC
|
||||
/obj/item/clothing/shoes/chameleon/noslip, // 10TC
|
||||
|
||||
@@ -328,7 +328,7 @@
|
||||
|
||||
/obj/effect/nanofrost_container/proc/Smoke()
|
||||
var/datum/effect_system/smoke_spread/freezing/S = new
|
||||
S.set_up(amount = 6, only_cardinals = FALSE, source = loc, desired_direction = null, chemicals = null, blasting = TRUE)
|
||||
S.set_up(amount = 6, only_cardinals = FALSE, source = loc)
|
||||
S.start()
|
||||
new /obj/effect/decal/cleanable/flour/nanofrost(get_turf(src))
|
||||
playsound(src, 'sound/effects/bamf.ogg', 100, TRUE)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
new /obj/item/holosign_creator/engineering(src)
|
||||
new /obj/item/flash(src)
|
||||
new /obj/item/clothing/glasses/meson/engine(src)
|
||||
new /obj/item/clothing/glasses/meson/engine/atmos(src)
|
||||
new /obj/item/door_remote/chief_engineer(src)
|
||||
new /obj/item/rpd(src)
|
||||
new /obj/item/reagent_containers/drinks/mug/ce(src)
|
||||
@@ -119,6 +120,6 @@
|
||||
new /obj/item/watertank/atmos(src)
|
||||
new /obj/item/clothing/suit/fire/firefighter/atmos(src)
|
||||
new /obj/item/clothing/head/hardhat/atmos(src)
|
||||
new /obj/item/clothing/glasses/meson/engine(src)
|
||||
new /obj/item/clothing/glasses/meson/engine/atmos(src)
|
||||
new /obj/item/rpd(src)
|
||||
new /obj/item/dest_tagger(src)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/obj/structure/plasmafire_generator
|
||||
name = "Plasmafire Generator"
|
||||
desc = "A magical thing that you really shouldn't be able to see."
|
||||
anchored = TRUE
|
||||
alpha = 0
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
resistance_flags = LAVA_PROOF | FIRE_PROOF
|
||||
flags = NODECONSTRUCT
|
||||
|
||||
/obj/structure/plasmafire_generator/Initialize(mapload)
|
||||
. = ..()
|
||||
START_PROCESSING(SSprocessing, src)
|
||||
|
||||
// for sanity checks
|
||||
/obj/structure/plasmafire_generator/Destroy()
|
||||
STOP_PROCESSING(SSprocessing, src)
|
||||
return ..()
|
||||
|
||||
/obj/structure/plasmafire_generator/process()
|
||||
var/turf/T = get_turf(src)
|
||||
if(!istype(T))
|
||||
return
|
||||
|
||||
var/datum/gas_mixture/fire = new()
|
||||
fire.set_toxins(10)
|
||||
fire.set_oxygen(4)
|
||||
fire.set_temperature(500)
|
||||
T.blind_release_air(fire)
|
||||
|
||||
/obj/structure/plasmafire_generator/shadow
|
||||
var/enabled = FALSE
|
||||
|
||||
/obj/structure/plasmafire_generator/shadow/onShuttleMove(turf/oldT, turf/T1, rotation, mob/caller)
|
||||
if(T1.z != 1)
|
||||
enabled = TRUE
|
||||
return ..()
|
||||
|
||||
/obj/structure/plasmafire_generator/shadow/process()
|
||||
if(!enabled)
|
||||
return
|
||||
return ..()
|
||||
@@ -30,6 +30,14 @@
|
||||
/// If a fire is ongoing, how much fuel did we burn last tick?
|
||||
/// Value is not updated while below PLASMA_MINIMUM_BURN_TEMPERATURE.
|
||||
var/fuel_burnt = 0
|
||||
/// When do we last remember having wind?
|
||||
var/wind_tick = null
|
||||
/// Wind's X component
|
||||
var/wind_x = null
|
||||
/// Wind's Y component
|
||||
var/wind_y = null
|
||||
/// Wind effect
|
||||
var/obj/effect/wind/wind_effect = null
|
||||
|
||||
/turf/simulated/proc/break_tile()
|
||||
return
|
||||
|
||||
@@ -56,12 +56,12 @@ GLOBAL_LIST_INIT(icons_to_ignore_at_floor_init, list("damaged1","damaged2","dama
|
||||
break_tile_to_plating()
|
||||
else
|
||||
break_tile()
|
||||
hotspot_expose(1000,CELL_VOLUME)
|
||||
hotspot_expose(1000, 100)
|
||||
if(prob(33)) new /obj/item/stack/sheet/metal(src)
|
||||
if(3.0)
|
||||
if(prob(50))
|
||||
break_tile()
|
||||
hotspot_expose(1000,CELL_VOLUME)
|
||||
hotspot_expose(1000, 100)
|
||||
return
|
||||
|
||||
/turf/simulated/floor/burn_down()
|
||||
@@ -118,6 +118,10 @@ GLOBAL_LIST_INIT(icons_to_ignore_at_floor_init, list("damaged1","damaged2","dama
|
||||
burnt = TRUE
|
||||
update_icon()
|
||||
|
||||
/turf/simulated/floor/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
|
||||
if(exposed_temperature > FIRE_MINIMUM_TEMPERATURE_TO_EXIST && prob(1))
|
||||
burn_tile()
|
||||
|
||||
/turf/simulated/floor/proc/make_plating()
|
||||
return ChangeTurf(/turf/simulated/floor/plating)
|
||||
|
||||
|
||||
@@ -9,6 +9,23 @@
|
||||
/turf/simulated/floor/plasteel/get_broken_states()
|
||||
return list("damaged1", "damaged2", "damaged3", "damaged4", "damaged5")
|
||||
|
||||
/turf/simulated/floor/plasteel/pressure_debug
|
||||
|
||||
/turf/simulated/floor/plasteel/pressure_debug/airless
|
||||
name = "airless floor"
|
||||
oxygen = 0
|
||||
nitrogen = 0
|
||||
temperature = TCMB
|
||||
|
||||
/turf/simulated/floor/plasteel/pressure_debug/Initialize(mapload)
|
||||
..()
|
||||
addtimer(CALLBACK(src, PROC_REF(update_color)), 1, TIMER_LOOP)
|
||||
|
||||
/turf/simulated/floor/plasteel/pressure_debug/proc/update_color()
|
||||
var/datum/gas_mixture/air = get_readonly_air()
|
||||
var/ratio = min(1, air.return_pressure() / ONE_ATMOSPHERE)
|
||||
color = rgb(255 * (1 - ratio), 0, 255 * ratio)
|
||||
|
||||
/turf/simulated/floor/plasteel/airless
|
||||
name = "airless floor"
|
||||
oxygen = 0
|
||||
|
||||
@@ -82,6 +82,9 @@
|
||||
|
||||
var/datum/gas_mixture/bound_to_turf/bound_air
|
||||
|
||||
/// The effect used to render a pressure overlay from this tile.
|
||||
var/obj/effect/pressure_overlay/pressure_overlay
|
||||
|
||||
/turf/Initialize(mapload)
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
if(initialized)
|
||||
@@ -651,8 +654,98 @@
|
||||
/datum/milla_safe/turf_blind_set/on_run(turf/T, datum/gas_mixture/air)
|
||||
get_turf_air(T).copy_from(air)
|
||||
|
||||
/turf/simulated/proc/update_hotspot()
|
||||
var/datum/gas_mixture/air = get_readonly_air()
|
||||
if(air.fuel_burnt() < 0.001)
|
||||
if(isnull(active_hotspot))
|
||||
return FALSE
|
||||
|
||||
// If it's old, delete it.
|
||||
if(active_hotspot.death_timer < SSair.times_fired)
|
||||
QDEL_NULL(active_hotspot)
|
||||
return FALSE
|
||||
else
|
||||
return TRUE
|
||||
|
||||
if(isnull(active_hotspot))
|
||||
active_hotspot = new(src)
|
||||
|
||||
active_hotspot.death_timer = SSair.times_fired + 4
|
||||
if(air.hotspot_volume() > 0)
|
||||
active_hotspot.temperature = air.hotspot_temperature()
|
||||
active_hotspot.volume = air.hotspot_volume() * CELL_VOLUME
|
||||
else
|
||||
active_hotspot.temperature = air.temperature()
|
||||
active_hotspot.volume = CELL_VOLUME
|
||||
|
||||
active_hotspot.update_visuals()
|
||||
return TRUE
|
||||
|
||||
/turf/simulated/proc/update_wind()
|
||||
if(wind_tick != SSair.times_fired)
|
||||
QDEL_NULL(wind_effect)
|
||||
wind_tick = null
|
||||
return FALSE
|
||||
|
||||
if(isnull(wind_effect))
|
||||
wind_effect = new(src)
|
||||
|
||||
wind_effect.dir = wind_direction(wind_x, wind_y)
|
||||
|
||||
var/datum/gas_mixture/air = get_readonly_air()
|
||||
var/wind = sqrt(wind_x ** 2 + wind_y ** 2)
|
||||
var/wind_strength = wind * air.total_moles() / MOLES_CELLSTANDARD
|
||||
wind_effect.alpha = min(255, 5 + wind_strength * 25)
|
||||
return TRUE
|
||||
|
||||
/turf/return_analyzable_air()
|
||||
return get_readonly_air()
|
||||
|
||||
/obj/effect/pressure_overlay
|
||||
icon_state = "nothing"
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
// I'm really not sure this is the right var for this, but it's what the suply shuttle is using to determine if anything is blocking a tile, so let's not do that.
|
||||
simulated = FALSE
|
||||
// Please do not splat the visual effect with a shuttle.
|
||||
flags_2 = IMMUNE_TO_SHUTTLECRUSH_2
|
||||
|
||||
var/image/overlay
|
||||
|
||||
/obj/effect/pressure_overlay/Initialize(mapload)
|
||||
. = ..()
|
||||
overlay = new(icon, src, "white")
|
||||
overlay.alpha = 0
|
||||
overlay.plane = ABOVE_LIGHTING_PLANE
|
||||
overlay.blend_mode = BLEND_OVERLAY
|
||||
overlay.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
|
||||
|
||||
/obj/effect/pressure_overlay/onShuttleMove(turf/oldT, turf/T1, rotation, mob/caller)
|
||||
// No, I don't think I will.
|
||||
return FALSE
|
||||
|
||||
/obj/effect/pressure_overlay/singularity_pull()
|
||||
// I am not a physical object, you have no control over me!
|
||||
return FALSE
|
||||
|
||||
/obj/effect/pressure_overlay/singularity_act()
|
||||
// I don't taste good, either!
|
||||
return FALSE
|
||||
|
||||
/turf/proc/ensure_pressure_overlay()
|
||||
if(isnull(pressure_overlay))
|
||||
for(var/obj/effect/pressure_overlay/found_overlay in src)
|
||||
pressure_overlay = found_overlay
|
||||
if(isnull(pressure_overlay))
|
||||
pressure_overlay = new(src)
|
||||
|
||||
if(isnull(pressure_overlay.loc))
|
||||
// Not sure how exactly this happens, but I've seen it happen, so fix it.
|
||||
pressure_overlay.forceMove(src)
|
||||
|
||||
if(isnull(pressure_overlay.overlay))
|
||||
pressure_overlay.Initialize()
|
||||
|
||||
return pressure_overlay
|
||||
|
||||
/turf/_clear_signal_refs()
|
||||
return
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
var/turf/location = get_turf(loc)
|
||||
if(location)
|
||||
location.hotspot_expose(1000, 1000)
|
||||
location.hotspot_expose(1000, 1)
|
||||
visible_message(
|
||||
"<span class='notice'>Sparks shoot out of [src].</span>",
|
||||
"<span class='warning'>You hear a shower of sparks shooting out from something!</span>"
|
||||
|
||||
@@ -2,41 +2,24 @@
|
||||
/atom/proc/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
|
||||
if(reagents)
|
||||
reagents.temperature_reagents(exposed_temperature)
|
||||
return null
|
||||
|
||||
/turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0)
|
||||
return FALSE
|
||||
|
||||
/turf/simulated/hotspot_expose(exposed_temperature, exposed_volume, soh)
|
||||
/turf/simulated/temperature_expose(exposed_temperature)
|
||||
if(reagents)
|
||||
reagents.temperature_reagents(exposed_temperature, 10, 300)
|
||||
if(!issimulatedturf(src))
|
||||
return FALSE
|
||||
var/datum/gas_mixture/air = get_readonly_air()
|
||||
if(!air)
|
||||
return FALSE
|
||||
if(active_hotspot)
|
||||
if(soh)
|
||||
if(air.toxins() > 0.5 && air.oxygen() > 0.5)
|
||||
if(active_hotspot.temperature < exposed_temperature)
|
||||
active_hotspot.temperature = exposed_temperature
|
||||
if(active_hotspot.volume < exposed_volume)
|
||||
active_hotspot.volume = exposed_volume
|
||||
return TRUE
|
||||
|
||||
if(exposed_temperature > PLASMA_MINIMUM_BURN_TEMPERATURE && air.oxygen() > 0.5 && air.toxins() > 0.5)
|
||||
var/total = air.total_moles()
|
||||
if(air.toxins() < 0.01 * total || air.oxygen() < 0.01 * total)
|
||||
// The rest of the gas is snuffing out the reaction.
|
||||
return FALSE
|
||||
active_hotspot = new /obj/effect/hotspot(src)
|
||||
active_hotspot.temperature = exposed_temperature
|
||||
active_hotspot.volume = exposed_volume
|
||||
return TRUE
|
||||
/turf/proc/hotspot_expose(exposed_temperature, exposed_volume)
|
||||
return
|
||||
|
||||
return FALSE
|
||||
/turf/simulated/hotspot_expose(exposed_temperature, exposed_volume)
|
||||
var/datum/milla_safe/make_hotspot/milla = new()
|
||||
milla.invoke_async(src, exposed_temperature, exposed_volume)
|
||||
|
||||
//This is the icon for fire on turfs, also helps for nurturing small fires until they are full tile
|
||||
/datum/milla_safe/make_hotspot
|
||||
|
||||
/datum/milla_safe/make_hotspot/on_run(turf/simulated/tile, exposed_temperature, exposed_volume)
|
||||
create_hotspot(tile, exposed_temperature, exposed_volume)
|
||||
|
||||
//This is the icon for fire on turfs.
|
||||
/obj/effect/hotspot
|
||||
anchored = TRUE
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
@@ -49,143 +32,25 @@
|
||||
|
||||
var/volume = 125
|
||||
var/temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST
|
||||
var/fake = FALSE
|
||||
var/burn_time = 0
|
||||
// The last tick this hotspot should be alive for.
|
||||
var/death_timer = 0
|
||||
|
||||
/obj/effect/hotspot/New()
|
||||
..()
|
||||
if(!fake)
|
||||
SSair.hotspots += src
|
||||
var/datum/milla_safe/hotspot_burn_plasma/milla = new()
|
||||
milla.invoke_async(src)
|
||||
dir = pick(GLOB.cardinal)
|
||||
|
||||
/datum/milla_safe/hotspot_burn_plasma
|
||||
|
||||
/// Burns the air affected by this hotspot. A hotspot is effectively a gas fire that might not cover the entire tile yet. This proc makes that "partial fire" burn, altering the tile as a whole, and potentially setting the entire tile on fire.
|
||||
/datum/milla_safe/hotspot_burn_plasma/on_run(obj/effect/hotspot/hotspot)
|
||||
var/turf/simulated/location = get_turf(hotspot)
|
||||
if(!istype(location) || location.blocks_air)
|
||||
// We're in the wrong neighborhood.
|
||||
qdel(hotspot)
|
||||
return
|
||||
|
||||
var/datum/gas_mixture/location_air = get_turf_air(location)
|
||||
if(location_air.temperature() >= min(hotspot.temperature, PLASMA_UPPER_TEMPERATURE))
|
||||
// The cell is already hot enough, no need to do more.
|
||||
hotspot.temperature = location_air.temperature()
|
||||
hotspot.volume = CELL_VOLUME
|
||||
hotspot.recolor()
|
||||
return
|
||||
|
||||
if(location_air.toxins() < 0.5 || location_air.oxygen() < 0.5)
|
||||
// Burn what, exactly?
|
||||
qdel(hotspot)
|
||||
return
|
||||
|
||||
var/total = location_air.total_moles()
|
||||
if(location_air.toxins() < 0.01 * total || location_air.oxygen() < 0.01 * total)
|
||||
// The rest of the gas is snuffing out the reaction.
|
||||
qdel(hotspot)
|
||||
return
|
||||
|
||||
// Get some of the surrounding air for the hotspot to burn.
|
||||
var/datum/gas_mixture/burning = location_air.remove_ratio(hotspot.volume / location_air.volume)
|
||||
|
||||
// Temporarily boost the temperature of this air to the hotspot temperature.
|
||||
var/old_temperature = burning.temperature()
|
||||
burning.set_temperature(hotspot.temperature)
|
||||
|
||||
// Record how much plasma we had initially.
|
||||
var/old_toxins = burning.toxins()
|
||||
|
||||
// Burn it.
|
||||
burning.react()
|
||||
|
||||
// Calculate how much thermal energy was produced.
|
||||
// (Yes, gas_mixture has its own .fuel_burnt, but I dont' trust that code.)
|
||||
var/fuel_burnt = old_toxins - burning.toxins()
|
||||
var/thermal_energy = FIRE_PLASMA_ENERGY_RELEASED * fuel_burnt
|
||||
|
||||
// Update the hotspot based on the reaction.
|
||||
hotspot.temperature = burning.temperature()
|
||||
hotspot.volume = min(CELL_VOLUME, fuel_burnt * FIRE_GROWTH_RATE)
|
||||
hotspot.recolor()
|
||||
|
||||
// Revert the air's temperature to where it started.
|
||||
burning.set_temperature(old_temperature)
|
||||
|
||||
var/heat_capacity = burning.heat_capacity()
|
||||
if(heat_capacity)
|
||||
// Add in the produced thermal energy.
|
||||
burning.set_temperature(burning.temperature() + thermal_energy / burning.heat_capacity())
|
||||
|
||||
// And add it back to the tile.
|
||||
location_air.merge(burning)
|
||||
|
||||
/obj/effect/hotspot/process()
|
||||
var/turf/simulated/location = loc
|
||||
if(!istype(location))
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
if((temperature < FIRE_MINIMUM_TEMPERATURE_TO_EXIST) || (volume <= 1))
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
var/datum/gas_mixture/location_air = location.get_readonly_air()
|
||||
if(location.blocks_air || location_air.toxins() < 0.5 || location_air.oxygen() < 0.5)
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
var/datum/milla_safe/hotspot_burn_plasma/milla = new()
|
||||
milla.invoke_async(src)
|
||||
if(QDELETED(src))
|
||||
return
|
||||
|
||||
for(var/A in loc)
|
||||
var/atom/item = A
|
||||
if(!QDELETED(item) && item != src) // It's possible that the item is deleted in temperature_expose
|
||||
item.fire_act(null, temperature, volume)
|
||||
|
||||
if(!istype(location))
|
||||
// We are now space. No need to do anything else.
|
||||
return
|
||||
|
||||
if(location.wet)
|
||||
location.wet = TURF_DRY
|
||||
|
||||
if(volume >= CELL_VOLUME * 0.95)
|
||||
/obj/effect/hotspot/proc/update_visuals()
|
||||
color = heat2color(temperature)
|
||||
set_light(l_color = color)
|
||||
var/turf/here = get_turf(src)
|
||||
var/datum/gas_mixture/gas = here.get_readonly_air()
|
||||
var/fuel_burnt = gas.fuel_burnt()
|
||||
if(fuel_burnt > 1)
|
||||
icon_state = "3"
|
||||
location.burn_tile()
|
||||
|
||||
//Possible spread due to radiated heat
|
||||
if(temperature > FIRE_MINIMUM_TEMPERATURE_TO_SPREAD)
|
||||
var/radiated_temperature = temperature * FIRE_SPREAD_RADIOSITY_SCALE
|
||||
for(var/direction in GLOB.cardinal)
|
||||
var/turf/simulated/T = get_step(location, direction)
|
||||
if(!istype(T))
|
||||
continue
|
||||
if(T.active_hotspot)
|
||||
continue
|
||||
if(src.CanAtmosPass(direction) && T.CanAtmosPass(turn(direction, 180)))
|
||||
T.hotspot_expose(radiated_temperature, CELL_VOLUME / 4)
|
||||
|
||||
else if(fuel_burnt > 0.1)
|
||||
icon_state = "2"
|
||||
else
|
||||
if(volume > CELL_VOLUME*0.4)
|
||||
icon_state = "2"
|
||||
else
|
||||
icon_state = "1"
|
||||
|
||||
if(temperature > location.max_fire_temperature_sustained)
|
||||
location.max_fire_temperature_sustained = temperature
|
||||
|
||||
if(location.heat_capacity && temperature > location.heat_capacity)
|
||||
location.to_be_destroyed = TRUE
|
||||
/*if(prob(25))
|
||||
location.ReplaceWithSpace()
|
||||
return 0*/
|
||||
return 1
|
||||
icon_state = "1"
|
||||
|
||||
|
||||
/obj/effect/hotspot/Initialize(mapload)
|
||||
@@ -199,18 +64,16 @@
|
||||
|
||||
/obj/effect/hotspot/Destroy()
|
||||
set_light(0)
|
||||
SSair.hotspots -= src
|
||||
var/turf/simulated/T = loc
|
||||
if(istype(T) && T.active_hotspot == src)
|
||||
T.active_hotspot = null
|
||||
if(!fake)
|
||||
DestroyTurf()
|
||||
return ..()
|
||||
|
||||
/obj/effect/hotspot/proc/recolor()
|
||||
color = heat2color(temperature)
|
||||
set_light(l_color = color)
|
||||
|
||||
// TODO: Vestigal, kept temporarily to avoid a merge conflict.
|
||||
/obj/effect/hotspot/proc/DestroyTurf()
|
||||
if(issimulatedturf(loc))
|
||||
var/turf/simulated/T = loc
|
||||
@@ -237,8 +100,7 @@
|
||||
|
||||
/// Largely for the fireflash procs below
|
||||
/obj/effect/hotspot/fake
|
||||
fake = TRUE
|
||||
burn_time = 30
|
||||
var/burn_time = 3 SECONDS
|
||||
|
||||
/obj/effect/hotspot/fake/New()
|
||||
..()
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
/turf/simulated/Destroy()
|
||||
QDEL_NULL(active_hotspot)
|
||||
QDEL_NULL(wet_overlay)
|
||||
QDEL_NULL(wind_effect)
|
||||
return ..()
|
||||
|
||||
/turf/simulated/proc/mimic_temperature_solid(turf/model, conduction_coefficient)
|
||||
@@ -82,43 +83,79 @@
|
||||
return null
|
||||
|
||||
/turf/proc/high_pressure_movements(flow_x, flow_y)
|
||||
var/atom/movable/M
|
||||
for(var/thing in src)
|
||||
M = thing
|
||||
for(var/atom/movable/M in src)
|
||||
if(QDELETED(M))
|
||||
continue
|
||||
if(M.anchored)
|
||||
continue
|
||||
if(M.pulledby)
|
||||
continue
|
||||
if(M.last_high_pressure_movement_air_cycle < SSair.times_fired)
|
||||
M.experience_pressure_difference(flow_x, flow_y)
|
||||
M.experience_pressure_difference(flow_x, flow_y)
|
||||
|
||||
/atom/movable/proc/experience_pressure_difference(flow_x, flow_y, pressure_resistance_prob_delta = 0)
|
||||
var/const/PROBABILITY_OFFSET = 25
|
||||
var/const/PROBABILITY_BASE_PRECENT = 75
|
||||
/proc/wind_direction(flow_x, flow_y)
|
||||
var/direction = 0
|
||||
if(flow_x > 0.5)
|
||||
direction |= EAST
|
||||
if(flow_x < -0.5)
|
||||
direction |= WEST
|
||||
if(flow_y > 0.5)
|
||||
direction |= NORTH
|
||||
if(flow_y < -0.5)
|
||||
direction |= SOUTH
|
||||
|
||||
var/pressure_difference = sqrt(flow_x ** 2 + flow_y ** 2)
|
||||
var/max_force = sqrt(pressure_difference) * (MOVE_FORCE_DEFAULT / 5)
|
||||
set waitfor = 0
|
||||
var/move_prob = 100
|
||||
if(pressure_resistance > 0)
|
||||
move_prob = (pressure_difference / pressure_resistance * PROBABILITY_BASE_PRECENT) - PROBABILITY_OFFSET
|
||||
move_prob += pressure_resistance_prob_delta
|
||||
if(move_prob > PROBABILITY_OFFSET && prob(move_prob) && (move_resist != INFINITY) && (!anchored && (max_force >= (move_resist * MOVE_FORCE_PUSH_RATIO))) || (anchored && (max_force >= (move_resist * MOVE_FORCE_FORCEPUSH_RATIO))))
|
||||
var/direction = 0
|
||||
if(flow_x > 0.5)
|
||||
direction |= EAST
|
||||
if(flow_x < -0.5)
|
||||
direction |= WEST
|
||||
if(flow_y > 0.5)
|
||||
direction |= NORTH
|
||||
if(flow_y < -0.5)
|
||||
direction |= SOUTH
|
||||
step(src, direction)
|
||||
last_high_pressure_movement_air_cycle = SSair.times_fired
|
||||
return direction
|
||||
|
||||
return pressure_difference
|
||||
/atom/movable/proc/experience_pressure_difference(flow_x, flow_y)
|
||||
if(move_resist == INFINITY)
|
||||
return
|
||||
|
||||
var/force_needed = max(move_resist, 1)
|
||||
if(anchored)
|
||||
force_needed *= MOVE_FORCE_FORCEPUSH_RATIO
|
||||
else
|
||||
force_needed *= MOVE_FORCE_PUSH_RATIO
|
||||
|
||||
var/turf/my_turf = get_turf(src)
|
||||
var/datum/gas_mixture/my_air = my_turf.get_readonly_air()
|
||||
|
||||
var/air = my_air.total_moles() / MOLES_CELLSTANDARD
|
||||
var/wind = sqrt(flow_x ** 2 + flow_y ** 2)
|
||||
var/force = wind * air * (MOVE_FORCE_DEFAULT / 5)
|
||||
|
||||
if(force < force_needed)
|
||||
return
|
||||
|
||||
var/direction = wind_direction(flow_x, flow_y)
|
||||
if(direction == 0)
|
||||
return
|
||||
|
||||
if(last_high_pressure_movement_time >= SSair.times_fired - 3)
|
||||
return
|
||||
last_high_pressure_movement_time = SSair.times_fired
|
||||
|
||||
air_push(direction, (force - force_needed) / force_needed)
|
||||
|
||||
/atom/movable/proc/air_push(direction, strength)
|
||||
step(src, direction)
|
||||
|
||||
/mob/living/air_push(direction, strength)
|
||||
if(HAS_TRAIT(src, TRAIT_MAGPULSE))
|
||||
return
|
||||
|
||||
apply_status_effect(STATUS_EFFECT_UNBALANCED)
|
||||
apply_status_effect(STATUS_EFFECT_DIRECTIONAL_SLOW, 1 SECONDS, REVERSE_DIR(direction), min(10, strength * 5))
|
||||
|
||||
if(client?.input_data?.desired_move_dir)
|
||||
return
|
||||
if(!pulling)
|
||||
return ..()
|
||||
|
||||
// Make sure we don't let go of something just because the wind pushed us into it.
|
||||
var/atom/movable/was_pulling = pulling
|
||||
. = ..()
|
||||
// We were just pulling it, so we can skip all the other stuff in start_pulling and just re-establish the pull.
|
||||
pulling = was_pulling
|
||||
was_pulling.pulledby = src
|
||||
|
||||
/turf/simulated/proc/radiate_to_spess() //Radiate excess tile heat to space
|
||||
if(temperature > T0C) //Considering 0 degC as te break even point for radiation in and out
|
||||
@@ -198,6 +235,14 @@
|
||||
reset_superconductivity(T)
|
||||
reduce_superconductivity(T, superconductivity)
|
||||
|
||||
/obj/effect/wind
|
||||
anchored = TRUE
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
icon = 'icons/effects/tile_effects.dmi'
|
||||
icon_state = "wind"
|
||||
layer = MASSIVE_OBJ_LAYER
|
||||
blend_mode = BLEND_OVERLAY
|
||||
|
||||
#undef INDEX_NORTH
|
||||
#undef INDEX_EAST
|
||||
#undef INDEX_SOUTH
|
||||
|
||||
@@ -33,6 +33,9 @@ What are the archived variables for?
|
||||
var/private_sleeping_agent = 0
|
||||
var/private_agent_b = 0
|
||||
var/private_temperature = 0 //in Kelvin
|
||||
var/private_hotspot_temperature = 0
|
||||
var/private_hotspot_volume = 0
|
||||
var/private_fuel_burnt = 0
|
||||
|
||||
// Archived versions of the private fields.
|
||||
// Only gas_mixture should use these.
|
||||
@@ -100,6 +103,15 @@ What are the archived variables for?
|
||||
/datum/gas_mixture/proc/set_temperature(value)
|
||||
private_temperature = value
|
||||
|
||||
/datum/gas_mixture/proc/hotspot_temperature()
|
||||
return private_hotspot_temperature
|
||||
|
||||
/datum/gas_mixture/proc/hotspot_volume()
|
||||
return private_hotspot_volume
|
||||
|
||||
/datum/gas_mixture/proc/fuel_burnt()
|
||||
return private_fuel_burnt
|
||||
|
||||
///joules per kelvin
|
||||
/datum/gas_mixture/proc/heat_capacity()
|
||||
return HEAT_CAPACITY_CALCULATION(private_oxygen, private_carbon_dioxide, private_nitrogen, private_toxins, private_sleeping_agent, private_agent_b, innate_heat_capacity)
|
||||
@@ -651,6 +663,9 @@ What are the archived variables for?
|
||||
private_agent_b = milla[MILLA_INDEX_AGENT_B]
|
||||
innate_heat_capacity = milla[MILLA_INDEX_INNATE_HEAT_CAPACITY]
|
||||
private_temperature = milla[MILLA_INDEX_TEMPERATURE]
|
||||
private_hotspot_temperature = milla[MILLA_INDEX_HOTSPOT_TEMPERATURE]
|
||||
private_hotspot_volume = milla[MILLA_INDEX_HOTSPOT_VOLUME]
|
||||
private_fuel_burnt = milla[MILLA_INDEX_FUEL_BURNT]
|
||||
|
||||
/proc/share_many_airs(list/mixtures)
|
||||
var/total_volume = 0
|
||||
@@ -700,16 +715,8 @@ What are the archived variables for?
|
||||
G.private_temperature = temperature
|
||||
G.set_dirty()
|
||||
|
||||
|
||||
|
||||
///Mathematical proofs:
|
||||
/**
|
||||
get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp
|
||||
get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles()
|
||||
|
||||
10/20*5 = 2.5
|
||||
10 = 2.5/5*20
|
||||
**/
|
||||
/datum/gas_mixture/proc/hotspot_expose(temperature, volume)
|
||||
return
|
||||
|
||||
#undef SPECIFIC_HEAT_TOXIN
|
||||
#undef SPECIFIC_HEAT_AIR
|
||||
@@ -742,6 +749,9 @@ get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles()
|
||||
readonly.private_sleeping_agent = private_sleeping_agent
|
||||
readonly.private_agent_b = private_agent_b
|
||||
readonly.private_temperature = private_temperature
|
||||
readonly.private_hotspot_temperature = private_hotspot_temperature
|
||||
readonly.private_hotspot_volume = private_hotspot_volume
|
||||
readonly.private_fuel_burnt = private_fuel_burnt
|
||||
|
||||
if(istype(bound_turf, /turf/simulated))
|
||||
var/turf/simulated/S = bound_turf
|
||||
@@ -776,6 +786,12 @@ get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles()
|
||||
private_temperature = value
|
||||
set_dirty()
|
||||
|
||||
/datum/gas_mixture/bound_to_turf/hotspot_expose(temperature, volume)
|
||||
if(temperature > private_temperature)
|
||||
set_dirty()
|
||||
private_hotspot_temperature = max(private_hotspot_temperature, temperature)
|
||||
private_hotspot_volume = max(private_hotspot_volume, (volume / CELL_VOLUME))
|
||||
|
||||
/datum/gas_mixture/bound_to_turf/proc/private_unsafe_write()
|
||||
set_tile_atmos(bound_turf, oxygen = private_oxygen, carbon_dioxide = private_carbon_dioxide, nitrogen = private_nitrogen, toxins = private_toxins, sleeping_agent = private_sleeping_agent, agent_b = private_agent_b, temperature = private_temperature)
|
||||
|
||||
@@ -796,6 +812,9 @@ get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles()
|
||||
private_agent_b = parent.private_agent_b
|
||||
|
||||
private_temperature = parent.private_temperature
|
||||
private_hotspot_temperature = parent.private_hotspot_temperature
|
||||
private_hotspot_volume = parent.private_hotspot_volume
|
||||
private_fuel_burnt = parent.private_fuel_burnt
|
||||
|
||||
/datum/gas_mixture/readonly/set_dirty()
|
||||
CRASH("Attempted to modify a readonly gas_mixture.")
|
||||
|
||||
@@ -47,6 +47,19 @@
|
||||
#define MAX_TEMPERATURE 363.15 // 90C
|
||||
#define MIN_TEMPERATURE 233.15 // -40C
|
||||
|
||||
GLOBAL_LIST_INIT(aalarm_modes, list(
|
||||
"[AALARM_MODE_FILTERING]" = "Filtering",
|
||||
"[AALARM_MODE_DRAUGHT]" = "Draught",
|
||||
"[AALARM_MODE_PANIC]" = "Panic",
|
||||
"[AALARM_MODE_CYCLE]" = "Cycle",
|
||||
"[AALARM_MODE_SIPHON]" = "Siphon",
|
||||
"[AALARM_MODE_CONTAMINATED]" = "Contaminated",
|
||||
"[AALARM_MODE_REFILL]" = "Refill",
|
||||
"[AALARM_MODE_CUSTOM]" = "Custom",
|
||||
"[AALARM_MODE_OFF]" = "Off",
|
||||
"[AALARM_MODE_FLOOD]" = "Flood",
|
||||
))
|
||||
|
||||
/obj/machinery/alarm
|
||||
name = "air alarm"
|
||||
desc = "A wall-mounted device used to control atmospheric equipment. It looks a little cheaply made..."
|
||||
@@ -75,7 +88,7 @@
|
||||
var/AAlarmwires = 31
|
||||
var/shorted = FALSE
|
||||
|
||||
var/mode = AALARM_MODE_SCRUBBING
|
||||
var/mode = AALARM_MODE_FILTERING
|
||||
var/preset = AALARM_PRESET_HUMAN
|
||||
var/area/alarm_area
|
||||
var/danger_level = ATMOS_ALARM_NONE
|
||||
@@ -187,7 +200,7 @@
|
||||
)
|
||||
|
||||
if(!no_cycle_after)
|
||||
mode = AALARM_MODE_REPLACEMENT
|
||||
mode = AALARM_MODE_CYCLE
|
||||
apply_mode()
|
||||
|
||||
/obj/machinery/alarm/Initialize(mapload, direction, building = 0)
|
||||
@@ -272,6 +285,15 @@
|
||||
cur_tlv = TLV["pressure"]
|
||||
var/environment_pressure = environment.return_pressure()
|
||||
var/pressure_dangerlevel = cur_tlv.get_danger_level(environment_pressure)
|
||||
if(environment_pressure < cur_tlv.min2 && mode == AALARM_MODE_FILTERING)
|
||||
mode = AALARM_MODE_OFF
|
||||
apply_mode()
|
||||
var/area/A = location.loc
|
||||
A.firealert(src)
|
||||
|
||||
if(mode == AALARM_MODE_REFILL && environment_pressure >= cur_tlv.min1)
|
||||
mode = AALARM_MODE_FILTERING
|
||||
apply_mode()
|
||||
|
||||
cur_tlv = TLV["oxygen"]
|
||||
var/oxygen_dangerlevel = cur_tlv.get_danger_level(environment.oxygen() * GET_PP)
|
||||
@@ -309,8 +331,9 @@
|
||||
if(old_danger_level != danger_level)
|
||||
apply_danger_level()
|
||||
|
||||
if(mode == AALARM_MODE_REPLACEMENT && environment_pressure < ONE_ATMOSPHERE * 0.05)
|
||||
mode = AALARM_MODE_SCRUBBING
|
||||
cur_tlv = TLV["pressure"]
|
||||
if(mode == AALARM_MODE_CYCLE && environment_pressure < cur_tlv.min2 * 0.05)
|
||||
mode = AALARM_MODE_REFILL
|
||||
apply_mode()
|
||||
|
||||
/datum/milla_safe/airalarm_heat_cool
|
||||
@@ -393,8 +416,9 @@
|
||||
underlays += emissive_appearance(icon, "alarm_lightmask")
|
||||
|
||||
/obj/machinery/alarm/proc/apply_mode()
|
||||
var/datum/tlv/pressure_tlv = TLV["pressure"]
|
||||
switch(mode)
|
||||
if(AALARM_MODE_SCRUBBING)
|
||||
if(AALARM_MODE_FILTERING)
|
||||
for(var/obj/machinery/atmospherics/unary/vent_scrubber/S as anything in alarm_area.scrubbers)
|
||||
if(S.stat & (NOPOWER|BROKEN))
|
||||
continue
|
||||
@@ -413,7 +437,7 @@
|
||||
continue
|
||||
P.on = TRUE
|
||||
P.pressure_checks = ONLY_CHECK_EXT_PRESSURE
|
||||
P.external_pressure_bound = ONE_ATMOSPHERE
|
||||
P.external_pressure_bound = (pressure_tlv.min1 + pressure_tlv.max1) / 2
|
||||
P.update_icon(UPDATE_ICON_STATE)
|
||||
|
||||
|
||||
@@ -434,11 +458,11 @@
|
||||
continue
|
||||
P.on = TRUE
|
||||
P.pressure_checks = ONLY_CHECK_EXT_PRESSURE
|
||||
P.external_pressure_bound = ONE_ATMOSPHERE
|
||||
P.external_pressure_bound = (pressure_tlv.min1 + pressure_tlv.max1) / 2
|
||||
P.update_icon(UPDATE_ICON_STATE)
|
||||
|
||||
|
||||
if(AALARM_MODE_VENTING)
|
||||
if(AALARM_MODE_DRAUGHT)
|
||||
for(var/obj/machinery/atmospherics/unary/vent_scrubber/S as anything in alarm_area.scrubbers)
|
||||
if(S.stat & (NOPOWER|BROKEN))
|
||||
continue
|
||||
@@ -452,7 +476,7 @@
|
||||
continue
|
||||
P.on = TRUE
|
||||
P.pressure_checks = ONLY_CHECK_EXT_PRESSURE
|
||||
P.external_pressure_bound = ONE_ATMOSPHERE * 2
|
||||
P.external_pressure_bound = pressure_tlv.max1
|
||||
P.update_icon(UPDATE_ICON_STATE)
|
||||
|
||||
|
||||
@@ -473,11 +497,11 @@
|
||||
continue
|
||||
P.on = TRUE
|
||||
P.pressure_checks = ONLY_CHECK_EXT_PRESSURE
|
||||
P.external_pressure_bound = ONE_ATMOSPHERE * 3
|
||||
P.external_pressure_bound = (pressure_tlv.min1 + pressure_tlv.max1) / 2
|
||||
P.update_icon(UPDATE_ICON_STATE)
|
||||
|
||||
|
||||
if(AALARM_MODE_PANIC, AALARM_MODE_REPLACEMENT)
|
||||
if(AALARM_MODE_PANIC, AALARM_MODE_CYCLE)
|
||||
for(var/obj/machinery/atmospherics/unary/vent_scrubber/S as anything in alarm_area.scrubbers)
|
||||
if(S.stat & (NOPOWER|BROKEN))
|
||||
continue
|
||||
@@ -671,15 +695,16 @@
|
||||
data["atmos_alarm"] = alarm_area.atmosalm
|
||||
data["emagged"] = emagged
|
||||
data["modes"] = list(
|
||||
AALARM_MODE_SCRUBBING = list("name"="Filtering", "desc"="Scrubs out contaminants", "id" = AALARM_MODE_SCRUBBING),\
|
||||
AALARM_MODE_VENTING = list("name"="Draught", "desc"="Siphons out air while replacing", "id" = AALARM_MODE_VENTING),\
|
||||
AALARM_MODE_PANIC = list("name"="Panic Siphon","desc"="Siphons air out of the room quickly", "id" = AALARM_MODE_PANIC),\
|
||||
AALARM_MODE_REPLACEMENT = list("name"="Cycle", "desc"="Siphons air before replacing", "id" = AALARM_MODE_REPLACEMENT),\
|
||||
AALARM_MODE_SIPHON = list("name"="Siphon", "desc"="Siphons air out of the room", "id" = AALARM_MODE_SIPHON),\
|
||||
AALARM_MODE_CONTAMINATED= list("name"="Contaminated","desc"="Scrubs out all contaminants quickly", "id" = AALARM_MODE_CONTAMINATED),\
|
||||
AALARM_MODE_REFILL = list("name"="Refill", "desc"="Triples vent output", "id" = AALARM_MODE_REFILL),\
|
||||
AALARM_MODE_OFF = list("name"="Off", "desc"="Shuts off vents and scrubbers", "id" = AALARM_MODE_OFF),\
|
||||
AALARM_MODE_FLOOD = list("name"="Flood", "desc"="Shuts off scrubbers and opens vents", "emagonly" = TRUE, "id" = AALARM_MODE_FLOOD)
|
||||
"mode[AALARM_MODE_FILTERING]" = list("name"=GLOB.aalarm_modes["[AALARM_MODE_FILTERING]"], "desc"="Scrubs out contaminants. Will shut off and drop firelocks if pressure drops too low.", "id" = AALARM_MODE_FILTERING),
|
||||
"mode[AALARM_MODE_DRAUGHT]" = list("name"=GLOB.aalarm_modes["[AALARM_MODE_DRAUGHT]"], "desc"="Siphons out air while replacing", "id" = AALARM_MODE_DRAUGHT),
|
||||
"mode[AALARM_MODE_PANIC]" = list("name"=GLOB.aalarm_modes["[AALARM_MODE_PANIC]"], "desc"="Siphons air out of the room quickly", "id" = AALARM_MODE_PANIC),
|
||||
"mode[AALARM_MODE_CYCLE]" = list("name"=GLOB.aalarm_modes["[AALARM_MODE_CYCLE]"], "desc"="Siphons air before replacing", "id" = AALARM_MODE_CYCLE),
|
||||
"mode[AALARM_MODE_SIPHON]" = list("name"=GLOB.aalarm_modes["[AALARM_MODE_SIPHON]"], "desc"="Siphons air out of the room", "id" = AALARM_MODE_SIPHON),
|
||||
"mode[AALARM_MODE_CONTAMINATED]" = list("name"=GLOB.aalarm_modes["[AALARM_MODE_CONTAMINATED]"], "desc"="Scrubs out all contaminants quickly", "id" = AALARM_MODE_CONTAMINATED),
|
||||
"mode[AALARM_MODE_REFILL]" = list("name"=GLOB.aalarm_modes["[AALARM_MODE_REFILL]"], "desc"="Refills a room to normal pressure, then switches to Filtering.", "id" = AALARM_MODE_REFILL),
|
||||
"mode[AALARM_MODE_CUSTOM]" = list("name"=GLOB.aalarm_modes["[AALARM_MODE_CUSTOM]"], "desc"="Custom settings with no automatic mode switching.", "id" = AALARM_MODE_CUSTOM),
|
||||
"mode[AALARM_MODE_OFF]" = list("name"=GLOB.aalarm_modes["[AALARM_MODE_OFF]"], "desc"="Shuts off vents and scrubbers", "id" = AALARM_MODE_OFF),
|
||||
"mode[AALARM_MODE_FLOOD]" = list("name"=GLOB.aalarm_modes["[AALARM_MODE_FLOOD]"], "desc"="Shuts off scrubbers and opens vents", "emagonly" = TRUE, "id" = AALARM_MODE_FLOOD)
|
||||
)
|
||||
data["mode"] = mode
|
||||
data["presets"] = list(
|
||||
@@ -872,6 +897,8 @@
|
||||
message_admins("<span class='boldannounceooc'>[key_name_admin(usr)] attempted to href-exploit an air alarm to control another object!!!</span>")
|
||||
return
|
||||
|
||||
mode = AALARM_MODE_CUSTOM
|
||||
|
||||
// Its a vent. Handle
|
||||
if(istype(U, /obj/machinery/atmospherics/unary/vent_pump))
|
||||
var/obj/machinery/atmospherics/unary/vent_pump/V = U
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
if(pressure_delta > 0)
|
||||
// transfer from pipe air to environment
|
||||
if((vent.air_contents.total_moles() > 0) && (vent.air_contents.temperature() > 0))
|
||||
var/transfer_moles = pressure_delta * environment.volume / (vent.air_contents.temperature() * R_IDEAL_GAS_EQUATION)
|
||||
var/transfer_moles = pressure_delta * vent.air_contents.volume / (vent.air_contents.temperature() * R_IDEAL_GAS_EQUATION)
|
||||
transfer_moles = min(transfer_moles, vent.volume)
|
||||
|
||||
var/datum/gas_mixture/removed = vent.air_contents.remove(transfer_moles)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
/// If false, siphons instead of releasing air
|
||||
var/releasing = TRUE
|
||||
var/max_transfer_joules = 200 /*kPa*/ * 2 * ONE_ATMOSPHERE
|
||||
|
||||
var/external_pressure_bound = EXTERNAL_PRESSURE_BOUND
|
||||
var/internal_pressure_bound = INTERNAL_PRESSURE_BOUND
|
||||
@@ -146,7 +147,9 @@
|
||||
pressure_delta = min(pressure_delta, (vent_pump.air_contents.return_pressure() - vent_pump.internal_pressure_bound))
|
||||
|
||||
if(pressure_delta > 0.5 && vent_pump.air_contents.temperature() > 0)
|
||||
var/transfer_moles = pressure_delta * environment.volume / (vent_pump.air_contents.temperature() * R_IDEAL_GAS_EQUATION)
|
||||
// 1kPa * 1L = 1J
|
||||
var/wanted_joules = pressure_delta * environment.volume
|
||||
var/transfer_moles = min(vent_pump.max_transfer_joules, wanted_joules) / (vent_pump.air_contents.temperature() * R_IDEAL_GAS_EQUATION)
|
||||
var/datum/gas_mixture/removed = vent_pump.air_contents.remove(transfer_moles)
|
||||
environment.merge(removed)
|
||||
vent_pump.parent.update = TRUE
|
||||
@@ -159,7 +162,9 @@
|
||||
pressure_delta = min(pressure_delta, (vent_pump.internal_pressure_bound - vent_pump.air_contents.return_pressure()))
|
||||
|
||||
if(pressure_delta > 0.5 && environment.temperature() > 0)
|
||||
var/transfer_moles = pressure_delta * vent_pump.air_contents.volume / (environment.temperature() * R_IDEAL_GAS_EQUATION)
|
||||
// 1kPa * 1L = 1J
|
||||
var/wanted_joules = pressure_delta * environment.volume
|
||||
var/transfer_moles = min(vent_pump.max_transfer_joules, wanted_joules) / (environment.temperature() * R_IDEAL_GAS_EQUATION)
|
||||
var/datum/gas_mixture/removed = environment.remove(transfer_moles)
|
||||
vent_pump.air_contents.merge(removed)
|
||||
vent_pump.parent.update = TRUE
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
#define MODE_MESON "meson"
|
||||
#define MODE_TRAY "t-ray"
|
||||
#define MODE_RAD "radiation"
|
||||
#define MODE_PRESSURE "pressure"
|
||||
|
||||
/obj/item/clothing/glasses/meson/engine
|
||||
name = "engineering scanner goggles"
|
||||
desc = "Goggles used by engineers. The Meson Scanner mode lets you see basic structural and terrain layouts through walls, the T-ray Scanner mode lets you see underfloor objects such as cables and pipes, and the Radiation Scanner mode lets you see objects contaminated by radiation."
|
||||
desc = "Goggles used by engineers. The Meson Scanner mode lets you see basic structural and terrain layouts through walls, the T-ray Scanner mode lets you see underfloor objects such as cables and pipes, the Radiation Scanner mode lets you see objects contaminated by radiation, and the Pressure Scanner mode lets you visualize air pressure."
|
||||
icon_state = "trayson-meson"
|
||||
item_state = "trayson-meson"
|
||||
actions_types = list(/datum/action/item_action/toggle_mode)
|
||||
@@ -37,6 +38,11 @@
|
||||
if(active_on_equip_rad && mode == MODE_RAD && slot == ITEM_SLOT_EYES)
|
||||
ADD_TRAIT(user, SM_HALLUCINATION_IMMUNE, "meson_glasses[UID()]")
|
||||
|
||||
if(mode == MODE_PRESSURE && slot == ITEM_SLOT_EYES)
|
||||
ADD_TRAIT(user, TRAIT_PRESSURE_VISION, "meson_glasses[UID()]")
|
||||
if(mode == MODE_PRESSURE && slot != ITEM_SLOT_EYES)
|
||||
REMOVE_TRAIT(user, TRAIT_PRESSURE_VISION, "meson_glasses[UID()]")
|
||||
|
||||
/obj/item/clothing/glasses/meson/engine/proc/toggle_mode(mob/user, voluntary)
|
||||
mode = modes[mode]
|
||||
to_chat(user, "<span class='[voluntary ? "notice" : "warning"]'>[voluntary ? "You turn the goggles" : "The goggles turn"] [mode ? "to [mode] mode" : "off"][voluntary ? "." : "!"]</span>")
|
||||
@@ -57,6 +63,12 @@
|
||||
REMOVE_TRAIT(user, SM_HALLUCINATION_IMMUNE, "meson_glasses[UID()]")
|
||||
active_on_equip_rad = FALSE
|
||||
|
||||
if(mode == MODE_PRESSURE)
|
||||
if(!HAS_TRAIT_FROM(user, TRAIT_PRESSURE_VISION, "meson_glasses[UID()]") && user.get_item_by_slot(ITEM_SLOT_EYES) == src)
|
||||
ADD_TRAIT(user, TRAIT_PRESSURE_VISION, "meson_glasses[UID()]")
|
||||
else
|
||||
REMOVE_TRAIT(user, TRAIT_PRESSURE_VISION, "meson_glasses[UID()]")
|
||||
|
||||
if(ishuman(user))
|
||||
var/mob/living/carbon/human/H = user
|
||||
if(H.glasses == src)
|
||||
@@ -101,17 +113,18 @@
|
||||
user.update_inv_l_hand()
|
||||
user.update_inv_r_hand()
|
||||
|
||||
/// atmos techs have lived far too long without tray goggles while those damned engineers get their dual-purpose gogles all to themselves
|
||||
/obj/item/clothing/glasses/meson/engine/tray
|
||||
name = "optical t-ray scanner"
|
||||
icon_state = "trayson-t-ray"
|
||||
item_state = "trayson-t-ray"
|
||||
desc = "Used by engineering staff to see underfloor objects such as cables and pipes."
|
||||
/// Atmospherics techs get their own version with T-ray and an exlusive Pressure view.
|
||||
/obj/item/clothing/glasses/meson/engine/atmos
|
||||
name = "atmospherics scanner goggles"
|
||||
icon_state = "trayson-pressure"
|
||||
item_state = "trayson-pressure"
|
||||
desc = "Used by atmospherics techs to visualize pressure, see station structure, and see underfloor objects such as cables and pipes."
|
||||
range = 2
|
||||
origin_tech = "materials=3;magnets=2;engineering=2"
|
||||
modes = list(MODE_NONE = MODE_TRAY, MODE_TRAY = MODE_NONE)
|
||||
modes = list(MODE_NONE = MODE_PRESSURE, MODE_PRESSURE = MODE_MESON, MODE_MESON = MODE_TRAY, MODE_TRAY = MODE_NONE)
|
||||
|
||||
#undef MODE_NONE
|
||||
#undef MODE_MESON
|
||||
#undef MODE_TRAY
|
||||
#undef MODE_RAD
|
||||
#undef MODE_PRESSURE
|
||||
|
||||
@@ -171,7 +171,6 @@ GLOBAL_LIST_EMPTY(event_last_fired)
|
||||
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Radiation Storm", /datum/event/radiation_storm, 25, list(ASSIGNMENT_MEDICAL = 50), TRUE),
|
||||
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Spider Infestation", /datum/event/spider_infestation, 100, list(ASSIGNMENT_SECURITY = 30), TRUE),
|
||||
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Ion Storm", /datum/event/ion_storm, 0, list(ASSIGNMENT_AI = 50, ASSIGNMENT_CYBORG = 50, ASSIGNMENT_ENGINEER = 15, ASSIGNMENT_SCIENTIST = 5)),
|
||||
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Immovable Rod", /datum/event/immovable_rod, 0, list(ASSIGNMENT_ENGINEER = 30), TRUE),
|
||||
//new /datum/event_meta/ninja(EVENT_LEVEL_MODERATE, "Space Ninja", /datum/event/space_ninja, 0, list(ASSIGNMENT_SECURITY = 15), TRUE),
|
||||
// NON-BAY EVENTS
|
||||
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Mass Hallucination", /datum/event/mass_hallucination, 300),
|
||||
@@ -207,8 +206,9 @@ GLOBAL_LIST_EMPTY(event_last_fired)
|
||||
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Traders", /datum/event/traders, 85, is_one_shot = TRUE),
|
||||
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Terror Spiders", /datum/event/spider_terror, 15, list(ASSIGNMENT_SECURITY = 3), TRUE),
|
||||
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Slaughter Demon", /datum/event/spawn_slaughter, 20, is_one_shot = TRUE),
|
||||
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Shadow Demon", /datum/event/spawn_slaughter/shadow, 20, is_one_shot = TRUE),
|
||||
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Shadow Demon", /datum/event/spawn_slaughter/shadow, 20, is_one_shot = TRUE),
|
||||
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Pulse Demon Infiltration", /datum/event/spawn_pulsedemon, 20, is_one_shot = TRUE),
|
||||
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Immovable Rod", /datum/event/immovable_rod, 0, list(ASSIGNMENT_ENGINEER = 10), TRUE)
|
||||
//new /datum/event_meta(EVENT_LEVEL_MAJOR, "Floor Cluwne", /datum/event/spawn_floor_cluwne, 15, is_one_shot = TRUE)
|
||||
)
|
||||
|
||||
|
||||
@@ -70,44 +70,69 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
|
||||
/obj/effect/immovablerod/singularity_pull()
|
||||
return
|
||||
|
||||
/obj/effect/immovablerod/Bump(atom/clong)
|
||||
/obj/effect/immovablerod/Move(turf/newloc, direction)
|
||||
if(!istype(newloc))
|
||||
return ..()
|
||||
|
||||
if(!direction)
|
||||
direction = get_dir(src, newloc)
|
||||
forceMove(newloc)
|
||||
setDir(direction)
|
||||
|
||||
if(prob(10))
|
||||
playsound(src, 'sound/effects/bang.ogg', 50, TRUE)
|
||||
audible_message("CLANG")
|
||||
|
||||
if(clong && prob(25))
|
||||
x = clong.x
|
||||
y = clong.y
|
||||
clong_turf(newloc)
|
||||
for(var/atom/victim as anything in newloc)
|
||||
clong_thing(victim)
|
||||
|
||||
if(isturf(clong) || isobj(clong))
|
||||
if(clong.density)
|
||||
clong.ex_act(EXPLODE_HEAVY)
|
||||
/obj/effect/immovablerod/proc/clong_turf(turf/victim)
|
||||
if(!victim.density)
|
||||
return
|
||||
|
||||
else if(ismob(clong))
|
||||
if(ishuman(clong))
|
||||
var/mob/living/carbon/human/H = clong
|
||||
if(iswallturf(victim))
|
||||
var/turf/simulated/wall/W = victim
|
||||
W.take_damage(rand(W.damage_cap / 3, W.damage_cap * 4 / 3))
|
||||
else
|
||||
victim.ex_act(EXPLODE_LIGHT)
|
||||
|
||||
/obj/effect/immovablerod/proc/clong_thing(atom/victim)
|
||||
if(isobj(victim) && victim.density)
|
||||
victim.ex_act(EXPLODE_HEAVY)
|
||||
else if(ismob(victim))
|
||||
if(ishuman(victim))
|
||||
var/mob/living/carbon/human/H = victim
|
||||
H.visible_message("<span class='danger'>[H.name] is penetrated by an immovable rod!</span>",
|
||||
"<span class='userdanger'>The rod penetrates you!</span>",
|
||||
"<span class ='danger'>You hear a CLANG!</span>")
|
||||
H.adjustBruteLoss(160)
|
||||
if(clong.density || prob(10))
|
||||
clong.ex_act(EXPLODE_HEAVY)
|
||||
if(victim.density || prob(10))
|
||||
victim.ex_act(EXPLODE_HEAVY)
|
||||
|
||||
/obj/effect/immovablerod/event
|
||||
// The base chance to "damage" the floor when passing. This is not guaranteed to cause a full on hull breach.
|
||||
// Chance to expose the tile to space is like 60% of this value.
|
||||
// Chance to expose the tile to space is like 15% of this value.
|
||||
var/floor_rip_chance = 40
|
||||
|
||||
/obj/effect/immovablerod/event/Move()
|
||||
var/atom/oldloc = loc
|
||||
. = ..()
|
||||
if(prob(floor_rip_chance))
|
||||
var/turf/simulated/floor/T = get_turf(oldloc)
|
||||
if(istype(T))
|
||||
T.ex_act(EXPLODE_HEAVY)
|
||||
if(loc == end)
|
||||
qdel(src)
|
||||
|
||||
/obj/effect/immovablerod/event/clong_turf(turf/victim)
|
||||
if(!isfloorturf(victim))
|
||||
return ..()
|
||||
|
||||
if(!prob(floor_rip_chance))
|
||||
return
|
||||
|
||||
var/turf/simulated/floor/T = victim
|
||||
if(prob(25))
|
||||
T.ex_act(EXPLODE_HEAVY)
|
||||
else
|
||||
T.ex_act(EXPLODE_LIGHT)
|
||||
|
||||
/obj/effect/immovablerod/deadchat_plays(mode = DEADCHAT_DEMOCRACY_MODE, cooldown = 6 SECONDS)
|
||||
return AddComponent(/datum/component/deadchat_control/immovable_rod, mode, list(), cooldown)
|
||||
|
||||
|
||||
@@ -160,8 +160,7 @@
|
||||
oil.name = "fat"
|
||||
oil.desc = "Uh oh, looks like some fat from [src]!"
|
||||
oil.loc = location
|
||||
location.hotspot_expose(700, 50, 1)
|
||||
//TODO have a chance of setting the tile on fire
|
||||
location.hotspot_expose(700, 1)
|
||||
|
||||
/obj/machinery/cooker/proc/changename(obj/item/name, obj/item/setme)
|
||||
setme.name = "[thiscooktype] [name.name]"
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
|
||||
/obj/structure/bonfire/proc/Burn()
|
||||
var/turf/current_location = get_turf(src)
|
||||
current_location.hotspot_expose(1000,500,1)
|
||||
current_location.hotspot_expose(1000, 10)
|
||||
for(var/A in current_location)
|
||||
if(A == src)
|
||||
continue
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_UNKNOWN), PROC_REF(on_unknown_trait))
|
||||
RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_UNKNOWN), PROC_REF(on_unknown_trait))
|
||||
|
||||
RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_PRESSURE_VISION), PROC_REF(on_pressure_vision_trait_gain))
|
||||
RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_PRESSURE_VISION), PROC_REF(on_pressure_vision_trait_loss))
|
||||
|
||||
/// Called when [TRAIT_KNOCKEDOUT] is added to the mob.
|
||||
/mob/living/proc/on_knockedout_trait_gain(datum/source)
|
||||
@@ -172,3 +174,17 @@
|
||||
/mob/living/proc/on_unknown_trait_part_2()
|
||||
name = get_visible_name()
|
||||
sec_hud_set_ID()
|
||||
|
||||
/// Called when [TRAIT_PRESSURE_VISION] is added to the mob.
|
||||
/mob/living/proc/on_pressure_vision_trait_gain(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
var/datum/atom_hud/data/pressure/hud = GLOB.huds[DATA_HUD_PRESSURE]
|
||||
if(!(src in hud.hudusers))
|
||||
hud.add_hud_to(src)
|
||||
|
||||
/// Called when [TRAIT_PRESSURE_VISION] is removed from the mob.
|
||||
/mob/living/proc/on_pressure_vision_trait_loss(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
var/datum/atom_hud/data/pressure/hud = GLOB.huds[DATA_HUD_PRESSURE]
|
||||
if(src in hud.hudusers)
|
||||
hud.remove_hud_from(src)
|
||||
|
||||
@@ -121,6 +121,10 @@
|
||||
if(moving_diagonally) //no mob swap during diagonal moves.
|
||||
return TRUE
|
||||
|
||||
if(has_status_effect(STATUS_EFFECT_UNBALANCED))
|
||||
// Don't swap while being shoved by air.
|
||||
return TRUE
|
||||
|
||||
if(a_intent == INTENT_HELP) // Help intent doesn't mob swap a mob pulling a structure
|
||||
if(isstructure(M.pulling) || isstructure(pulling))
|
||||
return TRUE
|
||||
@@ -589,6 +593,11 @@
|
||||
/mob/living/proc/UpdateDamageIcon()
|
||||
return
|
||||
|
||||
/mob/living/get_spacemove_backup(movement_dir)
|
||||
if(movement_dir == 0 && has_status_effect(STATUS_EFFECT_UNBALANCED))
|
||||
return
|
||||
return ..()
|
||||
|
||||
/mob/living/Move(atom/newloc, direct, movetime)
|
||||
if(buckled && buckled.loc != newloc) //not updating position
|
||||
if(!buckled.anchored)
|
||||
@@ -683,41 +692,7 @@
|
||||
/mob/living/experience_pressure_difference(flow_x, flow_y, pressure_resistance_prob_delta = 0)
|
||||
if(buckled)
|
||||
return
|
||||
if(client && client.move_delay >= world.time + world.tick_lag * 2)
|
||||
pressure_resistance_prob_delta -= 30
|
||||
|
||||
var/list/turfs_to_check = list()
|
||||
|
||||
if(has_limbs)
|
||||
var/direction = 0
|
||||
if(flow_x > 100)
|
||||
direction |= EAST
|
||||
if(flow_x < -100)
|
||||
direction |= WEST
|
||||
if(flow_y > 100)
|
||||
direction |= NORTH
|
||||
if(flow_y < -100)
|
||||
direction |= SOUTH
|
||||
|
||||
var/turf/T = get_step(src, angle2dir(dir2angle(direction) + 90))
|
||||
if(T)
|
||||
turfs_to_check += T
|
||||
|
||||
T = get_step(src, angle2dir(dir2angle(direction) - 90))
|
||||
if(T)
|
||||
turfs_to_check += T
|
||||
|
||||
for(var/t in turfs_to_check)
|
||||
T = t
|
||||
if(T.density)
|
||||
pressure_resistance_prob_delta -= 20
|
||||
continue
|
||||
for(var/atom/movable/AM in T)
|
||||
if(AM.density && AM.anchored)
|
||||
pressure_resistance_prob_delta -= 20
|
||||
break
|
||||
|
||||
..(flow_x, flow_y, pressure_resistance_prob_delta)
|
||||
..()
|
||||
|
||||
/*//////////////////////
|
||||
START RESIST PROCS
|
||||
@@ -982,6 +957,29 @@
|
||||
var/datum/status_effect/incapacitating/slowed/S = IsSlowed()
|
||||
if(S)
|
||||
. += S.slowdown_value
|
||||
else
|
||||
// Only apply directional slow if we don't have a full slow.
|
||||
var/datum/status_effect/incapacitating/directional_slow/DS = has_status_effect(STATUS_EFFECT_DIRECTIONAL_SLOW)
|
||||
if(DS)
|
||||
if(DS.direction == last_move)
|
||||
// Moving directly in the direction we're slowed, full penalty
|
||||
. += DS.slowdown_value
|
||||
else if(REVERSE_DIR(DS.direction) == last_move)
|
||||
// Moving directly opposite to the slow, no penalty.
|
||||
// Lint doesn't like this block being empty so, uh, add zero, I guess.
|
||||
. += 0
|
||||
else if(IS_DIR_CARDINAL(DS.direction) || IS_DIR_CARDINAL(last_move))
|
||||
if(DS.direction & last_move)
|
||||
// Moving roughly in the direction we're slowed, full penalty.
|
||||
. += DS.slowdown_value
|
||||
else if(!(REVERSE_DIR(DS.direction) & last_move))
|
||||
// Moving perpendicular to the slow, partial penalty.
|
||||
. += DS.slowdown_value / 2
|
||||
// Moving roughly opposite to the slow, no penalty.
|
||||
else
|
||||
// Diagonal move perpendicular to the slow, partial penalty.
|
||||
. += DS.slowdown_value / 2
|
||||
|
||||
if(forced_look)
|
||||
. += DIRECTION_LOCK_SLOWDOWN
|
||||
if(ignorewalk)
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
if(!G || G.oxygen() < 1)
|
||||
ExtinguishMob() //If there's no oxygen in the tile we're on, put out the fire
|
||||
return FALSE
|
||||
T.hotspot_expose(700, 50, 1)
|
||||
T.hotspot_expose(700, 10)
|
||||
SEND_SIGNAL(src, COMSIG_LIVING_FIRE_TICK)
|
||||
return TRUE
|
||||
|
||||
|
||||
@@ -512,26 +512,26 @@ STATUS EFFECTS
|
||||
/mob/living/proc/IsSlowed()
|
||||
return has_status_effect(STATUS_EFFECT_SLOWED)
|
||||
|
||||
/mob/living/proc/Slowed(amount, _slowdown_value)
|
||||
/mob/living/proc/Slowed(amount, slowdown_value)
|
||||
var/datum/status_effect/incapacitating/slowed/S = IsSlowed()
|
||||
if(S)
|
||||
S.duration = max(world.time + amount, S.duration)
|
||||
S.slowdown_value = _slowdown_value
|
||||
S.slowdown_value = slowdown_value
|
||||
else if(amount > 0)
|
||||
S = apply_status_effect(STATUS_EFFECT_SLOWED, amount, _slowdown_value)
|
||||
S = apply_status_effect(STATUS_EFFECT_SLOWED, amount, slowdown_value)
|
||||
return S
|
||||
|
||||
/mob/living/proc/SetSlowed(amount, _slowdown_value)
|
||||
/mob/living/proc/SetSlowed(amount, slowdown_value)
|
||||
var/datum/status_effect/incapacitating/slowed/S = IsSlowed()
|
||||
if(amount <= 0 || _slowdown_value <= 0)
|
||||
if(amount <= 0 || slowdown_value <= 0)
|
||||
if(S)
|
||||
qdel(S)
|
||||
else
|
||||
if(S)
|
||||
S.duration = amount
|
||||
S.slowdown_value = _slowdown_value
|
||||
S.slowdown_value = slowdown_value
|
||||
else
|
||||
S = apply_status_effect(STATUS_EFFECT_SLOWED, amount, _slowdown_value)
|
||||
S = apply_status_effect(STATUS_EFFECT_SLOWED, amount, slowdown_value)
|
||||
return S
|
||||
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#define MODE_MESON "meson"
|
||||
#define MODE_TRAY "t-ray"
|
||||
#define MODE_RAD "radiation"
|
||||
#define MODE_PRESSURE "pressure"
|
||||
#define RAD_RANGE 5
|
||||
|
||||
/datum/action/innate/robot_sight/engineering_scanner
|
||||
@@ -47,7 +48,7 @@
|
||||
sight_mode = BORGMESON
|
||||
button_overlay_icon = 'icons/obj/clothing/glasses.dmi'
|
||||
button_overlay_icon_state = "trayson-meson"
|
||||
var/list/mode_list = list(MODE_NONE = MODE_MESON, MODE_MESON = MODE_TRAY, MODE_TRAY = MODE_RAD, MODE_RAD = MODE_NONE)
|
||||
var/list/mode_list = list(MODE_NONE = MODE_MESON, MODE_MESON = MODE_TRAY, MODE_TRAY = MODE_RAD, MODE_RAD = MODE_PRESSURE, MODE_PRESSURE = MODE_NONE)
|
||||
var/mode = MODE_NONE
|
||||
|
||||
/datum/action/innate/robot_sight/engineering_scanner/Activate()
|
||||
@@ -67,8 +68,13 @@
|
||||
else
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
|
||||
if(mode == MODE_PRESSURE)
|
||||
ADD_TRAIT(R, TRAIT_PRESSURE_VISION, "borgsight")
|
||||
else
|
||||
REMOVE_TRAIT(R, TRAIT_PRESSURE_VISION, "borgsight")
|
||||
|
||||
/datum/action/innate/robot_sight/engineering_scanner/Deactivate()
|
||||
return
|
||||
REMOVE_TRAIT(owner, TRAIT_PRESSURE_VISION, "borgsight")
|
||||
|
||||
/datum/action/innate/robot_sight/engineering_scanner/process()
|
||||
var/mob/living/silicon/robot/user = owner
|
||||
@@ -84,6 +90,7 @@
|
||||
#undef MODE_MESON
|
||||
#undef MODE_TRAY
|
||||
#undef MODE_RAD
|
||||
#undef MODE_PRESSURE
|
||||
#undef RAD_RANGE
|
||||
|
||||
/datum/action/innate/robot_magpulse
|
||||
|
||||
@@ -25,5 +25,6 @@
|
||||
return ..() || mob_negates_gravity()
|
||||
|
||||
/mob/living/silicon/robot/experience_pressure_difference(flow_x, flow_y)
|
||||
if(!HAS_TRAIT(src, TRAIT_MAGPULSE))
|
||||
return ..()
|
||||
if(HAS_TRAIT(src, TRAIT_MAGPULSE))
|
||||
return
|
||||
..()
|
||||
|
||||
@@ -40,6 +40,11 @@
|
||||
|
||||
playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0)
|
||||
|
||||
/mob/living/silicon/robot/syndicate/air_push(direction, strength)
|
||||
// Syndicate borgs ignore airflow, because they're bloody expensive.
|
||||
// This should probably be revisited later, as part of a broader move_resist/move_force/pull_force rework.
|
||||
return
|
||||
|
||||
/mob/living/silicon/robot/syndicate/medical
|
||||
base_icon = "syndi-medi"
|
||||
icon_state = "syndi-medi"
|
||||
|
||||
@@ -596,7 +596,7 @@ Difficulty: Hard
|
||||
var/obj/effect/hotspot/hotspot = new /obj/effect/hotspot/fake(T)
|
||||
hotspot.temperature = 1000
|
||||
hotspot.recolor()
|
||||
T.hotspot_expose(700,50,1)
|
||||
T.hotspot_expose(700, 50)
|
||||
if(mode == VORTEX)
|
||||
var/turf/T = get_turf(src)
|
||||
for(var/atom/A in T)
|
||||
|
||||
@@ -282,7 +282,7 @@ Difficulty: Medium
|
||||
var/obj/effect/hotspot/hotspot = new /obj/effect/hotspot/fake(T)
|
||||
hotspot.temperature = 1000
|
||||
hotspot.recolor()
|
||||
T.hotspot_expose(700,50,1)
|
||||
T.hotspot_expose(700, 50)
|
||||
for(var/mob/living/L in T.contents)
|
||||
if((L in hit_list) || L == source)
|
||||
continue
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
IMPMINDSHIELD_HUD = 'icons/mob/hud/sechud.dmi',
|
||||
IMPCHEM_HUD = 'icons/mob/hud/sechud.dmi',
|
||||
IMPTRACK_HUD = 'icons/mob/hud/sechud.dmi',
|
||||
PRESSURE_HUD = 'icons/effects/effects.dmi',
|
||||
)
|
||||
|
||||
for(var/hud in hud_possible)
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
#define DAMAGE_INCREASE_MULTIPLIER 0.25
|
||||
|
||||
|
||||
#define THERMAL_RELEASE_MODIFIER 5 //Higher == less heat released during reaction, not to be confused with the above values
|
||||
#define THERMAL_RELEASE_MODIFIER 1 //Higher == less heat released during reaction, not to be confused with the above values
|
||||
#define PLASMA_RELEASE_MODIFIER 750 //Higher == less plasma released by reaction
|
||||
#define OXYGEN_RELEASE_MODIFIER 325 //Higher == less oxygen released at high temperature/power
|
||||
|
||||
@@ -230,6 +230,11 @@
|
||||
/// Run S-Class event? So we can only run one S-class event per round per crystal
|
||||
var/has_run_sclass = FALSE
|
||||
|
||||
/// How often do we want to process the crystal?
|
||||
var/ticks_per_run = 5
|
||||
/// How long has it been since we processed the crystal?
|
||||
var/tick_counter = 0
|
||||
|
||||
|
||||
/obj/machinery/atmospherics/supermatter_crystal/Initialize(mapload)
|
||||
. = ..()
|
||||
@@ -407,8 +412,11 @@
|
||||
qdel(src)
|
||||
|
||||
/obj/machinery/atmospherics/supermatter_crystal/process_atmos()
|
||||
var/datum/milla_safe/supermatter_process/milla = new()
|
||||
milla.invoke_async(src)
|
||||
tick_counter += SSair.wait
|
||||
if(tick_counter >= ticks_per_run)
|
||||
var/datum/milla_safe/supermatter_process/milla = new()
|
||||
milla.invoke_async(src)
|
||||
tick_counter -= ticks_per_run
|
||||
|
||||
/datum/milla_safe/supermatter_process
|
||||
|
||||
@@ -424,7 +432,7 @@
|
||||
if(isnull(T)) // We have a null turf...something is wrong, stop processing this entity.
|
||||
return PROCESS_KILL
|
||||
|
||||
if(!istype(T)) //We are in a crate or somewhere that isn't turf, if we return to turf resume processing but for now.
|
||||
if(!istype(loc, /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(T.density)
|
||||
@@ -566,15 +574,11 @@
|
||||
//Power * 0.55 * a value between 1 and 0.8
|
||||
var/device_energy = power * REACTION_POWER_MODIFIER
|
||||
|
||||
//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.set_temperature(removed.temperature() + max(0, min((2500 * dynamic_heat_modifier) - removed.temperature(), (((device_energy * dynamic_heat_modifier) / THERMAL_RELEASE_MODIFIER) * heat_multiplier))))
|
||||
// Calculate temperature change in terms of thermal energy, scaled by the average specific heat of the gas.
|
||||
if(removed.total_moles())
|
||||
var/produced_joules = max(0, ((device_energy * dynamic_heat_modifier) / THERMAL_RELEASE_MODIFIER) * heat_multiplier)
|
||||
produced_joules *= (removed.heat_capacity() / removed.total_moles())
|
||||
removed.set_temperature((removed.thermal_energy() + produced_joules) / removed.heat_capacity())
|
||||
|
||||
//Calculate how much gas to release
|
||||
//Varies based on power and gas content
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
var/obj/effect/hotspot/hotspot = new /obj/effect/hotspot/fake(location)
|
||||
hotspot.temperature = 1000
|
||||
hotspot.recolor()
|
||||
location.hotspot_expose(700, 50, 1)
|
||||
location.hotspot_expose(700, 50)
|
||||
|
||||
//magazines//
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
var/obj/effect/hotspot/hotspot = new /obj/effect/hotspot/fake(location)
|
||||
hotspot.temperature = 1000
|
||||
hotspot.recolor()
|
||||
location.hotspot_expose(700, 50, 1)
|
||||
location.hotspot_expose(700, 50)
|
||||
|
||||
/obj/item/projectile/bullet/incendiary/shell/dragonsbreath
|
||||
name = "dragonsbreath round"
|
||||
|
||||
@@ -92,14 +92,14 @@
|
||||
build_path = /obj/item/clothing/glasses/meson/engine
|
||||
category = list("Equipment")
|
||||
|
||||
/datum/design/tray_goggles
|
||||
name = "Optical T-Ray Scanners"
|
||||
desc = "Used by engineering staff to see underfloor objects such as cables and pipes."
|
||||
id = "tray_goggles"
|
||||
/datum/design/atmos_goggles
|
||||
name = "Atmospherics Scanner Goggles"
|
||||
desc = "Used by atmospherics techs to see pressure and underfloor objects such as cables and pipes."
|
||||
id = "atmos_goggles"
|
||||
req_tech = list("materials" = 3, "magnets" = 2, "engineering" = 2)
|
||||
build_type = PROTOLATHE
|
||||
materials = list(MAT_METAL = 500, MAT_GLASS = 500)
|
||||
build_path = /obj/item/clothing/glasses/meson/engine/tray
|
||||
build_path = /obj/item/clothing/glasses/meson/engine/atmos
|
||||
category = list("Equipment")
|
||||
|
||||
/datum/design/nvgmesons
|
||||
|
||||
@@ -219,6 +219,10 @@
|
||||
var/obj/effect/E = AM
|
||||
if(E.is_cleanable())
|
||||
return CARGO_OK
|
||||
return CARGO_SKIP_ATOM
|
||||
|
||||
if(istype(AM, /mob/dead))
|
||||
return CARGO_SKIP_ATOM
|
||||
|
||||
return CARGO_OK
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ GLOBAL_DATUM_INIT(ghost_hud_panel, /datum/ui_module/ghost_hud_panel, new)
|
||||
var/list/hud_type_lookup = list(
|
||||
"medical" = DATA_HUD_MEDICAL_ADVANCED,
|
||||
"security" = DATA_HUD_SECURITY_ADVANCED,
|
||||
"diagnostic" = DATA_HUD_DIAGNOSTIC_ADVANCED
|
||||
"diagnostic" = DATA_HUD_DIAGNOSTIC_ADVANCED,
|
||||
"pressure" = DATA_HUD_PRESSURE
|
||||
)
|
||||
|
||||
/datum/ui_module/ghost_hud_panel/ui_state(mob/user)
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
|
||||
if(location)
|
||||
if(prob(33))
|
||||
location.hotspot_expose(1000, 1000)
|
||||
location.hotspot_expose(1000, 1)
|
||||
sparks.start() //the most radical way to start plasma fires
|
||||
for(var/mob/living/carbon/victim in location)
|
||||
if(victim.body_position == LYING_DOWN)
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@@ -1346,6 +1346,7 @@
|
||||
#include "code\game\objects\structures\nest.dm"
|
||||
#include "code\game\objects\structures\noticeboard.dm"
|
||||
#include "code\game\objects\structures\plasma_geyser.dm"
|
||||
#include "code\game\objects\structures\plasmafire_generator.dm"
|
||||
#include "code\game\objects\structures\plasticflaps.dm"
|
||||
#include "code\game\objects\structures\pondering_orb.dm"
|
||||
#include "code\game\objects\structures\railings.dm"
|
||||
|
||||
@@ -36,6 +36,21 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic_float"
|
||||
version = "1.0.0"
|
||||
@@ -78,16 +93,14 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.69.4"
|
||||
version = "0.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
|
||||
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.12.1",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"itertools",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
@@ -96,7 +109,6 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.60",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -129,22 +141,20 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "byondapi"
|
||||
version = "0.4.7"
|
||||
source = "git+https://github.com/spacestation13/byondapi-rs.git#aa4addd95c33b66652b3e35e7401a0ad82aa028e"
|
||||
version = "0.4.11"
|
||||
source = "git+https://github.com/spacestation13/byondapi-rs.git#c3b15e8574eca87865c640616029e6ea72da4cd1"
|
||||
dependencies = [
|
||||
"byondapi-macros",
|
||||
"byondapi-sys",
|
||||
"inventory",
|
||||
"lazy_static",
|
||||
"libloading",
|
||||
"num_enum",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byondapi-macros"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/spacestation13/byondapi-rs.git#aa4addd95c33b66652b3e35e7401a0ad82aa028e"
|
||||
version = "0.1.3"
|
||||
source = "git+https://github.com/spacestation13/byondapi-rs.git#c3b15e8574eca87865c640616029e6ea72da4cd1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -153,14 +163,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "byondapi-sys"
|
||||
version = "0.11.1"
|
||||
source = "git+https://github.com/spacestation13/byondapi-rs.git#aa4addd95c33b66652b3e35e7401a0ad82aa028e"
|
||||
version = "0.11.2"
|
||||
source = "git+https://github.com/spacestation13/byondapi-rs.git#c3b15e8574eca87865c640616029e6ea72da4cd1"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"doxygen-rs",
|
||||
"libloading",
|
||||
"rustc_version",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -189,6 +197,15 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
@@ -204,6 +221,20 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.7.0"
|
||||
@@ -227,6 +258,12 @@ version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52fdfaf2bee6357023bf7f95b15a8ef0b82759d2bce705cc45efcae9ae10f0ff"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
@@ -325,16 +362,6 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.12"
|
||||
@@ -345,6 +372,15 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.33"
|
||||
@@ -411,12 +447,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -480,15 +530,6 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
@@ -496,16 +537,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
name = "js-sys"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
@@ -515,20 +554,14 @@ checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.3"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
|
||||
[[package]]
|
||||
name = "lodepng"
|
||||
version = "3.10.7"
|
||||
@@ -576,6 +609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -630,18 +664,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
|
||||
checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
|
||||
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
@@ -706,6 +740,19 @@ dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
@@ -887,40 +934,20 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustlibs"
|
||||
version = "1.0.0"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"atomic_float",
|
||||
"bitflags 2.5.0",
|
||||
"byondapi",
|
||||
"chrono",
|
||||
"diff",
|
||||
"dmm-tools",
|
||||
"eyre",
|
||||
"fxhash",
|
||||
"itertools 0.10.5",
|
||||
"itertools",
|
||||
"png",
|
||||
"rand",
|
||||
"regex",
|
||||
"scc",
|
||||
@@ -966,12 +993,6 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.210"
|
||||
@@ -1010,6 +1031,12 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
@@ -1122,17 +1149,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.2"
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@@ -1164,6 +1233,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustlibs"
|
||||
version = "1.0.0"
|
||||
version = "2.0.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@@ -33,3 +33,5 @@ serde_json = { version = "1.0" }
|
||||
walkdir = "2.5.0"
|
||||
# regex
|
||||
regex = "1.10.5"
|
||||
png = "0.17.16"
|
||||
chrono = "0.4.39"
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
use byondapi::global_call::call_global;
|
||||
use byondapi::prelude::ByondValue;
|
||||
use byondapi::threadsync::thread_sync;
|
||||
use chrono::prelude::Utc;
|
||||
use eyre;
|
||||
|
||||
/// Call stack trace dm method with message.
|
||||
pub(crate) fn dm_call_stack_trace(msg: String) {
|
||||
let msg = byondapi::value::ByondValue::try_from(msg).unwrap();
|
||||
// this is really ugly, cause we want to get id/ref to a proc name string
|
||||
// that is already allocated, and don't want to allocate a new string entirely
|
||||
byondapi::global_call::call_global_id(
|
||||
{
|
||||
static STRING_ID: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
|
||||
*STRING_ID.get_or_init(|| byondapi::byond_string::str_id_of("_stack_trace").unwrap())
|
||||
},
|
||||
&[msg],
|
||||
)
|
||||
.unwrap();
|
||||
pub(crate) fn dm_call_stack_trace(msg: String) -> eyre::Result<()> {
|
||||
call_global("stack_trace", &[ByondValue::new_str(msg)?])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Panic handler, called on unhandled errors.
|
||||
@@ -18,7 +16,17 @@ pub(crate) fn dm_call_stack_trace(msg: String) {
|
||||
pub(crate) fn setup_panic_handler() {
|
||||
std::panic::set_hook(Box::new(|info| {
|
||||
let msg = format!("Panic \n {:#?}", info);
|
||||
let _ = std::fs::write("./rustlibs_panic.txt", msg.clone());
|
||||
dm_call_stack_trace(msg);
|
||||
let msg_copy = msg.clone();
|
||||
let _ = thread_sync(
|
||||
|| -> ByondValue {
|
||||
if let Err(error) = dm_call_stack_trace(msg_copy) {
|
||||
let second_msg = format!("BYOND error \n {:#?}", error);
|
||||
let _ = std::fs::write(Utc::now().format("data/rustlibs_dm_trace_failed_%Y%m%d_%H%M%S.txt").to_string(), second_msg.clone());
|
||||
}
|
||||
Default::default()
|
||||
},
|
||||
true,
|
||||
);
|
||||
let _ = std::fs::write(Utc::now().format("data/rustlibs_panic_%Y%m%d_%H%M%S.txt").to_string(), msg.clone());
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ fn mapmanip_submap_extract_insert(
|
||||
|
||||
///
|
||||
#[byondapi::bind]
|
||||
fn mapmanip_read_dmm_file(path: ByondValue) {
|
||||
fn mapmanip_read_dmm_file(path: ByondValue) -> eyre::Result<ByondValue> {
|
||||
internal_mapmanip_read_dmm_file(path)
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ pub unsafe extern "C" fn read_dmm_file_ffi(
|
||||
) {
|
||||
Ok(val) => val,
|
||||
Err(info) => {
|
||||
dm_call_stack_trace(format!("Rustlibs ERROR read_dmm_file_ffi() \n {info:#?}"));
|
||||
let _ = dm_call_stack_trace(format!("Rustlibs ERROR read_dmm_file_ffi() \n {info:#?}"));
|
||||
ByondValue::null()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::logging;
|
||||
use crate::milla::constants::*;
|
||||
use crate::milla::conversion;
|
||||
use crate::milla::model::*;
|
||||
use crate::milla::simulate;
|
||||
use crate::milla::statics::*;
|
||||
use crate::milla::tick;
|
||||
use byondapi::global_call::call_global;
|
||||
@@ -9,20 +10,22 @@ use byondapi::map::byond_xyz;
|
||||
use byondapi::prelude::*;
|
||||
use eyre::eyre;
|
||||
use eyre::Result;
|
||||
use std::env;
|
||||
use std::thread;
|
||||
use std::time::Instant;
|
||||
|
||||
/// BYOND API for ensuring the buffers are usable.
|
||||
#[byondapi::bind]
|
||||
fn milla_initialize(byond_z: ByondValue) {
|
||||
fn milla_initialize(byond_z: ByondValue) -> eyre::Result<ByondValue> {
|
||||
logging::setup_panic_handler();
|
||||
env::set_var("RUST_BACKTRACE", "1");
|
||||
let z = f32::try_from(byond_z)? as i32 - 1;
|
||||
internal_initialize(z)?;
|
||||
Ok(Default::default())
|
||||
Ok(ByondValue::null())
|
||||
}
|
||||
|
||||
/// Ensure that buffers are available.
|
||||
pub(crate) fn internal_initialize(z: i32) -> Result<(), eyre::Error> {
|
||||
pub(crate) fn internal_initialize(z: i32) -> eyre::Result<ByondValue> {
|
||||
if z >= MAX_Z_LEVELS {
|
||||
return Err(eyre!(
|
||||
"Suspiciously high Z level {} initialized, update MAX_Z_LEVELS if this is intentional.",
|
||||
@@ -31,7 +34,7 @@ pub(crate) fn internal_initialize(z: i32) -> Result<(), eyre::Error> {
|
||||
}
|
||||
let buffers = BUFFERS.get_or_init(Buffers::new);
|
||||
buffers.init_to(z);
|
||||
Ok(Default::default())
|
||||
Ok(ByondValue::null())
|
||||
}
|
||||
|
||||
/// BYOND API for defining an environment that a tile can be exposed to.
|
||||
@@ -44,7 +47,7 @@ fn milla_create_environment(
|
||||
sleeping_agent: ByondValue,
|
||||
agent_b: ByondValue,
|
||||
temperature: ByondValue,
|
||||
) {
|
||||
) -> eyre::Result<ByondValue> {
|
||||
logging::setup_panic_handler();
|
||||
Ok(ByondValue::from(internal_create_environment(
|
||||
conversion::byond_to_option_f32(oxygen)?,
|
||||
@@ -112,7 +115,9 @@ fn milla_set_tile(
|
||||
agent_b: ByondValue,
|
||||
temperature: ByondValue,
|
||||
_innate_heat_capacity: ByondValue,
|
||||
) {
|
||||
hotspot_temperature: ByondValue,
|
||||
hotspot_volume: ByondValue,
|
||||
) -> eyre::Result<ByondValue> {
|
||||
logging::setup_panic_handler();
|
||||
let (x, y, z) = byond_xyz(&turf)?.coordinates();
|
||||
internal_set_tile(
|
||||
@@ -136,8 +141,10 @@ fn milla_set_tile(
|
||||
// Temporarily disabled to better match the existing system.
|
||||
//bounded_byond_to_option_f32(innate_heat_capacity, 0.0, f32::INFINITY)?,
|
||||
Some(0.0),
|
||||
conversion::bounded_byond_to_option_f32(hotspot_temperature, 0.0, f32::INFINITY)?,
|
||||
conversion::bounded_byond_to_option_f32(hotspot_volume, 0.0, 1.0)?,
|
||||
)?;
|
||||
Ok(Default::default())
|
||||
Ok(ByondValue::null())
|
||||
}
|
||||
|
||||
/// BYOND API for setting the directions a tile is airtight in.
|
||||
@@ -149,7 +156,7 @@ fn milla_set_tile_airtight(
|
||||
airtight_east: ByondValue,
|
||||
airtight_south: ByondValue,
|
||||
airtight_west: ByondValue,
|
||||
) {
|
||||
) -> eyre::Result<ByondValue> {
|
||||
logging::setup_panic_handler();
|
||||
let (x, y, z) = byond_xyz(&turf)?.coordinates();
|
||||
internal_set_tile(
|
||||
@@ -171,8 +178,10 @@ fn milla_set_tile_airtight(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
Ok(Default::default())
|
||||
Ok(ByondValue::null())
|
||||
}
|
||||
|
||||
/// Rust version of setting the atmos details of a tile.
|
||||
@@ -196,6 +205,8 @@ pub(crate) fn internal_set_tile(
|
||||
temperature: Option<f32>,
|
||||
thermal_energy: Option<f32>,
|
||||
innate_heat_capacity: Option<f32>,
|
||||
hotspot_temperature: Option<f32>,
|
||||
hotspot_volume: Option<f32>,
|
||||
) -> Result<()> {
|
||||
let buffers = BUFFERS.get().ok_or(eyre!("BUFFERS not initialized."))?;
|
||||
let active = buffers.get_active().read().unwrap();
|
||||
@@ -272,25 +283,45 @@ pub(crate) fn internal_set_tile(
|
||||
if let Some(value) = thermal_energy {
|
||||
tile.thermal_energy = value;
|
||||
}
|
||||
if let Some(value) = hotspot_temperature {
|
||||
tile.hotspot_temperature = value;
|
||||
}
|
||||
if let Some(value) = hotspot_volume {
|
||||
tile.hotspot_volume = value;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// BYOND API for fetching the atmos details of a tile.
|
||||
#[byondapi::bind]
|
||||
fn milla_get_tile(turf: ByondValue, list: ByondValue) {
|
||||
fn milla_get_tile(turf: ByondValue, list: ByondValue) -> eyre::Result<ByondValue> {
|
||||
logging::setup_panic_handler();
|
||||
let (x, y, z) = byond_xyz(&turf)?.coordinates();
|
||||
let tile = internal_get_tile(x as i32 - 1, y as i32 - 1, z as i32 - 1)?;
|
||||
let vec: Vec<ByondValue> = (&tile).into();
|
||||
let vec: Vec<ByondValue>;
|
||||
if let Ok(tile) = internal_get_tile(x as i32 - 1, y as i32 - 1, z as i32 - 1) {
|
||||
vec = (&tile).into();
|
||||
} else {
|
||||
// MILLA has died and is unrecoverable.
|
||||
// Uh... uh... report everything as breathable air, I guess?
|
||||
let mut air = Tile::new();
|
||||
air.gases.set_oxygen(20.0);
|
||||
air.gases.set_nitrogen(80.0);
|
||||
air.thermal_energy = air.heat_capacity() * T20C;
|
||||
vec = (&air).into();
|
||||
}
|
||||
list.write_list(vec.as_slice())?;
|
||||
Ok(Default::default())
|
||||
Ok(ByondValue::null())
|
||||
}
|
||||
|
||||
/// Rust version of fetching the atmos details of a tile.
|
||||
pub(crate) fn internal_get_tile(x: i32, y: i32, z: i32) -> Result<Tile> {
|
||||
let buffers = BUFFERS.get().ok_or(eyre!("BUFFERS not initialized."))?;
|
||||
let active = buffers.get_active().read().unwrap();
|
||||
let maybe_active = buffers.get_active().read();
|
||||
if maybe_active.is_err() {
|
||||
return Err(eyre!("MILLA buffers have been poisoned."));
|
||||
}
|
||||
let active = maybe_active.unwrap();
|
||||
let z_level = active.0[z as usize].read().unwrap();
|
||||
Ok(z_level
|
||||
.get_tile(ZLevel::maybe_get_index(x, y).ok_or(eyre!(
|
||||
@@ -308,7 +339,7 @@ pub(crate) fn internal_get_tile(x: i32, y: i32, z: i32) -> Result<Tile> {
|
||||
/// * Turfs that just passed the threshold for showing plasma or sleeping gas.
|
||||
/// * Turfs with strong airflow out.
|
||||
#[byondapi::bind]
|
||||
fn milla_get_interesting_tiles() {
|
||||
fn milla_get_interesting_tiles() -> eyre::Result<ByondValue> {
|
||||
logging::setup_panic_handler();
|
||||
let interesting_tiles = INTERESTING_TILES.lock().unwrap();
|
||||
let byond_interesting_tiles = interesting_tiles
|
||||
@@ -320,7 +351,7 @@ fn milla_get_interesting_tiles() {
|
||||
|
||||
/// BYOND API for getting a single random interesting tile.
|
||||
#[byondapi::bind]
|
||||
fn milla_get_random_interesting_tile() {
|
||||
fn milla_get_random_interesting_tile() -> eyre::Result<ByondValue> {
|
||||
logging::setup_panic_handler();
|
||||
let interesting_tiles = INTERESTING_TILES.lock().unwrap();
|
||||
let length = interesting_tiles.len() as f32;
|
||||
@@ -342,7 +373,8 @@ fn milla_reduce_superconductivity(
|
||||
east: ByondValue,
|
||||
south: ByondValue,
|
||||
west: ByondValue,
|
||||
) {
|
||||
) -> eyre::Result<ByondValue> {
|
||||
logging::setup_panic_handler();
|
||||
let (x, y, z) = byond_xyz(&turf)?.coordinates();
|
||||
let rust_north = conversion::bounded_byond_to_option_f32(north, 0.0, 1.0)?;
|
||||
let rust_east = conversion::bounded_byond_to_option_f32(east, 0.0, 1.0)?;
|
||||
@@ -357,7 +389,7 @@ fn milla_reduce_superconductivity(
|
||||
rust_south,
|
||||
rust_west,
|
||||
)?;
|
||||
Ok(Default::default())
|
||||
Ok(ByondValue::null())
|
||||
}
|
||||
|
||||
/// Rust version of capping the superconductivity of a tile.
|
||||
@@ -371,7 +403,7 @@ pub(crate) fn internal_reduce_superconductivity(
|
||||
east: Option<f32>,
|
||||
south: Option<f32>,
|
||||
west: Option<f32>,
|
||||
) -> Result<(), eyre::Error> {
|
||||
) -> eyre::Result<()> {
|
||||
let buffers = BUFFERS.get().ok_or(eyre!("BUFFERS not initialized."))?;
|
||||
let active = buffers.get_active().read().unwrap();
|
||||
let maybe_z_level = active.0[z as usize].try_write();
|
||||
@@ -404,10 +436,11 @@ pub(crate) fn internal_reduce_superconductivity(
|
||||
|
||||
/// BYOND API for resetting the superconductivity of a tile.
|
||||
#[byondapi::bind]
|
||||
fn milla_reset_superconductivity(turf: ByondValue) {
|
||||
fn milla_reset_superconductivity(turf: ByondValue) -> eyre::Result<ByondValue> {
|
||||
logging::setup_panic_handler();
|
||||
let (x, y, z) = byond_xyz(&turf)?.coordinates();
|
||||
internal_reset_superconductivity(x as i32 - 1, y as i32 - 1, z as i32 - 1)?;
|
||||
Ok(Default::default())
|
||||
Ok(ByondValue::null())
|
||||
}
|
||||
|
||||
/// Rust version of resetting the superconductivity of a tile.
|
||||
@@ -434,27 +467,185 @@ pub(crate) fn internal_reset_superconductivity(x: i32, y: i32, z: i32) -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// BYOND API for a heat source creating a hotspot on a tile.
|
||||
#[byondapi::bind]
|
||||
fn milla_create_hotspot(
|
||||
turf: ByondValue,
|
||||
temperature: ByondValue,
|
||||
volume: ByondValue,
|
||||
) -> eyre::Result<ByondValue> {
|
||||
logging::setup_panic_handler();
|
||||
let (x, y, z) = byond_xyz(&turf)?.coordinates();
|
||||
let rust_temperature =
|
||||
conversion::bounded_byond_to_option_f32(temperature, 0.0, f32::INFINITY)?
|
||||
.ok_or(eyre!("Hotspot temperature is required.."))?;
|
||||
let rust_volume = conversion::bounded_byond_to_option_f32(volume, 0.0, TILE_VOLUME)?
|
||||
.ok_or(eyre!("Hotspot volume is required.."))?;
|
||||
|
||||
internal_create_hotspot(
|
||||
x as i32 - 1,
|
||||
y as i32 - 1,
|
||||
z as i32 - 1,
|
||||
rust_temperature,
|
||||
rust_volume / TILE_VOLUME,
|
||||
)?;
|
||||
Ok(ByondValue::null())
|
||||
}
|
||||
|
||||
/// Rust version of a heat source creating a hotspot.
|
||||
pub(crate) fn internal_create_hotspot(
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
temperature: f32,
|
||||
volume: f32,
|
||||
) -> Result<()> {
|
||||
let buffers = BUFFERS.get().ok_or(eyre!("BUFFERS not initialized."))?;
|
||||
let active = buffers.get_active().read().unwrap();
|
||||
let maybe_z_level = active.0[z as usize].try_write();
|
||||
if maybe_z_level.is_err() {
|
||||
return Err(eyre!(
|
||||
"Tried to write during asynchronous, read-only atmos. Use a /datum/milla_safe/..."
|
||||
));
|
||||
}
|
||||
let mut z_level = maybe_z_level.unwrap();
|
||||
let tile = z_level.get_tile_mut(ZLevel::maybe_get_index(x, y).ok_or(eyre!(
|
||||
"Bad coordinates ({}, {}, {})",
|
||||
x + 1,
|
||||
y + 1,
|
||||
z + 1
|
||||
))?);
|
||||
|
||||
if temperature <= tile.temperature() || volume == 0.0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if tile.hotspot_volume == 0.0 {
|
||||
tile.hotspot_temperature = temperature;
|
||||
tile.hotspot_volume = volume;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let excess_thermal_energy = (temperature - tile.temperature()) * tile.heat_capacity() * volume;
|
||||
if excess_thermal_energy > 0.0 {
|
||||
simulate::adjust_hotspot(tile, excess_thermal_energy);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// BYOND API for tracking the pressure of all nearby tiles next tick.
|
||||
#[byondapi::bind]
|
||||
fn milla_track_pressure_tiles(
|
||||
turf: ByondValue,
|
||||
byond_radius: ByondValue,
|
||||
) -> eyre::Result<ByondValue> {
|
||||
logging::setup_panic_handler();
|
||||
let (x, y, z) = byond_xyz(&turf)?.coordinates();
|
||||
let radius = conversion::bounded_byond_to_option_f32(byond_radius, 0.0, MAP_SIZE as f32)?
|
||||
.ok_or(eyre!("Invalid radius: {:#?}", byond_radius))? as i32;
|
||||
|
||||
internal_track_pressure_tiles(x as i32 - 1, y as i32 - 1, z as i32 - 1, radius)?;
|
||||
Ok(ByondValue::null())
|
||||
}
|
||||
|
||||
/// Rust version of tracking the pressure of all nearby tiles next tick.
|
||||
fn internal_track_pressure_tiles(x: i32, y: i32, z: i32, radius: i32) -> eyre::Result<()> {
|
||||
let mut tracked_pressure_tiles = TRACKED_PRESSURE_TILES.lock().unwrap();
|
||||
for dx in -radius..=radius {
|
||||
if x + dx < 0 {
|
||||
continue;
|
||||
}
|
||||
if x + dx >= MAP_SIZE as i32 {
|
||||
break;
|
||||
}
|
||||
for dy in -radius..=radius {
|
||||
if y + dy < 0 {
|
||||
continue;
|
||||
}
|
||||
if y + dy >= MAP_SIZE as i32 {
|
||||
break;
|
||||
}
|
||||
tracked_pressure_tiles.push((x + dx, y + dy, z as usize));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// BYOND API for getting the tracked pressure tiles.
|
||||
#[byondapi::bind]
|
||||
fn milla_get_tracked_pressure_tiles() -> eyre::Result<ByondValue> {
|
||||
logging::setup_panic_handler();
|
||||
let tracked_pressures = internal_get_tracked_pressure_tiles()?
|
||||
.iter()
|
||||
.map(|v: &f32| ByondValue::from(*v))
|
||||
.collect::<Vec<ByondValue>>();
|
||||
Ok(tracked_pressures.as_slice().try_into()?)
|
||||
}
|
||||
|
||||
/// Rust version of getting the tracked pressure tiles.
|
||||
fn internal_get_tracked_pressure_tiles() -> eyre::Result<Vec<f32>> {
|
||||
let buffers = BUFFERS.get().ok_or(eyre!("BUFFERS not initialized."))?;
|
||||
let inactive = buffers.get_inactive().read().unwrap();
|
||||
let mut tracked_pressure_tiles = TRACKED_PRESSURE_TILES.lock().unwrap();
|
||||
|
||||
let mut tiles_by_zlevel: Vec<Vec<(i32, i32)>> = Vec::new();
|
||||
for _z in 0..inactive.0.len() {
|
||||
tiles_by_zlevel.push(Vec::new());
|
||||
}
|
||||
|
||||
for (x, y, z) in tracked_pressure_tiles.drain(..) {
|
||||
if z < tiles_by_zlevel.len() {
|
||||
tiles_by_zlevel[z].push((x, y));
|
||||
}
|
||||
}
|
||||
|
||||
// This is a bit gross, but the easiest way to return the coordinates and data is to cast the
|
||||
// coordinates to f32. It feels bad in Rust, but everything is an f32 in BYOND, so it's more or
|
||||
// less a no-op.
|
||||
let mut tracked_pressures: Vec<f32> = Vec::new();
|
||||
for z in 0..tiles_by_zlevel.len() {
|
||||
let z_level = inactive.0[z as usize].read().unwrap();
|
||||
for (x, y) in &tiles_by_zlevel[z] {
|
||||
if let Some(index) = ZLevel::maybe_get_index(*x, *y) {
|
||||
tracked_pressures.push(*x as f32 + 1.0);
|
||||
tracked_pressures.push(*y as f32 + 1.0);
|
||||
tracked_pressures.push(z as f32 + 1.0);
|
||||
tracked_pressures.push(z_level.get_tile(index).pressure());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tracked_pressures)
|
||||
}
|
||||
|
||||
/// BYOND API for starting an atmos tick.
|
||||
#[byondapi::bind]
|
||||
fn milla_spawn_tick_thread() {
|
||||
logging::setup_panic_handler();
|
||||
fn milla_spawn_tick_thread() -> eyre::Result<ByondValue> {
|
||||
thread::spawn(|| -> Result<(), eyre::Error> {
|
||||
let now = Instant::now();
|
||||
let buffers = BUFFERS.get_or_init(Buffers::new);
|
||||
tick::tick(buffers)?;
|
||||
let result = tick::tick(buffers);
|
||||
TICK_TIME.store(
|
||||
now.elapsed().as_millis() as usize,
|
||||
std::sync::atomic::Ordering::Relaxed,
|
||||
);
|
||||
call_global("milla_tick_finished", &[])?;
|
||||
if result.is_ok() {
|
||||
call_global("milla_tick_finished", &[])?;
|
||||
} else {
|
||||
let err = format!("MILLA tick error:\n----\n{:#?}\n----", result);
|
||||
call_global("milla_tick_error", &[ByondValue::new_str(err)?])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
Ok(Default::default())
|
||||
Ok(ByondValue::null())
|
||||
}
|
||||
|
||||
/// BYOND API for asking how long the prior tick took.
|
||||
#[byondapi::bind]
|
||||
fn milla_get_tick_time() {
|
||||
fn milla_get_tick_time() -> eyre::Result<ByondValue> {
|
||||
logging::setup_panic_handler();
|
||||
Ok(ByondValue::from(
|
||||
TICK_TIME.load(std::sync::atomic::Ordering::Relaxed) as f32,
|
||||
@@ -492,6 +683,8 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
Some(1.0),
|
||||
None,
|
||||
Some(1.0),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -507,6 +700,8 @@ mod tests {
|
||||
}
|
||||
assert_eq!(tile.thermal_energy, 0.0);
|
||||
assert_eq!(tile.innate_heat_capacity, 1.0);
|
||||
assert_eq!(tile.hotspot_temperature, 0.0);
|
||||
assert_eq!(tile.hotspot_volume, 1.0);
|
||||
}
|
||||
|
||||
// Set a different set of arbitrary data.
|
||||
@@ -529,6 +724,8 @@ mod tests {
|
||||
None,
|
||||
Some(1.0),
|
||||
None,
|
||||
Some(1.0),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -544,6 +741,8 @@ mod tests {
|
||||
}
|
||||
assert_eq!(tile.thermal_energy, 1.0);
|
||||
assert_eq!(tile.innate_heat_capacity, 0.0);
|
||||
assert_eq!(tile.hotspot_temperature, 1.0);
|
||||
assert_eq!(tile.hotspot_volume, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,12 @@ pub(crate) const MAX_Z_LEVELS: i32 = 10;
|
||||
/// How big is the map? Assumed square.
|
||||
pub(crate) const MAP_SIZE: usize = 255;
|
||||
|
||||
/// One atmosphere, in kPa.
|
||||
pub(crate) const ONE_ATMOSPHERE: f32 = 101.325;
|
||||
|
||||
/// The temperature of space, in Kelvin
|
||||
pub(crate) const TCMB: f32 = 2.725;
|
||||
|
||||
/// 0 degrees Celsius, in Kelvin
|
||||
pub(crate) const T0C: f32 = 273.15;
|
||||
|
||||
@@ -19,17 +25,15 @@ pub(crate) const R_IDEAL_GAS_EQUATION: f32 = 8.31;
|
||||
/// How big a tile is, in liters.
|
||||
pub(crate) const TILE_VOLUME: f32 = 2500.0;
|
||||
|
||||
/// How many moles are needed to make toxins visible.
|
||||
pub(crate) const TOXINS_VISIBILITY_MOLES: f32 = 0.5;
|
||||
/// How many moles of toxins are needed for a fire to exist. For reasons, this is also how hany
|
||||
/// moles are needed to be visible.
|
||||
pub(crate) const TOXINS_MIN_VISIBILITY_MOLES: f32 = 0.5;
|
||||
|
||||
/// How many moles are needed to make sleeping gas visible.
|
||||
pub(crate) const SLEEPING_GAS_VISIBILITY_MOLES: f32 = 1.0;
|
||||
|
||||
/// How many moles of each gas are needed before a reaction should happen.
|
||||
pub(crate) const REACTION_SIGNIFICANCE_MOLES: f32 = 0.5;
|
||||
|
||||
/// How much of the tile a reaction's gas needs to occupy to happen.
|
||||
pub(crate) const REACTION_SIGNIFICANCE_RATIO: f32 = 0.01;
|
||||
/// How much stuff needs to react before we think hotspots and BYOND care.
|
||||
pub(crate) const REACTION_SIGNIFICANCE_MOLES: f32 = 0.1;
|
||||
|
||||
/// How hot does it need to be for Agent B to work?
|
||||
pub(crate) const AGENT_B_CONVERSION_TEMP: f32 = 900.0;
|
||||
@@ -58,12 +62,25 @@ pub(crate) const GAS_AGENT_B: usize = 5;
|
||||
/// How many gases are there?
|
||||
pub(crate) const GAS_COUNT: usize = GAS_AGENT_B + 1;
|
||||
|
||||
/// The two axes, Y and X. The order is arbitrary, but will break the copy from active to inactive
|
||||
/// if changed.
|
||||
pub(crate) const AXES: [(i32, i32); 2] = [(0, 1), (1, 0)];
|
||||
/// The two axes, Y and X. The order is arbitrary, but may break things if changed.
|
||||
pub(crate) const AXES: [(i32, i32); 2] = [(1, 0), (0, 1)];
|
||||
|
||||
/// The four cardinal directions. The order is arbitrary, and doesn't matter.
|
||||
pub(crate) const DIRECTIONS: [(i32, i32); 4] = [(0, 1), (1, 0), (0, -1), (-1, 0)];
|
||||
/// The index of the X axis in AXES.
|
||||
pub(crate) const AXIS_X: usize = 0;
|
||||
/// The index of the Y axis in AXES.
|
||||
pub(crate) const AXIS_Y: usize = 1;
|
||||
|
||||
/// The four directions: up, down, right, and left. The order is arbitrary, but may break things if changed.
|
||||
pub(crate) const DIRECTIONS: [(i32, i32); 4] = [(1, 0), (-1, 0), (0, -1), (0, 1)];
|
||||
|
||||
/// Gives the axis for each direction.
|
||||
pub(crate) const DIRECTION_AXIS: [usize; 4] = [0, 0, 1, 1];
|
||||
|
||||
/// Index for incoming gas.
|
||||
pub(crate) const GAS_FLOW_IN: usize = 0;
|
||||
|
||||
/// Index for outgoing gas.
|
||||
pub(crate) const GAS_FLOW_OUT: usize = 1;
|
||||
|
||||
// The numbers here are completely wrong for actual gases, but they're what LINDA used, so we'll
|
||||
// keep them for now.
|
||||
@@ -102,17 +119,17 @@ pub(crate) const PLASMA_BURN_MIN_TEMP: f32 = 100.0 + T0C;
|
||||
/// How hot does it need to be for a plasma fire to work as well as possible?
|
||||
pub(crate) const PLASMA_BURN_OPTIMAL_TEMP: f32 = 1370.0 + T0C;
|
||||
|
||||
/// How much oxygen needs to be available per unit of plasma.
|
||||
pub(crate) const PLASMA_BURN_REQUIRED_OXYGEN_AVAILABILITY: f32 = 10.0;
|
||||
|
||||
/// How much of the plasma are we willing to burn each tick?
|
||||
pub(crate) const PLASMA_BURN_MAX_RATIO: f32 = 0.25;
|
||||
pub(crate) const PLASMA_BURN_MAX_RATIO: f32 = 0.05;
|
||||
|
||||
/// How much oxygen do we use per plasma at min temp?
|
||||
pub(crate) const PLASMA_BURN_WORST_OXYGEN_PER_PLASMA: f32 = 1.4;
|
||||
/// How much of the plasma do we burn anyway if the ratio would make it really small?
|
||||
pub(crate) const PLASMA_BURN_MIN_MOLES: f32 = 0.001;
|
||||
|
||||
/// How much oxygen do we use per plasma at optimal temp?
|
||||
pub(crate) const PLASMA_BURN_BEST_OXYGEN_PER_PLASMA: f32 = 0.4;
|
||||
/// How much of a boost to burn ratio do we give to hotspots?
|
||||
pub(crate) const PLASMA_BURN_HOTSPOT_RATIO_BOOST: f32 = 10.0;
|
||||
|
||||
/// How much oxygen do we use per plasma?
|
||||
pub(crate) const PLASMA_BURN_OXYGEN_PER_PLASMA: f32 = 0.4;
|
||||
|
||||
/// How much thermal energy is produced, in joules per mole of agent b.
|
||||
pub(crate) const AGENT_B_CONVERSION_ENERGY: f32 = 20_000.0;
|
||||
@@ -123,12 +140,67 @@ pub(crate) const NITROUS_BREAKDOWN_ENERGY: f32 = 200_000.0;
|
||||
/// How much thermal energy is produced, in joules per mole of sleeping toxins.
|
||||
pub(crate) const PLASMA_BURN_ENERGY: f32 = 3_000_000.0;
|
||||
|
||||
/// We allow small deviations in tests, so that floating point precision doesn't cause problems.
|
||||
/// We allow small deviations in tests as our spring chain solution is not exact.
|
||||
#[cfg(test)]
|
||||
pub(crate) const TEST_TOLERANCE: f32 = 0.00001;
|
||||
pub(crate) const TEST_TOLERANCE: f32 = 0.1;
|
||||
|
||||
/// Lose this multiple of heat energy per tick if above T20C.
|
||||
pub(crate) const SPACE_COOLING_FACTOR: f32 = 0.005;
|
||||
/// When space cooling starts, in Kelvin.
|
||||
pub(crate) const SPACE_COOLING_THRESHOLD: f32 = T20C;
|
||||
|
||||
/// Lose this amount of heat energy per tick if above SPACE_COOLING_THRESHOLD.
|
||||
pub(crate) const SPACE_COOLING_FLAT: f32 = 2000.0;
|
||||
|
||||
/// Lose this ratio of heat energy per tick if above SPACE_COOLING_THRESHOLD.
|
||||
pub(crate) const SPACE_COOLING_RATIO: f32 = 0.002;
|
||||
|
||||
/// Tiles with less than this much gas will become empty.
|
||||
pub(crate) const MINIMUM_NONZERO_MOLES: f32 = 0.1;
|
||||
|
||||
/// How many iterations of gas flow are we willing to run per tick?
|
||||
pub(crate) const MAX_ITERATIONS: usize = 100;
|
||||
|
||||
/// When we stop caring about gas changes and end iteration, in moles on a single tile.
|
||||
pub(crate) const GAS_CHANGE_SIGNIFICANCE: f32 = 0.01;
|
||||
|
||||
/// When we stop caring about gas changes and end iteration, roughly as a fraction of the gas.
|
||||
pub(crate) const GAS_CHANGE_SIGNIFICANCE_FRACTION: f32 = 0.001;
|
||||
|
||||
/// When we stop caring about thermal energy changes and end iteration, in thermal energy on a
|
||||
/// single tile.
|
||||
pub(crate) const THERMAL_CHANGE_SIGNIFICANCE: f32 = 0.1;
|
||||
|
||||
/// When we stop caring about thermal energy changes and end iteration, roughly as a fraction of
|
||||
/// the thermal energy.
|
||||
pub(crate) const THERMAL_CHANGE_SIGNIFICANCE_FRACTION: f32 = 0.001;
|
||||
|
||||
/// Controls how strongly each type of gas moves towards an even spread, ignoring wind.
|
||||
/// [0.0, f32::INFINITY]
|
||||
pub(crate) const DIFFUSION_SPEED: f32 = 0.2;
|
||||
|
||||
/// Controls how quickly the wind moves air.
|
||||
/// (0.0, f32::INFINITY]
|
||||
pub(crate) const WIND_STRENGTH: f32 = 8.0;
|
||||
|
||||
/// Controls how fast the wind changes towards what the current pressur gradient wants.
|
||||
/// (0.0, 1.0], a value of 0.25 means getting 25% closer every tick.
|
||||
pub(crate) const WIND_ACCELERATION: f32 = 0.05;
|
||||
|
||||
/// Controls how quickly the wind spreads from tile to tile.
|
||||
/// [0.0, f32::INFINITY]
|
||||
pub(crate) const WIND_SPEED: f32 = 0.5;
|
||||
|
||||
/// A hard cap on how strong wind can become.
|
||||
/// [0.0, f32::INFINITY]
|
||||
pub(crate) const MAX_WIND: f32 = f32::INFINITY;
|
||||
|
||||
/// How fast should temperature flow?
|
||||
/// [0.0, f32::INFINITY]
|
||||
pub(crate) const TEMPERATURE_FLOW_RATE: f32 = 0.2;
|
||||
|
||||
/// Direct multiplier on strength of wind reported to BYOND.
|
||||
/// [0.0, f32::INFINITY]
|
||||
pub(crate) const BYOND_WIND_MULTIPLIER: f32 = 0.5;
|
||||
|
||||
/// The smallest temperature allowed for the purpose of caluclating pressure.
|
||||
/// Prevents weirdness from absolute-zero gas having no pressure at all.
|
||||
pub(crate) const MINIMUM_TEMPERATURE_FOR_PRESSURE: f32 = 1.0;
|
||||
|
||||
@@ -3,6 +3,8 @@ use atomic_float::AtomicF32;
|
||||
use bitflags::bitflags;
|
||||
use byondapi::map::{byond_locatexyz, ByondXYZ};
|
||||
use byondapi::prelude::*;
|
||||
use std::collections::HashSet;
|
||||
use std::ops::Add;
|
||||
use std::sync::{atomic::AtomicBool, atomic::Ordering::Relaxed, RwLock};
|
||||
|
||||
/// Represents a collection of gases, with amounts in moles.
|
||||
@@ -69,7 +71,7 @@ impl GasSet {
|
||||
pub(crate) fn set_dirty(&mut self) {
|
||||
self.dirty.store(true, Relaxed);
|
||||
}
|
||||
fn recalculate(&self) {
|
||||
pub(crate) fn recalculate(&self) {
|
||||
let mut moles = 0.0;
|
||||
let mut heat_capacity = 0.0;
|
||||
for i in 0..GAS_COUNT {
|
||||
@@ -99,7 +101,7 @@ impl GasSet {
|
||||
pub(crate) fn copy_from(&mut self, other: &GasSet) {
|
||||
for i in 0..GAS_COUNT {
|
||||
self.values[i] = other.values[i];
|
||||
let dirty = self.dirty.load(Relaxed);
|
||||
let dirty = other.dirty.load(Relaxed);
|
||||
if dirty {
|
||||
self.dirty.store(true, Relaxed);
|
||||
} else {
|
||||
@@ -111,6 +113,17 @@ impl GasSet {
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) fn add_gases(&mut self, other: &Self) {
|
||||
for i in 0..GAS_COUNT {
|
||||
self.values[i] += other.values[i];
|
||||
}
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn clear(&mut self) {
|
||||
for i in 0..GAS_COUNT {
|
||||
self.values[i] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for GasSet {
|
||||
@@ -121,6 +134,16 @@ impl Clone for GasSet {
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for GasSet {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
let mut result = self.clone();
|
||||
result.add_gases(&other);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines the general behavior of a tile.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub(crate) enum AtmosMode {
|
||||
@@ -130,8 +153,8 @@ pub(crate) enum AtmosMode {
|
||||
Sealed,
|
||||
/// Tile is exposed to the given environment.
|
||||
ExposedTo { environment_id: u8 },
|
||||
/// Prevents hot tiles from automatically decaying towards T20C
|
||||
NoDecay,
|
||||
/// Prevents hot tiles from automatically decaying towards T20C
|
||||
NoDecay,
|
||||
}
|
||||
|
||||
impl From<AtmosMode> for ByondValue {
|
||||
@@ -140,7 +163,7 @@ impl From<AtmosMode> for ByondValue {
|
||||
AtmosMode::Space => ByondValue::from(0.0),
|
||||
AtmosMode::Sealed => ByondValue::from(1.0),
|
||||
AtmosMode::ExposedTo { .. } => ByondValue::from(2.0),
|
||||
AtmosMode::NoDecay => ByondValue::from(3.0),
|
||||
AtmosMode::NoDecay => ByondValue::from(3.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,6 +221,17 @@ pub(crate) struct Tile {
|
||||
pub(crate) superconductivity: Superconductivity,
|
||||
/// How much heat capacity the tile itself has, in joules per kelvin.
|
||||
pub(crate) innate_heat_capacity: f32,
|
||||
/// How hot the tile's hotspot is. A hotspot is a sub-tile reagion that's caught fire.
|
||||
pub(crate) hotspot_temperature: f32,
|
||||
/// How much of the tile the hotspot covers. 1.0 would be the entire tile.
|
||||
pub(crate) hotspot_volume: f32,
|
||||
/// How strongly the air in this tile is flowing towards +axis.
|
||||
pub(crate) wind: [f32; AXES.len()],
|
||||
/// Is there a wall in this direction?
|
||||
pub(crate) wall: [bool; AXES.len()],
|
||||
pub(crate) gas_flow: [[[f32; 2]; GAS_COUNT]; AXES.len()],
|
||||
/// How much fuel was burnt this tick?
|
||||
pub(crate) fuel_burnt: f32,
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
@@ -209,6 +243,12 @@ impl Tile {
|
||||
mode: AtmosMode::Space,
|
||||
superconductivity: Superconductivity::new(),
|
||||
innate_heat_capacity: 0.0,
|
||||
hotspot_temperature: 0.0,
|
||||
hotspot_volume: 0.0,
|
||||
wind: [0.0, 0.0],
|
||||
wall: [false, false],
|
||||
gas_flow: [[[0.0; 2]; GAS_COUNT]; AXES.len()],
|
||||
fuel_burnt: 0.0,
|
||||
}
|
||||
}
|
||||
/// The total heat capacity of this tile and its gases, in joules per kelvin.
|
||||
@@ -224,8 +264,7 @@ impl Tile {
|
||||
self.thermal_energy / heat_capacity
|
||||
}
|
||||
}
|
||||
// Calculates the pressure of a tile.
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
/// Calculates the pressure of a tile.
|
||||
pub(crate) fn pressure(&self) -> f32 {
|
||||
if let AtmosMode::Space = self.mode {
|
||||
return 0.0;
|
||||
@@ -233,12 +272,27 @@ impl Tile {
|
||||
let mut heat_capacity = self.gases.heat_capacity();
|
||||
heat_capacity += self.innate_heat_capacity;
|
||||
if heat_capacity <= 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
let temperature = self.thermal_energy / heat_capacity;
|
||||
self.gases.moles() * temperature * R_IDEAL_GAS_EQUATION / TILE_VOLUME
|
||||
return 0.0;
|
||||
}
|
||||
let temperature = self.thermal_energy / heat_capacity;
|
||||
self.gases.moles()
|
||||
* temperature.max(MINIMUM_TEMPERATURE_FOR_PRESSURE)
|
||||
* R_IDEAL_GAS_EQUATION
|
||||
/ TILE_VOLUME
|
||||
}
|
||||
/// Calculates the partial pressure of a gas in a tile.
|
||||
pub(crate) fn partial_pressure(&self, gas: usize) -> f32 {
|
||||
if self.gases.values[gas] <= 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
self.gases.values[gas]
|
||||
* self.temperature().max(MINIMUM_TEMPERATURE_FOR_PRESSURE)
|
||||
* R_IDEAL_GAS_EQUATION
|
||||
/ TILE_VOLUME
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
pub(crate) fn copy_from(&mut self, other: &Tile) {
|
||||
self.airtight_directions = other.airtight_directions;
|
||||
self.gases.copy_from(&other.gases);
|
||||
@@ -246,6 +300,17 @@ impl Tile {
|
||||
self.mode = other.mode;
|
||||
self.superconductivity.copy_from(&other.superconductivity);
|
||||
self.innate_heat_capacity = other.innate_heat_capacity;
|
||||
self.hotspot_temperature = other.hotspot_temperature;
|
||||
self.hotspot_volume = other.hotspot_volume;
|
||||
for axis in 0..AXES.len() {
|
||||
self.wind[axis] = other.wind[axis];
|
||||
self.wall[axis] = other.wall[axis];
|
||||
for gas in 0..GAS_COUNT {
|
||||
self.gas_flow[axis][gas][GAS_FLOW_IN] = other.gas_flow[axis][gas][GAS_FLOW_IN];
|
||||
self.gas_flow[axis][gas][GAS_FLOW_OUT] = other.gas_flow[axis][gas][GAS_FLOW_OUT];
|
||||
}
|
||||
}
|
||||
self.fuel_burnt = other.fuel_burnt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +341,11 @@ impl From<&Tile> for Vec<ByondValue> {
|
||||
ByondValue::from(value.superconductivity.west),
|
||||
ByondValue::from(value.innate_heat_capacity),
|
||||
ByondValue::from(value.temperature()),
|
||||
ByondValue::from(value.hotspot_temperature),
|
||||
ByondValue::from(value.hotspot_volume),
|
||||
ByondValue::from(value.wind[AXIS_X]),
|
||||
ByondValue::from(value.wind[AXIS_Y]),
|
||||
ByondValue::from(value.fuel_burnt),
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -298,12 +368,12 @@ pub(crate) struct InterestingTile {
|
||||
pub(crate) coords: ByondXYZ,
|
||||
/// A bitmask of reasons this tile is interesting, values are REASON_*.
|
||||
pub(crate) reasons: ReasonFlags,
|
||||
/// These represent the net force generated by air flowing out of the tile along each axis.
|
||||
/// These represent the net force generated by air winding out of the tile along each axis.
|
||||
/// For a tile losing air equally in all directions, they will be zero.
|
||||
/// For a tile losing air towards positive X only, flow_x will be the air pressure moved.
|
||||
pub(crate) flow_x: f32,
|
||||
/// See flow_x.
|
||||
pub(crate) flow_y: f32,
|
||||
/// For a tile losing air towards positive X only, wind_x will be the air pressure moved.
|
||||
pub(crate) wind_x: f32,
|
||||
/// See wind_x.
|
||||
pub(crate) wind_y: f32,
|
||||
}
|
||||
|
||||
/// Convert an InterestingTile into a flat vector of ByondValues.
|
||||
@@ -319,8 +389,8 @@ impl From<&InterestingTile> for Vec<ByondValue> {
|
||||
ret.extend(vec![
|
||||
byond_locatexyz(value.coords).unwrap(),
|
||||
ByondValue::from(value.reasons.bits() as f32),
|
||||
ByondValue::from(value.flow_x),
|
||||
ByondValue::from(value.flow_y),
|
||||
ByondValue::from(value.wind_x),
|
||||
ByondValue::from(value.wind_y),
|
||||
]);
|
||||
|
||||
ret
|
||||
@@ -328,7 +398,10 @@ impl From<&InterestingTile> for Vec<ByondValue> {
|
||||
}
|
||||
|
||||
/// A single Z level in the atmos model.
|
||||
pub(crate) struct ZLevel(Box<[Tile; MAP_SIZE * MAP_SIZE]>);
|
||||
pub(crate) struct ZLevel {
|
||||
tiles: Box<[Tile; MAP_SIZE * MAP_SIZE]>,
|
||||
pub(crate) active_pressure_chunks: HashSet<(u8, u8)>,
|
||||
}
|
||||
|
||||
impl ZLevel {
|
||||
pub(crate) fn new() -> Self {
|
||||
@@ -336,7 +409,10 @@ impl ZLevel {
|
||||
for _ in 0..MAP_SIZE * MAP_SIZE {
|
||||
unbuilt.push(Tile::new());
|
||||
}
|
||||
ZLevel(unbuilt.into_boxed_slice().try_into().unwrap())
|
||||
ZLevel {
|
||||
tiles: unbuilt.into_boxed_slice().try_into().unwrap(),
|
||||
active_pressure_chunks: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_get_index(x: i32, y: i32) -> Option<usize> {
|
||||
@@ -348,21 +424,22 @@ impl ZLevel {
|
||||
}
|
||||
|
||||
pub(crate) fn get_tile(&self, index: usize) -> &Tile {
|
||||
&self.0[index]
|
||||
&self.tiles[index]
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn maybe_get_tile(&self, x: i32, y: i32) -> Option<&Tile> {
|
||||
Some(&self.0[ZLevel::maybe_get_index(x, y)?])
|
||||
Some(&self.tiles[ZLevel::maybe_get_index(x, y)?])
|
||||
}
|
||||
|
||||
pub(crate) fn get_tile_mut(&mut self, index: usize) -> &mut Tile {
|
||||
&mut self.0[index]
|
||||
&mut self.tiles[index]
|
||||
}
|
||||
|
||||
pub(crate) fn get_pair_mut(&mut self, index1: usize, index2: usize) -> (&mut Tile, &mut Tile) {
|
||||
// Split borrow to get two mutable tiles at the same time.
|
||||
// Ref: https://doc.rust-lang.org/nomicon/borrow-splitting.html
|
||||
let ptr = self.0.as_mut_ptr();
|
||||
let ptr = self.tiles.as_mut_ptr();
|
||||
unsafe {
|
||||
assert!(index1 != index2);
|
||||
|
||||
@@ -374,9 +451,10 @@ impl ZLevel {
|
||||
}
|
||||
|
||||
pub(crate) fn copy_from(&mut self, other: &ZLevel) {
|
||||
for i in 0..self.0.len() {
|
||||
self.0[i].copy_from(&other.0[i]);
|
||||
for i in 0..self.tiles.len() {
|
||||
self.tiles[i].copy_from(&other.tiles[i]);
|
||||
}
|
||||
self.active_pressure_chunks = other.active_pressure_chunks.clone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,9 +533,11 @@ impl Buffers {
|
||||
}
|
||||
|
||||
/// Create an environment for ExposedTo.
|
||||
pub(crate) fn create_environment(&self, tile: Tile) -> u8 {
|
||||
pub(crate) fn create_environment(&self, mut tile: Tile) -> u8 {
|
||||
let mut environments = self.environments.write().unwrap();
|
||||
let id = environments.len() as u8;
|
||||
tile.mode = AtmosMode::ExposedTo { environment_id: id };
|
||||
tile.gases.recalculate();
|
||||
environments.push(tile);
|
||||
id
|
||||
}
|
||||
|
||||
@@ -10,5 +10,10 @@ pub(crate) static BUFFERS: OnceLock<Buffers> = OnceLock::new();
|
||||
/// We only write this once per tick, and only read it on user input.
|
||||
pub(crate) static INTERESTING_TILES: Mutex<Vec<InterestingTile>> = Mutex::new(Vec::new());
|
||||
|
||||
/// The current set of tiles BYOND wants the pressure of.
|
||||
/// Written to via BYOND call.
|
||||
/// Read from and cleared via BYOND call.
|
||||
pub(crate) static TRACKED_PRESSURE_TILES: Mutex<Vec<(i32, i32, usize)>> = Mutex::new(Vec::new());
|
||||
|
||||
/// How long the last tick took, in milliseconds.
|
||||
pub(crate) static TICK_TIME: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::milla::constants::*;
|
||||
use crate::milla::model::*;
|
||||
use crate::milla::simulate;
|
||||
use crate::milla::statics::*;
|
||||
use eyre;
|
||||
use scc::Bag;
|
||||
use std::sync::RwLock;
|
||||
use std::thread;
|
||||
@@ -13,11 +13,12 @@ pub(crate) fn tick(buffers: &Buffers) -> Result<(), eyre::Error> {
|
||||
assert!(thread_priority::ThreadPriority::Min
|
||||
.set_for_current()
|
||||
.is_ok());
|
||||
let active = buffers.get_active().read().unwrap();
|
||||
let inactive = buffers.get_inactive().read().unwrap();
|
||||
let prev = buffers.get_active().read().unwrap();
|
||||
let next = buffers.get_inactive().read().unwrap();
|
||||
|
||||
let new_interesting_tiles: Bag<InterestingTile> = Bag::default();
|
||||
let mut result: Result<(), eyre::Error> = Ok(());
|
||||
let mut result: eyre::Result<()> = Ok(());
|
||||
let handle_results: RwLock<Vec<eyre::Result<()>>> = RwLock::new(Vec::new());
|
||||
|
||||
// The scope tells Rust that all the threads we create here will end by the time the scope
|
||||
// closes. This allows us to pass things into them that are only borrowed for the lifetime of
|
||||
@@ -25,30 +26,39 @@ pub(crate) fn tick(buffers: &Buffers) -> Result<(), eyre::Error> {
|
||||
thread::scope(|s| {
|
||||
// Force most things to be captured by reference, despite the `move`
|
||||
// in the thread spawn, which is really just for `z`.
|
||||
let active = &active;
|
||||
let inactive = &inactive;
|
||||
let prev = &prev;
|
||||
let next = &next;
|
||||
let new_interesting_tiles = &new_interesting_tiles;
|
||||
|
||||
// Handle each Z level in its own thread.
|
||||
let mut handles = Vec::<ScopedJoinHandle<()>>::new();
|
||||
for z in 0..active.0.len() {
|
||||
let handle_results = &handle_results;
|
||||
for z in 0..prev.0.len() {
|
||||
handles.push(s.spawn(move || {
|
||||
assert!(thread_priority::ThreadPriority::Min
|
||||
.set_for_current()
|
||||
.is_ok());
|
||||
tick_z_level(
|
||||
let result = tick_z_level(
|
||||
buffers,
|
||||
&active.0[z],
|
||||
&inactive.0[z],
|
||||
&prev.0[z],
|
||||
&next.0[z],
|
||||
z as i32,
|
||||
&new_interesting_tiles,
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
let mut results = handle_results.write().unwrap();
|
||||
results.push(result);
|
||||
}));
|
||||
}
|
||||
for handle in handles {
|
||||
if let Err(e) = handle.join() {
|
||||
result = Err(eyre::eyre!("Worker thread failed: {:#?}", e));
|
||||
let _ = handle.join();
|
||||
}
|
||||
let readable_results = handle_results.read().unwrap();
|
||||
for index in 0..readable_results.len() {
|
||||
if readable_results[index].is_err() {
|
||||
result = Err(eyre::eyre!(
|
||||
"MILLA worker thread failed: {:#?}",
|
||||
readable_results[index].as_ref().err()
|
||||
));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -69,118 +79,28 @@ pub(crate) fn tick(buffers: &Buffers) -> Result<(), eyre::Error> {
|
||||
/// Runs a single tick of one Z level's atmospherics model.
|
||||
pub(crate) fn tick_z_level(
|
||||
buffers: &Buffers,
|
||||
active_atmos_lock: &RwLock<ZLevel>,
|
||||
inactive_atmos_lock: &RwLock<ZLevel>,
|
||||
prev_atmos_lock: &RwLock<ZLevel>,
|
||||
next_atmos_lock: &RwLock<ZLevel>,
|
||||
z: i32,
|
||||
new_interesting_tiles: &Bag<InterestingTile>,
|
||||
) -> Result<(), eyre::Error> {
|
||||
) -> eyre::Result<()> {
|
||||
let environments;
|
||||
{
|
||||
let global_environments = buffers.environments.read().unwrap();
|
||||
environments = global_environments.clone().into_boxed_slice();
|
||||
}
|
||||
let active = active_atmos_lock.read().unwrap();
|
||||
let mut inactive = inactive_atmos_lock.write().unwrap();
|
||||
let prev = prev_atmos_lock.read().unwrap();
|
||||
let mut next = next_atmos_lock.write().unwrap();
|
||||
|
||||
// Initialize the new frame as a copy of the old one.
|
||||
inactive.copy_from(&active);
|
||||
for my_index in 0..MAP_SIZE * MAP_SIZE {
|
||||
let x = (my_index / MAP_SIZE) as i32;
|
||||
let y = (my_index % MAP_SIZE) as i32;
|
||||
let my_tile = active.get_tile(my_index);
|
||||
let my_connected_dirs = simulate::count_connected_dirs(&active, my_index, x, y)?;
|
||||
next.copy_from(&prev);
|
||||
|
||||
// Handle gas and heat exchange with neighbors.
|
||||
for (dx, dy) in AXES {
|
||||
let maybe_their_index = ZLevel::maybe_get_index(x + dx, y + dy);
|
||||
let their_index;
|
||||
match maybe_their_index {
|
||||
Some(index) => their_index = index,
|
||||
None => continue,
|
||||
}
|
||||
simulate::find_walls(&mut next);
|
||||
simulate::update_wind(&prev, &mut next);
|
||||
simulate::flow_air(&prev, &mut next)?;
|
||||
simulate::post_process(&prev, &mut next, &environments, new_interesting_tiles, z)?;
|
||||
|
||||
let their_tile = active.get_tile(their_index);
|
||||
if let AtmosMode::Space = my_tile.mode {
|
||||
if let AtmosMode::Space = their_tile.mode {
|
||||
// Exchange what, exactly?
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let their_connected_dirs =
|
||||
simulate::count_connected_dirs(&active, their_index, x + dx, y + dy)?;
|
||||
|
||||
let (my_inactive_tile, their_inactive_tile) =
|
||||
inactive.get_pair_mut(my_index, their_index);
|
||||
|
||||
// Bail if we shouldn't share air this way, but still run superconductivity.
|
||||
if dx > 0
|
||||
&& (my_tile
|
||||
.airtight_directions
|
||||
.contains(AirtightDirections::EAST)
|
||||
|| their_tile
|
||||
.airtight_directions
|
||||
.contains(AirtightDirections::WEST))
|
||||
{
|
||||
simulate::superconduct(my_inactive_tile, their_inactive_tile, true);
|
||||
continue;
|
||||
}
|
||||
if dy > 0
|
||||
&& (my_tile
|
||||
.airtight_directions
|
||||
.contains(AirtightDirections::NORTH)
|
||||
|| their_tile
|
||||
.airtight_directions
|
||||
.contains(AirtightDirections::SOUTH))
|
||||
{
|
||||
simulate::superconduct(my_inactive_tile, their_inactive_tile, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate the amount of air and thermal energy transferred.
|
||||
let (gas_change, thermal_energy_change) =
|
||||
simulate::share_air(my_tile, their_tile, my_connected_dirs, their_connected_dirs);
|
||||
|
||||
// Transfer it.
|
||||
for i in 0..GAS_COUNT {
|
||||
if gas_change.values[i] != 0.0 {
|
||||
my_inactive_tile.gases.values[i] += gas_change.values[i];
|
||||
my_inactive_tile.gases.set_dirty();
|
||||
their_inactive_tile.gases.values[i] -= gas_change.values[i];
|
||||
their_inactive_tile.gases.set_dirty();
|
||||
}
|
||||
}
|
||||
my_inactive_tile.thermal_energy += thermal_energy_change;
|
||||
their_inactive_tile.thermal_energy -= thermal_energy_change;
|
||||
|
||||
// Run superconductivity.
|
||||
simulate::superconduct(my_inactive_tile, their_inactive_tile, dx > 0);
|
||||
}
|
||||
|
||||
let my_inactive_tile = inactive.get_tile_mut(my_index);
|
||||
simulate::apply_tile_mode(my_tile, my_inactive_tile, &environments)?;
|
||||
|
||||
if let AtmosMode::Space = my_tile.mode {
|
||||
// Space has no reactions, doesn't need to be sanitized, and is never interesting.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Track how much "fuel" was burnt across all reactions.
|
||||
let fuel_burnt = simulate::react(my_inactive_tile);
|
||||
|
||||
// Sanitize the tile, to avoid negative/NaN/infinity spread.
|
||||
simulate::sanitize(my_inactive_tile, my_tile);
|
||||
|
||||
simulate::check_interesting(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
&active,
|
||||
&environments,
|
||||
my_tile,
|
||||
my_inactive_tile,
|
||||
fuel_burnt,
|
||||
new_interesting_tiles,
|
||||
)?;
|
||||
}
|
||||
next.active_pressure_chunks.clear();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -189,6 +109,7 @@ pub(crate) fn tick_z_level(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::constants::*;
|
||||
|
||||
fn set_with_defaults<F>(legend: F) -> impl Fn(char) -> Tile
|
||||
where
|
||||
@@ -226,8 +147,8 @@ mod tests {
|
||||
where
|
||||
F: Fn(char) -> Tile,
|
||||
{
|
||||
let active = buffers.get_active().read().unwrap();
|
||||
let mut z_level = active.0[z as usize].write().unwrap();
|
||||
let current = buffers.get_active().read().unwrap();
|
||||
let mut z_level = current.0[z as usize].write().unwrap();
|
||||
for inv_y in 0..pattern.len() {
|
||||
// Reverse the Y direction, so +Y is up, like in the game.
|
||||
let y = pattern.len() - inv_y - 1;
|
||||
@@ -277,8 +198,8 @@ mod tests {
|
||||
where
|
||||
F: Fn(char) -> TileChecker,
|
||||
{
|
||||
let active = buffers.get_active().read().unwrap();
|
||||
let z_level = active.0[z as usize].read().unwrap();
|
||||
let current = buffers.get_active().read().unwrap();
|
||||
let z_level = current.0[z as usize].read().unwrap();
|
||||
for inv_y in 0..pattern.len() {
|
||||
// Reverse the Y direction, so +Y is up, like in the game.
|
||||
let y = pattern.len() - inv_y - 1;
|
||||
@@ -525,386 +446,5 @@ mod tests {
|
||||
// unfortunately, the compiler won't let it be applied in the middle of
|
||||
// the code right now. It formats right, it just won't compile.
|
||||
|
||||
// Air should split evenly to surrounding vacuum tiles.
|
||||
#[test]
|
||||
fn tick_splits_air() {
|
||||
let buffers = Buffers::new();
|
||||
buffers.init_to(0);
|
||||
|
||||
set_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"#######", //
|
||||
"# #", //
|
||||
"# 0 #", //
|
||||
"# 0X0 #", //
|
||||
"# 0 #", //
|
||||
"# #", //
|
||||
"#######", //
|
||||
],
|
||||
set_with_defaults(|_c: char| None),
|
||||
0,
|
||||
);
|
||||
|
||||
tick(&buffers).unwrap();
|
||||
|
||||
expect_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"#######", //
|
||||
"# #", //
|
||||
"# x #", //
|
||||
"# xxx #", //
|
||||
"# x #", //
|
||||
"# #", //
|
||||
"#######", //
|
||||
],
|
||||
expect_with_defaults(|c: char| match c {
|
||||
'x' => Some(
|
||||
TileChecker::new() //
|
||||
.oxygen(20.0)
|
||||
.thermal_energy(20.0),
|
||||
),
|
||||
_ => None,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Air should not flow into walls.
|
||||
#[test]
|
||||
fn tick_respects_walls() {
|
||||
let buffers = Buffers::new();
|
||||
buffers.init_to(0);
|
||||
|
||||
set_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"###", //
|
||||
"#X#", //
|
||||
"###", //
|
||||
],
|
||||
set_with_defaults(|_c: char| None),
|
||||
0,
|
||||
);
|
||||
|
||||
tick(&buffers).unwrap();
|
||||
|
||||
expect_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"#0#", //
|
||||
"0X0", //
|
||||
"#0#", //
|
||||
],
|
||||
expect_with_defaults(|_c: char| None),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Air should flow properly into dead-end tiles.
|
||||
#[test]
|
||||
fn tick_handles_different_connections() {
|
||||
let buffers = Buffers::new();
|
||||
buffers.init_to(0);
|
||||
|
||||
set_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"#####", //
|
||||
"##0##", //
|
||||
"#0X0#", //
|
||||
"##0##", //
|
||||
"#####", //
|
||||
],
|
||||
set_with_defaults(|_c: char| None),
|
||||
0,
|
||||
);
|
||||
|
||||
tick(&buffers).unwrap();
|
||||
|
||||
expect_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"#####", //
|
||||
"##x##", //
|
||||
"#xxx#", //
|
||||
"##x##", //
|
||||
"#####", //
|
||||
],
|
||||
expect_with_defaults(|c: char| match c {
|
||||
'x' => Some(
|
||||
TileChecker::new() //
|
||||
.oxygen(20.0)
|
||||
.thermal_energy(20.0),
|
||||
),
|
||||
_ => None,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Four equal dead-end tiles around a vacuum should distribute air evenly between all 5 tiles.
|
||||
// Temp: Or not, because we're matching MILLA behavior.
|
||||
#[test]
|
||||
fn tick_merges_properly() {
|
||||
let buffers = Buffers::new();
|
||||
buffers.init_to(0);
|
||||
|
||||
set_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"#####", //
|
||||
"##X##", //
|
||||
"#X0X#", //
|
||||
"##X##", //
|
||||
"#####", //
|
||||
],
|
||||
set_with_defaults(|_c: char| None),
|
||||
0,
|
||||
);
|
||||
|
||||
tick(&buffers).unwrap();
|
||||
|
||||
expect_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"#####", //
|
||||
"##x##", //
|
||||
"#x!x#", //
|
||||
"##x##", //
|
||||
"#####", //
|
||||
],
|
||||
expect_with_defaults(|c: char| match c {
|
||||
'!' => Some(
|
||||
TileChecker::new() //
|
||||
.oxygen(200.0)
|
||||
.thermal_energy(200.0),
|
||||
),
|
||||
'x' => Some(
|
||||
TileChecker::new() //
|
||||
.oxygen(50.0)
|
||||
.thermal_energy(50.0),
|
||||
),
|
||||
_ => None,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Energy should be transferred into superconducting walls.
|
||||
#[test]
|
||||
fn tick_superconducts_into_walls() {
|
||||
let buffers = Buffers::new();
|
||||
buffers.init_to(0);
|
||||
|
||||
set_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"######", //
|
||||
"##XS##", //
|
||||
"#S##X#", //
|
||||
"#X##S#", //
|
||||
"##SX##", //
|
||||
"######", //
|
||||
],
|
||||
set_with_defaults(|c: char| match c {
|
||||
'S' => Some(
|
||||
TileBuilder::wall() //
|
||||
.superconducts(OPEN_HEAT_TRANSFER_COEFFICIENT)
|
||||
.innate_heat_capacity(2000.0)
|
||||
.build(),
|
||||
),
|
||||
_ => None,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
tick(&buffers).unwrap();
|
||||
|
||||
expect_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"######", //
|
||||
"##xS##", //
|
||||
"#S##x#", //
|
||||
"#x##S#", //
|
||||
"##Sx##", //
|
||||
"######", //
|
||||
],
|
||||
expect_with_defaults(|c: char| match c {
|
||||
'x' => Some(
|
||||
TileChecker::new() //
|
||||
.oxygen(100.0)
|
||||
// This value is arbitrary, it's just what we get right now.
|
||||
// Update it if the calculations changed intentionally.
|
||||
.thermal_energy(80.0),
|
||||
),
|
||||
'S' => Some(
|
||||
TileChecker::new() //
|
||||
// This value is arbitrary, it's just what we get right now.
|
||||
// Update it if the calculations changed intentionally.
|
||||
.thermal_energy(20.0),
|
||||
),
|
||||
_ => None,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Air should flow into a space tile, and be deleted.
|
||||
#[test]
|
||||
fn tick_empties_space() {
|
||||
let buffers = Buffers::new();
|
||||
buffers.init_to(0);
|
||||
|
||||
set_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"#####", //
|
||||
"##X##", //
|
||||
"#X X#", //
|
||||
"##X##", //
|
||||
"#####", //
|
||||
],
|
||||
set_with_defaults(|c: char| match c {
|
||||
'x' => Some(
|
||||
TileBuilder::sealed() //
|
||||
.oxygen(100.0)
|
||||
.thermal_energy(100.0)
|
||||
.build(),
|
||||
),
|
||||
_ => None,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
tick(&buffers).unwrap();
|
||||
|
||||
expect_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"#####", //
|
||||
"##x##", //
|
||||
"#x0x#", //
|
||||
"##x##", //
|
||||
"#####", //
|
||||
],
|
||||
expect_with_defaults(|c: char| match c {
|
||||
'x' => Some(
|
||||
TileChecker::new() //
|
||||
.oxygen(50.0)
|
||||
.thermal_energy(50.0),
|
||||
),
|
||||
_ => None,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Air should tend towards the environment's temperature, if any.
|
||||
#[test]
|
||||
fn tick_affected_by_environment_temperature() {
|
||||
let buffers = Buffers::new();
|
||||
buffers.init_to(0);
|
||||
let lavaland = buffers.create_environment(
|
||||
TileBuilder::sealed() //
|
||||
.oxygen(8.0)
|
||||
.nitrogen(14.0)
|
||||
.temperature(500.0)
|
||||
.build(),
|
||||
);
|
||||
|
||||
set_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"###", //
|
||||
"#X#", //
|
||||
"###", //
|
||||
],
|
||||
set_with_defaults(|c: char| match c {
|
||||
'X' => Some(
|
||||
TileBuilder::exposed_to(lavaland)
|
||||
.oxygen(8.0)
|
||||
.nitrogen(14.0)
|
||||
.thermal_energy(0.0)
|
||||
.build(),
|
||||
),
|
||||
_ => None,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
tick(&buffers).unwrap();
|
||||
|
||||
expect_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"###", //
|
||||
"#Y#", //
|
||||
"###", //
|
||||
],
|
||||
expect_with_defaults(|c: char| match c {
|
||||
'Y' => Some(
|
||||
TileChecker::new() //
|
||||
.oxygen(8.0)
|
||||
.nitrogen(14.0)
|
||||
// This value is arbitrary, it's just what we get right now.
|
||||
// Update it if the calculations changed intentionally.
|
||||
.thermal_energy(44_000.0),
|
||||
),
|
||||
_ => None,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Air should exchange with the tile's environment, if any.
|
||||
#[test]
|
||||
fn tick_affected_by_environment() {
|
||||
let buffers = Buffers::new();
|
||||
buffers.init_to(0);
|
||||
let lavaland = buffers.create_environment(
|
||||
TileBuilder::sealed() //
|
||||
.oxygen(8.0)
|
||||
.nitrogen(14.0)
|
||||
.temperature(500.0)
|
||||
.build(),
|
||||
);
|
||||
|
||||
set_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"###", //
|
||||
"#X#", //
|
||||
"###", //
|
||||
],
|
||||
set_with_defaults(|c: char| match c {
|
||||
'X' => Some(TileBuilder::exposed_to(lavaland).build()),
|
||||
_ => None,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
tick(&buffers).unwrap();
|
||||
|
||||
expect_pattern(
|
||||
&buffers,
|
||||
&[
|
||||
"###", //
|
||||
"#Y#", //
|
||||
"###", //
|
||||
],
|
||||
expect_with_defaults(|c: char| match c {
|
||||
'Y' => Some(
|
||||
TileChecker::new() //
|
||||
.oxygen(4.0)
|
||||
.nitrogen(7.0)
|
||||
.temperature(500.0),
|
||||
),
|
||||
_ => None,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
}
|
||||
// TODO
|
||||
}
|
||||
|
||||
@@ -364,24 +364,24 @@ const AirAlarmModesView = (props, context) => {
|
||||
return (
|
||||
<>
|
||||
<Section title="System Mode">
|
||||
<Table>
|
||||
{modes.map(
|
||||
(m) =>
|
||||
(!m.emagonly || (m.emagonly && !!emagged)) && (
|
||||
<Table.Row key={m.name}>
|
||||
<Table.Cell textAlign="right" width={1}>
|
||||
<Button
|
||||
content={m.name}
|
||||
icon="cog"
|
||||
selected={m.id === mode}
|
||||
onClick={() => act('mode', { mode: m.id })}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{m.desc}</Table.Cell>
|
||||
</Table.Row>
|
||||
)
|
||||
)}
|
||||
</Table>
|
||||
{Object.keys(modes).map((key) => {
|
||||
let m = modes[key];
|
||||
if (!m.emagonly || !!emagged) {
|
||||
return (
|
||||
<Table.Row key={m.name}>
|
||||
<Table.Cell textAlign="right" width={1}>
|
||||
<Button
|
||||
content={m.name}
|
||||
icon="cog"
|
||||
selected={m.id === mode}
|
||||
onClick={() => act('mode', { mode: m.id })}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{m.desc}</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</Section>
|
||||
<Section title="System Presets">
|
||||
<Box italic>After making a selection, the system will automatically cycle in order to remove contaminants.</Box>
|
||||
|
||||
@@ -6,6 +6,7 @@ export const AtmosAlertConsole = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const priorityAlerts = data.priority || [];
|
||||
const minorAlerts = data.minor || [];
|
||||
const areaModes = data.mode || {};
|
||||
return (
|
||||
<Window width={350} height={300}>
|
||||
<Window.Content scrollable>
|
||||
@@ -23,6 +24,12 @@ export const AtmosAlertConsole = (props, context) => {
|
||||
{alert}
|
||||
</li>
|
||||
))}
|
||||
{Object.keys(areaModes).length === 0 && <li className="color-good">All Areas Filtering</li>}
|
||||
{Object.keys(areaModes).map((label) => (
|
||||
<li key={alert} className="color-good">
|
||||
{label} mode is {areaModes[label]}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
|
||||
@@ -4,14 +4,15 @@ import { Window } from '../layouts';
|
||||
|
||||
export const GhostHudPanel = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const { security, medical, diagnostic, radioactivity, ahud } = data;
|
||||
const { security, medical, diagnostic, pressure, radioactivity, ahud } = data;
|
||||
return (
|
||||
<Window width={250} height={207} theme="nologo">
|
||||
<Window width={250} height={217} theme="nologo">
|
||||
<Window.Content>
|
||||
<Section>
|
||||
<HudEntry label="Medical" type="medical" is_active={medical} />
|
||||
<HudEntry label="Security" type="security" is_active={security} />
|
||||
<HudEntry label="Diagnostic" type="diagnostic" is_active={diagnostic} />
|
||||
<HudEntry label="Pressure" type="pressure" is_active={pressure} />
|
||||
<Divider />
|
||||
<HudEntry
|
||||
label="Radioactivity"
|
||||
|
||||