"
-
- if (random)
- html += "\The [holder] appears to have tamper-resistant electronics installed.
" //maybe this could be more generic?
-
- return html
-
-/datum/wires/Topic(href, href_list)
- ..()
- if(in_range(holder, usr) && isliving(usr))
-
- var/mob/living/L = usr
- if(CanUse(L) && href_list["action"])
- holder.add_hiddenprint(L)
-
- var/list/items = L.get_all_held_items()
- var/success = FALSE
-
- if(href_list["cut"]) // Toggles the cut/mend status
- for(var/obj/item/I in items) // Paranoid about someone somehow grabbing a non-/obj/item, lets play it safe.
- if(I.is_wirecutter())
- var/colour = href_list["cut"]
- CutWireColour(colour)
- playsound(holder, I.usesound, 20, 1)
- success = TRUE
- break
- if(!success)
- to_chat(L, span("warning", "You need wirecutters!"))
-
- else if(href_list["pulse"])
- for(var/obj/item/I in items)
- if(I.is_multitool())
- var/colour = href_list["pulse"]
- PulseColour(colour)
- playsound(holder, 'sound/weapons/empty.ogg', 20, 1)
- success = TRUE
- break
- if(!success)
- to_chat(L, span("warning", "You need a multitool!"))
-
- else if(href_list["attach"])
- var/colour = href_list["attach"]
- // Detach
- if(IsAttached(colour))
- var/obj/item/O = Detach(colour)
- if(O)
- L.put_in_hands(O)
-
- // Attach
- else
- var/obj/item/device/assembly/signaler/S = L.is_holding_item_of_type(/obj/item/device/assembly/signaler)
- if(istype(S))
- L.drop_from_inventory(S)
- Attach(colour, S)
- else
- to_chat(L, span("warning", "You need a remote signaller!"))
-
-
-
-
- // Update Window
- Interact(usr)
-
- if(href_list["close"])
- usr << browse(null, "window=wires")
- usr.unset_machine(holder)
-
-//
-// Overridable Procs
-//
-
-// Called when wires cut/mended.
-/datum/wires/proc/UpdateCut(var/index, var/mended)
- return
-
-// Called when wire pulsed. Add code here.
-/datum/wires/proc/UpdatePulsed(var/index)
- return
-
-/datum/wires/proc/CanUse(var/mob/living/L)
- return 1
-
-// Example of use:
-/*
-
-var/const/BOLTED= 1
-var/const/SHOCKED = 2
-var/const/SAFETY = 4
-var/const/POWER = 8
-
-/datum/wires/door/UpdateCut(var/index, var/mended)
- var/obj/machinery/door/airlock/A = holder
- switch(index)
- if(BOLTED)
- if(!mended)
- A.bolt()
- if(SHOCKED)
- A.shock()
- if(SAFETY )
- A.safety()
-
-*/
-
-
-//
-// Helper Procs
-//
-
-/datum/wires/proc/PulseColour(var/colour)
- PulseIndex(GetIndex(colour))
-
-/datum/wires/proc/PulseIndex(var/index)
- if(IsIndexCut(index))
+ var/mob/user = usr
+ if(!interactable(user))
return
- UpdatePulsed(index)
-/datum/wires/proc/GetIndex(var/colour)
- if(wires[colour])
- var/index = wires[colour]
- return index
+ var/obj/item/I = user.get_active_hand()
+ var/color = lowertext(params["wire"])
+ holder.add_hiddenprint(user)
+
+ switch(action)
+ // Toggles the cut/mend status.
+ if("cut")
+ // if(!I.is_wirecutter() && !user.can_admin_interact())
+ if(!istype(I) || !I.is_wirecutter())
+ to_chat(user, "You need wirecutters!")
+ return
+
+ playsound(holder, I.usesound, 20, 1)
+ cut_color(color)
+ return TRUE
+
+ // Pulse a wire.
+ if("pulse")
+ // if(!I.is_multitool() && !user.can_admin_interact())
+ if(!istype(I) || !I.is_multitool())
+ to_chat(user, "You need a multitool!")
+ return
+
+ playsound(holder, 'sound/weapons/empty.ogg', 20, 1)
+ pulse_color(color)
+
+ // If they pulse the electrify wire, call interactable() and try to shock them.
+ if(get_wire(color) == WIRE_ELECTRIFY)
+ interactable(user)
+
+ return TRUE
+
+ // Attach a signaler to a wire.
+ if("attach")
+ if(is_attached(color))
+ var/obj/item/O = detach_assembly(color)
+ if(O)
+ user.put_in_hands(O)
+ return TRUE
+
+ if(!istype(I, /obj/item/device/assembly/signaler))
+ to_chat(user, "You need a remote signaller!")
+ return
+
+ if(user.unEquip(I))
+ attach_assembly(color, I)
+ return TRUE
+ else
+ to_chat(user, "[I] is stuck to your hand!")
+
+/**
+ * Proc called to determine if the user can see wire define information, such as "Contraband", "Door Bolts", etc.
+ *
+ * If the user is an admin, or has a multitool which reveals wire information in their active hand, the proc returns TRUE.
+ *
+ * Arguments:
+ * * user - the mob who is interacting with the wires.
+ */
+/datum/wires/proc/can_see_wire_info(mob/user)
+ // TODO: Reimplement this if we ever get Advanced Admin Interaction.
+ // if(user.can_admin_interact())
+ // return TRUE
+ var/obj/item/I = user.get_active_hand()
+ if(istype(I, /obj/item/device/multitool/alien))
+ return TRUE
+ return FALSE
+
+/**
+ * Base proc, intended to be overwritten. Put wire information you'll see at the botton of the TGUI window here, such as "The red light is blinking".
+ */
+/datum/wires/proc/get_status()
+ return list()
+
+/**
+ * Clears the `colors` list, and randomizes it to a new set of color-to-wire relations.
+ */
+/datum/wires/proc/shuffle_wires()
+ colors.Cut()
+ randomize()
+
+/**
+ * Repairs all cut wires.
+ */
+/datum/wires/proc/repair()
+ cut_wires.Cut()
+
+/**
+ * Adds in dud wires, which do nothing when cut/pulsed.
+ *
+ * Arguments:
+ * * duds - the amount of dud wires to generate.
+ */
+/datum/wires/proc/add_duds(duds)
+ while(duds)
+ var/dud = WIRE_DUD_PREFIX + "[--duds]"
+ if(dud in wires)
+ continue
+ wires += dud
+
+/**
+ * Determines if the passed in wire is a dud or not. Returns TRUE if the wire is a dud, FALSE otherwise.
+ *
+ * Arugments:
+ * * wire - a wire define, NOT a color. For example `WIRE_ELECTRIFY`.
+ */
+/datum/wires/proc/is_dud(wire)
+ return findtext(wire, WIRE_DUD_PREFIX, 1, length(WIRE_DUD_PREFIX) + 1)
+
+/**
+ * Returns TRUE if the wire that corresponds to the passed in color is a dud. FALSE otherwise.
+ *
+ * Arugments:
+ * * color - a wire color.
+ */
+/datum/wires/proc/is_dud_color(color)
+ return is_dud(get_wire(color))
+
+/**
+ * Gets the wire associated with the color passed in.
+ *
+ * Arugments:
+ * * color - a wire color.
+ */
+/datum/wires/proc/get_wire(color)
+ return colors[color]
+
+/**
+ * Determines if the passed in wire is cut or not. Returns TRUE if it's cut, FALSE otherwise.
+ *
+ * Arugments:
+ * * wire - a wire define, NOT a color. For example `WIRE_ELECTRIFY`.
+ */
+/datum/wires/proc/is_cut(wire)
+ return (wire in cut_wires)
+
+/**
+ * Determines if the wire associated with the passed in color, is cut or not. Returns TRUE if it's cut, FALSE otherwise.
+ *
+ * Arugments:
+ * * wire - a wire color.
+ */
+/datum/wires/proc/is_color_cut(color)
+ return is_cut(get_wire(color))
+
+/**
+ * Determines if all of the wires are cut. Returns TRUE they're all cut, FALSE otherwise.
+ */
+/datum/wires/proc/is_all_cut()
+ return (length(cut_wires) == length(wires))
+
+/**
+ * Cut or mend a wire. Calls `on_cut()`.
+ *
+ * Arugments:
+ * * wire - a wire define, NOT a color. For example `WIRE_ELECTRIFY`.
+ */
+/datum/wires/proc/cut(wire)
+ if(is_cut(wire))
+ cut_wires -= wire
+ on_cut(wire, mend = TRUE)
else
- CRASH("[colour] is not a key in wires.")
+ cut_wires += wire
+ on_cut(wire, mend = FALSE)
-//
-// Is Index/Colour Cut procs
-//
+/**
+ * Cut the wire which corresponds with the passed in color.
+ *
+ * Arugments:
+ * * color - a wire color.
+ */
+/datum/wires/proc/cut_color(color)
+ cut(get_wire(color))
-/datum/wires/proc/IsColourCut(var/colour)
- var/index = GetIndex(colour)
- return IsIndexCut(index)
+/**
+ * Cuts a random wire.
+ */
+/datum/wires/proc/cut_random()
+ cut(wires[rand(1, length(wires))])
-/datum/wires/proc/IsIndexCut(var/index)
- return (index & wires_status)
+/**
+ * Cuts all wires.
+ */
+/datum/wires/proc/cut_all()
+ for(var/wire in wires)
+ cut(wire)
-//
-// Signaller Procs
-//
+/**
+ * Proc called when any wire is cut.
+ *
+ * Base proc, intended to be overriden.
+ * Place an behavior you want to happen when certain wires are cut, into this proc.
+ *
+ * Arugments:
+ * * wire - a wire define, NOT color. For example 'WIRE_ELECTRIFY'.
+ * * mend - TRUE if we're mending the wire. FALSE if we're cutting.
+ */
+/datum/wires/proc/on_cut(wire, mend = FALSE)
+ return
-/datum/wires/proc/IsAttached(var/colour)
- if(signallers[colour])
- return 1
- return 0
+/**
+ * Pulses the given wire. Calls `on_pulse()`.
+ *
+ * Arugments:
+ * * wire - a wire define, NOT a color. For example `WIRE_ELECTRIFY`.
+ */
+/datum/wires/proc/pulse(wire)
+ if(is_cut(wire))
+ return
+ on_pulse(wire)
-/datum/wires/proc/GetAttached(var/colour)
- if(signallers[colour])
- return signallers[colour]
+/**
+ * Pulses the wire associated with the given color.
+ *
+ * Arugments:
+ * * wire - a wire color.
+ */
+/datum/wires/proc/pulse_color(color)
+ pulse(get_wire(color))
+
+/**
+ * Proc called when any wire is pulsed.
+ *
+ * Base proc, intended to be overriden.
+ * Place behavior you want to happen when certain wires are pulsed, into this proc.
+ *
+ * Arugments:
+ * * wire - a wire define, NOT color. For example 'WIRE_ELECTRIFY'.
+ */
+/datum/wires/proc/on_pulse(wire)
+ return
+
+/**
+ * Proc called when an attached signaler receives a signal.
+ *
+ * Searches through the `assemblies` list for the wire that the signaler is attached to. Pulses the wire when it's found.
+ *
+ * Arugments:
+ * * S - the attached signaler receiving the signal.
+ */
+/datum/wires/proc/pulse_assembly(obj/item/device/assembly/signaler/S)
+ for(var/color in assemblies)
+ if(S == assemblies[color])
+ pulse_color(color)
+ return TRUE
+
+/**
+ * Proc called when a mob tries to attach a signaler to a wire.
+ *
+ * Makes sure that `S` is actually a signaler and that something is not already attached to the wire.
+ * Adds the signaler to the `assemblies` list as a value, with the `color` as a the key.
+ *
+ * Arguments:
+ * * color - the wire color.
+ * * S - the signaler that a mob is trying to attach.
+ */
+/datum/wires/proc/attach_assembly(color, obj/item/device/assembly/signaler/S)
+ if(S && istype(S) && !is_attached(color))
+ assemblies[color] = S
+ S.forceMove(holder)
+ S.connected = src
+ return S
+
+/**
+ * Proc called when a mob tries to detach a signaler from a wire.
+ *
+ * First checks if there is a signaler on the wire. If so, removes the signaler, and clears it from `assemblies` list.
+ *
+ * Arguments:
+ * * color - the wire color.
+ */
+/datum/wires/proc/detach_assembly(color)
+ var/obj/item/device/assembly/signaler/S = get_attached(color)
+ if(S && istype(S))
+ assemblies -= color
+ S.connected = null
+ S.forceMove(holder.drop_location())
+ return S
+
+/**
+ * Gets the signaler attached to the given wire color, if there is one.
+ *
+ * Arguments:
+ * * color - the wire color.
+ */
+/datum/wires/proc/get_attached(color)
+ if(assemblies[color])
+ return assemblies[color]
return null
-/datum/wires/proc/Attach(var/colour, var/obj/item/device/assembly/signaler/S)
- if(colour && S)
- if(!IsAttached(colour))
- signallers[colour] = S
- S.loc = holder
- S.connected = src
- return S
-
-/datum/wires/proc/Detach(var/colour)
- if(colour)
- var/obj/item/device/assembly/signaler/S = GetAttached(colour)
- if(S)
- signallers -= colour
- S.connected = null
- S.loc = holder.loc
- return S
-
-
-/datum/wires/proc/Pulse(var/obj/item/device/assembly/signaler/S)
-
- for(var/colour in signallers)
- if(S == signallers[colour])
- PulseColour(colour)
- break
-
-
-//
-// Cut Wire Colour/Index procs
-//
-
-/datum/wires/proc/CutWireColour(var/colour)
- var/index = GetIndex(colour)
- CutWireIndex(index)
-
-/datum/wires/proc/CutWireIndex(var/index)
- if(IsIndexCut(index))
- wires_status &= ~index
- UpdateCut(index, 1)
- else
- wires_status |= index
- UpdateCut(index, 0)
-
-/datum/wires/proc/RandomCut()
- var/r = rand(1, wires.len)
- CutWireIndex(r)
-
-/datum/wires/proc/RandomCutAll(var/probability = 10)
- for(var/i = 1; i < MAX_FLAG && i < (1 << wire_count); i += i)
- if(prob(probability))
- CutWireIndex(i)
-
-/datum/wires/proc/CutAll()
- for(var/i = 1; i < MAX_FLAG && i < (1 << wire_count); i += i)
- CutWireIndex(i)
-
-/datum/wires/proc/IsAllCut()
- if(wires_status == (1 << wire_count) - 1)
- return 1
- return 0
-
-/datum/wires/proc/MendAll()
- for(var/i = 1; i < MAX_FLAG && i < (1 << wire_count); i += i)
- if(IsIndexCut(i))
- CutWireIndex(i)
-
-//
-//Shuffle and Mend
-//
-
-/datum/wires/proc/Shuffle()
- wires_status = 0
- GenerateWires()
+/**
+ * Checks if the given wire has a signaler on it.
+ *
+ * Arguments:
+ * * color - the wire color.
+ */
+/datum/wires/proc/is_attached(color)
+ if(assemblies[color])
+ return TRUE
diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm
index 1b71535bc4..0a851bed0b 100755
--- a/code/game/area/Space Station 13 areas.dm
+++ b/code/game/area/Space Station 13 areas.dm
@@ -32,6 +32,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
ambience = list('sound/ambience/ambispace.ogg','sound/music/title2.ogg','sound/music/space.ogg','sound/music/main.ogg','sound/music/traitor.ogg','sound/ambience/space/space_serithi.ogg','sound/music/freefallin.mid')
base_turf = /turf/space
ambience = AMBIENCE_SPACE
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/space/atmosalert()
return
@@ -69,7 +70,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/shuttle
requires_power = 0
- flags = RAD_SHIELDED
+ flags = RAD_SHIELDED | AREA_FLAG_IS_NOT_PERSISTENT
sound_env = SMALL_ENCLOSED
base_turf = /turf/space
forbid_events = TRUE
@@ -210,6 +211,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "\improper Alien base"
icon_state = "yellow"
requires_power = 0
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
// CENTCOM
@@ -218,6 +220,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "centcom"
requires_power = 0
dynamic_lighting = 0
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/centcom/control
name = "\improper CentCom Control"
@@ -295,6 +298,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
requires_power = 0
dynamic_lighting = 0
ambience = AMBIENCE_HIGHSEC
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/syndicate_mothership/control
name = "\improper Mercenary Control Room"
@@ -311,6 +315,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "asteroid"
requires_power = 0
sound_env = ASTEROID
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/asteroid/cave // -- TLE
name = "\improper Moon - Underground"
@@ -334,6 +339,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
requires_power = 0
dynamic_lighting = 0
sound_env = ARENA
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/tdome/tdome1
name = "\improper Thunderdome (Team 1)"
@@ -361,6 +367,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
flags = RAD_SHIELDED
base_turf = /turf/space
ambience = AMBIENCE_HIGHSEC
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/syndicate_station/start
name = "\improper Mercenary Forward Operating Base"
@@ -416,6 +423,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
requires_power = 0
dynamic_lighting = 0
ambience = AMBIENCE_OTHERWORLDLY
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/skipjack_station
name = "\improper Skipjack"
@@ -423,6 +431,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
requires_power = 0
base_turf = /turf/space
ambience = AMBIENCE_HIGHSEC
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/skipjack_station/start
name = "\improper Skipjack"
@@ -457,6 +466,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "\improper Prison Station"
icon_state = "brig"
ambience = AMBIENCE_HIGHSEC
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/prison/arrival_airlock
name = "\improper Prison Station Airlock"
@@ -642,6 +652,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/maintenance/disposal
name = "Waste Disposal"
icon_state = "disposal"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT //If trash items got this far, they can be safely deleted.
/area/maintenance/engineering
name = "Engineering Maintenance"
@@ -955,10 +966,12 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/crew_quarters/heads/hop
name = "\improper Command - HoP's Office"
icon_state = "head_quarters"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/crew_quarters/heads/hor
name = "\improper Research - RD's Office"
icon_state = "head_quarters"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/crew_quarters/heads/chief
name = "\improper Engineering - CE's Office"
@@ -971,6 +984,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/crew_quarters/heads/cmo
name = "\improper Medbay - CMO's Office"
icon_state = "head_quarters"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/crew_quarters/courtroom
name = "\improper Courtroom"
@@ -1297,6 +1311,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
dynamic_lighting = 0
sound_env = LARGE_ENCLOSED
forbid_events = TRUE
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/holodeck/alphadeck
name = "\improper Holodeck Alpha"
@@ -1658,10 +1673,12 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/medical/surgery
name = "\improper Operating Theatre 1"
icon_state = "surgery"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT //This WOULD become a filth pit
/area/medical/surgery2
name = "\improper Operating Theatre 2"
icon_state = "surgery"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/medical/surgeryobs
name = "\improper Operation Observation Room"
@@ -1698,6 +1715,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/medical/sleeper
name = "\improper Emergency Treatment Centre"
icon_state = "exam_room"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT //Trust me.
/area/medical/first_aid_station_starboard
name = "\improper Starboard First-Aid Station"
@@ -1912,6 +1930,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/quartermaster/delivery
name = "\improper Cargo - Delivery Office"
icon_state = "quart"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT //So trash doesn't pile up too hard.
/area/quartermaster/miningdock
name = "\improper Cargo Mining Dock"
@@ -1951,6 +1970,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/rnd/rdoffice
name = "\improper Research Director's Office"
icon_state = "head_quarters"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/rnd/supermatter
name = "\improper Supermatter Lab"
@@ -2082,6 +2102,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "\improper Derelict Station"
icon_state = "storage"
ambience = AMBIENCE_RUINS
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/derelict/hallway/primary
name = "\improper Derelict Primary Hallway"
@@ -2182,6 +2203,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/constructionsite
name = "\improper Construction Site"
icon_state = "storage"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/constructionsite/storage
name = "\improper Construction Site Storage Area"
@@ -2367,6 +2389,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/wreck
ambience = AMBIENCE_RUINS
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/wreck/ai
name = "\improper AI Chamber"
@@ -2443,6 +2466,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "\improper Strange Location"
icon_state = "away"
ambience = AMBIENCE_FOREBODING
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/awaymission/gateway
name = "\improper Gateway"
diff --git a/code/game/area/asteroid_areas.dm b/code/game/area/asteroid_areas.dm
index 3d15396046..e43b024ed1 100644
--- a/code/game/area/asteroid_areas.dm
+++ b/code/game/area/asteroid_areas.dm
@@ -3,6 +3,7 @@
/area/mine
icon_state = "mining"
sound_env = ASTEROID
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/mine/explored
name = "Mine"
@@ -27,21 +28,27 @@
/area/outpost/mining_north
name = "North Mining Outpost"
icon_state = "outpost_mine_north"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/outpost/mining_west
name = "West Mining Outpost"
icon_state = "outpost_mine_west"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/outpost/abandoned
name = "Abandoned Outpost"
icon_state = "dark"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
+
//lower level hallway
/area/outpost/hall
name = "Lower Level Hall"
icon_state = "dark"
+
// Main mining outpost
/area/outpost/mining_main
icon_state = "outpost_mine_main"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/outpost/mining_main/airlock
name = "Mining Outpost Airlock"
@@ -93,6 +100,7 @@
// Engineering Outpost
/area/outpost/engineering
icon_state = "outpost_engine"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/outpost/engineering/hallway
name = "Engineering Outpost Hallway"
@@ -166,6 +174,7 @@
// Research Outpost
/area/outpost/research
icon_state = "outpost_research"
+ flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/outpost/research/hallway
name = "Research Outpost Hallway"
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 01f5e687d8..09b226a076 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -1,6 +1,6 @@
/atom/movable
layer = OBJ_LAYER
- appearance_flags = TILE_BOUND|PIXEL_SCALE
+ appearance_flags = TILE_BOUND|PIXEL_SCALE|KEEP_TOGETHER
glide_size = 8
var/last_move = null //The direction the atom last moved
var/anchored = 0
@@ -24,7 +24,7 @@
var/datum/riding/riding_datum = null
var/does_spin = TRUE // Does the atom spin when thrown (of course it does :P)
var/movement_type = NONE
-
+
var/cloaked = FALSE //If we're cloaked or not
var/image/cloaked_selfimage //The image we use for our client to let them see where we are
@@ -107,7 +107,7 @@
glide_for(movetime)
loc = newloc
. = TRUE
-
+
// So objects can be informed of z-level changes
if (old_z != dest_z)
onTransitZ(old_z, dest_z)
@@ -133,7 +133,7 @@
var/atom/movable/thing = i
// We don't call parent so we are calling this for byond
thing.Crossed(src)
-
+
// We're a multi-tile object (multiple locs)
else if(. && newloc)
. = doMove(newloc)
@@ -282,7 +282,7 @@
glide_for(movetime)
last_move = isnull(direction) ? 0 : direction
loc = destination
-
+
// Unset this in case it was set in some other proc. We're no longer moving diagonally for sure.
moving_diagonally = 0
@@ -294,27 +294,27 @@
// If it's not the same area, Exited() it
if(old_area && old_area != destarea)
old_area.Exited(src, destination)
-
+
// Uncross everything where we left
for(var/i in oldloc)
var/atom/movable/AM = i
if(AM == src)
continue
AM.Uncrossed(src)
-
+
// Information about turf and z-levels for source and dest collected
var/turf/oldturf = get_turf(oldloc)
var/turf/destturf = get_turf(destination)
var/old_z = (oldturf ? oldturf.z : null)
var/dest_z = (destturf ? destturf.z : null)
-
+
// So objects can be informed of z-level changes
if (old_z != dest_z)
onTransitZ(old_z, dest_z)
-
+
// Destination atom Entered
destination.Entered(src, oldloc)
-
+
// Entered() the new area if it's not the same area
if(destarea && old_area != destarea)
destarea.Entered(src, oldloc)
@@ -366,7 +366,7 @@
glide_size = initial(glide_size)
else
glide_size = initial(glide_size)
-
+
/////////////////////////////////////////////////////////////////
//called when src is thrown into hit_atom
@@ -623,7 +623,7 @@
/atom/movable/proc/cloak_animation(var/length = 1 SECOND)
//Save these
var/initial_alpha = alpha
-
+
//Animate alpha fade
animate(src, alpha = 0, time = length)
diff --git a/code/game/gamemodes/changeling/changeling_powers.dm b/code/game/gamemodes/changeling/changeling_powers.dm
index 93d9ff4289..b03b986bca 100644
--- a/code/game/gamemodes/changeling/changeling_powers.dm
+++ b/code/game/gamemodes/changeling/changeling_powers.dm
@@ -182,20 +182,20 @@ turf/proc/AdjacentTurfsRangedSting()
add = 0
if(add && TurfBlockedNonWindow(t))
add = 0
- for(var/obj/O in t)
- if(!O.density)
+ for(var/obj/O in t)
+ if(O.density)
+ add = 0
+ break
+ if(istype(O, /obj/machinery/door))
+ //not sure why this doesn't fire on LinkBlocked()
+ add = 0
+ break
+ for(var/type in allowed)
+ if (istype(O, type))
add = 1
break
- if(istype(O, /obj/machinery/door))
- //not sure why this doesn't fire on LinkBlocked()
- add = 0
- break
- for(var/type in allowed)
- if (istype(O, type))
- add = 1
- break
- if(!add)
- break
+ if(!add)
+ break
if(add)
L.Add(t)
return L
diff --git a/code/game/gamemodes/cult/cultify/obj.dm b/code/game/gamemodes/cult/cultify/obj.dm
index d7c0cff5ec..3ac41e0047 100644
--- a/code/game/gamemodes/cult/cultify/obj.dm
+++ b/code/game/gamemodes/cult/cultify/obj.dm
@@ -50,7 +50,7 @@
src.invisibility = INVISIBILITY_MAXIMUM
density = 0
-/obj/machinery/cooker/cultify()
+/obj/machinery/appliance/cooker/cultify()
new /obj/structure/cult/talisman(loc)
qdel(src)
diff --git a/code/game/jobs/access_datum_vr.dm b/code/game/jobs/access_datum_vr.dm
index 506666fdfd..16038248d0 100644
--- a/code/game/jobs/access_datum_vr.dm
+++ b/code/game/jobs/access_datum_vr.dm
@@ -16,4 +16,15 @@ var/const/access_pilot = 67
id = access_talon
desc = "Talon"
access_type = ACCESS_TYPE_PRIVATE
-
\ No newline at end of file
+
+/var/const/access_xenobotany = 77
+/datum/access/xenobotany
+ id = access_xenobotany
+ desc = "Xenobotany Garden"
+ region = ACCESS_REGION_RESEARCH
+
+/var/const/access_entertainment = 72
+/datum/access/entertainment
+ id = access_entertainment
+ desc = "Entertainment Backstage"
+ region = ACCESS_REGION_GENERAL
\ No newline at end of file
diff --git a/code/game/jobs/job/civilian.dm b/code/game/jobs/job/civilian.dm
index b48ee239bf..f84e191b54 100644
--- a/code/game/jobs/job/civilian.dm
+++ b/code/game/jobs/job/civilian.dm
@@ -65,7 +65,7 @@
department_flag = CIVILIAN
faction = "Station"
total_positions = 2
- spawn_positions = 1
+ spawn_positions = 2
supervisors = "the Head of Personnel"
selection_color = "#515151"
access = list(access_hydroponics, access_bar, access_kitchen)
diff --git a/code/game/jobs/job/civilian_vr.dm b/code/game/jobs/job/civilian_vr.dm
index dae1ffbfce..4529c7c79a 100644
--- a/code/game/jobs/job/civilian_vr.dm
+++ b/code/game/jobs/job/civilian_vr.dm
@@ -57,3 +57,39 @@
/datum/job/chaplain
pto_type = PTO_CIVILIAN
+
+
+//////////////////////////////////
+// Entertainer
+//////////////////////////////////
+
+/datum/job/entertainer
+ title = "Entertainer"
+ flag = ENTERTAINER
+ departments = list(DEPARTMENT_CIVILIAN)
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 4
+ spawn_positions = 4
+ supervisors = "the Head of Personnel"
+ selection_color = "#515151"
+ access = list(access_entertainment)
+ minimal_access = list(access_entertainment)
+ pto_type = PTO_CIVILIAN
+
+ outfit_type = /decl/hierarchy/outfit/job/assistant
+ job_description = "An entertainer does just that, entertains! Put on plays, play music, sing songs, tell stories, or read your favorite fanfic."
+ alt_titles = list("Performer" = /datum/alt_title/performer, "Musician" = /datum/alt_title/musician, "Stagehand" = /datum/alt_title/stagehand)
+
+// Entertainer Alt Titles
+/datum/alt_title/performer
+ title = "Performer"
+ title_blurb = "A Performer is someone who performs! Acting, dancing, wrestling, etc!"
+
+/datum/alt_title/musician
+ title = "Musician"
+ title_blurb = "A Musician is someone who makes music! Singing, playing instruments, slam poetry, it's your call!"
+
+/datum/alt_title/stagehand
+ title = "Stagehand"
+ title_blurb = "A Stagehand typically performs everything the rest of the entertainers don't. Operate lights, shutters, windows, or narrate through your voicebox!"
\ No newline at end of file
diff --git a/code/game/jobs/job/science.dm b/code/game/jobs/job/science.dm
index 71159d5b81..1154d487bc 100644
--- a/code/game/jobs/job/science.dm
+++ b/code/game/jobs/job/science.dm
@@ -39,7 +39,7 @@
// Research Director Alt Titles
/datum/alt_title/research_supervisor
title = "Research Supervisor"
-
+
//////////////////////////////////
// Scientist
//////////////////////////////////
@@ -105,13 +105,15 @@
outfit_type = /decl/hierarchy/outfit/job/science/xenobiologist
job_description = "A Xenobiologist studies esoteric lifeforms, usually in the relative safety of their lab. They attempt to find ways to benefit \
from the byproducts of these lifeforms, and their main subject at present is the Giant Slime."
+/*VR edit start
alt_titles = list("Xenobotanist" = /datum/alt_title/xenobot)
-// Xenibiologist Alt Titles
+ Xenibiologist Alt Titles
/datum/alt_title/xenobot
title = "Xenobotanist"
title_blurb = "A Xenobotanist grows and cares for a variety of abnormal, custom made, and frequently dangerous plant life. When the products of these plants \
is both safe and beneficial to the station, they may choose to introduce it to the rest of the crew."
+VR edit end*/
//////////////////////////////////
// Roboticist
diff --git a/code/game/jobs/job/science_vr.dm b/code/game/jobs/job/science_vr.dm
index c1dd0d10b6..62f7b6eadf 100644
--- a/code/game/jobs/job/science_vr.dm
+++ b/code/game/jobs/job/science_vr.dm
@@ -31,4 +31,28 @@
/datum/job/roboticist
total_positions = 3
- pto_type = PTO_SCIENCE
\ No newline at end of file
+ pto_type = PTO_SCIENCE
+
+//////////////////////////////////
+// Xenobotanist
+//////////////////////////////////
+/datum/job/xenobotanist
+ title = "Xenobotanist"
+ flag = XENOBOTANIST
+ departments = list(DEPARTMENT_RESEARCH)
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 2
+ spawn_positions = 2
+ supervisors = "the Research Director"
+ selection_color = "#633D63"
+ economic_modifier = 7
+ access = list(access_robotics, access_tox, access_tox_storage, access_research, access_xenobotany, access_hydroponics)
+ minimal_access = list(access_research, access_xenobotany, access_hydroponics, access_tox_storage)
+ pto_type = PTO_SCIENCE
+
+ minimal_player_age = 14
+
+ outfit_type = /decl/hierarchy/outfit/job/science/xenobiologist
+ job_description = "A Xenobotanist grows and cares for a variety of abnormal, custom made, and frequently dangerous plant life. When the products of these plants \
+ are both safe and beneficial to the station, they may choose to introduce it to the rest of the crew."
\ No newline at end of file
diff --git a/code/game/jobs/job/special_vr.dm b/code/game/jobs/job/special_vr.dm
index 7a68691a56..c6f80c39a5 100644
--- a/code/game/jobs/job/special_vr.dm
+++ b/code/game/jobs/job/special_vr.dm
@@ -84,10 +84,10 @@
supervisors = "the spirit of laughter"
selection_color = "#515151"
economic_modifier = 1
- access = list()
- minimal_access = list()
+ access = list(access_entertainment)
+ minimal_access = list(access_entertainment)
job_description = "A Clown is there to entertain the crew and keep high morale using various harmless pranks and ridiculous jokes!"
- alt_titles = list("Clown" = /datum/alt_title/clown, "Comedian" = /datum/alt_title/comedian, "Jester" = /datum/alt_title/jester)
+ alt_titles = list("Clown" = /datum/alt_title/clown, "Jester" = /datum/alt_title/jester)
whitelist_only = 1
latejoin_only = 1
outfit_type = /decl/hierarchy/outfit/job/clown
@@ -96,9 +96,6 @@
/datum/alt_title/clown
title = "Clown"
-/datum/alt_title/comedian
- title = "Comedian"
-
/datum/alt_title/jester
title = "Jester"
@@ -119,10 +116,10 @@
supervisors = "the spirit of performance"
selection_color = "#515151"
economic_modifier = 1
- access = list()
- minimal_access = list()
+ access = list(access_entertainment)
+ minimal_access = list(access_entertainment)
job_description = "A Mime is there to entertain the crew and keep high morale using unbelievable performances and acting skills!"
- alt_titles = list("Mime" = /datum/alt_title/mime, "Performer" = /datum/alt_title/performer, "Interpretive Dancer" = /datum/alt_title/interpretive_dancer)
+ alt_titles = list("Mime" = /datum/alt_title/mime, "Interpretive Dancer" = /datum/alt_title/interpretive_dancer)
whitelist_only = 1
latejoin_only = 1
outfit_type = /decl/hierarchy/outfit/job/mime
@@ -131,9 +128,6 @@
/datum/alt_title/mime
title = "Mime"
-/datum/alt_title/performer
- title = "Performer"
-
/datum/alt_title/interpretive_dancer
title = "Interpretive Dancer"
diff --git a/code/game/jobs/jobs.dm b/code/game/jobs/jobs.dm
index 431d038870..13cf82273f 100644
--- a/code/game/jobs/jobs.dm
+++ b/code/game/jobs/jobs.dm
@@ -28,6 +28,7 @@ var/const/PSYCHIATRIST =(1<<7)
var/const/ROBOTICIST =(1<<8)
var/const/XENOBIOLOGIST =(1<<9)
var/const/PARAMEDIC =(1<<10)
+var/const/XENOBOTANIST =(1<<15) //VOREStation Add
var/const/CIVILIAN =(1<<2)
@@ -46,6 +47,7 @@ var/const/ASSISTANT =(1<<11)
var/const/BRIDGE =(1<<12)
var/const/CLOWN =(1<<13) //VOREStation Add
var/const/MIME =(1<<14) //VOREStation Add
+var/const/ENTERTAINER =(1<<15) //VOREStation Add
var/list/assistant_occupations = list(
)
diff --git a/code/game/machinery/air_alarm.dm b/code/game/machinery/air_alarm.dm
index bf0c8f78dd..dc17042f15 100644
--- a/code/game/machinery/air_alarm.dm
+++ b/code/game/machinery/air_alarm.dm
@@ -773,7 +773,7 @@
to_chat(user, "It does nothing.")
return
else
- if(allowed(usr) && !wires.IsIndexCut(AALARM_WIRE_IDSCAN))
+ if(allowed(usr) && !wires.is_cut(WIRE_IDSCAN))
locked = !locked
to_chat(user, "You [locked ? "lock" : "unlock"] the Air Alarm interface.")
else
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 454025d425..4bd203d0ba 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -42,24 +42,6 @@
var/list/camera_computers_using_this = list()
-/obj/machinery/camera/apply_visual(mob/living/carbon/human/M)
- if(!M.client)
- return
- M.overlay_fullscreen("fishbed",/obj/screen/fullscreen/fishbed)
- M.overlay_fullscreen("scanlines",/obj/screen/fullscreen/scanline)
- M.overlay_fullscreen("whitenoise",/obj/screen/fullscreen/noise)
- M.machine_visual = src
- return 1
-
-/obj/machinery/camera/remove_visual(mob/living/carbon/human/M)
- if(!M.client)
- return
- M.clear_fullscreen("fishbed",0)
- M.clear_fullscreen("scanlines")
- M.clear_fullscreen("whitenoise")
- M.machine_visual = null
- return 1
-
/obj/machinery/camera/New()
wires = new(src)
assembly = new(src)
@@ -232,12 +214,6 @@
else
to_chat(O, "[U] holds \a [itemname] up to one of your cameras ...")
O << browse(text("[][]", itemname, info), text("window=[]", itemname))
- for(var/mob/O in player_list)
- if (istype(O.machine, /obj/machinery/computer/security))
- var/obj/machinery/computer/security/S = O.machine
- if (S.current_camera == src)
- to_chat(O, "[U] holds \a [itemname] up to one of the cameras ...")
- O << browse(text("[][]", itemname, info), text("window=[]", itemname))
else if (istype(W, /obj/item/weapon/camera_bug))
if (!src.can_use())
@@ -298,7 +274,7 @@
//Used when someone breaks a camera
/obj/machinery/camera/proc/destroy()
stat |= BROKEN
- wires.RandomCutAll()
+ wires.cut_all()
triggerCameraAlarm()
update_icon()
@@ -333,7 +309,7 @@
camera_alarm.triggerAlarm(loc, src, duration)
/obj/machinery/camera/proc/cancelCameraAlarm()
- if(wires.IsIndexCut(CAMERA_WIRE_ALARM))
+ if(wires.is_cut(WIRE_CAM_ALARM))
return
alarm_on = 0
@@ -494,15 +470,12 @@
else
cameranet.updateVisibility(src, 0)
- invalidateCameraCache()
-
// Resets the camera's wires to fully operational state. Used by one of Malfunction abilities.
/obj/machinery/camera/proc/reset_wires()
if(!wires)
return
if (stat & BROKEN) // Fix the camera
stat &= ~BROKEN
- wires.CutAll()
- wires.MendAll()
+ wires.repair()
update_icon()
update_coverage()
diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm
index 30dc0db197..58279abec0 100644
--- a/code/game/machinery/camera/presets.dm
+++ b/code/game/machinery/camera/presets.dm
@@ -189,7 +189,6 @@ var/global/list/engineering_networks = list(
var/number = my_area.len
c_tag = "[A.name] #[number]"
- invalidateCameraCache()
/obj/machinery/camera/autoname/Destroy()
var/area/A = get_area(src)
diff --git a/code/game/machinery/computer/aifixer.dm b/code/game/machinery/computer/aifixer.dm
index daca64cc42..c3041344a0 100644
--- a/code/game/machinery/computer/aifixer.dm
+++ b/code/game/machinery/computer/aifixer.dm
@@ -1,142 +1,132 @@
/obj/machinery/computer/aifixer
name = "\improper AI system integrity restorer"
- desc = "Restores AI units to working condition, assuming you have one inside!"
- icon_keyboard = "rd_key"
- icon_screen = "ai-fixer"
- light_color = "#a97faa"
- circuit = /obj/item/weapon/circuitboard/aifixer
+ desc = "Used with intelliCards containing nonfunctional AIs to restore them to working order."
req_one_access = list(access_robotics, access_heads)
- var/mob/living/silicon/ai/occupant = null
- var/active = 0
+ circuit = /obj/item/weapon/circuitboard/aifixer
+ icon_keyboard = "tech_key"
+ icon_screen = "ai-fixer"
+ light_color = LIGHT_COLOR_PINK
+
+ active_power_usage = 1000
-/obj/machinery/computer/aifixer/New()
- ..()
- update_icon()
-
-/obj/machinery/computer/aifixer/proc/load_ai(var/mob/living/silicon/ai/transfer, var/obj/item/device/aicard/card, var/mob/user)
-
- if(!transfer)
- return
-
- // Transfer over the AI.
- to_chat(transfer, "You have been transferred into a stationary terminal. Sadly, there is no remote access from here.")
- to_chat(user, "Transfer successful: [transfer.name] placed within stationary terminal.")
-
- transfer.loc = src
- transfer.cancel_camera()
- transfer.control_disabled = 1
- occupant = transfer
-
- if(card)
- card.clear()
-
- update_icon()
-
-/obj/machinery/computer/aifixer/attackby(I as obj, user as mob)
+ /// Variable containing transferred AI
+ var/mob/living/silicon/ai/occupier
+ /// Variable dictating if we are in the process of restoring the occupier AI
+ var/restoring = FALSE
+/obj/machinery/computer/aifixer/attackby(obj/item/I, mob/living/user)
+ if(I.is_screwdriver())
+ if(occupier)
+ if(stat & (NOPOWER|BROKEN))
+ to_chat(user, "The screws on [name]'s screen won't budge.")
+ else
+ to_chat(user, "The screws on [name]'s screen won't budge and it emits a warning beep.")
+ return
if(istype(I, /obj/item/device/aicard))
-
if(stat & (NOPOWER|BROKEN))
- to_chat(user, "This terminal isn't functioning right now.")
+ to_chat(user, "This terminal isn't functioning right now.")
+ return
+ if(restoring)
+ to_chat(user, "Terminal is busy restoring [occupier] right now.")
return
var/obj/item/device/aicard/card = I
- var/mob/living/silicon/ai/comp_ai = locate() in src
- var/mob/living/silicon/ai/card_ai = locate() in card
+ if(occupier)
+ if(card.grab_ai(occupier, user))
+ occupier = null
+ else if(card.carded_ai)
+ var/mob/living/silicon/ai/new_occupant = card.carded_ai
+ to_chat(new_occupant, "You have been transferred into a stationary terminal. Sadly there is no remote access from here.")
+ to_chat(user, "Transfer Successful: [new_occupant] placed within stationary terminal.")
+ new_occupant.forceMove(src)
+ new_occupant.cancel_camera()
+ new_occupant.control_disabled = TRUE
+ occupier = new_occupant
+ card.clear()
+ update_icon()
+ else
+ to_chat(user, "There is no AI loaded onto this computer, and no AI loaded onto [I]. What exactly are you trying to do here?")
+ return ..()
- if(istype(comp_ai))
- if(active)
- to_chat(user, "ERROR: Reconstruction in progress.")
- return
- card.grab_ai(comp_ai, user)
- if(!(locate(/mob/living/silicon/ai) in src)) occupant = null
- else if(istype(card_ai))
- load_ai(card_ai,card,user)
- occupant = locate(/mob/living/silicon/ai) in src
-
- update_icon()
+/obj/machinery/computer/aifixer/attack_hand(mob/user)
+ if(stat & (NOPOWER|BROKEN))
return
- ..()
- return
+ tgui_interact(user)
-/obj/machinery/computer/aifixer/attack_ai(var/mob/user as mob)
- return attack_hand(user)
+/obj/machinery/computer/aifixer/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "AiRestorer", name)
+ ui.open()
-/obj/machinery/computer/aifixer/attack_hand(var/mob/user as mob)
+/obj/machinery/computer/aifixer/tgui_data(mob/user)
+ var/list/data = list()
+
+ data["ejectable"] = FALSE
+ data["AI_present"] = FALSE
+ data["error"] = null
+ if(!occupier)
+ data["error"] = "Please transfer an AI unit."
+ else
+ data["AI_present"] = TRUE
+ data["name"] = occupier.name
+ data["restoring"] = restoring
+ data["health"] = (occupier.health + 100) / 2
+ data["isDead"] = occupier.stat == DEAD
+ var/list/laws = list()
+ for(var/datum/ai_law/law in occupier.laws.all_laws())
+ laws += "[law.get_index()]: [law.law]"
+ data["laws"] = laws
+
+ return data
+
+/obj/machinery/computer/aifixer/tgui_act(action, params)
if(..())
return
+ if(!occupier)
+ restoring = FALSE
- user.set_machine(src)
- var/dat = "
AI System Integrity Restorer
"
+ switch(action)
+ if("PRG_beginReconstruction")
+ if(occupier?.health < 100)
+ to_chat(usr, "Reconstruction in progress. This will take several minutes.")
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 25, FALSE)
+ restoring = TRUE
+ var/mob/observer/dead/ghost = occupier.get_ghost()
+ if(ghost)
+ ghost.notify_revive("Your core files are being restored!", source = src)
+ . = TRUE
- if (src.occupant)
- var/laws
- dat += "Stored AI: [src.occupant.name] System integrity: [src.occupant.hardware_integrity()]% Backup Capacitor: [src.occupant.backup_capacitor()]% "
+/obj/machinery/computer/aifixer/proc/Fix()
+ use_power(active_power_usage)
+ occupier.adjustOxyLoss(-5, 0, FALSE)
+ occupier.adjustFireLoss(-5, 0, FALSE)
+ occupier.adjustBruteLoss(-5, 0)
+ if(occupier.health >= 0 && occupier.stat == DEAD)
+ occupier.revive()
- for (var/datum/ai_law/law in occupant.laws.all_laws())
- laws += "[law.get_index()]: [law.law] "
-
- dat += "Laws: [laws] "
-
- if (src.occupant.stat == 2)
- dat += "AI nonfunctional"
- else
- dat += "AI functional"
- if (!src.active)
- dat += {"
` by default that can be changed
+with the `as` property. Let's say you want to use a `` instead:
+
+```jsx
+
+
+
+```
+
+This works great when the changes can be isolated to a new DOM element.
+For instance, you can change the margin this way.
+
+However, sometimes you have to target the underlying DOM element.
+For instance, you want to change the text color of the button. The Button
+component defines its own color. CSS inheritance doesn't help.
+
+To workaround this problem, the Box children accept a render props function.
+This way, `Button` can pull out the `className` generated by the `Box`.
+
+```jsx
+
+ {props => }
+
+```
+
+**Box Units**
+
+`Box` units, like width, height and margins can be defined in two ways:
+
+- By plain numbers
+ - 1 unit equals `1rem` for width, height and positioning properties.
+ - 1 unit equals `0.5rem` for margins and paddings.
+- By strings with proper CSS units
+ - For example: `100px`, `2em`, `1rem`, `100%`, etc.
+
+If you need more precision, you can always use fractional numbers.
+
+Default font size (`1rem`) is equal to `12px`.
+
+**Props:**
+
+- `as: string` - The component used for the root node.
+- `color: string` - Applies an atomic `color-` class to the element.
+ - See `styles/atomic/color.scss`.
+- `width: number` - Box width.
+- `minWidth: number` - Box minimum width.
+- `maxWidth: number` - Box maximum width.
+- `height: number` - Box height.
+- `minHeight: number` - Box minimum height.
+- `maxHeight: number` - Box maximum height.
+- `fontSize: number` - Font size.
+- `fontFamily: string` - Font family.
+- `lineHeight: number` - Directly affects the height of text lines.
+Useful for adjusting button height.
+- `inline: boolean` - Forces the `Box` to appear as an `inline-block`,
+or in other words, makes the `Box` flow with the text instead of taking
+all available horizontal space.
+- `m: number` - Margin on all sides.
+- `mx: number` - Horizontal margin.
+- `my: number` - Vertical margin.
+- `mt: number` - Top margin.
+- `mb: number` - Bottom margin.
+- `ml: number` - Left margin.
+- `mr: number` - Right margin.
+- `p: number` - Padding on all sides.
+- `px: number` - Horizontal padding.
+- `py: number` - Vertical padding.
+- `pt: number` - Top padding.
+- `pb: number` - Bottom padding.
+- `pl: number` - Left padding.
+- `pr: number` - Right padding.
+- `opacity: number` - Opacity, from 0 to 1.
+- `bold: boolean` - Make text bold.
+- `italic: boolean` - Make text italic.
+- `nowrap: boolean` - Stops text from wrapping.
+- `textAlign: string` - Align text inside the box.
+ - `left` (default)
+ - `center`
+ - `right`
+- `position: string` - A direct mapping to `position` CSS property.
+ - `relative` - Relative positioning.
+ - `absolute` - Absolute positioning.
+ - `fixed` - Fixed positioning.
+- `color: string` - An alias to `textColor`.
+- `textColor: string` - Sets text color.
+ - `#ffffff` - Hex format
+ - `rgba(255, 255, 255, 1)` - RGB format
+ - `purple` - Applies an atomic `color-` class to the element.
+ See `styles/color-map.scss`.
+- `backgroundColor: string` - Sets background color.
+ - `#ffffff` - Hex format
+ - `rgba(255, 255, 255, 1)` - RGB format
+
+### `Button`
+
+Buttons allow users to take actions, and make choices, with a single click.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `fluid: boolean` - Fill all available horizontal space.
+- `icon: string` - Adds an icon to the button.
+- `color: string` - Button color, as defined in `variables.scss`.
+ - There is also a special color `transparent` - makes the button
+ transparent and slightly dim when inactive.
+- `disabled: boolean` - Disables and greys out the button.
+- `selected: boolean` - Activates the button (gives it a green color).
+- `tooltip: string` - A fancy, boxy tooltip, which appears when hovering
+over the button.
+- `tooltipPosition: string` - Position of the tooltip.
+ - `top` - Show tooltip above the button.
+ - `bottom` (default) - Show tooltip below the button.
+ - `left` - Show tooltip on the left of the button.
+ - `right` - Show tooltip on the right of the button.
+- `ellipsis: boolean` - If button width is constrained, button text will
+be truncated with an ellipsis. Be careful however, because this prop breaks
+the baseline alignment.
+- `title: string` - A native browser tooltip, which appears when hovering
+over the button.
+- `content/children: any` - Content to render inside the button.
+- `onClick: function` - Called when element is clicked.
+
+### `Button.Checkbox`
+
+A ghetto checkbox, made entirely using existing Button API.
+
+**Props:**
+
+- See inherited props: [Button](#button)
+- `checked: boolean` - Boolean value, which marks the checkbox as checked.
+
+### `Button.Confirm`
+
+A button with a an extra confirmation step, using native button component.
+
+**Props:**
+
+- See inherited props: [Button](#button)
+- `confirmMessage: string` - Text to display after first click; defaults to "Confirm?"
+- `confirmColor: string` - Color to display after first click; defaults to "bad"
+
+### `Button.Input`
+
+A button that turns into an input box after the first click. Turns back into a
+button after the user hits enter, defocuses, or hits escape. Enter and defocus
+commit, while escape cancels.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `fluid`: fill availible horizontal space
+- `onCommit: (e, value) => void`: function that is called after the user
+defocuses the input or presses enter
+- `currentValue: string`: default string to display when the input is shown
+- `defaultValue: string`: default value emitted if the user leaves the box
+blank when hitting enter or defocusing. If left undefined, will cancel the
+change on a blank defocus/enter
+
+### `ByondUi`
+
+Displays a BYOND UI element on top of the browser, and leverages browser's
+layout engine to position it just like any other HTML element. It is
+especially useful if you want to display a secondary game map in your
+interface.
+
+Example (button):
+
+```
+
+```
+
+Example (map):
+
+```
+
+```
+
+It supports a full set of `Box` properties for layout purposes.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `params: any` - An object with parameters, which are directly passed to
+the `winset` proc call. You can find a full reference of these parameters
+in [BYOND controls and parameters guide](https://secure.byond.com/docs/ref/skinparams.html).
+
+### `Collapsible`
+
+Displays contents when open, acts as a fluid button when closed. Click to
+toggle, closed by default.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `children: any` - What is collapsed when closed
+- `title: string` - Text to display on the button for collapsing
+- `color: string` - Color of the button; see [Button](#button)
+- `buttons: any` - Buttons or other content to render inline with the button
+
+### `ColorBox`
+
+Displays a 1-character wide colored square. Can be used as a status indicator,
+or for visually representing a color.
+
+If you want to set a background color on an element, use a plain
+[Box](#box) instead.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `color: string` - Color of the box.
+
+### `Dimmer`
+
+Dims surrounding area to emphasize content placed inside.
+
+Content is automatically centered inside the dimmer.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+
+### `Divider`
+
+Draws a horizontal or vertical line, dividing a section into groups.
+Works like the good old `` element, but it's fancier.
+
+**Props:**
+
+- `vertical: boolean` - Divide content vertically.
+- `hidden: boolean` - Divider can divide content without creating a dividing
+line.
+
+### `Dropdown`
+
+A simple dropdown box component. Lets the user select from a list of options
+and displays selected entry.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `options: string[]` - An array of strings which will be displayed in the
+dropdown when open
+- `selected: string` - Currently selected entry
+- `width: number` - Width of dropdown button and resulting menu
+- `over: boolean` - dropdown renders over instead of below
+- `color: string` - color of dropdown button
+- `onClick: (e) => void` - Called when dropdown button is clicked
+- `onSelected: (value) => void` - Called when a value is picked from the list, `value` is the value that was picked
+
+### `Flex`
+
+Quickly manage the layout, alignment, and sizing of grid columns, navigation,
+components, and more with a full suite of responsive flexbox utilities.
+
+If you are new to or unfamiliar with flexbox, we encourage you to read this
+[CSS-Tricks flexbox guide](https://css-tricks.com/snippets/css/a-guide-to-flexbox/).
+
+Consists of two elements: `` and ``. Both of them provide
+the most straight-forward mapping to flex CSS properties as possible.
+
+One of the most basic usage of flex, is to align certain elements
+to the left, and certain elements to the right:
+
+```jsx
+
+
+ Button description
+
+
+
+
+
+
+```
+
+Flex item with `grow` property serves as a "filler", to separate the other
+two flex items as far as possible from each other.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `spacing: number` - Spacing between flex items, in integer units
+(1 unit - 0.5em). Does not directly relate to a flex css property
+(adds a modifier class under the hood), and only integer numbers are
+supported.
+- `inline: boolean` - Makes flexbox container inline, with similar behavior
+to an `inline` property on a `Box`.
+- `direction: string` - This establishes the main-axis, thus defining the
+direction flex items are placed in the flex container.
+ - `row` (default) - left to right.
+ - `row-reverse` - right to left.
+ - `column` - top to bottom.
+ - `column-reverse` - bottom to top.
+- `wrap: string` - By default, flex items will all try to fit onto one line.
+You can change that and allow the items to wrap as needed with this property.
+ - `nowrap` (default) - all flex items will be on one line
+ - `wrap` - flex items will wrap onto multiple lines, from top to bottom.
+ - `wrap-reverse` - flex items will wrap onto multiple lines from bottom to top.
+- `align: string` - Default alignment of all children.
+ - `stretch` (default) - stretch to fill the container.
+ - `start` - items are placed at the start of the cross axis.
+ - `end` - items are placed at the end of the cross axis.
+ - `center` - items are centered on the cross axis.
+ - `baseline` - items are aligned such as their baselines align.
+- `justify: string` - This defines the alignment along the main axis.
+It helps distribute extra free space leftover when either all the flex
+items on a line are inflexible, or are flexible but have reached their
+maximum size. It also exerts some control over the alignment of items
+when they overflow the line.
+ - `flex-start` (default) - items are packed toward the start of the
+ flex-direction.
+ - `flex-end` - items are packed toward the end of the flex-direction.
+ - `space-between` - items are evenly distributed in the line; first item is
+ on the start line, last item on the end line
+ - `space-around` - items are evenly distributed in the line with equal space
+ around them. Note that visually the spaces aren't equal, since all the items
+ have equal space on both sides. The first item will have one unit of space
+ against the container edge, but two units of space between the next item
+ because that next item has its own spacing that applies.
+ - `space-evenly` - items are distributed so that the spacing between any two
+ items (and the space to the edges) is equal.
+ - TBD (not all properties are supported in IE11).
+
+### `Flex.Item`
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `order: number` - By default, flex items are laid out in the source order.
+However, the order property controls the order in which they appear in the
+flex container.
+- `grow: number` - This defines the ability for a flex item to grow if
+necessary. It accepts a unitless value that serves as a proportion. It
+dictates what amount of the available space inside the flex container the
+item should take up. This number is unit-less and is relative to other
+siblings.
+- `shrink: number` - This defines the ability for a flex item to shrink
+if necessary. Inverse of `grow`.
+- `basis: string` - This defines the default size of an element before any
+flex-related calculations are done. Has to be a length (e.g. `20%`, `5rem`),
+an `auto` or `content` keyword.
+ - **Important:** IE11 flex is buggy, and auto width/height calculations
+ can sometimes end up in a circular dependency. This usually happens, when
+ working with tables inside flex (they have wacky internal widths and such).
+ Setting basis to `0` breaks the loop and fixes all of the problems.
+- `align: string` - This allows the default alignment (or the one specified by
+align-items) to be overridden for individual flex items. See: [Flex](#flex).
+
+### `Grid`
+
+> **Deprecated:** This component is no longer recommended due to the variety
+> of bugs that come with table-based layouts.
+> We recommend using [Flex](#flex) instead.
+
+Helps you to divide horizontal space into two or more equal sections.
+It is essentially a single-row `Table`, but with some extra features.
+
+Example:
+
+```jsx
+
+
+
+ Hello world!
+
+
+
+
+ Hello world!
+
+
+
+```
+
+**Props:**
+
+- See inherited props: [Table](#table)
+
+### `Grid.Column`
+
+**Props:**
+
+- See inherited props: [Table.Cell](#tablecell)
+- `size: number` (default: 1) - Size of the column relative to other columns.
+
+### `Icon`
+
+Renders one of the FontAwesome icons of your choice.
+
+```jsx
+
+```
+
+To smoothen the transition from v4 to v5, we have added a v4 semantic to
+transform names with `-o` suffixes to FA Regular icons. For example:
+- `square` will get transformed to `fas square`
+- `square-o` will get transformed to `far square`
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `name: string` - Icon name.
+- `size: number` - Icon size. `1` is normal size, `2` is two times bigger.
+Fractional numbers are supported.
+- `rotation: number` - Icon rotation, in degrees.
+- `spin: boolean` - Whether an icon should be spinning. Good for load
+indicators.
+
+### `Input`
+
+A basic text input, which allow users to enter text into a UI.
+
+> Input does not support custom font size and height due to the way
+> it's implemented in CSS. Eventually, this needs to be fixed.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `value: string` - Value of an input.
+- `placeholder: string` - Text placed into Input box when it's empty,
+otherwise nothing. Clears automatically when focused.
+- `fluid: boolean` - Fill all available horizontal space.
+- `selfClear: boolean` - Clear after hitting enter, as well as remain focused
+when this happens. Useful for things like chat inputs.
+- `onChange: (e, value) => void` - An event, which fires when you commit
+the text by either unfocusing the input box, or by pressing the Enter key.
+- `onInput: (e, value) => void` - An event, which fires on every keypress.
+
+### `Knob`
+
+A radial control, which allows dialing in precise values by dragging it
+up and down.
+
+Single click opens an input box to manually type in a number.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `animated: boolean` - Animates the value if it was changed externally.
+- `bipolar: boolean` - Knob can be bipolar or unipolar.
+- `size: number` - Relative size of the knob. `1` is normal size, `2` is two
+times bigger. Fractional numbers are supported.
+- `color: string` - Color of the outer ring around the knob.
+- `value: number` - Value itself, controls the position of the cursor.
+- `unit: string` - Unit to display to the right of value.
+- `minValue: number` - Lowest possible value.
+- `maxValue: number` - Highest possible value.
+- `fillValue: number` - If set, this value will be used to set the fill
+percentage of the outer ring independently of the main value.
+- `ranges: { color: [from, to] }` - Applies a `color` to the outer ring around
+the knob based on whether the value lands in the range between `from` and `to`.
+See an example of this prop in [ProgressBar](#progressbar).
+- `step: number` (default: 1) - Adjust value by this amount when
+dragging the input.
+- `stepPixelSize: number` (default: 1) - Screen distance mouse needs
+to travel to adjust value by one `step`.
+- `format: value => value` - Format value using this function before
+displaying it.
+- `suppressFlicker: number` - A number in milliseconds, for which the input
+will hold off from updating while events propagate through the backend.
+Default is about 250ms, increase it if you still see flickering.
+- `onChange: (e, value) => void` - An event, which fires when you release
+the input, or successfully enter a number.
+- `onDrag: (e, value) => void` - An event, which fires about every 500ms
+when you drag the input up and down, on release and on manual editing.
+
+### `LabeledControls`
+
+LabeledControls is a horizontal grid, that is designed to hold various
+controls, like [Knobs](#knob) or small [Buttons](#button). Every item in
+this grid is labeled at the bottom.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `children: LabeledControls.Item` - Items to render.
+
+### `LabeledControls.Item`
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `label: string` - Item label.
+
+### `LabeledList`
+
+LabeledList is a continuous, vertical list of text and other content, where
+every item is labeled. It works just like a two column table, where first
+column is labels, and second column is content.
+
+```jsx
+
+
+ Content
+
+
+```
+
+If you want to have a button on the right side of an item (for example,
+to perform some sort of action), there is a way to do that:
+
+```jsx
+
+
+ )}>
+ Content
+
+
+```
+
+**Props:**
+
+- `children: LabeledList.Item` - Items to render.
+
+### `LabeledList.Item`
+
+**Props:**
+
+- `label: string` - Item label.
+- `color: string` - Sets the color of the text.
+- `buttons: any` - Buttons to render aside the content.
+- `content/children: any` - Content of this labeled item.
+
+### `LabeledList.Divider`
+
+Adds some empty space between LabeledList items.
+
+Example:
+
+```jsx
+
+
+ Content
+
+
+
+```
+
+**Props:**
+
+- `size: number` - Size of the divider.
+
+### `Modal`
+
+A modal window. Uses a [Dimmer](#dimmer) under the hood, and dynamically
+adjusts its own size to fit the content you're trying to display.
+
+Must be a direct child of a layout component (e.g. [Window](#window)).
+
+**Props:**
+
+- See inherited props: [Box](#box)
+
+### `NoticeBox`
+
+A notice box, which warns you about something very important.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `info: boolean` - Info box
+- `success: boolean` - Success box
+- `warning: bolean` - Warning box
+- `danger: boolean` - Danger box
+
+### `NumberInput`
+
+A fancy, interactive number input, which you can either drag up and down
+to fine tune the value, or single click it to manually type a number.
+
+**Props:**
+
+- `animated: boolean` - Animates the value if it was changed externally.
+- `fluid: boolean` - Fill all available horizontal space.
+- `value: number` - Value itself.
+- `unit: string` - Unit to display to the right of value.
+- `minValue: number` - Lowest possible value.
+- `maxValue: number` - Highest possible value.
+- `step: number` (default: 1) - Adjust value by this amount when
+dragging the input.
+- `stepPixelSize: number` (default: 1) - Screen distance mouse needs
+to travel to adjust value by one `step`.
+- `width: string|number` - Width of the element, in `Box` units or pixels.
+- `height: string|numer` - Height of the element, in `Box` units or pixels.
+- `lineHeight: string|number` - lineHeight of the element, in `Box` units or pixels.
+- `fontSize: string|number` - fontSize of the element, in `Box` units or pixels.
+- `format: value => value` - Format value using this function before
+displaying it.
+- `suppressFlicker: number` - A number in milliseconds, for which the input
+will hold off from updating while events propagate through the backend.
+Default is about 250ms, increase it if you still see flickering.
+- `onChange: (e, value) => void` - An event, which fires when you release
+the input, or successfully enter a number.
+- `onDrag: (e, value) => void` - An event, which fires about every 500ms
+when you drag the input up and down, on release and on manual editing.
+
+### `ProgressBar`
+
+Progress indicators inform users about the status of ongoing processes.
+
+```jsx
+
+```
+
+Usage of `ranges` prop:
+
+```jsx
+
+```
+
+**Props:**
+
+- `value: number` - Current progress as a floating point number between
+`minValue` (default: 0) and `maxValue` (default: 1). Determines the
+percentage and how filled the bar is.
+- `minValue: number` - Lowest possible value.
+- `maxValue: number` - Highest possible value.
+- `ranges: { color: [from, to] }` - Applies a `color` to the progress bar
+based on whether the value lands in the range between `from` and `to`.
+- `color: string` - Color of the progress bar.
+- `content/children: any` - Content to render inside the progress bar.
+
+### `Section`
+
+Section is a surface that displays content and actions on a single topic.
+
+They should be easy to scan for relevant and actionable information.
+Elements, like text and images, should be placed in them in a way that
+clearly indicates hierarchy.
+
+Section can also be titled to clearly define its purpose.
+
+```jsx
+
+ Here you can order supply crates.
+
+```
+
+If you want to have a button on the right side of an section title
+(for example, to perform some sort of action), there is a way to do that:
+
+```jsx
+
+ )}>
+ Here you can order supply crates.
+
+```
+
+- See inherited props: [Box](#box)
+- `title: string` - Title of the section.
+- `level: number` - Section level in hierarchy. Default is 1, higher number
+means deeper level of nesting. Must be an integer number.
+- `buttons: any` - Buttons to render aside the section title.
+- `content/children: any` - Content of this section.
+
+### `Slider`
+
+A horizontal, [ProgressBar](#progressbar)-like control, which allows dialing
+in precise values by dragging it left and right.
+
+Single click opens an input box to manually type in a number.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `animated: boolean` - Animates the value if it was changed externally.
+- `color: string` - Color of the slider.
+- `value: number` - Value itself, controls the position of the cursor.
+- `unit: string` - Unit to display to the right of value.
+- `minValue: number` - Lowest possible value.
+- `maxValue: number` - Highest possible value.
+- `fillValue: number` - If set, this value will be used to set the fill
+percentage of the progress bar filler independently of the main value.
+- `ranges: { color: [from, to] }` - Applies a `color` to the slider
+based on whether the value lands in the range between `from` and `to`.
+See an example of this prop in [ProgressBar](#progressbar).
+- `step: number` (default: 1) - Adjust value by this amount when
+dragging the input.
+- `stepPixelSize: number` (default: 1) - Screen distance mouse needs
+to travel to adjust value by one `step`.
+- `format: value => value` - Format value using this function before
+displaying it.
+- `suppressFlicker: number` - A number in milliseconds, for which the input
+will hold off from updating while events propagate through the backend.
+Default is about 250ms, increase it if you still see flickering.
+- `onChange: (e, value) => void` - An event, which fires when you release
+the input, or successfully enter a number.
+- `onDrag: (e, value) => void` - An event, which fires about every 500ms
+when you drag the input up and down, on release and on manual editing.
+
+### `Table`
+
+A straight forward mapping to a standard html table, which is slightly
+simplified (does not need a `` tag) and with sane default styles
+(e.g. table width is 100% by default).
+
+Example:
+
+```jsx
+
+
+
+ Hello world!
+
+
+ Label
+
+
+
+```
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `collapsing: boolean` - Collapses table to the smallest possible size.
+
+### `Table.Row`
+
+A straight forward mapping to `
` element.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+
+### `Table.Cell`
+
+A straight forward mapping to `
` element.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `collapsing: boolean` - Collapses table cell to the smallest possible size,
+and stops any text inside from wrapping.
+
+### `Tabs`
+
+Tabs make it easy to explore and switch between different views.
+
+Here is an example of how you would construct a simple tabbed view:
+
+```jsx
+
+ setTabIndex(1)}>
+ Tab one
+
+ setTabIndex(2)}>
+ Tab two
+
+
+
+ Tab selected: {tabIndex}
+
+```
+
+Notice that tabs do not contain state. It is your job to track the selected
+tab, handle clicks and place tab content where you need it. In return, you get
+a lot of flexibility in regards to how you can layout your tabs.
+
+Tabs also support a vertical configuration. This is usually paired with a
+[Flex](#flex) component to render tab content to the right.
+
+```jsx
+
+
+
+ ...
+
+
+
+ Tab content.
+
+
+```
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `vertical: boolean` - Use a vertical configuration, where tabs will be
+stacked vertically.
+- `children: Tab[]` - This component only accepts tabs as its children.
+
+### `Tabs.Tab`
+
+An individual tab element. Tabs function like buttons, so they inherit
+a lot of `Button` props.
+
+**Props:**
+
+- See inherited props: [Button](#button)
+- `altSelection` - Whether the tab buttons select via standard select (color
+change) or by adding a white indicator to the selected tab.
+Intended for usage on interfaces where tab color has relevance.
+- `icon: string` - Tab icon.
+- `children: any` - Tab text.
+- `onClick: function` - Called when element is clicked.
+
+### `Tooltip`
+
+A boxy tooltip from tgui 1. It is very hacky in its current state, and
+requires setting `position: relative` on the container.
+
+Please note, that [Button](#button) component has a `tooltip` prop, and
+it is recommended to use that prop instead.
+
+Usage:
+
+```jsx
+
+ Sample text.
+
+
+```
+
+**Props:**
+
+- `position: string` - Tooltip position.
+- `content/children: string` - Content of the tooltip. Must be a plain string.
+Fragments or other elements are **not** supported.
+
+## `tgui/layouts`
+
+### `Window`
+
+A root-level component, which draws the window chrome, titlebar, resize
+handlers, and controls the UI theme. All tgui interfaces must implement
+it in one way or another.
+
+Example:
+
+```jsx
+
+
+ Hello, world!
+
+
+```
+
+**Props:**
+
+- `className: string` - Applies a CSS class to the element.
+- `theme: string` - A name of the theme.
+ - For a list of themes, see `packages/tgui/styles/themes`.
+- `title: string` - Window title.
+- `resizable: boolean` - Controls resizability of the window.
+- `children: any` - Child elements, which are rendered directly inside the
+window. If you use a [Dimmer](#dimmer) or [Modal](#modal) in your UI,
+they should be put as direct childs of a Window, otherwise you should be
+putting your content into [Window.Content](#windowcontent).
+
+### `Window.Content`
+
+Canonical window content, which is usually the main target of window focus.
+Can be scrollable.
+
+**Props:**
+
+- `className: string` - Applies a CSS class to the element.
+- `scrollable: boolean` - Shows or hides the scrollbar.
+- `children: any` - Main content of your window.
diff --git a/tgui/docs/converting-old-nano-interfaces.md b/tgui/docs/converting-old-nano-interfaces.md
new file mode 100644
index 0000000000..83d44220b1
--- /dev/null
+++ b/tgui/docs/converting-old-nano-interfaces.md
@@ -0,0 +1,321 @@
+# Converting old NanoUI interfaces to TGUI
+
+This guide is going to assume you already know roughly how tgui-next works, how to make new uis, etc. It's mostly aimed at helping translate concepts between nano and tgui-next, and clarify some confusing parts of the transition.
+
+## Backend
+
+Backend in almost every case does not require any changes. In particularly heavy ui cases, something to be aware of is the new `tgui_static_data()` proc. This proc allows you to split some data sent to the interface off into data that will only be sent on ui initialize and when manually updated by elsewhere in the code. Useful for things like cargo where you have a very large set of mostly identical code.
+
+Keep in mind that for uis where *all* data doesn't need to be live updating, you can just toggle off autoupdate for the ui instead of messing with static data.
+
+## Frontend
+
+The very first thing to note is the name of the `tmpl` file containing the old interface. Whatever the name is (minus the extension) is going to be what the route key is going to be.
+
+One thing I like to do before starting work on a conversion is screenshot what the old interface looks like so I have something to reference to make sure that the styling can line up as well.
+
+## General syntax changes
+
+Ractive has a fairly different templating syntax from React.
+
+### `data`
+
+You likely already know that React data inserts look like this
+
+```jsx
+{data.example_data}
+```
+
+Ractive looks very similar, the only real difference is that React uses one paranthesis instead of two.
+
+```tmpl
+{{data.example_data}}
+```
+
+However, you may occasionally come across data inserts that instead of referencing the `data` var or things contained within it instead reference `adata`. `adata` was short for animated data, and was used for smooth number animations in interfaces. instead of having a seperate data structure for this. tgui-next instead uses a component, which is `AnimatedNumber`.
+
+`AnimatedNumber` is used like this
+
+```jsx
+
+```
+
+Make sure you don't forget to import it.
+
+### Conditionals
+
+Template conditionals look very different from React conditionals.
+
+A template `if` (only render if result of expression is true) looks like this
+
+```tmpl
+{{#if data.condition}}
+ Example Render
+{{/if}}
+```
+
+The equivalent React would be
+
+```jsx
+{!!data.condition && (
+ Example Render
+)}
+```
+
+This might look a bit intimidating compared to the reactive part but it's not as complicated as it seems:
+
+1. A new jsx context is opened with `{}`
+2. jsx contexts like this always render whatever the return value is, so we can use `&&` to return a value we want. `&&` returns the last true value (or not "falsey" because this is js).
+3. jsx tags are never "falsey", so a conditioned paired with a jsx tag will mean the condition being true will continue on and return the tag. `()` is just used to contain the tag
+4. The `!!` is not a special operator, it is a literal double negation. This is because most `false` values coming from byond are going to actually be `0`, which would be rendered if the condition is false. Negating `0` returns `true`, negating `true` returns `false`, which isn't rendered.
+5. `Fragment` is actually a true "dead tag". It's similar to `span` in that it just contains things without providing functionality, but it's unwrapped before the final render and children of it are injected into its parent. In a case where you only need to render text without any styling, it's probably better to just return a string literal (`"Example Render"`), but this was just to illustrate that you can put any tag in this expression.
+
+You don't really need to know all this to understand how to use it, but I find it helps with understanding when things go wrong.
+
+Template conditionals can have an `else` as well
+```tmpl
+{{#if data.condition}}
+ value
+{{else}}
+ other value
+{{/if}}
+```
+
+Similarly to the previous example, just add a `||` operator to handle the
+"falsy" condition:
+
+```jsx
+{!!data.condition && (
+
+ value
+
+) || (
+
+ other value
+
+)}
+```
+
+There's also our good old friend - the ternary:
+
+```jsx
+{data.condition ? 'value' : 'other value'}
+```
+
+Keep in mind you can also use tags here like the conditional example,
+and you can mix string literals, values, and tags as well.
+
+```jsx
+{data.is_robot ? (
+
+) : 'Not a robot'}
+```
+
+### Loops
+
+Templates have loops for iterating over data and inserting something for each
+member of an array or object
+
+```tmpl
+{{for data.entries}}
+ {{:value.name}}
+{{/for}}
+```
+
+This didn't care whether the data was an array or an object, and members of each entry of the loop were "unwrapped" so to say. `{{number}}` in that example is referring to the `{{number}}` value on the entry of the list for that iterate.
+
+The React equivalent to this is going to be `map`.
+
+_AN IMPORTANT DISTINCTION HERE IS THAT NOW WE CARE WHETHER THIS IS AN OBJECT OR AN ARRAY BEING ACTED ON._
+
+Objects are represented by `{}`, arrays by `[]`
+
+"How can I tell?" you may ask. It's fairly simple, associated lists on the byond side are going to be turned into objects when they get json converted, normal lists are going to be turned into arrays.
+
+`list("bla", "blo")` would become `["bla", "blo"]` and `list("foo" = 1, "bar" = 2)` would become `{"foo": 1, "bar": 2}`
+
+First things first, above the `return` of the function you're making the interface in, you're going to want to add something like this
+```jsx
+const things = data.things || [];
+```
+
+This ensures that you'll never be reading a null entry by mistake. Substitute `{}` for objects as appropriate.
+
+If it's an array, you'll want to do this in the template
+```jsx
+{things.map(thing => (
+
+ Thing {thing.number} is here!
+
+))}
+```
+
+`map` is a function that calls a passed function (a lambda) on each entry, and returns the value. You should already know that returned tags and values (except `false`) get rendered, so that's how it's rendering each time.
+
+A lambda is what's known as an anonymous function, it's a function that doesn't have a name that's only used for a specific usage. `map` wants a function that has one parameter, so we define one parameter then use `=>` to say the parameter has to do with the following block.
+
+`parameter => ()` is just a shorthand for `parameter => {return();}`
+
+This is quite a bit higher concept than ractive's each statements, so feel free to look around and ~~copy paste~~ learn from how other interfaces use this.
+
+Now for objects, there's a genuinely pretty gross syntax here. We apoligize, it's related to ie8 compatibility nonsense.
+
+```jsx
+{map((value, key) => (
+
+ Key is {key}, value is {value}
+
+))(fooObject)}
+```
+
+Again, sorry for this syntax. `fooObject` would be the object being iterated on, value would be the value of the iterated entry on the list, and key would be the key. the naming of value and key isn't important here, but knowing that it goes `value`, `key` in that order is important.
+
+It is sometimes better to preemptively convert an object to array before
+the big return statement, like this:
+
+```jsx
+const fooArray = map((value, key) => {
+ return { key, value };
+})(fooObject);
+```
+
+Or if you just want to discard all keys, this will also work nicely:
+
+```jsx
+const fooArray = toArray(fooObject);
+```
+
+
+If you want to see if an array has no contents and output a message, just check if array is empty like this:
+
+```jsx
+{fooArray.length === 0 && 'fooArray is empty.'}
+{fooArray.map(foo => (
+
+ Foo is {foo}
+
+))}
+```
+
+### Extra Stuff
+
+I'll put some extra stuff here when I think of it.
+
+## Components
+
+This will be a reference of tgui components and the tgui-next equivalent.
+
+### `ui-display`
+
+Equivalent of `` is ``
+
+```
+
+ Contents
+
+```
+
+becomes
+
+```jsx
+
+ Contents
+
+```
+
+A feature sometimes used is if `ui-display` has the `button` property, it will contain a `partial` command. This becomes the `buttons` property on `Section`:
+
+```
+
+ {{#partial button}}
+ // lots more button bullshit here
+ {{/partial}}
+ Contents
+
+```
+
+becomes
+
+```jsx
+
+ )}>
+ Contents
+
+```
+
+### `ui-section`
+
+Very important to note `ui-section` is NOT the equivalent of `Section`
+
+`` does not have a direct equivalent, but the closest equivalent is ``
+
+```
+
+ No Power
+
+
+ No Connection
+
+```
+
+becomes
+
+```jsx
+
+
+ No Power
+
+
+ No Connection
+
+
+```
+
+Important to note that `LabeledList.Item` has `buttons` as well.
+
+Also good to know that if you need the contents of a `LabeledList.Item` to be colored, you can just set the `color` prop on it instead of putting a `span` inside it.
+
+### `ui-notice`
+
+`` has a direct equivalent in ``
+
+```
+
+ Notice stuff!
+
+```
+
+becomes
+
+```jsx
+
+ Notice stuff!
+
+```
+
+### `ui-button`
+
+The equivalent of `ui-button` is `Button` but it works quite a bit differently.
+
+```
+
+ Click
+
+```
+
+becomes
+
+```jsx
+
+ )}>
+
+
+ {localStorage.length}
+
+
+ {formatSiUnit(localStorage.remainingSpace, 0, 'B')}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/debug/index.js b/tgui/packages/tgui/debug/index.js
new file mode 100644
index 0000000000..83fc136548
--- /dev/null
+++ b/tgui/packages/tgui/debug/index.js
@@ -0,0 +1,49 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import { subscribeToHotKey } from '../hotkeys';
+
+export const toggleKitchenSink = () => ({
+ type: 'debug/toggleKitchenSink',
+});
+
+export const toggleDebugLayout = () => ({
+ type: 'debug/toggleDebugLayout',
+});
+
+subscribeToHotKey('F11', () => toggleDebugLayout());
+subscribeToHotKey('F12', () => toggleKitchenSink());
+subscribeToHotKey('Ctrl+Alt+[8]', () => {
+ // NOTE: We need to call this in a timeout, because we need a clean
+ // stack in order for this to be a fatal error.
+ setTimeout(() => {
+ throw new Error(
+ 'OOPSIE WOOPSIE!! UwU We made a fucky wucky!! A wittle'
+ + ' fucko boingo! The code monkeys at our headquarters are'
+ + ' working VEWY HAWD to fix this!');
+ });
+});
+
+export const selectDebug = state => state.debug;
+
+export const useDebug = context => selectDebug(context.store.getState());
+
+export const debugReducer = (state = {}, action) => {
+ const { type, payload } = action;
+ if (type === 'debug/toggleKitchenSink') {
+ return {
+ ...state,
+ kitchenSink: !state.kitchenSink,
+ };
+ }
+ if (type === 'debug/toggleDebugLayout') {
+ return {
+ ...state,
+ debugLayout: !state.debugLayout,
+ };
+ }
+ return state;
+};
diff --git a/tgui/packages/tgui/drag.js b/tgui/packages/tgui/drag.js
new file mode 100644
index 0000000000..99db716410
--- /dev/null
+++ b/tgui/packages/tgui/drag.js
@@ -0,0 +1,245 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import { storage } from 'common/storage';
+import { vecAdd, vecInverse, vecMultiply, vecScale } from 'common/vector';
+import { createLogger } from './logging';
+
+const logger = createLogger('drag');
+
+let windowKey = window.__windowId__;
+let dragging = false;
+let resizing = false;
+let screenOffset = [0, 0];
+let screenOffsetPromise;
+let dragPointOffset;
+let resizeMatrix;
+let initialSize;
+let size;
+
+export const setWindowKey = key => {
+ windowKey = key;
+};
+
+export const getWindowPosition = () => [
+ window.screenLeft,
+ window.screenTop,
+];
+
+export const getWindowSize = () => [
+ window.innerWidth,
+ window.innerHeight,
+];
+
+export const setWindowPosition = vec => {
+ const byondPos = vecAdd(vec, screenOffset);
+ return Byond.winset(window.__windowId__, {
+ pos: byondPos[0] + ',' + byondPos[1],
+ });
+};
+
+export const setWindowSize = vec => {
+ return Byond.winset(window.__windowId__, {
+ size: vec[0] + 'x' + vec[1],
+ });
+};
+
+export const getScreenPosition = () => [
+ 0 - screenOffset[0],
+ 0 - screenOffset[1],
+];
+
+export const getScreenSize = () => [
+ window.screen.availWidth,
+ window.screen.availHeight,
+];
+
+/**
+ * Moves an item to the top of the recents array, and keeps its length
+ * limited to the number in `limit` argument.
+ *
+ * Uses a strict equality check for comparisons.
+ *
+ * Returns new recents and an item which was trimmed.
+ */
+const touchRecents = (recents, touchedItem, limit = 50) => {
+ const nextRecents = [touchedItem];
+ let trimmedItem;
+ for (let i = 0; i < recents.length; i++) {
+ const item = recents[i];
+ if (item === touchedItem) {
+ continue;
+ }
+ if (nextRecents.length < limit) {
+ nextRecents.push(item);
+ }
+ else {
+ trimmedItem = item;
+ }
+ }
+ return [nextRecents, trimmedItem];
+};
+
+export const storeWindowGeometry = windowKey => {
+ logger.log('storing geometry');
+ const geometry = {
+ pos: getWindowPosition(),
+ size: getWindowSize(),
+ };
+ storage.set(windowKey, geometry);
+ // Update the list of stored geometries
+ const [geometries, trimmedKey] = touchRecents(
+ storage.get('geometries') || [],
+ windowKey);
+ if (trimmedKey) {
+ storage.remove(trimmedKey);
+ }
+ storage.set('geometries', geometries);
+};
+
+export const recallWindowGeometry = async (windowKey, options = {}) => {
+ // Only recall geometry in fancy mode
+ const geometry = options.fancy && storage.get(windowKey);
+ if (geometry) {
+ logger.log('recalled geometry:', geometry);
+ }
+ let pos = geometry?.pos || options.pos;
+ const size = options.size;
+ // Set window size
+ if (size) {
+ setWindowSize(size);
+ }
+ // Set window position
+ if (pos) {
+ await screenOffsetPromise;
+ // Constraint window position if monitor lock was set in preferences.
+ if (size && options.locked) {
+ pos = constraintPosition(pos, size)[1];
+ }
+ setWindowPosition(pos);
+ }
+ // Set window position at the center of the screen.
+ else if (size) {
+ await screenOffsetPromise;
+ const areaAvailable = [
+ window.screen.availWidth - Math.abs(screenOffset[0]),
+ window.screen.availHeight - Math.abs(screenOffset[1]),
+ ];
+ const pos = vecAdd(
+ vecScale(areaAvailable, 0.5),
+ vecScale(size, -0.5),
+ vecScale(screenOffset, -1.0));
+ setWindowPosition(pos);
+ }
+};
+
+export const setupDrag = async () => {
+ // Calculate screen offset caused by the windows taskbar
+ screenOffsetPromise = Byond.winget(window.__windowId__, 'pos')
+ .then(pos => [
+ pos.x - window.screenLeft,
+ pos.y - window.screenTop,
+ ]);
+ screenOffset = await screenOffsetPromise;
+ logger.debug('screen offset', screenOffset);
+};
+
+/**
+ * Constraints window position to safe screen area, accounting for safe
+ * margins which could be a system taskbar.
+ */
+const constraintPosition = (pos, size) => {
+ const screenPos = getScreenPosition();
+ const screenSize = getScreenSize();
+ const nextPos = [pos[0], pos[1]];
+ let relocated = false;
+ for (let i = 0; i < 2; i++) {
+ const leftBoundary = screenPos[i];
+ const rightBoundary = screenPos[i] + screenSize[i];
+ if (pos[i] < leftBoundary) {
+ nextPos[i] = leftBoundary;
+ relocated = true;
+ }
+ else if (pos[i] + size[i] > rightBoundary) {
+ nextPos[i] = rightBoundary - size[i];
+ relocated = true;
+ }
+ }
+ return [relocated, nextPos];
+};
+
+export const dragStartHandler = event => {
+ logger.log('drag start');
+ dragging = true;
+ dragPointOffset = [
+ window.screenLeft - event.screenX,
+ window.screenTop - event.screenY,
+ ];
+ document.addEventListener('mousemove', dragMoveHandler);
+ document.addEventListener('mouseup', dragEndHandler);
+ dragMoveHandler(event);
+};
+
+const dragEndHandler = event => {
+ logger.log('drag end');
+ dragMoveHandler(event);
+ document.removeEventListener('mousemove', dragMoveHandler);
+ document.removeEventListener('mouseup', dragEndHandler);
+ dragging = false;
+ storeWindowGeometry(windowKey);
+};
+
+const dragMoveHandler = event => {
+ if (!dragging) {
+ return;
+ }
+ event.preventDefault();
+ setWindowPosition(vecAdd(
+ [event.screenX, event.screenY],
+ dragPointOffset));
+};
+
+export const resizeStartHandler = (x, y) => event => {
+ resizeMatrix = [x, y];
+ logger.log('resize start', resizeMatrix);
+ resizing = true;
+ dragPointOffset = [
+ window.screenLeft - event.screenX,
+ window.screenTop - event.screenY,
+ ];
+ initialSize = [
+ window.innerWidth,
+ window.innerHeight,
+ ];
+ document.addEventListener('mousemove', resizeMoveHandler);
+ document.addEventListener('mouseup', resizeEndHandler);
+ resizeMoveHandler(event);
+};
+
+const resizeEndHandler = event => {
+ logger.log('resize end', size);
+ resizeMoveHandler(event);
+ document.removeEventListener('mousemove', resizeMoveHandler);
+ document.removeEventListener('mouseup', resizeEndHandler);
+ resizing = false;
+ storeWindowGeometry(windowKey);
+};
+
+const resizeMoveHandler = event => {
+ if (!resizing) {
+ return;
+ }
+ event.preventDefault();
+ size = vecAdd(initialSize, vecMultiply(resizeMatrix, vecAdd(
+ [event.screenX, event.screenY],
+ vecInverse([window.screenLeft, window.screenTop]),
+ dragPointOffset,
+ [1, 1])));
+ // Sane window size values
+ size[0] = Math.max(size[0], 150);
+ size[1] = Math.max(size[1], 50);
+ setWindowSize(size);
+};
diff --git a/tgui/packages/tgui/format.js b/tgui/packages/tgui/format.js
new file mode 100644
index 0000000000..1f8420860e
--- /dev/null
+++ b/tgui/packages/tgui/format.js
@@ -0,0 +1,96 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import { clamp, round, toFixed } from 'common/math';
+
+const SI_SYMBOLS = [
+ 'f', // femto
+ 'p', // pico
+ 'n', // nano
+ 'μ', // micro
+ 'm', // milli
+ // NOTE: This is a space for a reason. When we right align si numbers,
+ // in monospace mode, we want to units and numbers stay in their respective
+ // columns. If rendering in HTML mode, this space will collapse into
+ // a single space anyway.
+ ' ',
+ 'k', // kilo
+ 'M', // mega
+ 'G', // giga
+ 'T', // tera
+ 'P', // peta
+ 'E', // exa
+ 'Z', // zetta
+ 'Y', // yotta
+];
+
+const SI_BASE_INDEX = SI_SYMBOLS.indexOf(' ');
+
+
+/**
+ * Formats a number to a human readable form, by reducing it to SI units.
+ * TODO: This is quite a shit code and shit math, needs optimization.
+ */
+export const formatSiUnit = (
+ value,
+ minBase1000 = -SI_BASE_INDEX,
+ unit = ''
+) => {
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
+ return value;
+ }
+ const realBase10 = Math.floor(Math.log10(value));
+ const base10 = Math.floor(Math.max(minBase1000 * 3, realBase10));
+ const realBase1000 = Math.floor(realBase10 / 3);
+ const base1000 = Math.floor(base10 / 3);
+ const symbolIndex = clamp(
+ SI_BASE_INDEX + base1000,
+ 0,
+ SI_SYMBOLS.length);
+ const symbol = SI_SYMBOLS[symbolIndex];
+ const scaledNumber = value / Math.pow(1000, base1000);
+ const scaledPrecision = realBase1000 > minBase1000
+ ? (2 + base1000 * 3 - base10)
+ : 0;
+ // TODO: Make numbers bigger than precision value show
+ // up to 2 decimal numbers.
+ const finalString = (
+ toFixed(scaledNumber, scaledPrecision)
+ + ' ' + symbol + unit
+ );
+ return finalString.trim();
+};
+
+export const formatPower = (value, minBase1000 = 0) => {
+ return formatSiUnit(value, minBase1000, 'W');
+};
+
+export const formatMoney = (value, precision = 0) => {
+ if (!Number.isFinite(value)) {
+ return value;
+ }
+ // Round the number and make it fixed precision
+ let fixed = round(value, precision);
+ if (precision > 0) {
+ fixed = toFixed(value, precision);
+ }
+ fixed = String(fixed);
+ // Place thousand separators
+ const length = fixed.length;
+ let indexOfPoint = fixed.indexOf('.');
+ if (indexOfPoint === -1) {
+ indexOfPoint = length;
+ }
+ let result = '';
+ for (let i = 0; i < length; i++) {
+ if (i > 0 && i < indexOfPoint && (indexOfPoint - i) % 3 === 0) {
+ // Thin space
+ result += '\u2009';
+ }
+ result += fixed.charAt(i);
+ }
+ return result;
+};
diff --git a/tgui/packages/tgui/global.d.ts b/tgui/packages/tgui/global.d.ts
new file mode 100644
index 0000000000..ab8870f15c
--- /dev/null
+++ b/tgui/packages/tgui/global.d.ts
@@ -0,0 +1,95 @@
+interface ByondType {
+ /**
+ * True if javascript is running in BYOND.
+ */
+ IS_BYOND: boolean;
+
+ /**
+ * True if browser is IE8 or lower.
+ */
+ IS_LTE_IE8: boolean;
+
+ /**
+ * True if browser is IE9 or lower.
+ */
+ IS_LTE_IE9: boolean;
+
+ /**
+ * True if browser is IE10 or lower.
+ */
+ IS_LTE_IE10: boolean;
+
+ /**
+ * True if browser is IE11 or lower.
+ */
+ IS_LTE_IE11: boolean;
+
+ /**
+ * Makes a BYOND call.
+ *
+ * If path is empty, this will trigger a Topic call.
+ * You can reference a specific object by setting the "src" parameter.
+ *
+ * See: https://secure.byond.com/docs/ref/skinparams.html
+ */
+ call(path: string, params: object): void;
+
+ /**
+ * Makes an asynchronous BYOND call. Returns a promise.
+ */
+ callAsync(path: string, params: object): Promise;
+
+ /**
+ * Makes a Topic call.
+ *
+ * You can reference a specific object by setting the "src" parameter.
+ */
+ topic(params: object): void;
+
+ /**
+ * Runs a command or a verb.
+ */
+ command(command: string): void;
+
+ /**
+ * Retrieves all properties of the BYOND skin element.
+ *
+ * Returns a promise with a key-value object containing all properties.
+ */
+ winget(id: string): Promise