diff --git a/code/__defines/_compile_options.dm b/code/__defines/_compile_options.dm
index 50ff3fbf2b..cf266542c2 100644
--- a/code/__defines/_compile_options.dm
+++ b/code/__defines/_compile_options.dm
@@ -1,42 +1,42 @@
-#define BACKGROUND_ENABLED 0 // The default value for all uses of set background. Set background can cause gradual lag and is recommended you only turn this on if necessary.
- // 1 will enable set background. 0 will disable set background.
-
-#define PRELOAD_RSC 1 /*set to:
- 0 to allow using external resources or on-demand behaviour;
- 1 to use the default behaviour (preload compiled in recourses, not player uploaded ones);
- 2 for preloading absolutely everything;
- */
-
-// ZAS Compile Options
-//#define ZASDBG // Uncomment to turn on super detailed ZAS debugging that probably won't even compile.
-#define MULTIZAS // Uncomment to turn on Multi-Z ZAS Support!
-
-// Movement Compile Options
-//#define CARDINAL_INPUT_ONLY // Uncomment to disable diagonal player movement (restore previous cardinal-moves-only behavior)
-
-// Comment/Uncomment this to turn off/on shuttle code debugging logs
-#define DEBUG_SHUTTLES
-
-// If we are doing the map test build, do not include the main maps, only the submaps.
-#if MAP_TEST
- #define USING_MAP_DATUM /datum/map
- #define MAP_OVERRIDE 1
-#endif
-
-///Used to find the sources of harddels, quite laggy, don't be surpised if it freezes your client for a good while
-//#define REFERENCE_TRACKING
-#ifdef REFERENCE_TRACKING
-
-///Should we be logging our findings or not
-#define REFERENCE_TRACKING_LOG
-
-///Used for doing dry runs of the reference finder, to test for feature completeness
-//#define REFERENCE_TRACKING_DEBUG
-
-///Run a lookup on things hard deleting by default.
-//#define GC_FAILURE_HARD_LOOKUP
-#ifdef GC_FAILURE_HARD_LOOKUP
-#define FIND_REF_NO_CHECK_TICK
-#endif //ifdef GC_FAILURE_HARD_LOOKUP
-
-#endif //ifdef REFERENCE_TRACKING
+#define BACKGROUND_ENABLED 0 // The default value for all uses of set background. Set background can cause gradual lag and is recommended you only turn this on if necessary.
+ // 1 will enable set background. 0 will disable set background.
+
+#define PRELOAD_RSC 1 /*set to:
+ 0 to allow using external resources or on-demand behaviour;
+ 1 to use the default behaviour (preload compiled in recourses, not player uploaded ones);
+ 2 for preloading absolutely everything;
+ */
+
+// ZAS Compile Options
+//#define ZASDBG // Uncomment to turn on super detailed ZAS debugging that probably won't even compile.
+#define MULTIZAS // Uncomment to turn on Multi-Z ZAS Support!
+
+// Movement Compile Options
+//#define CARDINAL_INPUT_ONLY // Uncomment to disable diagonal player movement (restore previous cardinal-moves-only behavior)
+
+// Comment/Uncomment this to turn off/on shuttle code debugging logs
+#define DEBUG_SHUTTLES
+
+// If we are doing the map test build, do not include the main maps, only the submaps.
+#if MAP_TEST
+ #define USING_MAP_DATUM /datum/map
+ #define MAP_OVERRIDE 1
+#endif
+
+///Used to find the sources of harddels, quite laggy, don't be surpised if it freezes your client for a good while
+//#define REFERENCE_TRACKING
+#ifdef REFERENCE_TRACKING
+
+///Should we be logging our findings or not
+#define REFERENCE_TRACKING_LOG
+
+///Used for doing dry runs of the reference finder, to test for feature completeness
+//#define REFERENCE_TRACKING_DEBUG
+
+///Run a lookup on things hard deleting by default.
+//#define GC_FAILURE_HARD_LOOKUP
+#ifdef GC_FAILURE_HARD_LOOKUP
+#define FIND_REF_NO_CHECK_TICK
+#endif //ifdef GC_FAILURE_HARD_LOOKUP
+
+#endif //ifdef REFERENCE_TRACKING
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index 140ad4ef16..1bd54feb97 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -1,525 +1,525 @@
-
-#define FIREDOOR_MAX_PRESSURE_DIFF 25 // kPa
-#define FIREDOOR_MAX_TEMP 50 // °C
-#define FIREDOOR_MIN_TEMP 0
-
-// Bitflags
-#define FIREDOOR_ALERT_HOT 1
-#define FIREDOOR_ALERT_COLD 2
-// Not used #define FIREDOOR_ALERT_LOWPRESS 4
-
-/obj/machinery/door/firedoor
- name = "\improper Emergency Shutter"
- desc = "Emergency air-tight shutter, capable of sealing off breached areas."
- icon = 'icons/obj/doors/DoorHazard.dmi'
- icon_state = "door_open"
- req_one_access = list(access_eva) //access_atmospherics, access_engine_equip)
- opacity = 0
- density = FALSE
- layer = DOOR_OPEN_LAYER - 0.01
- open_layer = DOOR_OPEN_LAYER - 0.01 // Just below doors when open
- closed_layer = DOOR_CLOSED_LAYER + 0.01 // Just above doors when closed
-
- //These are frequenly used with windows, so make sure zones can pass.
- //Generally if a firedoor is at a place where there should be a zone boundery then there will be a regular door underneath it.
- block_air_zones = 0
-
- var/blocked = 0
- var/prying = 0
- var/lockdown = 0 // When the door has detected a problem, it locks.
- var/pdiff_alert = 0
- var/pdiff = 0
- var/nextstate = null
- var/net_id
- var/list/areas_added
- var/list/users_to_open = new
- var/next_process_time = 0
-
- var/hatch_open = 0
-
- power_channel = ENVIRON
- use_power = USE_POWER_IDLE
- idle_power_usage = 5
-
- var/list/tile_info[4]
- var/list/dir_alerts[4] // 4 dirs, bitflags
-
- // MUST be in same order as FIREDOOR_ALERT_*
- var/list/ALERT_STATES=list(
- "hot",
- "cold"
- )
- var/open_sound = 'sound/machines/firelockopen.ogg' //CHOMPEdit firedoor sound variable.
- var/close_sound = 'sound/machines/firelockclose.ogg' //CHOMPEdit firedoor sound variable.
-
-/obj/machinery/door/firedoor/Initialize()
- . = ..()
- //Delete ourselves if we find extra mapped in firedoors
- for(var/obj/machinery/door/firedoor/F in loc)
- if(F != src)
- log_debug("Duplicate firedoors at [x],[y],[z]")
- return INITIALIZE_HINT_QDEL
-
- var/area/A = get_area(src)
- ASSERT(istype(A))
-
- LAZYADD(A.all_doors, src)
- areas_added = list(A)
-
- for(var/direction in cardinal)
- A = get_area(get_step(src,direction))
- if(istype(A) && !(A in areas_added))
- LAZYADD(A.all_doors, src)
- areas_added += A
-
-/obj/machinery/door/firedoor/Destroy()
- for(var/area/A in areas_added)
- LAZYREMOVE(A.all_doors, src)
- . = ..()
-
-/obj/machinery/door/firedoor/get_material()
- return get_material_by_name(MAT_STEEL)
-
-/obj/machinery/door/firedoor/examine(mob/user)
- . = ..()
-
- if(!Adjacent(user))
- return .
-
- if(pdiff >= FIREDOOR_MAX_PRESSURE_DIFF)
- . += "WARNING: Current pressure differential is [pdiff]kPa! Opening door may result in injury!"
-
- . += "Sensor readings:"
- for(var/index = 1; index <= tile_info.len; index++)
- var/o = " "
- switch(index)
- if(1)
- o += "NORTH: "
- if(2)
- o += "SOUTH: "
- if(3)
- o += "EAST: "
- if(4)
- o += "WEST: "
- if(tile_info[index] == null)
- o += "DATA UNAVAILABLE"
- . += o
- continue
- var/celsius = convert_k2c(tile_info[index][1])
- var/pressure = tile_info[index][2]
- o += ""
- o += "[celsius]°C "
- o += ""
- o += "[pressure]kPa"
- . += o
-
- if(islist(users_to_open) && users_to_open.len)
- var/users_to_open_string = users_to_open[1]
- if(users_to_open.len >= 2)
- for(var/i = 2 to users_to_open.len)
- users_to_open_string += ", [users_to_open[i]]"
- . += "These people have opened \the [src] during an alert: [users_to_open_string]."
-
-/obj/machinery/door/firedoor/Bumped(atom/AM)
- if(p_open || operating)
- return
- if(!density)
- return ..()
- if(istype(AM, /obj/mecha))
- var/obj/mecha/mecha = AM
- if(mecha.occupant)
- var/mob/M = mecha.occupant
- if(world.time - M.last_bumped <= 10) return //Can bump-open one airlock per second. This is to prevent popup message spam.
- M.last_bumped = world.time
- attack_hand(M)
- return 0
-
-/obj/machinery/door/firedoor/attack_hand(mob/user as mob)
- add_fingerprint(user)
- if(operating)
- return//Already doing something.
-
- if(istype(user, /mob/living/carbon/human))
- var/mob/living/carbon/human/X = user
- if(istype(X.species, /datum/species/xenos))
- src.attack_alien(user)
- return
-
- if(blocked)
- to_chat(user, "\The [src] is welded solid!")
- return
-
- var/alarmed = lockdown
- for(var/area/A in areas_added) //Checks if there are fire alarms in any areas associated with that firedoor
- if(A.firedoors_closed)
- alarmed = 1
-
- var/answer = tgui_alert(user, "Would you like to [density ? "open" : "close"] this [src.name]?[ alarmed && density ? "\nNote that by doing so, you acknowledge any damages from opening this\n[src.name] as being your own fault, and you will be held accountable under the law." : ""]",\
- "\The [src]", list("Yes, [density ? "open" : "close"]", "No"))
- if(answer == "No")
- return
- if(user.incapacitated() || (get_dist(src, user) > 1 && !issilicon(user)))
- to_chat(user, "Sorry, you must remain able bodied and close to \the [src] in order to use it.")
- return
- if(density && (stat & (BROKEN|NOPOWER))) //can still close without power
- to_chat(user, "\The [src] is not functioning, you'll have to force it open manually.")
- return
-
- if(alarmed && density && lockdown && !allowed(user))
- to_chat(user, "Access denied. Please wait for authorities to arrive, or for the alert to clear.")
- return
- else
- user.visible_message("\The [src] [density ? "open" : "close"]s for \the [user].",\
- "\The [src] [density ? "open" : "close"]s.",\
- "You hear a beep, and a door opening.")
-
- var/needs_to_close = 0
- if(density)
- if(alarmed)
- // Accountability!
- users_to_open |= user.name
- needs_to_close = !issilicon(user)
- spawn()
- open()
- else
- spawn()
- close()
-
- if(needs_to_close)
- spawn(50)
- alarmed = 0
- for(var/area/A in areas_added) //Just in case a fire alarm is turned off while the firedoor is going through an autoclose cycle
- if(A.firedoors_closed)
- alarmed = 1
- if(alarmed)
- nextstate = FIREDOOR_CLOSED
- close()
-
-/obj/machinery/door/firedoor/attack_alien(var/mob/user) //Familiar, right? Doors.
- if(istype(user, /mob/living/carbon/human))
- var/mob/living/carbon/human/X = user
- if(istype(X.species, /datum/species/xenos))
- if(src.blocked)
- visible_message("\The [user] begins digging into \the [src] internals!")
- if(do_after(user,5 SECONDS,src))
- playsound(src, 'sound/machines/door/airlock_creaking.ogg', 100, 1)
- src.blocked = 0
- update_icon()
- open(1)
- else if(src.density)
- visible_message("\The [user] begins forcing \the [src] open!")
- if(do_after(user, 2 SECONDS,src))
- playsound(src, 'sound/machines/door/airlock_creaking.ogg', 100, 1)
- visible_message("\The [user] forces \the [src] open!")
- open(1)
- else
- visible_message("\The [user] forces \the [src] closed!")
- close(1)
- else
- visible_message("\The [user] strains fruitlessly to force \the [src] [density ? "open" : "closed"].")
- return
- ..()
-
-/obj/machinery/door/firedoor/attack_generic(var/mob/living/user, var/damage)
- if(stat & (BROKEN|NOPOWER))
- if(damage >= STRUCTURE_MIN_DAMAGE_THRESHOLD)
- var/time_to_force = (2 + (2 * blocked)) * 5
- if(src.density)
- visible_message("\The [user] starts forcing \the [src] open!")
- user.set_AI_busy(TRUE) // If the mob doesn't have an AI attached, this won't do anything.
- if(do_after(user, time_to_force, src))
- visible_message("\The [user] forces \the [src] open!")
- src.blocked = 0
- open(1)
- user.set_AI_busy(FALSE)
- else
- time_to_force = (time_to_force / 2)
- visible_message("\The [user] starts forcing \the [src] closed!")
- user.set_AI_busy(TRUE) // If the mob doesn't have an AI attached, this won't do anything.
- if(do_after(user, time_to_force, src))
- visible_message("\The [user] forces \the [src] closed!")
- close(1)
- user.set_AI_busy(FALSE)
- else
- visible_message("\The [user] strains fruitlessly to force \the [src] [density ? "open" : "closed"].")
- return
- ..()
-
-/obj/machinery/door/firedoor/attackby(obj/item/weapon/C as obj, mob/user as mob)
- add_fingerprint(user)
- if(istype(C, /obj/item/taperoll))
- return //Don't open the door if we're putting tape on it to tell people 'don't open the door'.
- if(operating)
- return//Already doing something.
- if(C.has_tool_quality(TOOL_WELDER) && !repairing)
- if(prying)
- to_chat(user, "Someone's busy prying that [density ? "open" : "closed"]!")
- var/obj/item/weapon/weldingtool/W = C.get_welder()
- if(W.remove_fuel(0, user))
- blocked = !blocked
- user.visible_message("\The [user] [blocked ? "welds" : "unwelds"] \the [src] with \a [W].",\
- "You [blocked ? "weld" : "unweld"] \the [src] with \the [W].",\
- "You hear something being welded.")
- playsound(src, W.usesound, 100, 1)
- update_icon()
- return
-
- if(density && C.has_tool_quality(TOOL_SCREWDRIVER))
- hatch_open = !hatch_open
- playsound(src, C.usesound, 50, 1)
- user.visible_message("[user] has [hatch_open ? "opened" : "closed"] \the [src] maintenance hatch.",
- "You have [hatch_open ? "opened" : "closed"] the [src] maintenance hatch.")
- update_icon()
- return
-
- if(blocked && C.has_tool_quality(TOOL_CROWBAR) && !repairing)
- if(!hatch_open)
- to_chat(user, "You must open the maintenance hatch first!")
- else
- user.visible_message("[user] is removing the electronics from \the [src].",
- "You start to remove the electronics from [src].")
- if(do_after(user,30))
- if(blocked && density && hatch_open)
- playsound(src, C.usesound, 50, 1)
- user.visible_message("[user] has removed the electronics from \the [src].",
- "You have removed the electronics from [src].")
-
- if (stat & BROKEN)
- new /obj/item/weapon/circuitboard/broken(src.loc)
- else
- new/obj/item/weapon/circuitboard/airalarm(src.loc)
-
- var/obj/structure/firedoor_assembly/FA = new/obj/structure/firedoor_assembly(src.loc)
- FA.anchored = TRUE
- FA.density = TRUE
- FA.wired = 1
- FA.glass = glass
- FA.update_icon()
- qdel(src)
- return
-
- if(blocked)
- to_chat(user, "\The [src] is welded shut!")
- return
-
- if(C.pry == 1)
- if(operating)
- return
-
- if(blocked && C.has_tool_quality(TOOL_CROWBAR))
- user.visible_message("\The [user] pries at \the [src] with \a [C], but \the [src] is welded in place!",\
- "You try to pry \the [src] [density ? "open" : "closed"], but it is welded in place!",\
- "You hear someone struggle and metal straining.")
- return
-
- if(istype(C,/obj/item/weapon/material/twohanded/fireaxe))
- var/obj/item/weapon/material/twohanded/fireaxe/F = C
- if(!F.wielded)
- return
-
- if(prying)
- to_chat(user, "Someone's already prying that [density ? "open" : "closed"].")
- return
-
- user.visible_message("\The [user] starts to force \the [src] [density ? "open" : "closed"] with \a [C]!",\
- "You start forcing \the [src] [density ? "open" : "closed"] with \the [C]!",\
- "You hear metal strain.")
- prying = 1
- update_icon()
- playsound(src, C.usesound, 100, 1)
- if(do_after(user,30 * C.toolspeed))
- if(C.has_tool_quality(TOOL_CROWBAR))
- if(stat & (BROKEN|NOPOWER) || !density)
- user.visible_message("\The [user] forces \the [src] [density ? "open" : "closed"] with \a [C]!",\
- "You force \the [src] [density ? "open" : "closed"] with \the [C]!",\
- "You hear metal strain, and a door [density ? "open" : "close"].")
- else
- user.visible_message("\The [user] forces \the [ blocked ? "welded" : "" ] [src] [density ? "open" : "closed"] with \a [C]!",\
- "You force \the [ blocked ? "welded" : "" ] [src] [density ? "open" : "closed"] with \the [C]!",\
- "You hear metal strain and groan, and a door [density ? "opening" : "closing"].")
- if(density)
- spawn(0)
- open(1)
- else
- spawn(0)
- close()
- prying = 0
- update_icon()
- return
-
- return ..()
-
-// CHECK PRESSURE
-/obj/machinery/door/firedoor/process()
- ..()
-
- if(!density)
- return PROCESS_KILL
- if(next_process_time <= world.time)
- next_process_time = world.time + 100 // 10 second delays between process updates
- var/changed = 0
- lockdown=0
- // Pressure alerts
- pdiff = getOPressureDifferential(src.loc)
- if(pdiff >= FIREDOOR_MAX_PRESSURE_DIFF)
- lockdown = 1
- if(!pdiff_alert)
- pdiff_alert = 1
- changed = 1 // update_icon()
- else
- if(pdiff_alert)
- pdiff_alert = 0
- changed = 1 // update_icon()
-
- tile_info = getCardinalAirInfo(src.loc,list("temperature","pressure"))
- var/old_alerts = dir_alerts
- for(var/index = 1; index <= 4; index++)
- var/list/tileinfo=tile_info[index]
- if(tileinfo==null)
- continue // Bad data.
- var/celsius = convert_k2c(tileinfo[1])
-
- var/alerts=0
-
- // Temperatures
- if(celsius >= FIREDOOR_MAX_TEMP)
- alerts |= FIREDOOR_ALERT_HOT
- lockdown = 1
- else if(celsius <= FIREDOOR_MIN_TEMP)
- alerts |= FIREDOOR_ALERT_COLD
- lockdown = 1
-
- dir_alerts[index]=alerts
-
- if(dir_alerts != old_alerts)
- changed = 1
- if(changed)
- update_icon()
-
-/obj/machinery/door/firedoor/proc/latetoggle()
- if(operating || !nextstate)
- return
- switch(nextstate)
- if(FIREDOOR_OPEN)
- nextstate = null
-
- open()
- if(FIREDOOR_CLOSED)
- nextstate = null
- close()
- return
-
-/obj/machinery/door/firedoor/close()
- latetoggle()
- . = ..()
- // Queue us for processing when we are closed!
- if(density)
- START_MACHINE_PROCESSING(src)
-
-/obj/machinery/door/firedoor/open(var/forced = 0)
- if(hatch_open)
- hatch_open = 0
- visible_message("The maintenance hatch of \the [src] closes.")
- update_icon()
-
- if(!forced)
- if(stat & (BROKEN|NOPOWER))
- return //needs power to open unless it was forced
- else
- use_power(360)
- else
- if(usr && usr.ckey)
- log_admin("[usr]([usr.ckey]) has forced open an emergency shutter.")
- message_admins("[usr]([usr.ckey]) has forced open an emergency shutter.")
- latetoggle()
- return ..()
-
-/obj/machinery/door/firedoor/do_animate(animation)
- switch(animation)
- if("opening")
- flick("door_opening", src)
- playsound(src, open_sound, 37, 1) //CHOMPEdit var
- if("closing")
- playsound(src, close_sound, 37, 1) //CHOMPEdit var
- flick("door_closing", src)
- return
-
-
-/obj/machinery/door/firedoor/update_icon()
- cut_overlays()
- if(density)
- icon_state = "door_closed"
- if(prying)
- icon_state = "prying_closed"
- if(hatch_open)
- add_overlay("hatch")
- if(blocked)
- add_overlay("welded")
- if(pdiff_alert)
- add_overlay("palert")
- if(dir_alerts)
- for(var/d=1;d<=4;d++)
- var/cdir = cardinal[d]
- for(var/i=1;i<=ALERT_STATES.len;i++)
- if(dir_alerts[d] & (1<<(i-1)))
- add_overlay(new/icon(icon,"alert_[ALERT_STATES[i]]", dir=cdir))
- else
- icon_state = "door_open"
- if(prying)
- icon_state = "prying_open"
- if(blocked)
- add_overlay("welded_open")
- return
-
-//These are playing merry hell on ZAS. Sorry fellas :(
-
-/obj/machinery/door/firedoor/border_only
-/*
- icon = 'icons/obj/doors/edge_Doorfire.dmi'
- glass = 1 //There is a glass window so you can see through the door
- //This is needed due to BYOND limitations in controlling visibility
- heat_proof = 1
- air_properties_vary_with_direction = 1
-
- CanPass(atom/movable/mover, turf/target)
- if(istype(mover) && mover.checkpass(PASSGLASS))
- return 1
- if(get_dir(loc, target) == dir) //Make sure looking at appropriate border
- return !density
- else
- return 1
-
- CheckExit(atom/movable/mover as mob|obj, turf/target as turf)
- if(istype(mover) && mover.checkpass(PASSGLASS))
- return 1
- if(get_dir(loc, target) == dir)
- return !density
- else
- return 1
-
-
- update_nearby_tiles(need_rebuild)
- if(!air_master) return 0
-
- var/turf/simulated/source = loc
- var/turf/simulated/destination = get_step(source,dir)
-
- update_heat_protection(loc)
-
- if(istype(source)) air_master.tiles_to_update += source
- if(istype(destination)) air_master.tiles_to_update += destination
- return 1
-*/
-
-/obj/machinery/door/firedoor/multi_tile
- icon = 'icons/obj/doors/DoorHazard2x1.dmi'
- width = 2
- open_sound = 'sound/machines/firewide1o.ogg' //CHOMPEdit
- close_sound = 'sound/machines/firewide1c.ogg' //CHOMPEdit
-
-/obj/machinery/door/firedoor/glass
- name = "\improper Emergency Glass Shutter"
- desc = "Emergency air-tight shutter, capable of sealing off breached areas. This one has a resilient glass window, allowing you to see the danger."
- icon = 'icons/obj/doors/DoorHazardGlass.dmi'
- icon_state = "door_open"
- glass = 1
+
+#define FIREDOOR_MAX_PRESSURE_DIFF 25 // kPa
+#define FIREDOOR_MAX_TEMP 50 // °C
+#define FIREDOOR_MIN_TEMP 0
+
+// Bitflags
+#define FIREDOOR_ALERT_HOT 1
+#define FIREDOOR_ALERT_COLD 2
+// Not used #define FIREDOOR_ALERT_LOWPRESS 4
+
+/obj/machinery/door/firedoor
+ name = "\improper Emergency Shutter"
+ desc = "Emergency air-tight shutter, capable of sealing off breached areas."
+ icon = 'icons/obj/doors/DoorHazard.dmi'
+ icon_state = "door_open"
+ req_one_access = list(access_eva) //access_atmospherics, access_engine_equip)
+ opacity = 0
+ density = FALSE
+ layer = DOOR_OPEN_LAYER - 0.01
+ open_layer = DOOR_OPEN_LAYER - 0.01 // Just below doors when open
+ closed_layer = DOOR_CLOSED_LAYER + 0.01 // Just above doors when closed
+
+ //These are frequenly used with windows, so make sure zones can pass.
+ //Generally if a firedoor is at a place where there should be a zone boundery then there will be a regular door underneath it.
+ block_air_zones = 0
+
+ var/blocked = 0
+ var/prying = 0
+ var/lockdown = 0 // When the door has detected a problem, it locks.
+ var/pdiff_alert = 0
+ var/pdiff = 0
+ var/nextstate = null
+ var/net_id
+ var/list/areas_added
+ var/list/users_to_open = new
+ var/next_process_time = 0
+
+ var/hatch_open = 0
+
+ power_channel = ENVIRON
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 5
+
+ var/list/tile_info[4]
+ var/list/dir_alerts[4] // 4 dirs, bitflags
+
+ // MUST be in same order as FIREDOOR_ALERT_*
+ var/list/ALERT_STATES=list(
+ "hot",
+ "cold"
+ )
+ var/open_sound = 'sound/machines/firelockopen.ogg' //CHOMPEdit firedoor sound variable.
+ var/close_sound = 'sound/machines/firelockclose.ogg' //CHOMPEdit firedoor sound variable.
+
+/obj/machinery/door/firedoor/Initialize()
+ . = ..()
+ //Delete ourselves if we find extra mapped in firedoors
+ for(var/obj/machinery/door/firedoor/F in loc)
+ if(F != src)
+ log_debug("Duplicate firedoors at [x],[y],[z]")
+ return INITIALIZE_HINT_QDEL
+
+ var/area/A = get_area(src)
+ ASSERT(istype(A))
+
+ LAZYADD(A.all_doors, src)
+ areas_added = list(A)
+
+ for(var/direction in cardinal)
+ A = get_area(get_step(src,direction))
+ if(istype(A) && !(A in areas_added))
+ LAZYADD(A.all_doors, src)
+ areas_added += A
+
+/obj/machinery/door/firedoor/Destroy()
+ for(var/area/A in areas_added)
+ LAZYREMOVE(A.all_doors, src)
+ . = ..()
+
+/obj/machinery/door/firedoor/get_material()
+ return get_material_by_name(MAT_STEEL)
+
+/obj/machinery/door/firedoor/examine(mob/user)
+ . = ..()
+
+ if(!Adjacent(user))
+ return .
+
+ if(pdiff >= FIREDOOR_MAX_PRESSURE_DIFF)
+ . += "WARNING: Current pressure differential is [pdiff]kPa! Opening door may result in injury!"
+
+ . += "Sensor readings:"
+ for(var/index = 1; index <= tile_info.len; index++)
+ var/o = " "
+ switch(index)
+ if(1)
+ o += "NORTH: "
+ if(2)
+ o += "SOUTH: "
+ if(3)
+ o += "EAST: "
+ if(4)
+ o += "WEST: "
+ if(tile_info[index] == null)
+ o += "DATA UNAVAILABLE"
+ . += o
+ continue
+ var/celsius = convert_k2c(tile_info[index][1])
+ var/pressure = tile_info[index][2]
+ o += ""
+ o += "[celsius]°C "
+ o += ""
+ o += "[pressure]kPa"
+ . += o
+
+ if(islist(users_to_open) && users_to_open.len)
+ var/users_to_open_string = users_to_open[1]
+ if(users_to_open.len >= 2)
+ for(var/i = 2 to users_to_open.len)
+ users_to_open_string += ", [users_to_open[i]]"
+ . += "These people have opened \the [src] during an alert: [users_to_open_string]."
+
+/obj/machinery/door/firedoor/Bumped(atom/AM)
+ if(p_open || operating)
+ return
+ if(!density)
+ return ..()
+ if(istype(AM, /obj/mecha))
+ var/obj/mecha/mecha = AM
+ if(mecha.occupant)
+ var/mob/M = mecha.occupant
+ if(world.time - M.last_bumped <= 10) return //Can bump-open one airlock per second. This is to prevent popup message spam.
+ M.last_bumped = world.time
+ attack_hand(M)
+ return 0
+
+/obj/machinery/door/firedoor/attack_hand(mob/user as mob)
+ add_fingerprint(user)
+ if(operating)
+ return//Already doing something.
+
+ if(istype(user, /mob/living/carbon/human))
+ var/mob/living/carbon/human/X = user
+ if(istype(X.species, /datum/species/xenos))
+ src.attack_alien(user)
+ return
+
+ if(blocked)
+ to_chat(user, "\The [src] is welded solid!")
+ return
+
+ var/alarmed = lockdown
+ for(var/area/A in areas_added) //Checks if there are fire alarms in any areas associated with that firedoor
+ if(A.firedoors_closed)
+ alarmed = 1
+
+ var/answer = tgui_alert(user, "Would you like to [density ? "open" : "close"] this [src.name]?[ alarmed && density ? "\nNote that by doing so, you acknowledge any damages from opening this\n[src.name] as being your own fault, and you will be held accountable under the law." : ""]",\
+ "\The [src]", list("Yes, [density ? "open" : "close"]", "No"))
+ if(answer == "No")
+ return
+ if(user.incapacitated() || (get_dist(src, user) > 1 && !issilicon(user)))
+ to_chat(user, "Sorry, you must remain able bodied and close to \the [src] in order to use it.")
+ return
+ if(density && (stat & (BROKEN|NOPOWER))) //can still close without power
+ to_chat(user, "\The [src] is not functioning, you'll have to force it open manually.")
+ return
+
+ if(alarmed && density && lockdown && !allowed(user))
+ to_chat(user, "Access denied. Please wait for authorities to arrive, or for the alert to clear.")
+ return
+ else
+ user.visible_message("\The [src] [density ? "open" : "close"]s for \the [user].",\
+ "\The [src] [density ? "open" : "close"]s.",\
+ "You hear a beep, and a door opening.")
+
+ var/needs_to_close = 0
+ if(density)
+ if(alarmed)
+ // Accountability!
+ users_to_open |= user.name
+ needs_to_close = !issilicon(user)
+ spawn()
+ open()
+ else
+ spawn()
+ close()
+
+ if(needs_to_close)
+ spawn(50)
+ alarmed = 0
+ for(var/area/A in areas_added) //Just in case a fire alarm is turned off while the firedoor is going through an autoclose cycle
+ if(A.firedoors_closed)
+ alarmed = 1
+ if(alarmed)
+ nextstate = FIREDOOR_CLOSED
+ close()
+
+/obj/machinery/door/firedoor/attack_alien(var/mob/user) //Familiar, right? Doors.
+ if(istype(user, /mob/living/carbon/human))
+ var/mob/living/carbon/human/X = user
+ if(istype(X.species, /datum/species/xenos))
+ if(src.blocked)
+ visible_message("\The [user] begins digging into \the [src] internals!")
+ if(do_after(user,5 SECONDS,src))
+ playsound(src, 'sound/machines/door/airlock_creaking.ogg', 100, 1)
+ src.blocked = 0
+ update_icon()
+ open(1)
+ else if(src.density)
+ visible_message("\The [user] begins forcing \the [src] open!")
+ if(do_after(user, 2 SECONDS,src))
+ playsound(src, 'sound/machines/door/airlock_creaking.ogg', 100, 1)
+ visible_message("\The [user] forces \the [src] open!")
+ open(1)
+ else
+ visible_message("\The [user] forces \the [src] closed!")
+ close(1)
+ else
+ visible_message("\The [user] strains fruitlessly to force \the [src] [density ? "open" : "closed"].")
+ return
+ ..()
+
+/obj/machinery/door/firedoor/attack_generic(var/mob/living/user, var/damage)
+ if(stat & (BROKEN|NOPOWER))
+ if(damage >= STRUCTURE_MIN_DAMAGE_THRESHOLD)
+ var/time_to_force = (2 + (2 * blocked)) * 5
+ if(src.density)
+ visible_message("\The [user] starts forcing \the [src] open!")
+ user.set_AI_busy(TRUE) // If the mob doesn't have an AI attached, this won't do anything.
+ if(do_after(user, time_to_force, src))
+ visible_message("\The [user] forces \the [src] open!")
+ src.blocked = 0
+ open(1)
+ user.set_AI_busy(FALSE)
+ else
+ time_to_force = (time_to_force / 2)
+ visible_message("\The [user] starts forcing \the [src] closed!")
+ user.set_AI_busy(TRUE) // If the mob doesn't have an AI attached, this won't do anything.
+ if(do_after(user, time_to_force, src))
+ visible_message("\The [user] forces \the [src] closed!")
+ close(1)
+ user.set_AI_busy(FALSE)
+ else
+ visible_message("\The [user] strains fruitlessly to force \the [src] [density ? "open" : "closed"].")
+ return
+ ..()
+
+/obj/machinery/door/firedoor/attackby(obj/item/weapon/C as obj, mob/user as mob)
+ add_fingerprint(user)
+ if(istype(C, /obj/item/taperoll))
+ return //Don't open the door if we're putting tape on it to tell people 'don't open the door'.
+ if(operating)
+ return//Already doing something.
+ if(C.has_tool_quality(TOOL_WELDER) && !repairing)
+ if(prying)
+ to_chat(user, "Someone's busy prying that [density ? "open" : "closed"]!")
+ var/obj/item/weapon/weldingtool/W = C.get_welder()
+ if(W.remove_fuel(0, user))
+ blocked = !blocked
+ user.visible_message("\The [user] [blocked ? "welds" : "unwelds"] \the [src] with \a [W].",\
+ "You [blocked ? "weld" : "unweld"] \the [src] with \the [W].",\
+ "You hear something being welded.")
+ playsound(src, W.usesound, 100, 1)
+ update_icon()
+ return
+
+ if(density && C.has_tool_quality(TOOL_SCREWDRIVER))
+ hatch_open = !hatch_open
+ playsound(src, C.usesound, 50, 1)
+ user.visible_message("[user] has [hatch_open ? "opened" : "closed"] \the [src] maintenance hatch.",
+ "You have [hatch_open ? "opened" : "closed"] the [src] maintenance hatch.")
+ update_icon()
+ return
+
+ if(blocked && C.has_tool_quality(TOOL_CROWBAR) && !repairing)
+ if(!hatch_open)
+ to_chat(user, "You must open the maintenance hatch first!")
+ else
+ user.visible_message("[user] is removing the electronics from \the [src].",
+ "You start to remove the electronics from [src].")
+ if(do_after(user,30))
+ if(blocked && density && hatch_open)
+ playsound(src, C.usesound, 50, 1)
+ user.visible_message("[user] has removed the electronics from \the [src].",
+ "You have removed the electronics from [src].")
+
+ if (stat & BROKEN)
+ new /obj/item/weapon/circuitboard/broken(src.loc)
+ else
+ new/obj/item/weapon/circuitboard/airalarm(src.loc)
+
+ var/obj/structure/firedoor_assembly/FA = new/obj/structure/firedoor_assembly(src.loc)
+ FA.anchored = TRUE
+ FA.density = TRUE
+ FA.wired = 1
+ FA.glass = glass
+ FA.update_icon()
+ qdel(src)
+ return
+
+ if(blocked)
+ to_chat(user, "\The [src] is welded shut!")
+ return
+
+ if(C.pry == 1)
+ if(operating)
+ return
+
+ if(blocked && C.has_tool_quality(TOOL_CROWBAR))
+ user.visible_message("\The [user] pries at \the [src] with \a [C], but \the [src] is welded in place!",\
+ "You try to pry \the [src] [density ? "open" : "closed"], but it is welded in place!",\
+ "You hear someone struggle and metal straining.")
+ return
+
+ if(istype(C,/obj/item/weapon/material/twohanded/fireaxe))
+ var/obj/item/weapon/material/twohanded/fireaxe/F = C
+ if(!F.wielded)
+ return
+
+ if(prying)
+ to_chat(user, "Someone's already prying that [density ? "open" : "closed"].")
+ return
+
+ user.visible_message("\The [user] starts to force \the [src] [density ? "open" : "closed"] with \a [C]!",\
+ "You start forcing \the [src] [density ? "open" : "closed"] with \the [C]!",\
+ "You hear metal strain.")
+ prying = 1
+ update_icon()
+ playsound(src, C.usesound, 100, 1)
+ if(do_after(user,30 * C.toolspeed))
+ if(C.has_tool_quality(TOOL_CROWBAR))
+ if(stat & (BROKEN|NOPOWER) || !density)
+ user.visible_message("\The [user] forces \the [src] [density ? "open" : "closed"] with \a [C]!",\
+ "You force \the [src] [density ? "open" : "closed"] with \the [C]!",\
+ "You hear metal strain, and a door [density ? "open" : "close"].")
+ else
+ user.visible_message("\The [user] forces \the [ blocked ? "welded" : "" ] [src] [density ? "open" : "closed"] with \a [C]!",\
+ "You force \the [ blocked ? "welded" : "" ] [src] [density ? "open" : "closed"] with \the [C]!",\
+ "You hear metal strain and groan, and a door [density ? "opening" : "closing"].")
+ if(density)
+ spawn(0)
+ open(1)
+ else
+ spawn(0)
+ close()
+ prying = 0
+ update_icon()
+ return
+
+ return ..()
+
+// CHECK PRESSURE
+/obj/machinery/door/firedoor/process()
+ ..()
+
+ if(!density)
+ return PROCESS_KILL
+ if(next_process_time <= world.time)
+ next_process_time = world.time + 100 // 10 second delays between process updates
+ var/changed = 0
+ lockdown=0
+ // Pressure alerts
+ pdiff = getOPressureDifferential(src.loc)
+ if(pdiff >= FIREDOOR_MAX_PRESSURE_DIFF)
+ lockdown = 1
+ if(!pdiff_alert)
+ pdiff_alert = 1
+ changed = 1 // update_icon()
+ else
+ if(pdiff_alert)
+ pdiff_alert = 0
+ changed = 1 // update_icon()
+
+ tile_info = getCardinalAirInfo(src.loc,list("temperature","pressure"))
+ var/old_alerts = dir_alerts
+ for(var/index = 1; index <= 4; index++)
+ var/list/tileinfo=tile_info[index]
+ if(tileinfo==null)
+ continue // Bad data.
+ var/celsius = convert_k2c(tileinfo[1])
+
+ var/alerts=0
+
+ // Temperatures
+ if(celsius >= FIREDOOR_MAX_TEMP)
+ alerts |= FIREDOOR_ALERT_HOT
+ lockdown = 1
+ else if(celsius <= FIREDOOR_MIN_TEMP)
+ alerts |= FIREDOOR_ALERT_COLD
+ lockdown = 1
+
+ dir_alerts[index]=alerts
+
+ if(dir_alerts != old_alerts)
+ changed = 1
+ if(changed)
+ update_icon()
+
+/obj/machinery/door/firedoor/proc/latetoggle()
+ if(operating || !nextstate)
+ return
+ switch(nextstate)
+ if(FIREDOOR_OPEN)
+ nextstate = null
+
+ open()
+ if(FIREDOOR_CLOSED)
+ nextstate = null
+ close()
+ return
+
+/obj/machinery/door/firedoor/close()
+ latetoggle()
+ . = ..()
+ // Queue us for processing when we are closed!
+ if(density)
+ START_MACHINE_PROCESSING(src)
+
+/obj/machinery/door/firedoor/open(var/forced = 0)
+ if(hatch_open)
+ hatch_open = 0
+ visible_message("The maintenance hatch of \the [src] closes.")
+ update_icon()
+
+ if(!forced)
+ if(stat & (BROKEN|NOPOWER))
+ return //needs power to open unless it was forced
+ else
+ use_power(360)
+ else
+ if(usr && usr.ckey)
+ log_admin("[usr]([usr.ckey]) has forced open an emergency shutter.")
+ message_admins("[usr]([usr.ckey]) has forced open an emergency shutter.")
+ latetoggle()
+ return ..()
+
+/obj/machinery/door/firedoor/do_animate(animation)
+ switch(animation)
+ if("opening")
+ flick("door_opening", src)
+ playsound(src, open_sound, 37, 1) //CHOMPEdit var
+ if("closing")
+ playsound(src, close_sound, 37, 1) //CHOMPEdit var
+ flick("door_closing", src)
+ return
+
+
+/obj/machinery/door/firedoor/update_icon()
+ cut_overlays()
+ if(density)
+ icon_state = "door_closed"
+ if(prying)
+ icon_state = "prying_closed"
+ if(hatch_open)
+ add_overlay("hatch")
+ if(blocked)
+ add_overlay("welded")
+ if(pdiff_alert)
+ add_overlay("palert")
+ if(dir_alerts)
+ for(var/d=1;d<=4;d++)
+ var/cdir = cardinal[d]
+ for(var/i=1;i<=ALERT_STATES.len;i++)
+ if(dir_alerts[d] & (1<<(i-1)))
+ add_overlay(new/icon(icon,"alert_[ALERT_STATES[i]]", dir=cdir))
+ else
+ icon_state = "door_open"
+ if(prying)
+ icon_state = "prying_open"
+ if(blocked)
+ add_overlay("welded_open")
+ return
+
+//These are playing merry hell on ZAS. Sorry fellas :(
+
+/obj/machinery/door/firedoor/border_only
+/*
+ icon = 'icons/obj/doors/edge_Doorfire.dmi'
+ glass = 1 //There is a glass window so you can see through the door
+ //This is needed due to BYOND limitations in controlling visibility
+ heat_proof = 1
+ air_properties_vary_with_direction = 1
+
+ CanPass(atom/movable/mover, turf/target)
+ if(istype(mover) && mover.checkpass(PASSGLASS))
+ return 1
+ if(get_dir(loc, target) == dir) //Make sure looking at appropriate border
+ return !density
+ else
+ return 1
+
+ CheckExit(atom/movable/mover as mob|obj, turf/target as turf)
+ if(istype(mover) && mover.checkpass(PASSGLASS))
+ return 1
+ if(get_dir(loc, target) == dir)
+ return !density
+ else
+ return 1
+
+
+ update_nearby_tiles(need_rebuild)
+ if(!air_master) return 0
+
+ var/turf/simulated/source = loc
+ var/turf/simulated/destination = get_step(source,dir)
+
+ update_heat_protection(loc)
+
+ if(istype(source)) air_master.tiles_to_update += source
+ if(istype(destination)) air_master.tiles_to_update += destination
+ return 1
+*/
+
+/obj/machinery/door/firedoor/multi_tile
+ icon = 'icons/obj/doors/DoorHazard2x1.dmi'
+ width = 2
+ open_sound = 'sound/machines/firewide1o.ogg' //CHOMPEdit
+ close_sound = 'sound/machines/firewide1c.ogg' //CHOMPEdit
+
+/obj/machinery/door/firedoor/glass
+ name = "\improper Emergency Glass Shutter"
+ desc = "Emergency air-tight shutter, capable of sealing off breached areas. This one has a resilient glass window, allowing you to see the danger."
+ icon = 'icons/obj/doors/DoorHazardGlass.dmi'
+ icon_state = "door_open"
+ glass = 1
diff --git a/code/modules/clothing/under/accessories/accessory_vr.dm b/code/modules/clothing/under/accessories/accessory_vr.dm
index c674328193..48ab4635f4 100644
--- a/code/modules/clothing/under/accessories/accessory_vr.dm
+++ b/code/modules/clothing/under/accessories/accessory_vr.dm
@@ -512,7 +512,14 @@
/obj/item/clothing/accessory/collar/shock/bluespace/relaymove(var/mob/living/user,var/direction)
return //For some reason equipping this item was triggering this proc, putting the wearer inside of the collars belly for some reason.
-/obj/item/clothing/accessory/collar/shock/bluespace/attackby(var/obj/item/device/assembly/signaler/component, mob/user as mob)
+/obj/item/clothing/accessory/collar/shock/bluespace/attackby(var/obj/item/component, mob/user as mob)
+ if (component.has_tool_quality(TOOL_WRENCH))
+ to_chat(user, "You crack the bluespace crystal [src].")
+ var/turf/T = get_turf(src)
+ new /obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning(T)
+ user.drop_from_inventory(src)
+ qdel(src)
+ return
if (!istype(component,/obj/item/device/assembly/signaler))
..()
return
@@ -536,7 +543,14 @@
target_size = 1
on = 1
-/obj/item/clothing/accessory/collar/shock/bluespace/modified/attackby(var/obj/item/device/assembly/signaler/component, mob/user as mob)
+/obj/item/clothing/accessory/collar/shock/bluespace/modified/attackby(var/obj/item/component, mob/user as mob)
+ if (component.has_tool_quality(TOOL_WRENCH))
+ to_chat(user, "You crack the bluespace crystal [src], the attached signaler disconnects.")
+ var/turf/T = get_turf(src)
+ new /obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning(T)
+ user.drop_from_inventory(src)
+ qdel(src)
+ return
if (!istype(component,/obj/item/device/assembly/signaler))
..()
return
@@ -644,6 +658,141 @@
s.start()
return
+//bluespace collar malfunctioning (random size)
+
+/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning
+ name = "Bluespace collar"
+ desc = "A collar that can manipulate the size of the wearer, and can be modified when unequiped. It has a crack on the crystal."
+ icon_state = "collar_size_malf"
+ item_state = "collar_size"
+ overlay_state = "collar_size"
+ target_size = 1
+ on = 1
+ var/currently_shrinking = 0
+
+/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning/attackby(var/obj/item/component, mob/user as mob)
+ if (!istype(component,/obj/item/device/assembly/signaler))
+ ..()
+ return
+ to_chat(user, "The signaler doesn't respond to the connection attempt [src].")
+ return
+
+/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning/attack_self(mob/user as mob, flag1)
+ if(!istype(user, /mob/living/carbon/human))
+ return
+ user.set_machine(src)
+ var/dat = {"
+ Frequency/Code for collar:
+ Frequency:
+ -
+ - [format_frequency(frequency)]
+ +
+ +
+
+ Code:
+ -
+ - [code]
+ +
+ +
+
+ Tag:
+ Set tag
+
+ Size:
+ Input Disabled!
+ "}
+ user << browse(dat, "window=radio")
+ onclose(user, "radio")
+ return
+
+/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning/Topic(href, href_list)
+ if(usr.stat || usr.restrained())
+ return
+ if(((istype(usr, /mob/living/carbon/human) && ((!( ticker ) || (ticker && ticker.mode != "monkey")) && usr.contents.Find(src))) || (usr.contents.Find(master) || (in_range(src, usr) && istype(loc, /turf)))))
+ usr.set_machine(src)
+ if(href_list["freq"])
+ var/new_frequency = sanitize_frequency(frequency + text2num(href_list["freq"]))
+ set_frequency(new_frequency)
+ if(href_list["tag"])
+ var/str = copytext(reject_bad_text(tgui_input_text(usr,"Tag text?","Set tag","",MAX_NAME_LEN)),1,MAX_NAME_LEN)
+ if(!str || !length(str))
+ to_chat(usr,"[name]'s tag set to be blank.")
+ name = initial(name)
+ desc = initial(desc)
+ else
+ to_chat(usr,"You set the [name]'s tag to '[str]'.")
+ name = initial(name) + " ([str])"
+ desc = initial(desc) + " The tag says \"[str]\"."
+ else
+ if(href_list["code"])
+ code += text2num(href_list["code"])
+ code = round(code)
+ code = min(100, code)
+ code = max(1, code)
+ if(!( master ))
+ if(istype(loc, /mob))
+ attack_self(loc)
+ else
+ for(var/mob/M in viewers(1, src))
+ if(M.client)
+ attack_self(M)
+ else
+ if(istype(master.loc, /mob))
+ attack_self(master.loc)
+ else
+ for(var/mob/M in viewers(1, master))
+ if(M.client)
+ attack_self(M)
+ else
+ usr << browse(null, "window=radio")
+ return
+ return
+
+/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning/receive_signal(datum/signal/signal)
+ if(!signal)
+ return
+ target_size = (rand(25,200)) /100
+ if(on)
+ var/mob/M = null
+ if(ismob(loc))
+ M = loc
+ if(ismob(loc.loc))
+ M = loc.loc // This is about as terse as I can make my solution to the whole 'collar won't work when attached as accessory' thing.
+ var/mob/living/carbon/human/H = M
+ var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
+ if(!H.resizable)
+ H.visible_message("The space around [H] compresses for a moment but then nothing happens.","The space around you distorts but nothing happens to you.")
+ return
+ if (target_size < 0.25)
+ H.visible_message("The collar on [H] flickers, but fizzles out.","Your collar flickers, but is not powerful enough to shrink you that small.")
+ return
+ if(currently_shrinking == 0)
+ if(!(world.time - last_activated > 10 SECONDS))
+ to_chat(M, "\The [src] flickers. It seems to be recharging.")
+ return
+ last_activated = world.time
+ original_size = H.size_multiplier
+ currently_shrinking = 1
+ H.resize(target_size, ignore_prefs = FALSE) //In case someone else tries to put it on you.
+ H.visible_message("The space around [H] distorts as they change size!","The space around you distorts as you change size!")
+ log_admin("Admin [key_name(M)]'s size was altered by a bluespace collar.")
+ s.set_up(3, 1, M)
+ s.start()
+ else if(currently_shrinking == 1)
+ if(original_size == null)
+ H.visible_message("The space around [H] twists and turns for a moment but then nothing happens.","The space around you distorts but stay the same size.")
+ return
+ last_activated = world.time
+ H.resize(original_size, ignore_prefs = FALSE)
+ original_size = null
+ currently_shrinking = 0
+ H.visible_message("The space around [H] distorts as they return to their original size!","The space around you distorts as you return to your original size!")
+ log_admin("Admin [key_name(M)]'s size was altered by a bluespace collar.")
+ to_chat(M, "\The [src] flickers. It is now recharging and will be ready again in ten seconds.")
+ s.set_up(3, 1, M)
+ s.start()
+ return
+
//Machete Holsters
/obj/item/clothing/accessory/holster/machete
name = "machete sheath"
diff --git a/code/modules/research/message_server.dm b/code/modules/research/message_server.dm
index adfbef6938..ad0585200a 100644
--- a/code/modules/research/message_server.dm
+++ b/code/modules/research/message_server.dm
@@ -1,455 +1,455 @@
-#define MESSAGE_SERVER_SPAM_REJECT 1
-#define MESSAGE_SERVER_DEFAULT_SPAM_LIMIT 10
-
-var/global/list/obj/machinery/message_server/message_servers = list()
-
-/datum/data_pda_msg
- var/recipient = "Unspecified" //name of the person
- var/sender = "Unspecified" //name of the sender
- var/message = "Blank" //transferred message
-
-/datum/data_pda_msg/New(var/param_rec = "",var/param_sender = "",var/param_message = "")
-
- if(param_rec)
- recipient = param_rec
- if(param_sender)
- sender = param_sender
- if(param_message)
- message = param_message
-
-/datum/data_rc_msg
- var/rec_dpt = "Unspecified" //name of the person
- var/send_dpt = "Unspecified" //name of the sender
- var/message = "Blank" //transferred message
- var/stamp = "Unstamped"
- var/id_auth = "Unauthenticated"
- var/priority = "Normal"
-
-/datum/data_rc_msg/New(var/param_rec = "",var/param_sender = "",var/param_message = "",var/param_stamp = "",var/param_id_auth = "",var/param_priority)
- if(param_rec)
- rec_dpt = param_rec
- if(param_sender)
- send_dpt = param_sender
- if(param_message)
- message = param_message
- if(param_stamp)
- stamp = param_stamp
- if(param_id_auth)
- id_auth = param_id_auth
- if(param_priority)
- switch(param_priority)
- if(1)
- priority = "Normal"
- if(2)
- priority = "High"
- if(3)
- priority = "Extreme"
- else
- priority = "Undetermined"
-
-/obj/machinery/message_server
- icon = 'icons/obj/machines/research.dmi'
- icon_state = "server"
- name = "Messaging Server"
- desc = "Facilitates both PDA messages and request console functions."
- density = TRUE
- anchored = TRUE
- unacidable = TRUE
- use_power = USE_POWER_IDLE
- idle_power_usage = 10
- active_power_usage = 100
-
- var/list/datum/data_pda_msg/pda_msgs = list()
- var/list/datum/data_rc_msg/rc_msgs = list()
- var/active = 1
- var/decryptkey = "password"
-
- //Spam filtering stuff
- var/list/spamfilter = list("You have won", "your prize", "male enhancement", "shitcurity", \
- "are happy to inform you", "account number", "enter your PIN")
- //Messages having theese tokens will be rejected by server. Case sensitive
- var/spamfilter_limit = MESSAGE_SERVER_DEFAULT_SPAM_LIMIT //Maximal amount of tokens
-
- var/datum/looping_sound/tcomms/soundloop // CHOMPStation Add: Hummy noises
- var/noisy = FALSE // CHOMPStation Add: Hummy noises
-
-/obj/machinery/message_server/New()
- message_servers += src
- decryptkey = GenerateKey()
- send_pda_message("System Administrator", "system", "This is an automated message. The messaging system is functioning correctly.")
-
- // CHOMPAdd: PDA Messaging Server humming
- soundloop = new(list(src), FALSE)
- if(prob(60)) // 60% chance to change the midloop
- if(prob(40))
- soundloop.mid_sounds = list('sound/machines/tcomms/tcomms_02.ogg' = 1)
- soundloop.mid_length = 40
- else if(prob(20))
- soundloop.mid_sounds = list('sound/machines/tcomms/tcomms_03.ogg' = 1)
- soundloop.mid_length = 10
- else
- soundloop.mid_sounds = list('sound/machines/tcomms/tcomms_04.ogg' = 1)
- soundloop.mid_length = 30
- // CHOMPAdd End
- ..()
- return
-
-/obj/machinery/message_server/Destroy()
- message_servers -= src
- QDEL_NULL(soundloop) // CHOMPStation Add: Hummy noises
- ..()
- return
-
-/obj/machinery/message_server/examine(mob/user, distance, infix, suffix)
- . = ..()
- . += "It appears to be [active ? "online" : "offline"]."
-
-/obj/machinery/message_server/proc/GenerateKey()
- //Feel free to move to Helpers.
- var/newKey
- newKey += pick("the", "if", "of", "as", "in", "a", "you", "from", "to", "an", "too", "little", "snow", "dead", "drunk", "rosebud", "duck", "al", "le")
- newKey += pick("diamond", "beer", "mushroom", "assistant", "clown", "captain", "twinkie", "security", "nuke", "small", "big", "escape", "yellow", "gloves", "monkey", "engine", "nuclear", "ai")
- newKey += pick("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
- return newKey
-
-/obj/machinery/message_server/process()
- //if(decryptkey == "password")
- // decryptkey = generateKey()
- if(active && (stat & (BROKEN|NOPOWER)))
- active = 0
- soundloop.stop() // CHOMPStation Add: Hummy noises
- noisy = FALSE // CHOMPStation Add: Hummy noises
- return
- if(!noisy && active) // CHOMPStation Add: Hummy noises
- soundloop.start() // CHOMPStation Add: Hummy noises
- noisy = TRUE // CHOMPStation Add: Hummy noises
- update_icon()
- return
-
-/obj/machinery/message_server/proc/send_pda_message(var/recipient = "",var/sender = "",var/message = "")
- var/result
- for (var/token in spamfilter)
- if (findtextEx(message,token))
- message = "[message]" //Rejected messages will be indicated by red color.
- result = token //Token caused rejection (if there are multiple, last will be chosen>.
- pda_msgs += new/datum/data_pda_msg(recipient,sender,message)
- return result
-
-/obj/machinery/message_server/proc/send_rc_message(var/recipient = "",var/sender = "",var/message = "",var/stamp = "", var/id_auth = "", var/priority = 1)
- rc_msgs += new/datum/data_rc_msg(recipient,sender,message,stamp,id_auth)
- var/authmsg = "[message]\n"
- if (id_auth)
- authmsg += "([id_auth])\n"
- if (stamp)
- authmsg += "([stamp])\n"
- for (var/obj/machinery/requests_console/Console in allConsoles)
- if (ckey(Console.department) == ckey(recipient))
- if(Console.inoperable())
- Console.message_log += list(list("Message lost due to console failure.","Please contact [station_name()] system adminsitrator or AI for technical assistance."))
- continue
- if(Console.newmessagepriority < priority)
- Console.newmessagepriority = priority
- Console.icon_state = "req_comp[priority]"
- switch(priority)
- if(2)
- if(!Console.silent)
- playsound(Console, 'sound/machines/twobeep.ogg', 50, 1)
- Console.audible_message(text("\icon[Console][bicon(Console)] *The Requests Console beeps: 'PRIORITY Alert in [sender]'"),,5, runemessage = "beep! beep!")
- Console.message_log += list(list("High Priority message from [sender]", "[authmsg]"))
- else
- if(!Console.silent)
- playsound(Console, 'sound/machines/twobeep.ogg', 50, 1)
- Console.audible_message(text("\icon[Console][bicon(Console)] *The Requests Console beeps: 'Message from [sender]'"),,4, runemessage = "beep beep")
- Console.message_log += list(list("Message from [sender]", "[authmsg]"))
- Console.set_light(2)
-
-
-/obj/machinery/message_server/attack_hand(user as mob)
-// to_chat(user, "There seem to be some parts missing from this server. They should arrive on the station in a few days, give or take a few CentCom delays.")
- to_chat(user, "You toggle PDA message passing from [active ? "On" : "Off"] to [active ? "Off" : "On"].")
- active = !active
- update_icon()
-
- return
-
-/obj/machinery/message_server/attackby(obj/item/weapon/O as obj, mob/living/user as mob)
- if (active && !(stat & (BROKEN|NOPOWER)) && (spamfilter_limit < MESSAGE_SERVER_DEFAULT_SPAM_LIMIT*2) && \
- istype(O,/obj/item/weapon/circuitboard/message_monitor))
- spamfilter_limit += round(MESSAGE_SERVER_DEFAULT_SPAM_LIMIT / 2)
- user.drop_item()
- qdel(O)
- to_chat(user, "You install additional memory and processors into message server. Its filtering capabilities been enhanced.")
- else
- ..(O, user)
-
-/obj/machinery/message_server/update_icon()
- if((stat & (BROKEN|NOPOWER)))
- icon_state = "server-nopower"
- else if (!active)
- icon_state = "server-off"
- else
- icon_state = "server-on"
-
- return
-
-
-/datum/feedback_variable
- var/variable
- var/value
- var/details
-
-/datum/feedback_variable/vv_edit_var(var_name, var_value)
- if(var_name == NAMEOF(src, variable) || var_name == NAMEOF(src, value) || var_name == NAMEOF(src, details))
- return FALSE
- return ..()
-
-/datum/feedback_variable/New(var/param_variable,var/param_value = 0)
- variable = param_variable
- value = param_value
-
-/datum/feedback_variable/proc/inc(var/num = 1)
- if(isnum(value))
- value += num
- else
- value = text2num(value)
- if(isnum(value))
- value += num
- else
- value = num
-
-/datum/feedback_variable/proc/dec(var/num = 1)
- if(isnum(value))
- value -= num
- else
- value = text2num(value)
- if(isnum(value))
- value -= num
- else
- value = -num
-
-/datum/feedback_variable/proc/set_value(var/num)
- if(isnum(num))
- value = num
-
-/datum/feedback_variable/proc/get_value()
- return value
-
-/datum/feedback_variable/proc/get_variable()
- return variable
-
-/datum/feedback_variable/proc/set_details(var/text)
- if(istext(text))
- details = text
-
-/datum/feedback_variable/proc/add_details(var/text)
- if(istext(text))
- if(!details)
- details = text
- else
- details += " [text]"
-
-/datum/feedback_variable/proc/get_details()
- return details
-
-/datum/feedback_variable/proc/get_parsed()
- return list(variable,value,details)
-
-var/obj/machinery/blackbox_recorder/blackbox
-
-/obj/machinery/blackbox_recorder
- icon = 'icons/obj/stationobjs.dmi'
- icon_state = "blackbox"
- name = "Blackbox Recorder"
- desc = "Records all radio communications, as well as various other information in case of the worst."
- density = TRUE
- anchored = TRUE
- unacidable = TRUE
- use_power = USE_POWER_IDLE
- idle_power_usage = 10
- active_power_usage = 100
- var/list/messages = list() //Stores messages of non-standard frequencies
- var/list/messages_admin = list()
-
- var/list/msg_common = list()
- var/list/msg_science = list()
- var/list/msg_command = list()
- var/list/msg_medical = list()
- var/list/msg_engineering = list()
- var/list/msg_security = list()
- var/list/msg_deathsquad = list()
- var/list/msg_syndicate = list()
- var/list/msg_raider = list()
- var/list/msg_cargo = list()
- var/list/msg_service = list()
- var/list/msg_explorer = list()
-
- var/list/datum/feedback_variable/feedback = new()
-
- //Only one can exist in the world!
-/obj/machinery/blackbox_recorder/New()
- if(blackbox)
- if(istype(blackbox,/obj/machinery/blackbox_recorder))
- qdel(src)
- blackbox = src
-
-/obj/machinery/blackbox_recorder/Destroy()
- var/turf/T = locate(1,1,2)
- if(T)
- blackbox = null
- var/obj/machinery/blackbox_recorder/BR = new/obj/machinery/blackbox_recorder(T)
- BR.msg_common = msg_common
- BR.msg_science = msg_science
- BR.msg_command = msg_command
- BR.msg_medical = msg_medical
- BR.msg_engineering = msg_engineering
- BR.msg_security = msg_security
- BR.msg_deathsquad = msg_deathsquad
- BR.msg_syndicate = msg_syndicate
- BR.msg_cargo = msg_cargo
- BR.msg_service = msg_service
- BR.feedback = feedback
- BR.messages = messages
- BR.messages_admin = messages_admin
- if(blackbox != BR)
- blackbox = BR
- ..()
-
-/obj/machinery/blackbox_recorder/proc/find_feedback_datum(var/variable)
- for(var/datum/feedback_variable/FV in feedback)
- if(FV.get_variable() == variable)
- return FV
- var/datum/feedback_variable/FV = new(variable)
- feedback += FV
- return FV
-
-/obj/machinery/blackbox_recorder/proc/get_round_feedback()
- return feedback
-
-/obj/machinery/blackbox_recorder/proc/round_end_data_gathering()
-
- var/pda_msg_amt = 0
- var/rc_msg_amt = 0
-
- for(var/obj/machinery/message_server/MS in machines)
- if(MS.pda_msgs.len > pda_msg_amt)
- pda_msg_amt = MS.pda_msgs.len
- if(MS.rc_msgs.len > rc_msg_amt)
- rc_msg_amt = MS.rc_msgs.len
-
- feedback_set_details("radio_usage","")
-
- feedback_add_details("radio_usage","COM-[msg_common.len]")
- feedback_add_details("radio_usage","SCI-[msg_science.len]")
- feedback_add_details("radio_usage","HEA-[msg_command.len]")
- feedback_add_details("radio_usage","MED-[msg_medical.len]")
- feedback_add_details("radio_usage","ENG-[msg_engineering.len]")
- feedback_add_details("radio_usage","SEC-[msg_security.len]")
- feedback_add_details("radio_usage","DTH-[msg_deathsquad.len]")
- feedback_add_details("radio_usage","SYN-[msg_syndicate.len]")
- feedback_add_details("radio_usage","CAR-[msg_cargo.len]")
- feedback_add_details("radio_usage","SRV-[msg_service.len]")
- feedback_add_details("radio_usage","OTH-[messages.len]")
- feedback_add_details("radio_usage","PDA-[pda_msg_amt]")
- feedback_add_details("radio_usage","RC-[rc_msg_amt]")
-
-
- feedback_set_details("round_end","[time2text(world.realtime)]") //This one MUST be the last one that gets set.
-
-/obj/machinery/blackbox_recorder/vv_edit_var(var_name, var_value)
- var/static/list/blocked_vars //hacky as fuck kill me
- if(!blocked_vars)
- var/obj/machinery/M = new
- var/list/parent_vars = M.vars.Copy()
- blocked_vars = vars.Copy() - parent_vars
- if(var_name in blocked_vars)
- return FALSE
- return ..()
-
-//This proc is only to be called at round end.
-/obj/machinery/blackbox_recorder/proc/save_all_data_to_sql()
- if(!feedback) return
-
- round_end_data_gathering() //round_end time logging and some other data processing
- establish_db_connection()
- if(!SSdbcore.IsConnected()) return //CHOMPEdit TGSQL
- var/round_id
-
- var/DBQuery/query = SSdbcore.NewQuery("SELECT MAX(round_id) AS round_id FROM erro_feedback") //CHOMPEdit TGSQL
- query.Execute()
- while(query.NextRow())
- round_id = query.item[1]
- qdel(query) //CHOMPEdit TGSQL
- if(!isnum(round_id))
- round_id = text2num(round_id)
- round_id++
-
- for(var/datum/feedback_variable/FV in feedback)
- var/list/sqlargs = list("t_roundid" = round_id, "t_variable" = "[FV.get_variable()]", "t_value" = "[FV.get_value()]", "t_details" = "[FV.get_details()]") //CHOMPEdit TGSQL
- var/sql = "INSERT INTO erro_feedback VALUES (null, Now(), :t_roundid, :t_variable, :t_value, :t_details)" //CHOMPEdit TGSQL
- var/DBQuery/query_insert = SSdbcore.NewQuery(sql, sqlargs) //CHOMPEdit TGSQL
- query_insert.Execute()
- qdel(query_insert) //CHOMPEdit TGSQL
-
-// Sanitize inputs to avoid SQL injection attacks //CHOMPEdit NOTE: This is not secure. Basic filters like this are pretty easy to bypass. Use the format for arguments used in the above.
-/proc/sql_sanitize_text(var/text)
- text = replacetext(text, "'", "''")
- text = replacetext(text, ";", "")
- text = replacetext(text, "&", "")
- return text
-
-/proc/feedback_set(var/variable,var/value)
- if(!blackbox) return
-
- variable = sql_sanitize_text(variable)
-
- var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
-
- if(!FV) return
-
- FV.set_value(value)
-
-/proc/feedback_inc(var/variable,var/value)
- if(!blackbox) return
-
- variable = sql_sanitize_text(variable)
-
- var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
-
- if(!FV) return
-
- FV.inc(value)
-
-/proc/feedback_dec(var/variable,var/value)
- if(!blackbox) return
-
- variable = sql_sanitize_text(variable)
-
- var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
-
- if(!FV) return
-
- FV.dec(value)
-
-/proc/feedback_set_details(var/variable,var/details)
- if(!blackbox) return
-
- variable = sql_sanitize_text(variable)
- details = sql_sanitize_text(details)
-
- var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
-
- if(!FV) return
-
- FV.set_details(details)
-
-/proc/feedback_add_details(var/variable,var/details)
- if(!blackbox) return
-
- variable = sql_sanitize_text(variable)
- details = sql_sanitize_text(details)
-
- var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
-
- if(!FV) return
-
- FV.add_details(details)
+#define MESSAGE_SERVER_SPAM_REJECT 1
+#define MESSAGE_SERVER_DEFAULT_SPAM_LIMIT 10
+
+var/global/list/obj/machinery/message_server/message_servers = list()
+
+/datum/data_pda_msg
+ var/recipient = "Unspecified" //name of the person
+ var/sender = "Unspecified" //name of the sender
+ var/message = "Blank" //transferred message
+
+/datum/data_pda_msg/New(var/param_rec = "",var/param_sender = "",var/param_message = "")
+
+ if(param_rec)
+ recipient = param_rec
+ if(param_sender)
+ sender = param_sender
+ if(param_message)
+ message = param_message
+
+/datum/data_rc_msg
+ var/rec_dpt = "Unspecified" //name of the person
+ var/send_dpt = "Unspecified" //name of the sender
+ var/message = "Blank" //transferred message
+ var/stamp = "Unstamped"
+ var/id_auth = "Unauthenticated"
+ var/priority = "Normal"
+
+/datum/data_rc_msg/New(var/param_rec = "",var/param_sender = "",var/param_message = "",var/param_stamp = "",var/param_id_auth = "",var/param_priority)
+ if(param_rec)
+ rec_dpt = param_rec
+ if(param_sender)
+ send_dpt = param_sender
+ if(param_message)
+ message = param_message
+ if(param_stamp)
+ stamp = param_stamp
+ if(param_id_auth)
+ id_auth = param_id_auth
+ if(param_priority)
+ switch(param_priority)
+ if(1)
+ priority = "Normal"
+ if(2)
+ priority = "High"
+ if(3)
+ priority = "Extreme"
+ else
+ priority = "Undetermined"
+
+/obj/machinery/message_server
+ icon = 'icons/obj/machines/research.dmi'
+ icon_state = "server"
+ name = "Messaging Server"
+ desc = "Facilitates both PDA messages and request console functions."
+ density = TRUE
+ anchored = TRUE
+ unacidable = TRUE
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 10
+ active_power_usage = 100
+
+ var/list/datum/data_pda_msg/pda_msgs = list()
+ var/list/datum/data_rc_msg/rc_msgs = list()
+ var/active = 1
+ var/decryptkey = "password"
+
+ //Spam filtering stuff
+ var/list/spamfilter = list("You have won", "your prize", "male enhancement", "shitcurity", \
+ "are happy to inform you", "account number", "enter your PIN")
+ //Messages having theese tokens will be rejected by server. Case sensitive
+ var/spamfilter_limit = MESSAGE_SERVER_DEFAULT_SPAM_LIMIT //Maximal amount of tokens
+
+ var/datum/looping_sound/tcomms/soundloop // CHOMPStation Add: Hummy noises
+ var/noisy = FALSE // CHOMPStation Add: Hummy noises
+
+/obj/machinery/message_server/New()
+ message_servers += src
+ decryptkey = GenerateKey()
+ send_pda_message("System Administrator", "system", "This is an automated message. The messaging system is functioning correctly.")
+
+ // CHOMPAdd: PDA Messaging Server humming
+ soundloop = new(list(src), FALSE)
+ if(prob(60)) // 60% chance to change the midloop
+ if(prob(40))
+ soundloop.mid_sounds = list('sound/machines/tcomms/tcomms_02.ogg' = 1)
+ soundloop.mid_length = 40
+ else if(prob(20))
+ soundloop.mid_sounds = list('sound/machines/tcomms/tcomms_03.ogg' = 1)
+ soundloop.mid_length = 10
+ else
+ soundloop.mid_sounds = list('sound/machines/tcomms/tcomms_04.ogg' = 1)
+ soundloop.mid_length = 30
+ // CHOMPAdd End
+ ..()
+ return
+
+/obj/machinery/message_server/Destroy()
+ message_servers -= src
+ QDEL_NULL(soundloop) // CHOMPStation Add: Hummy noises
+ ..()
+ return
+
+/obj/machinery/message_server/examine(mob/user, distance, infix, suffix)
+ . = ..()
+ . += "It appears to be [active ? "online" : "offline"]."
+
+/obj/machinery/message_server/proc/GenerateKey()
+ //Feel free to move to Helpers.
+ var/newKey
+ newKey += pick("the", "if", "of", "as", "in", "a", "you", "from", "to", "an", "too", "little", "snow", "dead", "drunk", "rosebud", "duck", "al", "le")
+ newKey += pick("diamond", "beer", "mushroom", "assistant", "clown", "captain", "twinkie", "security", "nuke", "small", "big", "escape", "yellow", "gloves", "monkey", "engine", "nuclear", "ai")
+ newKey += pick("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
+ return newKey
+
+/obj/machinery/message_server/process()
+ //if(decryptkey == "password")
+ // decryptkey = generateKey()
+ if(active && (stat & (BROKEN|NOPOWER)))
+ active = 0
+ soundloop.stop() // CHOMPStation Add: Hummy noises
+ noisy = FALSE // CHOMPStation Add: Hummy noises
+ return
+ if(!noisy && active) // CHOMPStation Add: Hummy noises
+ soundloop.start() // CHOMPStation Add: Hummy noises
+ noisy = TRUE // CHOMPStation Add: Hummy noises
+ update_icon()
+ return
+
+/obj/machinery/message_server/proc/send_pda_message(var/recipient = "",var/sender = "",var/message = "")
+ var/result
+ for (var/token in spamfilter)
+ if (findtextEx(message,token))
+ message = "[message]" //Rejected messages will be indicated by red color.
+ result = token //Token caused rejection (if there are multiple, last will be chosen>.
+ pda_msgs += new/datum/data_pda_msg(recipient,sender,message)
+ return result
+
+/obj/machinery/message_server/proc/send_rc_message(var/recipient = "",var/sender = "",var/message = "",var/stamp = "", var/id_auth = "", var/priority = 1)
+ rc_msgs += new/datum/data_rc_msg(recipient,sender,message,stamp,id_auth)
+ var/authmsg = "[message]\n"
+ if (id_auth)
+ authmsg += "([id_auth])\n"
+ if (stamp)
+ authmsg += "([stamp])\n"
+ for (var/obj/machinery/requests_console/Console in allConsoles)
+ if (ckey(Console.department) == ckey(recipient))
+ if(Console.inoperable())
+ Console.message_log += list(list("Message lost due to console failure.","Please contact [station_name()] system adminsitrator or AI for technical assistance."))
+ continue
+ if(Console.newmessagepriority < priority)
+ Console.newmessagepriority = priority
+ Console.icon_state = "req_comp[priority]"
+ switch(priority)
+ if(2)
+ if(!Console.silent)
+ playsound(Console, 'sound/machines/twobeep.ogg', 50, 1)
+ Console.audible_message(text("\icon[Console][bicon(Console)] *The Requests Console beeps: 'PRIORITY Alert in [sender]'"),,5, runemessage = "beep! beep!")
+ Console.message_log += list(list("High Priority message from [sender]", "[authmsg]"))
+ else
+ if(!Console.silent)
+ playsound(Console, 'sound/machines/twobeep.ogg', 50, 1)
+ Console.audible_message(text("\icon[Console][bicon(Console)] *The Requests Console beeps: 'Message from [sender]'"),,4, runemessage = "beep beep")
+ Console.message_log += list(list("Message from [sender]", "[authmsg]"))
+ Console.set_light(2)
+
+
+/obj/machinery/message_server/attack_hand(user as mob)
+// to_chat(user, "There seem to be some parts missing from this server. They should arrive on the station in a few days, give or take a few CentCom delays.")
+ to_chat(user, "You toggle PDA message passing from [active ? "On" : "Off"] to [active ? "Off" : "On"].")
+ active = !active
+ update_icon()
+
+ return
+
+/obj/machinery/message_server/attackby(obj/item/weapon/O as obj, mob/living/user as mob)
+ if (active && !(stat & (BROKEN|NOPOWER)) && (spamfilter_limit < MESSAGE_SERVER_DEFAULT_SPAM_LIMIT*2) && \
+ istype(O,/obj/item/weapon/circuitboard/message_monitor))
+ spamfilter_limit += round(MESSAGE_SERVER_DEFAULT_SPAM_LIMIT / 2)
+ user.drop_item()
+ qdel(O)
+ to_chat(user, "You install additional memory and processors into message server. Its filtering capabilities been enhanced.")
+ else
+ ..(O, user)
+
+/obj/machinery/message_server/update_icon()
+ if((stat & (BROKEN|NOPOWER)))
+ icon_state = "server-nopower"
+ else if (!active)
+ icon_state = "server-off"
+ else
+ icon_state = "server-on"
+
+ return
+
+
+/datum/feedback_variable
+ var/variable
+ var/value
+ var/details
+
+/datum/feedback_variable/vv_edit_var(var_name, var_value)
+ if(var_name == NAMEOF(src, variable) || var_name == NAMEOF(src, value) || var_name == NAMEOF(src, details))
+ return FALSE
+ return ..()
+
+/datum/feedback_variable/New(var/param_variable,var/param_value = 0)
+ variable = param_variable
+ value = param_value
+
+/datum/feedback_variable/proc/inc(var/num = 1)
+ if(isnum(value))
+ value += num
+ else
+ value = text2num(value)
+ if(isnum(value))
+ value += num
+ else
+ value = num
+
+/datum/feedback_variable/proc/dec(var/num = 1)
+ if(isnum(value))
+ value -= num
+ else
+ value = text2num(value)
+ if(isnum(value))
+ value -= num
+ else
+ value = -num
+
+/datum/feedback_variable/proc/set_value(var/num)
+ if(isnum(num))
+ value = num
+
+/datum/feedback_variable/proc/get_value()
+ return value
+
+/datum/feedback_variable/proc/get_variable()
+ return variable
+
+/datum/feedback_variable/proc/set_details(var/text)
+ if(istext(text))
+ details = text
+
+/datum/feedback_variable/proc/add_details(var/text)
+ if(istext(text))
+ if(!details)
+ details = text
+ else
+ details += " [text]"
+
+/datum/feedback_variable/proc/get_details()
+ return details
+
+/datum/feedback_variable/proc/get_parsed()
+ return list(variable,value,details)
+
+var/obj/machinery/blackbox_recorder/blackbox
+
+/obj/machinery/blackbox_recorder
+ icon = 'icons/obj/stationobjs.dmi'
+ icon_state = "blackbox"
+ name = "Blackbox Recorder"
+ desc = "Records all radio communications, as well as various other information in case of the worst."
+ density = TRUE
+ anchored = TRUE
+ unacidable = TRUE
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 10
+ active_power_usage = 100
+ var/list/messages = list() //Stores messages of non-standard frequencies
+ var/list/messages_admin = list()
+
+ var/list/msg_common = list()
+ var/list/msg_science = list()
+ var/list/msg_command = list()
+ var/list/msg_medical = list()
+ var/list/msg_engineering = list()
+ var/list/msg_security = list()
+ var/list/msg_deathsquad = list()
+ var/list/msg_syndicate = list()
+ var/list/msg_raider = list()
+ var/list/msg_cargo = list()
+ var/list/msg_service = list()
+ var/list/msg_explorer = list()
+
+ var/list/datum/feedback_variable/feedback = new()
+
+ //Only one can exist in the world!
+/obj/machinery/blackbox_recorder/New()
+ if(blackbox)
+ if(istype(blackbox,/obj/machinery/blackbox_recorder))
+ qdel(src)
+ blackbox = src
+
+/obj/machinery/blackbox_recorder/Destroy()
+ var/turf/T = locate(1,1,2)
+ if(T)
+ blackbox = null
+ var/obj/machinery/blackbox_recorder/BR = new/obj/machinery/blackbox_recorder(T)
+ BR.msg_common = msg_common
+ BR.msg_science = msg_science
+ BR.msg_command = msg_command
+ BR.msg_medical = msg_medical
+ BR.msg_engineering = msg_engineering
+ BR.msg_security = msg_security
+ BR.msg_deathsquad = msg_deathsquad
+ BR.msg_syndicate = msg_syndicate
+ BR.msg_cargo = msg_cargo
+ BR.msg_service = msg_service
+ BR.feedback = feedback
+ BR.messages = messages
+ BR.messages_admin = messages_admin
+ if(blackbox != BR)
+ blackbox = BR
+ ..()
+
+/obj/machinery/blackbox_recorder/proc/find_feedback_datum(var/variable)
+ for(var/datum/feedback_variable/FV in feedback)
+ if(FV.get_variable() == variable)
+ return FV
+ var/datum/feedback_variable/FV = new(variable)
+ feedback += FV
+ return FV
+
+/obj/machinery/blackbox_recorder/proc/get_round_feedback()
+ return feedback
+
+/obj/machinery/blackbox_recorder/proc/round_end_data_gathering()
+
+ var/pda_msg_amt = 0
+ var/rc_msg_amt = 0
+
+ for(var/obj/machinery/message_server/MS in machines)
+ if(MS.pda_msgs.len > pda_msg_amt)
+ pda_msg_amt = MS.pda_msgs.len
+ if(MS.rc_msgs.len > rc_msg_amt)
+ rc_msg_amt = MS.rc_msgs.len
+
+ feedback_set_details("radio_usage","")
+
+ feedback_add_details("radio_usage","COM-[msg_common.len]")
+ feedback_add_details("radio_usage","SCI-[msg_science.len]")
+ feedback_add_details("radio_usage","HEA-[msg_command.len]")
+ feedback_add_details("radio_usage","MED-[msg_medical.len]")
+ feedback_add_details("radio_usage","ENG-[msg_engineering.len]")
+ feedback_add_details("radio_usage","SEC-[msg_security.len]")
+ feedback_add_details("radio_usage","DTH-[msg_deathsquad.len]")
+ feedback_add_details("radio_usage","SYN-[msg_syndicate.len]")
+ feedback_add_details("radio_usage","CAR-[msg_cargo.len]")
+ feedback_add_details("radio_usage","SRV-[msg_service.len]")
+ feedback_add_details("radio_usage","OTH-[messages.len]")
+ feedback_add_details("radio_usage","PDA-[pda_msg_amt]")
+ feedback_add_details("radio_usage","RC-[rc_msg_amt]")
+
+
+ feedback_set_details("round_end","[time2text(world.realtime)]") //This one MUST be the last one that gets set.
+
+/obj/machinery/blackbox_recorder/vv_edit_var(var_name, var_value)
+ var/static/list/blocked_vars //hacky as fuck kill me
+ if(!blocked_vars)
+ var/obj/machinery/M = new
+ var/list/parent_vars = M.vars.Copy()
+ blocked_vars = vars.Copy() - parent_vars
+ if(var_name in blocked_vars)
+ return FALSE
+ return ..()
+
+//This proc is only to be called at round end.
+/obj/machinery/blackbox_recorder/proc/save_all_data_to_sql()
+ if(!feedback) return
+
+ round_end_data_gathering() //round_end time logging and some other data processing
+ establish_db_connection()
+ if(!SSdbcore.IsConnected()) return //CHOMPEdit TGSQL
+ var/round_id
+
+ var/DBQuery/query = SSdbcore.NewQuery("SELECT MAX(round_id) AS round_id FROM erro_feedback") //CHOMPEdit TGSQL
+ query.Execute()
+ while(query.NextRow())
+ round_id = query.item[1]
+ qdel(query) //CHOMPEdit TGSQL
+ if(!isnum(round_id))
+ round_id = text2num(round_id)
+ round_id++
+
+ for(var/datum/feedback_variable/FV in feedback)
+ var/list/sqlargs = list("t_roundid" = round_id, "t_variable" = "[FV.get_variable()]", "t_value" = "[FV.get_value()]", "t_details" = "[FV.get_details()]") //CHOMPEdit TGSQL
+ var/sql = "INSERT INTO erro_feedback VALUES (null, Now(), :t_roundid, :t_variable, :t_value, :t_details)" //CHOMPEdit TGSQL
+ var/DBQuery/query_insert = SSdbcore.NewQuery(sql, sqlargs) //CHOMPEdit TGSQL
+ query_insert.Execute()
+ qdel(query_insert) //CHOMPEdit TGSQL
+
+// Sanitize inputs to avoid SQL injection attacks //CHOMPEdit NOTE: This is not secure. Basic filters like this are pretty easy to bypass. Use the format for arguments used in the above.
+/proc/sql_sanitize_text(var/text)
+ text = replacetext(text, "'", "''")
+ text = replacetext(text, ";", "")
+ text = replacetext(text, "&", "")
+ return text
+
+/proc/feedback_set(var/variable,var/value)
+ if(!blackbox) return
+
+ variable = sql_sanitize_text(variable)
+
+ var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
+
+ if(!FV) return
+
+ FV.set_value(value)
+
+/proc/feedback_inc(var/variable,var/value)
+ if(!blackbox) return
+
+ variable = sql_sanitize_text(variable)
+
+ var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
+
+ if(!FV) return
+
+ FV.inc(value)
+
+/proc/feedback_dec(var/variable,var/value)
+ if(!blackbox) return
+
+ variable = sql_sanitize_text(variable)
+
+ var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
+
+ if(!FV) return
+
+ FV.dec(value)
+
+/proc/feedback_set_details(var/variable,var/details)
+ if(!blackbox) return
+
+ variable = sql_sanitize_text(variable)
+ details = sql_sanitize_text(details)
+
+ var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
+
+ if(!FV) return
+
+ FV.set_details(details)
+
+/proc/feedback_add_details(var/variable,var/details)
+ if(!blackbox) return
+
+ variable = sql_sanitize_text(variable)
+ details = sql_sanitize_text(details)
+
+ var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
+
+ if(!FV) return
+
+ FV.add_details(details)
diff --git a/code/modules/vore/resizing/sizegun_slow_vr.dm b/code/modules/vore/resizing/sizegun_slow_vr.dm
index 648c0f91c7..9a993e7a87 100644
--- a/code/modules/vore/resizing/sizegun_slow_vr.dm
+++ b/code/modules/vore/resizing/sizegun_slow_vr.dm
@@ -3,7 +3,7 @@
/obj/item/device/slow_sizegun
name = "gradual size gun"
- desc = "A highly advanced ray gun, designed for progressive and gradual changing of size."
+ desc = "A highly advanced ray gun, designed for progressive and gradual changing of size. Size trading can be toggled on via alt-clicking."
icon = 'icons/obj/gun_vr.dmi'
icon_state = "sizegun-old-0"
var/base_icon_state = "sizegun-old"
@@ -17,6 +17,7 @@
var/dorm_size = TRUE
var/size_increment = 0.01
var/current_target
+ var/trading = 0
/obj/item/device/slow_sizegun/update_icon()
icon_state = "[base_icon_state]-[sizeshift_mode]"
@@ -50,6 +51,9 @@
if(unresizable)
return TRUE
+ if(trading == 1 && !(user.resizable))
+ return TRUE
+
if(!(target.has_large_resize_bounds()) && (target.size_multiplier >= RESIZE_MAXIMUM) && sizeshift_mode == SIZE_GROW)
return TRUE
@@ -62,6 +66,18 @@
if(target.size_multiplier <= RESIZE_MINIMUM_DORMS && sizeshift_mode == SIZE_SHRINK)
return TRUE
+ if(trading == 1 && !(user.has_large_resize_bounds()) && (user.size_multiplier >= RESIZE_MAXIMUM) && sizeshift_mode == SIZE_GROW)
+ return TRUE
+
+ if(trading == 1 && user.size_multiplier >= RESIZE_MAXIMUM_DORMS && sizeshift_mode == SIZE_GROW)
+ return TRUE
+
+ if(trading == 1 && !(user.has_large_resize_bounds()) && (user.size_multiplier <= RESIZE_MINIMUM) && sizeshift_mode == SIZE_SHRINK)
+ return TRUE
+
+ if(trading == 1 && user.size_multiplier <= RESIZE_MINIMUM_DORMS && sizeshift_mode == SIZE_SHRINK)
+ return TRUE
+
return FALSE
/obj/item/device/slow_sizegun/afterattack(atom/target, mob/user, proximity_flag)
@@ -81,6 +97,7 @@
return
var/mob/living/L = target
+ var/mob/living/U = user
if(get_dist(target, user) > beam_range)
to_chat(user, span("warning", "You are too far away from \the [target] to affect it. Get closer."))
@@ -100,6 +117,9 @@
if(!(L.resizable))
unresizable = TRUE
+ if(trading == 1 && !(user.resizable))
+ unresizable = TRUE
+
if(unresizable)
to_chat(user, span("warning", "\the [target] is immune to resizing."))
@@ -119,14 +139,25 @@
var/active_hand = user.get_active_hand()
- while(!should_stop(target, user, active_hand))
- stoplag(3)
+ if (trading == 0)
+ while(!should_stop(target, user, active_hand))
+ stoplag(3)
- if(sizeshift_mode == SIZE_SHRINK)
- L.resize((L.size_multiplier - size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE)
- else if(sizeshift_mode == SIZE_GROW)
- L.resize((L.size_multiplier + size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE)
+ if(sizeshift_mode == SIZE_SHRINK)
+ L.resize((L.size_multiplier - size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE)
+ else if(sizeshift_mode == SIZE_GROW)
+ L.resize((L.size_multiplier + size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE)
+ if (trading == 1)
+ while(!should_stop(target, user, active_hand))
+ stoplag(3)
+
+ if(sizeshift_mode == SIZE_SHRINK)
+ L.resize((L.size_multiplier - size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE)
+ U.resize((U.size_multiplier + size_increment), uncapped = U.has_large_resize_bounds(), aura_animation = FALSE)
+ else if(sizeshift_mode == SIZE_GROW)
+ L.resize((L.size_multiplier + size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE)
+ U.resize((U.size_multiplier - size_increment), uncapped = U.has_large_resize_bounds(), aura_animation = FALSE)
busy = FALSE
current_target = null
@@ -219,3 +250,13 @@
/obj/item/device/slow_sizegun/proc/color_box(list/box_segments, new_color, new_time)
for(var/i in box_segments)
animate(i, color = new_color, time = new_time)
+
+//Alt click to activate size trading
+
+/obj/item/device/slow_sizegun/AltClick(mob/user)
+ if (trading == 0)
+ trading = 1
+ to_chat(user, span("notice", "\The [src] will now trade your targets size for your own."))
+ else
+ trading = 0
+ to_chat(user, span("notice", "\The [src] will no longer trade your targets size for your own."))
diff --git a/code/modules/vore/resizing/sizegun_vr.dm b/code/modules/vore/resizing/sizegun_vr.dm
index c5fc1f3648..982d5185f9 100644
--- a/code/modules/vore/resizing/sizegun_vr.dm
+++ b/code/modules/vore/resizing/sizegun_vr.dm
@@ -26,11 +26,20 @@
/obj/item/weapon/gun/energy/sizegun/New()
..()
verbs += PROC_REF(select_size)
+ verbs += PROC_REF(spin_dial)
/obj/item/weapon/gun/energy/sizegun/attack_self(mob/user)
. = ..()
select_size()
+/obj/item/weapon/gun/energy/sizegun/proc/spin_dial()
+ set name = "Spin Size Dial"
+ set category = "Object"
+ set src in view(1)
+
+ size_set_to = (rand(25,200)) /100
+ usr.visible_message("\The [usr] spins the size dial to a random value!","You spin the dial to a random value!")
+
/obj/item/weapon/gun/energy/sizegun/consume_next_projectile()
. = ..()
var/obj/item/projectile/beam/sizelaser/G = .
diff --git a/code/modules/xgm/xgm_gas_mixture.dm b/code/modules/xgm/xgm_gas_mixture.dm
index fd52efca22..a89d2d8c35 100644
--- a/code/modules/xgm/xgm_gas_mixture.dm
+++ b/code/modules/xgm/xgm_gas_mixture.dm
@@ -1,490 +1,490 @@
-/datum/gas_mixture
- //Associative list of gas moles.
- //Gases with 0 moles are not tracked and are pruned by update_values()
- var/list/gas
- //Temperature in Kelvin of this gas mix.
- var/temperature = 0
-
- //Sum of all the gas moles in this mix. Updated by update_values()
- var/total_moles = 0
- //Volume of this mix.
- var/volume = CELL_VOLUME
- //Size of the group this gas_mixture is representing. 1 for singletons.
- var/group_multiplier = 1
-
- //List of active tile overlays for this gas_mixture. Updated by check_tile_graphic()
- var/list/graphic
-
-/datum/gas_mixture/New(vol = CELL_VOLUME)
- volume = vol
- gas = list()
-
-//Takes a gas string and the amount of moles to adjust by. Calls update_values() if update isn't 0.
-/datum/gas_mixture/proc/adjust_gas(gasid, moles, update = 1)
- if(moles == 0)
- return
-
- if (group_multiplier != 1)
- gas[gasid] += moles/group_multiplier
- else
- gas[gasid] += moles
-
- if(update)
- update_values()
-
-
-//Same as adjust_gas(), but takes a temperature which is mixed in with the gas.
-/datum/gas_mixture/proc/adjust_gas_temp(gasid, moles, temp, update = 1)
- if(moles == 0)
- return
-
- if(moles > 0 && abs(temperature - temp) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
- var/self_heat_capacity = heat_capacity()
- var/giver_heat_capacity = gas_data.specific_heat[gasid] * moles
- var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
- if(combined_heat_capacity != 0)
- temperature = (temp * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity
-
- if (group_multiplier != 1)
- gas[gasid] += moles/group_multiplier
- else
- gas[gasid] += moles
-
- if(update)
- update_values()
-
-
-//Variadic version of adjust_gas(). Takes any number of gas and mole pairs and applies them.
-/datum/gas_mixture/proc/adjust_multi()
- ASSERT(!(args.len % 2))
-
- for(var/i = 1; i < args.len; i += 2)
- adjust_gas(args[i], args[i+1], update = 0)
-
- update_values()
-
-
-//Variadic version of adjust_gas_temp(). Takes any number of gas, mole and temperature associations and applies them.
-/datum/gas_mixture/proc/adjust_multi_temp()
- ASSERT(!(args.len % 3))
-
- for(var/i = 1; i < args.len; i += 3)
- adjust_gas_temp(args[i], args[i + 1], args[i + 2], update = 0)
-
- update_values()
-
-
-//Merges all the gas from another mixture into this one. Respects group_multipliers and adjusts temperature correctly.
-//Does not modify giver in any way.
-/datum/gas_mixture/proc/merge(const/datum/gas_mixture/giver)
- if(!giver)
- return
-
- if(abs(temperature-giver.temperature)>MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
- var/self_heat_capacity = heat_capacity()
- var/giver_heat_capacity = giver.heat_capacity()
- var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
- if(combined_heat_capacity != 0)
- temperature = (giver.temperature*giver_heat_capacity + temperature*self_heat_capacity)/combined_heat_capacity
-
- if((group_multiplier != 1)||(giver.group_multiplier != 1))
- for(var/g in giver.gas)
- gas[g] += giver.gas[g] * giver.group_multiplier / group_multiplier
- else
- for(var/g in giver.gas)
- gas[g] += giver.gas[g]
-
- update_values()
-
-
-// Used to equalize the mixture between two zones before sleeping an edge.
-/datum/gas_mixture/proc/equalize(datum/gas_mixture/sharer)
- var/our_heatcap = heat_capacity()
- var/share_heatcap = sharer.heat_capacity()
-
- // Special exception: there isn't enough air around to be worth processing this edge next tick, zap both to zero.
- if(total_moles + sharer.total_moles <= MINIMUM_AIR_TO_SUSPEND)
- gas.Cut()
- sharer.gas.Cut()
-
- for(var/g in gas|sharer.gas)
- var/comb = gas[g] + sharer.gas[g]
- comb /= volume + sharer.volume
- gas[g] = comb * volume
- sharer.gas[g] = comb * sharer.volume
-
- if(our_heatcap + share_heatcap)
- temperature = ((temperature * our_heatcap) + (sharer.temperature * share_heatcap)) / (our_heatcap + share_heatcap)
- sharer.temperature = temperature
-
- update_values()
- sharer.update_values()
-
- return 1
-
-
-//Returns the heat capacity of the gas mix based on the specific heat of the gases.
-/datum/gas_mixture/proc/heat_capacity()
- . = 0
- for(var/g in gas)
- . += gas_data.specific_heat[g] * gas[g]
- . *= group_multiplier
-
-
-//Adds or removes thermal energy. Returns the actual thermal energy change, as in the case of removing energy we can't go below TCMB.
-/datum/gas_mixture/proc/add_thermal_energy(var/thermal_energy)
- if (total_moles == 0)
- return 0
-
- var/heat_capacity = heat_capacity()
- if (thermal_energy < 0)
- if (temperature < TCMB)
- return 0
- var/thermal_energy_limit = -(temperature - TCMB)*heat_capacity //ensure temperature does not go below TCMB
- thermal_energy = max( thermal_energy, thermal_energy_limit ) //thermal_energy and thermal_energy_limit are negative here.
- temperature += thermal_energy/heat_capacity
- return thermal_energy
-
-//Returns the thermal energy change required to get to a new temperature
-/datum/gas_mixture/proc/get_thermal_energy_change(var/new_temperature)
- return heat_capacity()*(max(new_temperature, 0) - temperature)
-
-
-//Technically vacuum doesn't have a specific entropy. Just use a really big number (infinity would be ideal) here so that it's easy to add gas to vacuum and hard to take gas out.
-#define SPECIFIC_ENTROPY_VACUUM 150000
-
-
-//Returns the ideal gas specific entropy of the whole mix. This is the entropy per mole of /mixed/ gas.
-/datum/gas_mixture/proc/specific_entropy()
- if (!gas.len || total_moles == 0)
- return SPECIFIC_ENTROPY_VACUUM
-
- . = 0
- for(var/g in gas)
- . += gas[g] * specific_entropy_gas(g)
- . /= total_moles
-
-
-/*
- It's arguable whether this should even be called entropy anymore. It's more "based on" entropy than actually entropy now.
-
- Returns the ideal gas specific entropy of a specific gas in the mix. This is the entropy due to that gas per mole of /that/ gas in the mixture, not the entropy due to that gas per mole of gas mixture.
-
- For the purposes of SS13, the specific entropy is just a number that tells you how hard it is to move gas. You can replace this with whatever you want.
- Just remember that returning a SMALL number == adding gas to this gas mix is HARD, taking gas away is EASY, and that returning a LARGE number means the opposite (so a vacuum should approach infinity).
-
- So returning a constant/(partial pressure) would probably do what most players expect. Although the version I have implemented below is a bit more nuanced than simply 1/P in that it scales in a way
- which is bit more realistic (natural log), and returns a fairly accurate entropy around room temperatures and pressures.
-*/
-/datum/gas_mixture/proc/specific_entropy_gas(var/gasid)
- if (!(gasid in gas) || gas[gasid] == 0)
- return SPECIFIC_ENTROPY_VACUUM //that gas isn't here
-
- //group_multiplier gets divided out in volume/gas[gasid] - also, V/(m*T) = R/(partial pressure)
- var/molar_mass = gas_data.molar_mass[gasid]
- var/specific_heat = gas_data.specific_heat[gasid]
- return R_IDEAL_GAS_EQUATION * ( log( (IDEAL_GAS_ENTROPY_CONSTANT*volume/(gas[gasid] * temperature)) * (molar_mass*specific_heat*temperature)**(2/3) + 1 ) + 15 )
-
- //alternative, simpler equation
- //var/partial_pressure = gas[gasid] * R_IDEAL_GAS_EQUATION * temperature / volume
- //return R_IDEAL_GAS_EQUATION * ( log (1 + IDEAL_GAS_ENTROPY_CONSTANT/partial_pressure) + 20 )
-
-
-//Updates the total_moles count and trims any empty gases.
-/datum/gas_mixture/proc/update_values()
- total_moles = 0
- for(var/g in gas)
- if(gas[g] <= 0)
- gas -= g
- else
- total_moles += gas[g]
-
-
-//Returns the pressure of the gas mix. Only accurate if there have been no gas modifications since update_values() has been called.
-/datum/gas_mixture/proc/return_pressure()
- if(volume)
- return total_moles * R_IDEAL_GAS_EQUATION * temperature / volume
- return 0
-
-
-//Removes moles from the gas mixture and returns a gas_mixture containing the removed air.
-/datum/gas_mixture/proc/remove(amount)
- amount = min(amount, total_moles * group_multiplier) //Can not take more air than the gas mixture has!
- if(amount <= 0)
- return null
-
- var/datum/gas_mixture/removed = new
-
- for(var/g in gas)
- removed.gas[g] = QUANTIZE((gas[g] / total_moles) * amount)
- gas[g] -= removed.gas[g] / group_multiplier
-
- removed.temperature = temperature
- update_values()
- removed.update_values()
-
- return removed
-
-
-//Removes a ratio of gas from the mixture and returns a gas_mixture containing the removed air.
-/datum/gas_mixture/proc/remove_ratio(ratio, out_group_multiplier = 1)
- if(ratio <= 0)
- return null
- out_group_multiplier = between(1, out_group_multiplier, group_multiplier)
-
- ratio = min(ratio, 1)
-
- var/datum/gas_mixture/removed = new
- removed.group_multiplier = out_group_multiplier
-
- for(var/g in gas)
- removed.gas[g] = (gas[g] * ratio * group_multiplier / out_group_multiplier)
- gas[g] = gas[g] * (1 - ratio)
-
- removed.temperature = temperature
- removed.volume = volume * group_multiplier / out_group_multiplier
- update_values()
- removed.update_values()
-
- return removed
-
-//Removes a volume of gas from the mixture and returns a gas_mixture containing the removed air with the given volume
-/datum/gas_mixture/proc/remove_volume(removed_volume)
- var/datum/gas_mixture/removed = remove_ratio(removed_volume/(volume*group_multiplier), 1)
- removed.volume = removed_volume
- return removed
-
-//Removes moles from the gas mixture, limited by a given flag. Returns a gax_mixture containing the removed air.
-/datum/gas_mixture/proc/remove_by_flag(flag, amount)
- if(!flag || amount <= 0)
- return
-
- var/sum = 0
- for(var/g in gas)
- if(gas_data.flags[g] & flag)
- sum += gas[g]
-
- var/datum/gas_mixture/removed = new
-
- for(var/g in gas)
- if(gas_data.flags[g] & flag)
- removed.gas[g] = QUANTIZE((gas[g] / sum) * amount)
- gas[g] -= removed.gas[g] / group_multiplier
-
- removed.temperature = temperature
- update_values()
- removed.update_values()
-
- return removed
-
-//Returns the amount of gas that has the given flag, in moles
-/datum/gas_mixture/proc/get_by_flag(flag)
- . = 0
- for(var/g in gas)
- if(gas_data.flags[g] & flag)
- . += gas[g]
-
-//Copies gas and temperature from another gas_mixture.
-/datum/gas_mixture/proc/copy_from(const/datum/gas_mixture/sample)
- gas = sample.gas.Copy()
- temperature = sample.temperature
-
- update_values()
-
- return 1
-
-
-//Checks if we are within acceptable range of another gas_mixture to suspend processing or merge.
-/datum/gas_mixture/proc/compare(const/datum/gas_mixture/sample, var/vacuum_exception = 0)
- if(!sample) return 0
-
- if(vacuum_exception)
- // Special case - If one of the two is zero pressure, the other must also be zero.
- // This prevents suspending processing when an air-filled room is next to a vacuum,
- // an edge case which is particually obviously wrong to players
- if(total_moles == 0 && sample.total_moles != 0 || sample.total_moles == 0 && total_moles != 0)
- return 0
-
- var/list/marked = list()
- for(var/g in gas)
- if((abs(gas[g] - sample.gas[g]) > MINIMUM_AIR_TO_SUSPEND) && \
- ((gas[g] < (1 - MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g]) || \
- (gas[g] > (1 + MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g])))
- return 0
- marked[g] = 1
-
- for(var/g in sample.gas)
- if(!marked[g])
- if((abs(gas[g] - sample.gas[g]) > MINIMUM_AIR_TO_SUSPEND) && \
- ((gas[g] < (1 - MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g]) || \
- (gas[g] > (1 + MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g])))
- return 0
-
- if(total_moles > MINIMUM_AIR_TO_SUSPEND)
- if((abs(temperature - sample.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) && \
- ((temperature < (1 - MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND)*sample.temperature) || \
- (temperature > (1 + MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND)*sample.temperature)))
- return 0
-
- return 1
-
-
-/datum/gas_mixture/proc/react()
- zburn(null, force_burn=0, no_check=0) //could probably just call zburn() here with no args but I like being explicit.
-
-
-//Rechecks the gas_mixture and adjusts the graphic list if needed.
-//Two lists can be passed by reference if you need know specifically which graphics were added and removed.
-/datum/gas_mixture/proc/check_tile_graphic(list/graphic_add = null, list/graphic_remove = null)
- var/list/cur_graphic = graphic // Cache for sanic speed
- for(var/g in gas_data.overlay_limit)
- if(cur_graphic && cur_graphic.Find(gas_data.tile_overlay[g]))
- //Overlay is already applied for this gas, check if it's still valid.
- if(gas[g] <= gas_data.overlay_limit[g])
- LAZYADD(graphic_remove, gas_data.tile_overlay[g])
- else
- //Overlay isn't applied for this gas, check if it's valid and needs to be added.
- if(gas[g] > gas_data.overlay_limit[g])
- LAZYADD(graphic_add, gas_data.tile_overlay[g])
-
- . = 0
- //Apply changes
- if(LAZYLEN(graphic_add))
- LAZYADD(graphic, graphic_add)
- . = 1
- if(LAZYLEN(graphic_remove))
- LAZYREMOVE(graphic, graphic_remove)
- . = 1
-
-
-//Simpler version of merge(), adjusts gas amounts directly and doesn't account for temperature or group_multiplier.
-/datum/gas_mixture/proc/add(datum/gas_mixture/right_side)
- for(var/g in right_side.gas)
- gas[g] += right_side.gas[g]
-
- update_values()
- return 1
-
-
-//Simpler version of remove(), adjusts gas amounts directly and doesn't account for group_multiplier.
-/datum/gas_mixture/proc/subtract(datum/gas_mixture/right_side)
- for(var/g in right_side.gas)
- gas[g] -= right_side.gas[g]
-
- update_values()
- return 1
-
-
-//Multiply all gas amounts by a factor.
-/datum/gas_mixture/proc/multiply(factor)
- for(var/g in gas)
- gas[g] *= factor
-
- update_values()
- return 1
-
-
-//Divide all gas amounts by a factor.
-/datum/gas_mixture/proc/divide(factor)
- for(var/g in gas)
- gas[g] /= factor
-
- update_values()
- return 1
-
-
-//Shares gas with another gas_mixture based on the amount of connecting tiles and a fixed lookup table.
-/datum/gas_mixture/proc/share_ratio(datum/gas_mixture/other, connecting_tiles, share_size = null, one_way = 0)
- var/static/list/sharing_lookup_table = list(0.30, 0.40, 0.48, 0.54, 0.60, 0.66)
- //Shares a specific ratio of gas between mixtures using simple weighted averages.
- var/ratio = sharing_lookup_table[6]
-
- var/size = max(1, group_multiplier)
- if(isnull(share_size)) share_size = max(1, other.group_multiplier)
-
- var/full_heat_capacity = heat_capacity()
- var/s_full_heat_capacity = other.heat_capacity()
-
- var/list/avg_gas = list()
-
- for(var/g in gas)
- avg_gas[g] += gas[g] * size
-
- for(var/g in other.gas)
- avg_gas[g] += other.gas[g] * share_size
-
- for(var/g in avg_gas)
- avg_gas[g] /= (size + share_size)
-
- var/temp_avg = 0
- if(full_heat_capacity + s_full_heat_capacity)
- temp_avg = (temperature * full_heat_capacity + other.temperature * s_full_heat_capacity) / (full_heat_capacity + s_full_heat_capacity)
-
- //WOOT WOOT TOUCH THIS AND YOU ARE A MISCHIEVIOUS LITTLE ELF
- if(sharing_lookup_table.len >= connecting_tiles) //6 or more interconnecting tiles will max at 42% of air moved per tick.
- ratio = sharing_lookup_table[connecting_tiles]
- //WOOT WOOT TOUCH THIS AND YOU ARE A MISCHIEVIOUS LITTLE ELF
-
- for(var/g in avg_gas)
- gas[g] = max(0, (gas[g] - avg_gas[g]) * (1 - ratio) + avg_gas[g])
- if(!one_way)
- other.gas[g] = max(0, (other.gas[g] - avg_gas[g]) * (1 - ratio) + avg_gas[g])
-
- temperature = max(0, (temperature - temp_avg) * (1-ratio) + temp_avg)
- if(!one_way)
- other.temperature = max(0, (other.temperature - temp_avg) * (1-ratio) + temp_avg)
-
- update_values()
- other.update_values()
-
- return compare(other)
-
-
-//A wrapper around share_ratio for spacing gas at the same rate as if it were going into a large airless room.
-/datum/gas_mixture/proc/share_space(datum/gas_mixture/unsim_air)
- return share_ratio(unsim_air, unsim_air.group_multiplier, max(1, max(group_multiplier + 3, 1) + unsim_air.group_multiplier), one_way = 1)
-
-
-//Equalizes a list of gas mixtures. Used for pipe networks.
-/proc/equalize_gases(list/datum/gas_mixture/gases)
- //Calculate totals from individual components
- var/total_volume = 0
- var/total_thermal_energy = 0
- var/total_heat_capacity = 0
-
- var/list/total_gas = list()
- for(var/datum/gas_mixture/gasmix in gases)
- total_volume += gasmix.volume
- var/temp_heatcap = gasmix.heat_capacity()
- total_thermal_energy += gasmix.temperature * temp_heatcap
- total_heat_capacity += temp_heatcap
- for(var/g in gasmix.gas)
- total_gas[g] += gasmix.gas[g]
-
- if(total_volume > 0)
- var/datum/gas_mixture/combined = new(total_volume)
- combined.gas = total_gas
-
- //Calculate temperature
- if(total_heat_capacity > 0)
- combined.temperature = total_thermal_energy / total_heat_capacity
- combined.update_values()
-
- //Allow for reactions
- combined.react()
-
- //Average out the gases
- for(var/g in combined.gas)
- combined.gas[g] /= total_volume
-
- //Update individual gas_mixtures
- for(var/datum/gas_mixture/gasmix in gases)
- gasmix.gas = combined.gas.Copy()
- gasmix.temperature = combined.temperature
- gasmix.multiply(gasmix.volume)
-
- return 1
-
-/datum/gas_mixture/proc/get_mass()
- for(var/g in gas)
- . += gas[g] * gas_data.molar_mass[g] * group_multiplier
+/datum/gas_mixture
+ //Associative list of gas moles.
+ //Gases with 0 moles are not tracked and are pruned by update_values()
+ var/list/gas
+ //Temperature in Kelvin of this gas mix.
+ var/temperature = 0
+
+ //Sum of all the gas moles in this mix. Updated by update_values()
+ var/total_moles = 0
+ //Volume of this mix.
+ var/volume = CELL_VOLUME
+ //Size of the group this gas_mixture is representing. 1 for singletons.
+ var/group_multiplier = 1
+
+ //List of active tile overlays for this gas_mixture. Updated by check_tile_graphic()
+ var/list/graphic
+
+/datum/gas_mixture/New(vol = CELL_VOLUME)
+ volume = vol
+ gas = list()
+
+//Takes a gas string and the amount of moles to adjust by. Calls update_values() if update isn't 0.
+/datum/gas_mixture/proc/adjust_gas(gasid, moles, update = 1)
+ if(moles == 0)
+ return
+
+ if (group_multiplier != 1)
+ gas[gasid] += moles/group_multiplier
+ else
+ gas[gasid] += moles
+
+ if(update)
+ update_values()
+
+
+//Same as adjust_gas(), but takes a temperature which is mixed in with the gas.
+/datum/gas_mixture/proc/adjust_gas_temp(gasid, moles, temp, update = 1)
+ if(moles == 0)
+ return
+
+ if(moles > 0 && abs(temperature - temp) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
+ var/self_heat_capacity = heat_capacity()
+ var/giver_heat_capacity = gas_data.specific_heat[gasid] * moles
+ var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
+ if(combined_heat_capacity != 0)
+ temperature = (temp * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity
+
+ if (group_multiplier != 1)
+ gas[gasid] += moles/group_multiplier
+ else
+ gas[gasid] += moles
+
+ if(update)
+ update_values()
+
+
+//Variadic version of adjust_gas(). Takes any number of gas and mole pairs and applies them.
+/datum/gas_mixture/proc/adjust_multi()
+ ASSERT(!(args.len % 2))
+
+ for(var/i = 1; i < args.len; i += 2)
+ adjust_gas(args[i], args[i+1], update = 0)
+
+ update_values()
+
+
+//Variadic version of adjust_gas_temp(). Takes any number of gas, mole and temperature associations and applies them.
+/datum/gas_mixture/proc/adjust_multi_temp()
+ ASSERT(!(args.len % 3))
+
+ for(var/i = 1; i < args.len; i += 3)
+ adjust_gas_temp(args[i], args[i + 1], args[i + 2], update = 0)
+
+ update_values()
+
+
+//Merges all the gas from another mixture into this one. Respects group_multipliers and adjusts temperature correctly.
+//Does not modify giver in any way.
+/datum/gas_mixture/proc/merge(const/datum/gas_mixture/giver)
+ if(!giver)
+ return
+
+ if(abs(temperature-giver.temperature)>MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
+ var/self_heat_capacity = heat_capacity()
+ var/giver_heat_capacity = giver.heat_capacity()
+ var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
+ if(combined_heat_capacity != 0)
+ temperature = (giver.temperature*giver_heat_capacity + temperature*self_heat_capacity)/combined_heat_capacity
+
+ if((group_multiplier != 1)||(giver.group_multiplier != 1))
+ for(var/g in giver.gas)
+ gas[g] += giver.gas[g] * giver.group_multiplier / group_multiplier
+ else
+ for(var/g in giver.gas)
+ gas[g] += giver.gas[g]
+
+ update_values()
+
+
+// Used to equalize the mixture between two zones before sleeping an edge.
+/datum/gas_mixture/proc/equalize(datum/gas_mixture/sharer)
+ var/our_heatcap = heat_capacity()
+ var/share_heatcap = sharer.heat_capacity()
+
+ // Special exception: there isn't enough air around to be worth processing this edge next tick, zap both to zero.
+ if(total_moles + sharer.total_moles <= MINIMUM_AIR_TO_SUSPEND)
+ gas.Cut()
+ sharer.gas.Cut()
+
+ for(var/g in gas|sharer.gas)
+ var/comb = gas[g] + sharer.gas[g]
+ comb /= volume + sharer.volume
+ gas[g] = comb * volume
+ sharer.gas[g] = comb * sharer.volume
+
+ if(our_heatcap + share_heatcap)
+ temperature = ((temperature * our_heatcap) + (sharer.temperature * share_heatcap)) / (our_heatcap + share_heatcap)
+ sharer.temperature = temperature
+
+ update_values()
+ sharer.update_values()
+
+ return 1
+
+
+//Returns the heat capacity of the gas mix based on the specific heat of the gases.
+/datum/gas_mixture/proc/heat_capacity()
+ . = 0
+ for(var/g in gas)
+ . += gas_data.specific_heat[g] * gas[g]
+ . *= group_multiplier
+
+
+//Adds or removes thermal energy. Returns the actual thermal energy change, as in the case of removing energy we can't go below TCMB.
+/datum/gas_mixture/proc/add_thermal_energy(var/thermal_energy)
+ if (total_moles == 0)
+ return 0
+
+ var/heat_capacity = heat_capacity()
+ if (thermal_energy < 0)
+ if (temperature < TCMB)
+ return 0
+ var/thermal_energy_limit = -(temperature - TCMB)*heat_capacity //ensure temperature does not go below TCMB
+ thermal_energy = max( thermal_energy, thermal_energy_limit ) //thermal_energy and thermal_energy_limit are negative here.
+ temperature += thermal_energy/heat_capacity
+ return thermal_energy
+
+//Returns the thermal energy change required to get to a new temperature
+/datum/gas_mixture/proc/get_thermal_energy_change(var/new_temperature)
+ return heat_capacity()*(max(new_temperature, 0) - temperature)
+
+
+//Technically vacuum doesn't have a specific entropy. Just use a really big number (infinity would be ideal) here so that it's easy to add gas to vacuum and hard to take gas out.
+#define SPECIFIC_ENTROPY_VACUUM 150000
+
+
+//Returns the ideal gas specific entropy of the whole mix. This is the entropy per mole of /mixed/ gas.
+/datum/gas_mixture/proc/specific_entropy()
+ if (!gas.len || total_moles == 0)
+ return SPECIFIC_ENTROPY_VACUUM
+
+ . = 0
+ for(var/g in gas)
+ . += gas[g] * specific_entropy_gas(g)
+ . /= total_moles
+
+
+/*
+ It's arguable whether this should even be called entropy anymore. It's more "based on" entropy than actually entropy now.
+
+ Returns the ideal gas specific entropy of a specific gas in the mix. This is the entropy due to that gas per mole of /that/ gas in the mixture, not the entropy due to that gas per mole of gas mixture.
+
+ For the purposes of SS13, the specific entropy is just a number that tells you how hard it is to move gas. You can replace this with whatever you want.
+ Just remember that returning a SMALL number == adding gas to this gas mix is HARD, taking gas away is EASY, and that returning a LARGE number means the opposite (so a vacuum should approach infinity).
+
+ So returning a constant/(partial pressure) would probably do what most players expect. Although the version I have implemented below is a bit more nuanced than simply 1/P in that it scales in a way
+ which is bit more realistic (natural log), and returns a fairly accurate entropy around room temperatures and pressures.
+*/
+/datum/gas_mixture/proc/specific_entropy_gas(var/gasid)
+ if (!(gasid in gas) || gas[gasid] == 0)
+ return SPECIFIC_ENTROPY_VACUUM //that gas isn't here
+
+ //group_multiplier gets divided out in volume/gas[gasid] - also, V/(m*T) = R/(partial pressure)
+ var/molar_mass = gas_data.molar_mass[gasid]
+ var/specific_heat = gas_data.specific_heat[gasid]
+ return R_IDEAL_GAS_EQUATION * ( log( (IDEAL_GAS_ENTROPY_CONSTANT*volume/(gas[gasid] * temperature)) * (molar_mass*specific_heat*temperature)**(2/3) + 1 ) + 15 )
+
+ //alternative, simpler equation
+ //var/partial_pressure = gas[gasid] * R_IDEAL_GAS_EQUATION * temperature / volume
+ //return R_IDEAL_GAS_EQUATION * ( log (1 + IDEAL_GAS_ENTROPY_CONSTANT/partial_pressure) + 20 )
+
+
+//Updates the total_moles count and trims any empty gases.
+/datum/gas_mixture/proc/update_values()
+ total_moles = 0
+ for(var/g in gas)
+ if(gas[g] <= 0)
+ gas -= g
+ else
+ total_moles += gas[g]
+
+
+//Returns the pressure of the gas mix. Only accurate if there have been no gas modifications since update_values() has been called.
+/datum/gas_mixture/proc/return_pressure()
+ if(volume)
+ return total_moles * R_IDEAL_GAS_EQUATION * temperature / volume
+ return 0
+
+
+//Removes moles from the gas mixture and returns a gas_mixture containing the removed air.
+/datum/gas_mixture/proc/remove(amount)
+ amount = min(amount, total_moles * group_multiplier) //Can not take more air than the gas mixture has!
+ if(amount <= 0)
+ return null
+
+ var/datum/gas_mixture/removed = new
+
+ for(var/g in gas)
+ removed.gas[g] = QUANTIZE((gas[g] / total_moles) * amount)
+ gas[g] -= removed.gas[g] / group_multiplier
+
+ removed.temperature = temperature
+ update_values()
+ removed.update_values()
+
+ return removed
+
+
+//Removes a ratio of gas from the mixture and returns a gas_mixture containing the removed air.
+/datum/gas_mixture/proc/remove_ratio(ratio, out_group_multiplier = 1)
+ if(ratio <= 0)
+ return null
+ out_group_multiplier = between(1, out_group_multiplier, group_multiplier)
+
+ ratio = min(ratio, 1)
+
+ var/datum/gas_mixture/removed = new
+ removed.group_multiplier = out_group_multiplier
+
+ for(var/g in gas)
+ removed.gas[g] = (gas[g] * ratio * group_multiplier / out_group_multiplier)
+ gas[g] = gas[g] * (1 - ratio)
+
+ removed.temperature = temperature
+ removed.volume = volume * group_multiplier / out_group_multiplier
+ update_values()
+ removed.update_values()
+
+ return removed
+
+//Removes a volume of gas from the mixture and returns a gas_mixture containing the removed air with the given volume
+/datum/gas_mixture/proc/remove_volume(removed_volume)
+ var/datum/gas_mixture/removed = remove_ratio(removed_volume/(volume*group_multiplier), 1)
+ removed.volume = removed_volume
+ return removed
+
+//Removes moles from the gas mixture, limited by a given flag. Returns a gax_mixture containing the removed air.
+/datum/gas_mixture/proc/remove_by_flag(flag, amount)
+ if(!flag || amount <= 0)
+ return
+
+ var/sum = 0
+ for(var/g in gas)
+ if(gas_data.flags[g] & flag)
+ sum += gas[g]
+
+ var/datum/gas_mixture/removed = new
+
+ for(var/g in gas)
+ if(gas_data.flags[g] & flag)
+ removed.gas[g] = QUANTIZE((gas[g] / sum) * amount)
+ gas[g] -= removed.gas[g] / group_multiplier
+
+ removed.temperature = temperature
+ update_values()
+ removed.update_values()
+
+ return removed
+
+//Returns the amount of gas that has the given flag, in moles
+/datum/gas_mixture/proc/get_by_flag(flag)
+ . = 0
+ for(var/g in gas)
+ if(gas_data.flags[g] & flag)
+ . += gas[g]
+
+//Copies gas and temperature from another gas_mixture.
+/datum/gas_mixture/proc/copy_from(const/datum/gas_mixture/sample)
+ gas = sample.gas.Copy()
+ temperature = sample.temperature
+
+ update_values()
+
+ return 1
+
+
+//Checks if we are within acceptable range of another gas_mixture to suspend processing or merge.
+/datum/gas_mixture/proc/compare(const/datum/gas_mixture/sample, var/vacuum_exception = 0)
+ if(!sample) return 0
+
+ if(vacuum_exception)
+ // Special case - If one of the two is zero pressure, the other must also be zero.
+ // This prevents suspending processing when an air-filled room is next to a vacuum,
+ // an edge case which is particually obviously wrong to players
+ if(total_moles == 0 && sample.total_moles != 0 || sample.total_moles == 0 && total_moles != 0)
+ return 0
+
+ var/list/marked = list()
+ for(var/g in gas)
+ if((abs(gas[g] - sample.gas[g]) > MINIMUM_AIR_TO_SUSPEND) && \
+ ((gas[g] < (1 - MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g]) || \
+ (gas[g] > (1 + MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g])))
+ return 0
+ marked[g] = 1
+
+ for(var/g in sample.gas)
+ if(!marked[g])
+ if((abs(gas[g] - sample.gas[g]) > MINIMUM_AIR_TO_SUSPEND) && \
+ ((gas[g] < (1 - MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g]) || \
+ (gas[g] > (1 + MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g])))
+ return 0
+
+ if(total_moles > MINIMUM_AIR_TO_SUSPEND)
+ if((abs(temperature - sample.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) && \
+ ((temperature < (1 - MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND)*sample.temperature) || \
+ (temperature > (1 + MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND)*sample.temperature)))
+ return 0
+
+ return 1
+
+
+/datum/gas_mixture/proc/react()
+ zburn(null, force_burn=0, no_check=0) //could probably just call zburn() here with no args but I like being explicit.
+
+
+//Rechecks the gas_mixture and adjusts the graphic list if needed.
+//Two lists can be passed by reference if you need know specifically which graphics were added and removed.
+/datum/gas_mixture/proc/check_tile_graphic(list/graphic_add = null, list/graphic_remove = null)
+ var/list/cur_graphic = graphic // Cache for sanic speed
+ for(var/g in gas_data.overlay_limit)
+ if(cur_graphic && cur_graphic.Find(gas_data.tile_overlay[g]))
+ //Overlay is already applied for this gas, check if it's still valid.
+ if(gas[g] <= gas_data.overlay_limit[g])
+ LAZYADD(graphic_remove, gas_data.tile_overlay[g])
+ else
+ //Overlay isn't applied for this gas, check if it's valid and needs to be added.
+ if(gas[g] > gas_data.overlay_limit[g])
+ LAZYADD(graphic_add, gas_data.tile_overlay[g])
+
+ . = 0
+ //Apply changes
+ if(LAZYLEN(graphic_add))
+ LAZYADD(graphic, graphic_add)
+ . = 1
+ if(LAZYLEN(graphic_remove))
+ LAZYREMOVE(graphic, graphic_remove)
+ . = 1
+
+
+//Simpler version of merge(), adjusts gas amounts directly and doesn't account for temperature or group_multiplier.
+/datum/gas_mixture/proc/add(datum/gas_mixture/right_side)
+ for(var/g in right_side.gas)
+ gas[g] += right_side.gas[g]
+
+ update_values()
+ return 1
+
+
+//Simpler version of remove(), adjusts gas amounts directly and doesn't account for group_multiplier.
+/datum/gas_mixture/proc/subtract(datum/gas_mixture/right_side)
+ for(var/g in right_side.gas)
+ gas[g] -= right_side.gas[g]
+
+ update_values()
+ return 1
+
+
+//Multiply all gas amounts by a factor.
+/datum/gas_mixture/proc/multiply(factor)
+ for(var/g in gas)
+ gas[g] *= factor
+
+ update_values()
+ return 1
+
+
+//Divide all gas amounts by a factor.
+/datum/gas_mixture/proc/divide(factor)
+ for(var/g in gas)
+ gas[g] /= factor
+
+ update_values()
+ return 1
+
+
+//Shares gas with another gas_mixture based on the amount of connecting tiles and a fixed lookup table.
+/datum/gas_mixture/proc/share_ratio(datum/gas_mixture/other, connecting_tiles, share_size = null, one_way = 0)
+ var/static/list/sharing_lookup_table = list(0.30, 0.40, 0.48, 0.54, 0.60, 0.66)
+ //Shares a specific ratio of gas between mixtures using simple weighted averages.
+ var/ratio = sharing_lookup_table[6]
+
+ var/size = max(1, group_multiplier)
+ if(isnull(share_size)) share_size = max(1, other.group_multiplier)
+
+ var/full_heat_capacity = heat_capacity()
+ var/s_full_heat_capacity = other.heat_capacity()
+
+ var/list/avg_gas = list()
+
+ for(var/g in gas)
+ avg_gas[g] += gas[g] * size
+
+ for(var/g in other.gas)
+ avg_gas[g] += other.gas[g] * share_size
+
+ for(var/g in avg_gas)
+ avg_gas[g] /= (size + share_size)
+
+ var/temp_avg = 0
+ if(full_heat_capacity + s_full_heat_capacity)
+ temp_avg = (temperature * full_heat_capacity + other.temperature * s_full_heat_capacity) / (full_heat_capacity + s_full_heat_capacity)
+
+ //WOOT WOOT TOUCH THIS AND YOU ARE A MISCHIEVIOUS LITTLE ELF
+ if(sharing_lookup_table.len >= connecting_tiles) //6 or more interconnecting tiles will max at 42% of air moved per tick.
+ ratio = sharing_lookup_table[connecting_tiles]
+ //WOOT WOOT TOUCH THIS AND YOU ARE A MISCHIEVIOUS LITTLE ELF
+
+ for(var/g in avg_gas)
+ gas[g] = max(0, (gas[g] - avg_gas[g]) * (1 - ratio) + avg_gas[g])
+ if(!one_way)
+ other.gas[g] = max(0, (other.gas[g] - avg_gas[g]) * (1 - ratio) + avg_gas[g])
+
+ temperature = max(0, (temperature - temp_avg) * (1-ratio) + temp_avg)
+ if(!one_way)
+ other.temperature = max(0, (other.temperature - temp_avg) * (1-ratio) + temp_avg)
+
+ update_values()
+ other.update_values()
+
+ return compare(other)
+
+
+//A wrapper around share_ratio for spacing gas at the same rate as if it were going into a large airless room.
+/datum/gas_mixture/proc/share_space(datum/gas_mixture/unsim_air)
+ return share_ratio(unsim_air, unsim_air.group_multiplier, max(1, max(group_multiplier + 3, 1) + unsim_air.group_multiplier), one_way = 1)
+
+
+//Equalizes a list of gas mixtures. Used for pipe networks.
+/proc/equalize_gases(list/datum/gas_mixture/gases)
+ //Calculate totals from individual components
+ var/total_volume = 0
+ var/total_thermal_energy = 0
+ var/total_heat_capacity = 0
+
+ var/list/total_gas = list()
+ for(var/datum/gas_mixture/gasmix in gases)
+ total_volume += gasmix.volume
+ var/temp_heatcap = gasmix.heat_capacity()
+ total_thermal_energy += gasmix.temperature * temp_heatcap
+ total_heat_capacity += temp_heatcap
+ for(var/g in gasmix.gas)
+ total_gas[g] += gasmix.gas[g]
+
+ if(total_volume > 0)
+ var/datum/gas_mixture/combined = new(total_volume)
+ combined.gas = total_gas
+
+ //Calculate temperature
+ if(total_heat_capacity > 0)
+ combined.temperature = total_thermal_energy / total_heat_capacity
+ combined.update_values()
+
+ //Allow for reactions
+ combined.react()
+
+ //Average out the gases
+ for(var/g in combined.gas)
+ combined.gas[g] /= total_volume
+
+ //Update individual gas_mixtures
+ for(var/datum/gas_mixture/gasmix in gases)
+ gasmix.gas = combined.gas.Copy()
+ gasmix.temperature = combined.temperature
+ gasmix.multiply(gasmix.volume)
+
+ return 1
+
+/datum/gas_mixture/proc/get_mass()
+ for(var/g in gas)
+ . += gas[g] * gas_data.molar_mass[g] * group_multiplier
diff --git a/icons/inventory/accessory/item_vr.dmi b/icons/inventory/accessory/item_vr.dmi
index de59e5e17d..5a85d7bb33 100644
Binary files a/icons/inventory/accessory/item_vr.dmi and b/icons/inventory/accessory/item_vr.dmi differ