")
text = replacetext(text, "\[cell\]", "")
- text = replacetext(text, "\[logo\]", " ")
+ text = replacetext(text, "\[logo\]", "​ ")
text = replacetext(text, "\[time\]", "[station_time_timestamp()]") // TO DO
if(!no_font)
if(P)
@@ -615,5 +615,3 @@ proc/checkhtml(var/t)
text = replacetext(text, " | ", "\[cell\]")
text = replacetext(text, " ", "\[logo\]")
return text
-
-#define string2charlist(string) (splittext(string, regex("(\\x0A|.)")) - splittext(string, ""))
diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm
index 83347c20d3b..a1da66b1373 100644
--- a/code/__HELPERS/time.dm
+++ b/code/__HELPERS/time.dm
@@ -60,7 +60,7 @@
return time2text(station_time(time, TRUE), format)
/* Returns 1 if it is the selected month and day */
-proc/isDay(var/month, var/day)
+/proc/isDay(var/month, var/day)
if(isnum(month) && isnum(day))
var/MM = text2num(time2text(world.timeofday, "MM")) // get the current month
var/DD = text2num(time2text(world.timeofday, "DD")) // get the current day
@@ -95,7 +95,7 @@ proc/isDay(var/month, var/day)
/proc/seconds_to_time(var/seconds as num)
var/numSeconds = seconds % 60
var/numMinutes = (seconds - numSeconds) / 60
- return "[numMinutes] [numMinutes > 1 ? "minutes" : "minute"] and [numSeconds] seconds."
+ return "[numMinutes] [numMinutes > 1 ? "minutes" : "minute"] and [numSeconds] seconds"
//Take a value in seconds and makes it display like a clock
/proc/seconds_to_clock(var/seconds as num)
diff --git a/code/__HELPERS/unique_ids.dm b/code/__HELPERS/unique_ids.dm
index b66b1a11b90..188918429e9 100644
--- a/code/__HELPERS/unique_ids.dm
+++ b/code/__HELPERS/unique_ids.dm
@@ -14,16 +14,33 @@
// var/myUID = mydatum.UID()
// var/datum/D = locateUID(myUID)
+/// The next UID to be used (Increments by 1 for each UID)
GLOBAL_VAR_INIT(next_unique_datum_id, 1)
+/// Log of all UIDs created in the round. Assoc list with type as key and amount as value
+GLOBAL_LIST_EMPTY(uid_log)
+/**
+ * Gets or creates the UID of a datum
+ *
+ * BYOND refs are recycled, so this system prevents that. If a datum does not have a UID when this proc is ran, one will be created
+ * Returns the UID of the datum
+ */
/datum/proc/UID()
if(!unique_datum_id)
var/tag_backup = tag
tag = null // Grab the raw ref, not the tag
- unique_datum_id = "\ref[src]_[GLOB.next_unique_datum_id++]"
+ // num2text can output 8 significant figures max. If we go above 10 million UIDs in a round, shit breaks
+ unique_datum_id = "\ref[src]_[num2text(GLOB.next_unique_datum_id++, 8)]"
tag = tag_backup
+ GLOB.uid_log[type]++
return unique_datum_id
+/**
+ * Locates a datum based off of the UID
+ *
+ * Replacement for locate() which takes a UID instead of a ref
+ * Returns the datum, if found
+ */
/proc/locateUID(uid)
if(!istext(uid))
return null
@@ -38,3 +55,24 @@ GLOBAL_VAR_INIT(next_unique_datum_id, 1)
if(D && D.unique_datum_id == uid)
return D
return null
+
+/**
+ * Opens a lof of UIDs
+ *
+ * In-round ability to view what has created a UID, and how many times a UID for that path has been declared
+ */
+/client/proc/uid_log()
+ set name = "View UID Log"
+ set category = "Debug"
+ set desc = "Shows the log of created UIDs this round"
+
+ if(!check_rights(R_DEBUG))
+ return
+
+ var/list/sorted = sortTim(GLOB.uid_log, cmp=/proc/cmp_numeric_dsc, associative = TRUE)
+ var/list/text = list("UID Log", "Current UID: [GLOB.next_unique_datum_id] ", "")
+ for(var/key in sorted)
+ text += "- [key] - [sorted[key]]
"
+
+ text += " "
+ usr << browse(text.Join(), "window=uidlog")
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 7e57a394988..46559eb6b5c 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -20,31 +20,6 @@
return 0
-//Inverts the colour of an HTML string
-/proc/invertHTML(HTMLstring)
-
- if(!( istext(HTMLstring) ))
- CRASH("Given non-text argument!")
- else
- if(length(HTMLstring) != 7)
- CRASH("Given non-HTML argument!")
- var/textr = copytext(HTMLstring, 2, 4)
- var/textg = copytext(HTMLstring, 4, 6)
- var/textb = copytext(HTMLstring, 6, 8)
- var/r = hex2num(textr)
- var/g = hex2num(textg)
- var/b = hex2num(textb)
- textr = num2hex(255 - r)
- textg = num2hex(255 - g)
- textb = num2hex(255 - b)
- if(length(textr) < 2)
- textr = text("0[]", textr)
- if(length(textg) < 2)
- textr = text("0[]", textg)
- if(length(textb) < 2)
- textr = text("0[]", textb)
- return text("#[][][]", textr, textg, textb)
-
//Returns the middle-most value
/proc/dd_range(var/low, var/high, var/num)
return max(low,min(high,num))
@@ -449,6 +424,18 @@ Turf and target are seperate in case you want to teleport some distance from a t
if(M.ckey == key)
return M
+/proc/get_client_by_ckey(ckey)
+ if(cmptext(copytext(ckey, 1, 2),"@"))
+ ckey = findStealthKey(ckey)
+ return GLOB.directory[ckey]
+
+
+/proc/findStealthKey(txt)
+ if(txt)
+ for(var/P in GLOB.stealthminID)
+ if(GLOB.stealthminID[P] == txt)
+ return P
+
// Returns the atom sitting on the turf.
// For example, using this on a disk, which is in a bag, on a mob, will return the mob because it's on the turf.
/proc/get_atom_on_turf(var/atom/movable/M)
@@ -529,7 +516,7 @@ Returns 1 if the chain up to the area contains the given typepath
return max(min(middle, high), low)
//returns random gauss number
-proc/GaussRand(var/sigma)
+/proc/GaussRand(var/sigma)
var/x,y,rsq
do
x=2*rand()-1
@@ -539,7 +526,7 @@ proc/GaussRand(var/sigma)
return sigma*y*sqrt(-2*log(rsq)/rsq)
//returns random gauss number, rounded to 'roundto'
-proc/GaussRandRound(var/sigma,var/roundto)
+/proc/GaussRandRound(var/sigma,var/roundto)
return round(GaussRand(sigma),roundto)
//Will return the contents of an atom recursivly to a depth of 'searchDepth'
@@ -583,7 +570,7 @@ proc/GaussRandRound(var/sigma,var/roundto)
return 1
-proc/is_blocked_turf(turf/T, exclude_mobs)
+/proc/is_blocked_turf(turf/T, exclude_mobs)
if(T.density)
return 1
for(var/i in T)
@@ -984,16 +971,16 @@ proc/is_blocked_turf(turf/T, exclude_mobs)
-proc/get_cardinal_dir(atom/A, atom/B)
+/proc/get_cardinal_dir(atom/A, atom/B)
var/dx = abs(B.x - A.x)
var/dy = abs(B.y - A.y)
return get_dir(A, B) & (rand() * (dx+dy) < dy ? 3 : 12)
//chances are 1:value. anyprob(1) will always return true
-proc/anyprob(value)
+/proc/anyprob(value)
return (rand(1,value)==value)
-proc/view_or_range(distance = world.view , center = usr , type)
+/proc/view_or_range(distance = world.view , center = usr , type)
switch(type)
if("view")
. = view(distance,center)
@@ -1001,7 +988,7 @@ proc/view_or_range(distance = world.view , center = usr , type)
. = range(distance,center)
return
-proc/oview_or_orange(distance = world.view , center = usr , type)
+/proc/oview_or_orange(distance = world.view , center = usr , type)
switch(type)
if("view")
. = oview(distance,center)
@@ -1009,7 +996,7 @@ proc/oview_or_orange(distance = world.view , center = usr , type)
. = orange(distance,center)
return
-proc/get_mob_with_client_list()
+/proc/get_mob_with_client_list()
var/list/mobs = list()
for(var/mob/M in GLOB.mob_list)
if(M.client)
@@ -1233,10 +1220,10 @@ GLOBAL_LIST_INIT(wall_items, typecacheof(list(/obj/machinery/power/apc, /obj/mac
return 0
-proc/get_angle(atom/a, atom/b)
+/proc/get_angle(atom/a, atom/b)
return atan2(b.y - a.y, b.x - a.x)
-proc/atan2(x, y)
+/proc/atan2(x, y)
if(!x && !y) return 0
return y >= 0 ? arccos(x / sqrt(x * x + y * y)) : -arccos(x / sqrt(x * x + y * y))
@@ -1329,7 +1316,7 @@ Standard way to write links -Sayu
return FACING_INIT_FACING_TARGET_TARGET_FACING_PERPENDICULAR
-atom/proc/GetTypeInAllContents(typepath)
+/atom/proc/GetTypeInAllContents(typepath)
var/list/processing_list = list(src)
var/list/processed = list()
@@ -2017,5 +2004,31 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
set waitfor = FALSE
return call(source, proctype)(arglist(arguments))
+/proc/IsFrozen(atom/A)
+ if(A in GLOB.frozen_atom_list)
+ return TRUE
+ return FALSE
+
+/**
+ * Proc which gets all adjacent turfs to `src`, including the turf that `src` is on.
+ *
+ * This is similar to doing `for(var/turf/T in range(1, src))`. However it is slightly more performant.
+ * Additionally, the above proc becomes more costly the more atoms there are nearby. This proc does not care about that.
+ */
+/atom/proc/get_all_adjacent_turfs()
+ var/turf/src_turf = get_turf(src)
+ var/list/_list = list(
+ src_turf,
+ get_step(src_turf, NORTH),
+ get_step(src_turf, NORTHEAST),
+ get_step(src_turf, NORTHWEST),
+ get_step(src_turf, SOUTH),
+ get_step(src_turf, SOUTHEAST),
+ get_step(src_turf, SOUTHWEST),
+ get_step(src_turf, EAST),
+ get_step(src_turf, WEST)
+ )
+ return _list
+
/// Waits at a line of code until X is true
#define UNTIL(X) while(!(X)) stoplag()
diff --git a/code/_globalvars/lists/misc.dm b/code/_globalvars/lists/misc.dm
index 53040777467..3a0c58bffb3 100644
--- a/code/_globalvars/lists/misc.dm
+++ b/code/_globalvars/lists/misc.dm
@@ -40,6 +40,7 @@ GLOBAL_LIST_INIT(round_end_sounds, list(
'sound/AI/newroundsexy.ogg' = 2.3 SECONDS,
'sound/misc/apcdestroyed.ogg' = 3 SECONDS,
'sound/misc/bangindonk.ogg' = 1.6 SECONDS,
+ 'sound/misc/berightback.ogg' = 2.9 SECONDS,
'sound/goonstation/misc/newround1.ogg' = 6.9 SECONDS,
'sound/goonstation/misc/newround2.ogg' = 14.8 SECONDS
)) // Maps available round end sounds to their duration
diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm
index 21af95cab48..ab3bfa85451 100644
--- a/code/_globalvars/lists/objects.dm
+++ b/code/_globalvars/lists/objects.dm
@@ -47,7 +47,10 @@ GLOBAL_LIST_EMPTY(ladders)
GLOBAL_LIST_INIT(active_diseases, list()) //List of Active disease in all mobs; purely for quick referencing.
GLOBAL_LIST_EMPTY(mob_spawners) // All mob_spawn objects
-
+GLOBAL_LIST_EMPTY(alert_consoles) // Station alert consoles, /obj/machinery/computer/station_alert
GLOBAL_LIST_EMPTY(explosive_walls)
GLOBAL_LIST_EMPTY(engine_beacon_list)
+
+/// List of wire colors for each object type of that round. One for airlocks, one for vendors, etc.
+GLOBAL_LIST_EMPTY(wire_color_directory) // This is an associative list with the `holder_type` as the key, and a list of colors as the value.
diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm
index f5f71e65c57..50e70bc62a4 100644
--- a/code/_globalvars/logging.dm
+++ b/code/_globalvars/logging.dm
@@ -34,4 +34,6 @@ GLOBAL_PROTECT(IClog)
GLOBAL_LIST_EMPTY(OOClog)
GLOBAL_PROTECT(OOClog)
+GLOBAL_DATUM_INIT(logging, /datum/logging, new /datum/logging())
+
GLOBAL_LIST_INIT(investigate_log_subjects, list("notes", "watchlist", "hrefs"))
diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm
index 283d92a954f..9c6fa81c1a2 100644
--- a/code/_globalvars/misc.dm
+++ b/code/_globalvars/misc.dm
@@ -10,12 +10,12 @@ GLOBAL_DATUM_INIT(command_announcer, /obj/item/radio/intercom/command, create_co
// Load order issues means this can't be new'd until other code runs
// This is probably not the way I should be doing this, but I don't know how to do it right!
-proc/create_global_announcer()
+/proc/create_global_announcer()
spawn(0)
GLOB.global_announcer = new(null)
return
-proc/create_command_announcer()
+/proc/create_command_announcer()
spawn(0)
GLOB.command_announcer = new(null)
return
@@ -92,6 +92,7 @@ GLOBAL_VAR(map_name) // Self explanatory
GLOBAL_DATUM_INIT(data_core, /datum/datacore, new) // Station datacore, manifest, etc
GLOBAL_VAR_INIT(panic_bunker_enabled, FALSE) // Is the panic bunker enabled
+GLOBAL_VAR_INIT(pending_server_update, FALSE)
//Database connections
//A connection is established on world creation. Ideally, the connection dies when the server restarts (After feedback logging.).
diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm
index fbb04d4c021..03d34395479 100644
--- a/code/_onclick/ai.dm
+++ b/code/_onclick/ai.dm
@@ -9,7 +9,7 @@
Note that AI have no need for the adjacency proc, and so this proc is a lot cleaner.
*/
-/mob/living/silicon/ai/DblClickOn(var/atom/A, params)
+/mob/living/silicon/ai/DblClickOn(atom/A, params)
if(client.click_intercept)
// Not doing a click intercept here, because otherwise we double-tap with the `ClickOn` proc.
// But we return here since we don't want to do regular dblclick handling
@@ -23,7 +23,7 @@
A.move_camera_by_click()
-/mob/living/silicon/ai/ClickOn(var/atom/A, params)
+/mob/living/silicon/ai/ClickOn(atom/A, params)
if(client.click_intercept)
client.click_intercept.InterceptClickOn(src, params, A)
return
@@ -123,7 +123,7 @@
/mob/living/silicon/ai/RangedAttack(atom/A, params)
A.attack_ai(src)
-/atom/proc/attack_ai(mob/user as mob)
+/atom/proc/attack_ai(mob/user)
return
/*
@@ -132,25 +132,23 @@
for AI shift, ctrl, and alt clicking.
*/
-/mob/living/silicon/ai/CtrlShiftClickOn(var/atom/A)
+/mob/living/silicon/ai/CtrlShiftClickOn(atom/A)
A.AICtrlShiftClick(src)
-/mob/living/silicon/ai/AltShiftClickOn(var/atom/A)
+/mob/living/silicon/ai/AltShiftClickOn(atom/A)
A.AIAltShiftClick(src)
-/mob/living/silicon/ai/ShiftClickOn(var/atom/A)
+/mob/living/silicon/ai/ShiftClickOn(atom/A)
A.AIShiftClick(src)
-/mob/living/silicon/ai/CtrlClickOn(var/atom/A)
+/mob/living/silicon/ai/CtrlClickOn(atom/A)
A.AICtrlClick(src)
-/mob/living/silicon/ai/AltClickOn(var/atom/A)
+/mob/living/silicon/ai/AltClickOn(atom/A)
A.AIAltClick(src)
-/mob/living/silicon/ai/MiddleClickOn(var/atom/A)
+/mob/living/silicon/ai/MiddleClickOn(atom/A)
A.AIMiddleClick(src)
-/*
- The following criminally helpful code is just the previous code cleaned up;
- I have no idea why it was in atoms.dm instead of respective files.
-*/
-/atom/proc/AICtrlShiftClick(var/mob/user) // Examines
+// DEFAULT PROCS TO OVERRIDE
+
+/atom/proc/AICtrlShiftClick(mob/user) // Examines
if(user.client)
user.examinate(src)
return
@@ -158,74 +156,78 @@
/atom/proc/AIAltShiftClick()
return
-/obj/machinery/door/airlock/AIAltShiftClick() // Sets/Unsets Emergency Access Override
- if(density)
- Topic(src, list("src" = UID(), "command"="emergency", "activate" = "1"), 1) // 1 meaning no window (consistency!)
- else
- Topic(src, list("src" = UID(), "command"="emergency", "activate" = "0"), 1)
- return
-
-/atom/proc/AIShiftClick(var/mob/user)
+/atom/proc/AIShiftClick(mob/living/user) // borgs use this too
if(user.client)
user.examinate(src)
return
-/obj/machinery/door/airlock/AIShiftClick() // Opens and closes doors!
- if(density)
- Topic(src, list("src" = UID(), "command"="open", "activate" = "1"), 1) // 1 meaning no window (consistency!)
- else
- Topic(src, list("src" = UID(), "command"="open", "activate" = "0"), 1)
+/atom/proc/AICtrlClick(mob/living/silicon/user)
return
-/atom/proc/AICtrlClick(var/mob/living/silicon/ai/user)
- return
-
-/obj/machinery/door/airlock/AICtrlClick() // Bolts doors
- if(locked)
- Topic(src, list("src" = UID(), "command"="bolts", "activate" = "0"), 1)// 1 meaning no window (consistency!)
- else
- Topic(src, list("src" = UID(), "command"="bolts", "activate" = "1"), 1)
-
-/obj/machinery/power/apc/AICtrlClick() // turns off/on APCs.
- Topic("breaker=1", list("breaker"="1"), 0) // 0 meaning no window (consistency! wait...)
-
-/obj/machinery/turretid/AICtrlClick() //turns off/on Turrets
- Topic(src, list("src" = UID(), "command"="enable", "value"="[!enabled]"), 1) // 1 meaning no window (consistency!)
-
-/atom/proc/AIAltClick(var/atom/A)
+/atom/proc/AIAltClick(atom/A)
AltClick(A)
-/obj/machinery/door/airlock/AIAltClick() // Electrifies doors.
- if(!electrified_until)
- // permanent shock
- Topic(src, list("src" = UID(), "command"="electrify_permanently", "activate" = "1"), 1) // 1 meaning no window (consistency!)
- else
- // disable/6 is not in Topic; disable/5 disables both temporary and permanent shock
- Topic(src, list("src" = UID(), "command"="electrify_permanently", "activate" = "0"), 1)
+/atom/proc/AIMiddleClick(mob/living/user)
return
+/mob/living/silicon/ai/TurfAdjacent(turf/T)
+ return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(T))
+
+
+// APC
+
+/obj/machinery/power/apc/AICtrlClick(mob/living/user) // turns off/on APCs.
+ toggle_breaker(user)
+
+
+// TURRETCONTROL
+
+/obj/machinery/turretid/AICtrlClick(mob/living/silicon/user) //turns off/on Turrets
+ enabled = !enabled
+ updateTurrets()
+
/obj/machinery/turretid/AIAltClick() //toggles lethal on turrets
- Topic(src, list("src" = UID(), "command"="lethal", "value"="[!lethal]"), 1) // 1 meaning no window (consistency!)
+ if(lethal_is_configurable)
+ lethal = !lethal
+ updateTurrets()
-/atom/proc/AIMiddleClick()
- return
+// AIRLOCKS
-/obj/machinery/door/airlock/AIMiddleClick() // Toggles door bolt lights.
- if(!src.lights)
- Topic(src, list("src" = UID(), "command"="lights", "activate" = "1"), 1) // 1 meaning no window (consistency!)
+/obj/machinery/door/airlock/AIAltShiftClick(mob/user) // Sets/Unsets Emergency Access Override
+ if(!ai_control_check(user))
+ return
+ toggle_emergency_status(user)
+
+/obj/machinery/door/airlock/AIShiftClick(mob/user) // Opens and closes doors!
+ if(!ai_control_check(user))
+ return
+ open_close(user)
+
+/obj/machinery/door/airlock/AICtrlClick(mob/living/silicon/user) // Bolts doors
+ if(!ai_control_check(user))
+ return
+ toggle_bolt(user)
+
+/obj/machinery/door/airlock/AIAltClick(mob/living/silicon/user) // Electrifies doors.
+ if(!ai_control_check(user))
+ return
+ if(wires.is_cut(WIRE_ELECTRIFY))
+ to_chat(user, "The electrification wire is cut - Cannot electrify the door.")
+ if(isElectrified())
+ electrify(0, user, TRUE) // un-shock
else
- Topic(src, list("src" = UID(), "command"="lights", "activate" = "0"), 1)
- return
+ electrify(-1, user, TRUE) // permanent shock
-/obj/machinery/ai_slipper/AICtrlClick() //Turns liquid dispenser on or off
+
+/obj/machinery/door/airlock/AIMiddleClick(mob/living/user) // Toggles door bolt lights.
+ if(!ai_control_check(user))
+ return
+ toggle_light(user)
+
+// AI-CONTROLLED SLIP GENERATOR IN AI CORE
+
+/obj/machinery/ai_slipper/AICtrlClick(mob/living/silicon/ai/user) //Turns liquid dispenser on or off
ToggleOn()
/obj/machinery/ai_slipper/AIAltClick() //Dispenses liquid if on
Activate()
-
-//
-// Override AdjacentQuick for AltClicking
-//
-
-/mob/living/silicon/ai/TurfAdjacent(var/turf/T)
- return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(T))
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index d840f64bb4a..36b6d647b7d 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -73,7 +73,9 @@
var/dragged = modifiers["drag"]
if(dragged && !modifiers[dragged])
return
-
+ if(IsFrozen(A) && !is_admin(usr))
+ to_chat(usr, "Interacting with admin-frozen players is not permitted.")
+ return
if(modifiers["middle"] && modifiers["shift"] && modifiers["ctrl"])
MiddleShiftControlClickOn(A)
return
diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm
index f9e5cbf3e5a..fa29e857ab7 100644
--- a/code/_onclick/cyborg.dm
+++ b/code/_onclick/cyborg.dm
@@ -6,7 +6,7 @@
adjacency code.
*/
-/mob/living/silicon/robot/ClickOn(var/atom/A, var/params)
+/mob/living/silicon/robot/ClickOn(atom/A, params)
if(client.click_intercept)
client.click_intercept.InterceptClickOn(src, params, A)
return
@@ -15,6 +15,8 @@
return
changeNext_click(1)
+ if(is_ventcrawling(src)) // To stop drones interacting with anything while ventcrawling
+ return
var/list/modifiers = params2list(params)
if(modifiers["shift"] && modifiers["ctrl"])
@@ -98,12 +100,12 @@
return
//Ctrl+Middle click cycles through modules
-/mob/living/silicon/robot/proc/CtrlMiddleClickOn(var/atom/A)
+/mob/living/silicon/robot/proc/CtrlMiddleClickOn(atom/A)
cycle_modules()
return
//Middle click points
-/mob/living/silicon/robot/MiddleClickOn(var/atom/A)
+/mob/living/silicon/robot/MiddleClickOn(atom/A)
if(istype(src, /mob/living/silicon/robot/drone))
// Drones cannot point.
return
@@ -112,18 +114,31 @@
//Give cyborgs hotkey clicks without breaking existing uses of hotkey clicks
// for non-doors/apcs
-/mob/living/silicon/robot/CtrlShiftClickOn(var/atom/A)
- A.BorgCtrlShiftClick(src)
-/mob/living/silicon/robot/AltShiftClickOn(var/atom/A)
- A.BorgAltShiftClick(src)
-/mob/living/silicon/robot/ShiftClickOn(var/atom/A)
+/mob/living/silicon/robot/ShiftClickOn(atom/A)
A.BorgShiftClick(src)
-/mob/living/silicon/robot/CtrlClickOn(var/atom/A)
+/mob/living/silicon/robot/CtrlClickOn(atom/A)
A.BorgCtrlClick(src)
-/mob/living/silicon/robot/AltClickOn(var/atom/A)
+/mob/living/silicon/robot/AltClickOn(atom/A)
A.BorgAltClick(src)
+/mob/living/silicon/robot/CtrlShiftClickOn(atom/A)
+ A.BorgCtrlShiftClick(src)
+/mob/living/silicon/robot/AltShiftClickOn(atom/A)
+ A.BorgAltShiftClick(src)
-/atom/proc/BorgCtrlShiftClick(var/mob/user) // Examines
+
+/atom/proc/BorgShiftClick(mob/user)
+ if(user.client && user.client.eye == user)
+ user.examinate(src)
+ return
+
+/atom/proc/BorgCtrlClick(mob/living/silicon/robot/user) //forward to human click if not overriden
+ CtrlClick(user)
+
+/atom/proc/BorgAltClick(mob/living/silicon/robot/user)
+ AltClick(user)
+ return
+
+/atom/proc/BorgCtrlShiftClick(mob/user) // Examines
if(user.client && user.client.eye == user)
user.examinate(src)
return
@@ -131,45 +146,45 @@
/atom/proc/BorgAltShiftClick()
return
-/obj/machinery/door/airlock/BorgAltShiftClick() // Enables emergency override on doors! Forwards to AI code.
- AIAltShiftClick()
-/atom/proc/BorgShiftClick(var/mob/user)
- if(user.client && user.client.eye == user)
- user.examinate(src)
- return
+// AIRLOCKS
-/obj/machinery/door/airlock/BorgShiftClick() // Opens and closes doors! Forwards to AI code.
- AIShiftClick()
+/obj/machinery/door/airlock/BorgShiftClick(mob/living/silicon/robot/user) // Opens and closes doors! Forwards to AI code.
+ AIShiftClick(user)
-/atom/proc/BorgCtrlClick(var/mob/living/silicon/robot/user) //forward to human click if not overriden
- CtrlClick(user)
+/obj/machinery/door/airlock/BorgCtrlClick(mob/living/silicon/robot/user) // Bolts doors. Forwards to AI code.
+ AICtrlClick(user)
-/obj/machinery/door/airlock/BorgCtrlClick() // Bolts doors. Forwards to AI code.
- AICtrlClick()
+/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code.
+ AIAltClick(user)
-/obj/machinery/power/apc/BorgCtrlClick() // turns off/on APCs. Forwards to AI code.
- AICtrlClick()
+/obj/machinery/door/airlock/BorgAltShiftClick(mob/living/silicon/robot/user) // Enables emergency override on doors! Forwards to AI code.
+ AIAltShiftClick(user)
-/obj/machinery/turretid/BorgCtrlClick() //turret control on/off. Forwards to AI code.
- AICtrlClick()
-/atom/proc/BorgAltClick(var/mob/living/silicon/robot/user)
- AltClick(user)
- return
+// APC
-/obj/machinery/door/airlock/BorgAltClick() // Eletrifies doors. Forwards to AI code.
- AIAltClick()
+/obj/machinery/power/apc/BorgCtrlClick(mob/living/silicon/robot/user) // turns off/on APCs. Forwards to AI code.
+ AICtrlClick(user)
-/obj/machinery/turretid/BorgAltClick() //turret lethal on/off. Forwards to AI code.
- AIAltClick()
-/obj/machinery/ai_slipper/BorgCtrlClick() //Turns liquid dispenser on or off
+// AI SLIPPER
+
+/obj/machinery/ai_slipper/BorgCtrlClick(mob/living/silicon/robot/user) //Turns liquid dispenser on or off
ToggleOn()
-/obj/machinery/ai_slipper/BorgAltClick() //Dispenses liquid if on
+/obj/machinery/ai_slipper/BorgAltClick(mob/living/silicon/robot/user) //Dispenses liquid if on
Activate()
+
+// TURRETCONTROL
+
+/obj/machinery/turretid/BorgCtrlClick(mob/living/silicon/robot/user) //turret control on/off. Forwards to AI code.
+ AICtrlClick(user)
+
+/obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code.
+ AIAltClick(user)
+
/*
As with AI, these are not used in click code,
because the code for robots is specific, not generic.
@@ -184,6 +199,6 @@
/mob/living/silicon/robot/RangedAttack(atom/A, params)
A.attack_robot(src)
-/atom/proc/attack_robot(mob/user as mob)
+/atom/proc/attack_robot(mob/user)
attack_ai(user)
return
diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm
index ada0ce8cd30..856f724f925 100644
--- a/code/_onclick/hud/ai.dm
+++ b/code/_onclick/hud/ai.dm
@@ -64,7 +64,7 @@
/obj/screen/ai/alerts/Click()
if(isAI(usr))
var/mob/living/silicon/ai/AI = usr
- AI.subsystem_alarm_monitor()
+ AI.ai_alerts()
/obj/screen/ai/announcement
name = "Make Announcement"
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index a93e65c762b..a07a29ccc53 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -3,7 +3,7 @@
//PUBLIC - call these wherever you want
-/mob/proc/throw_alert(category, type, severity, obj/new_master, override = FALSE)
+/mob/proc/throw_alert(category, type, severity, obj/new_master, override = FALSE, timeout_override, no_anim)
/*
Proc to create or update an alert. Returns the alert if the alert is new or updated, 0 if it was thrown already
@@ -59,14 +59,16 @@
LAZYSET(alerts, category, alert) // This also creates the list if it doesn't exist
if(client && hud_used)
hud_used.reorganize_alerts()
- alert.transform = matrix(32, 6, MATRIX_TRANSLATE)
- animate(alert, transform = matrix(), time = 2.5, easing = CUBIC_EASING)
- if(alert.timeout)
- spawn(alert.timeout)
- if(alert.timeout && alerts[category] == alert && world.time >= alert.timeout)
- clear_alert(category)
- alert.timeout = world.time + alert.timeout - world.tick_lag
+ if(!no_anim)
+ alert.transform = matrix(32, 6, MATRIX_TRANSLATE)
+ animate(alert, transform = matrix(), time = 2.5, easing = CUBIC_EASING)
+
+ var/timeout = timeout_override || alert.timeout
+ if(timeout)
+ addtimer(CALLBACK(alert, /obj/screen/alert/.proc/do_timeout, src, category), timeout)
+ alert.timeout = world.time + timeout - world.tick_lag
+
return alert
// Proc to clear an existing alert.
@@ -94,7 +96,6 @@
var/alerttooltipstyle = ""
var/override_alerts = FALSE //If it is overriding other alerts of the same type
-
/obj/screen/alert/MouseEntered(location,control,params)
openToolTip(usr, src, params, title = name, content = desc, theme = alerttooltipstyle)
@@ -102,6 +103,12 @@
/obj/screen/alert/MouseExited()
closeToolTip(usr)
+/obj/screen/alert/proc/do_timeout(mob/M, category)
+ if(!M || !M.alerts)
+ return
+
+ if(timeout && M.alerts[category] == src && world.time >= timeout)
+ M.clear_alert(category)
//Gas alerts
/obj/screen/alert/not_enough_oxy
@@ -507,6 +514,34 @@ so as to remain in compliance with the most up-to-date laws."
timeout = 300
var/atom/target = null
var/action = NOTIFY_JUMP
+ var/show_time_left = FALSE // If true you need to call START_PROCESSING manually
+ var/image/time_left_overlay // The last image showing the time left
+ var/datum/candidate_poll/poll // If set, on Click() it'll register the player as a candidate
+
+/obj/screen/alert/notify_action/process()
+ if(show_time_left)
+ var/timeleft = timeout - world.time
+ if(timeleft <= 0)
+ return PROCESS_KILL
+
+ if(time_left_overlay)
+ overlays -= time_left_overlay
+
+ var/obj/O = new
+ O.maptext = "[CEILING(timeleft / 10, 1)]"
+ O.maptext_width = O.maptext_height = 128
+ var/matrix/M = new
+ M.Translate(4, 16)
+ O.transform = M
+
+ var/image/I = image(O)
+ I.layer = FLOAT_LAYER
+ I.plane = FLOAT_PLANE + 1
+ overlays += I
+
+ time_left_overlay = I
+ qdel(O)
+ ..()
/obj/screen/alert/notify_action/Destroy()
target = null
@@ -515,20 +550,52 @@ so as to remain in compliance with the most up-to-date laws."
/obj/screen/alert/notify_action/Click()
if(!usr || !usr.client)
return
- if(!target)
- return
var/mob/dead/observer/G = usr
if(!istype(G))
return
- switch(action)
- if(NOTIFY_ATTACK)
- target.attack_ghost(G)
- if(NOTIFY_JUMP)
- var/turf/T = get_turf(target)
- if(T && isturf(T))
- G.loc = T
- if(NOTIFY_FOLLOW)
- G.ManualFollow(target)
+
+ if(poll)
+ if(poll.sign_up(G))
+ // Add a small overlay to indicate we've signed up
+ display_signed_up()
+ else if(target)
+ switch(action)
+ if(NOTIFY_ATTACK)
+ target.attack_ghost(G)
+ if(NOTIFY_JUMP)
+ var/turf/T = get_turf(target)
+ if(T && isturf(T))
+ G.loc = T
+ if(NOTIFY_FOLLOW)
+ G.ManualFollow(target)
+
+/obj/screen/alert/notify_action/Topic(href, href_list)
+ if(href_list["signup"] && poll?.sign_up(usr))
+ display_signed_up()
+
+/obj/screen/alert/notify_action/proc/display_signed_up()
+ var/image/I = image('icons/mob/screen_gen.dmi', icon_state = "selector")
+ I.layer = FLOAT_LAYER
+ I.plane = FLOAT_PLANE + 2
+ overlays += I
+
+/obj/screen/alert/notify_action/proc/display_stacks(stacks = 1)
+ if(stacks <= 1)
+ return
+
+ var/obj/O = new
+ O.maptext = "[stacks]x"
+ O.maptext_width = O.maptext_height = 128
+ var/matrix/M = new
+ M.Translate(4, 2)
+ O.transform = M
+
+ var/image/I = image(O)
+ I.layer = FLOAT_LAYER
+ I.plane = FLOAT_PLANE + 1
+ overlays += I
+
+ qdel(O)
/obj/screen/alert/notify_soulstone
name = "Soul Stone"
diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm
index a291120fb85..67df5eb3013 100644
--- a/code/_onclick/hud/ghost.dm
+++ b/code/_onclick/hud/ghost.dm
@@ -81,6 +81,9 @@
/obj/screen/ghost/respawn_pai/Click()
var/mob/dead/observer/G = usr
+ if(!GLOB.paiController.check_recruit(G))
+ to_chat(G, "You are not eligible to become a pAI.")
+ return
GLOB.paiController.recruitWindow(G)
/datum/hud/ghost
diff --git a/code/_onclick/hud/guardian.dm b/code/_onclick/hud/guardian.dm
index d628124e052..6c9e95f457e 100644
--- a/code/_onclick/hud/guardian.dm
+++ b/code/_onclick/hud/guardian.dm
@@ -13,7 +13,7 @@
using.icon_state = mymob.a_intent
static_inventory += using
action_intent = using
-
+
using = new /obj/screen/guardian/Manifest()
using.screen_loc = ui_rhand
static_inventory += using
@@ -49,8 +49,8 @@
/obj/screen/guardian/Manifest/Click()
if(isguardian(usr))
var/mob/living/simple_animal/hostile/guardian/G = usr
- G.Manifest()
-
+ if(G.loc == G.summoner)
+ G.Manifest()
/obj/screen/guardian/Recall
icon_state = "recall"
diff --git a/code/_onclick/hud/map_popups.dm b/code/_onclick/hud/map_popups.dm
new file mode 100644
index 00000000000..dc9e255cba9
--- /dev/null
+++ b/code/_onclick/hud/map_popups.dm
@@ -0,0 +1,170 @@
+/client
+ /**
+ * Assoc list with all the active maps - when a screen obj is added to
+ * a map, it's put in here as well.
+ *
+ * Format: list( = list(/obj/screen))
+ */
+ var/list/screen_maps = list()
+
+/obj/screen
+ /**
+ * Map name assigned to this object.
+ * Automatically set by /client/proc/add_obj_to_map.
+ */
+ var/assigned_map
+ /**
+ * Mark this object as garbage-collectible after you clean the map
+ * it was registered on.
+ *
+ * This could probably be changed to be a proc, for conditional removal.
+ * But for now, this works.
+ */
+ var/del_on_map_removal = TRUE
+
+/**
+ * A screen object, which acts as a container for turfs and other things
+ * you want to show on the map, which you usually attach to "vis_contents".
+ */
+/obj/screen/map_view
+ // Map view has to be on the lowest plane to enable proper lighting
+ layer = GAME_PLANE
+ plane = GAME_PLANE
+
+/**
+ * A generic background object.
+ * It is also implicitly used to allocate a rectangle on the map, which will
+ * be used for auto-scaling the map.
+ */
+/obj/screen/background
+ name = "background"
+ icon = 'icons/mob/map_backgrounds.dmi'
+ icon_state = "clear"
+ layer = GAME_PLANE
+ plane = GAME_PLANE
+
+/**
+ * Sets screen_loc of this screen object, in form of point coordinates,
+ * with optional pixel offset (px, py).
+ *
+ * If applicable, "assigned_map" has to be assigned before this proc call.
+ */
+/obj/screen/proc/set_position(x, y, px = 0, py = 0)
+ if(assigned_map)
+ screen_loc = "[assigned_map]:[x]:[px],[y]:[py]"
+ else
+ screen_loc = "[x]:[px],[y]:[py]"
+
+/**
+ * Sets screen_loc to fill a rectangular area of the map.
+ *
+ * If applicable, "assigned_map" has to be assigned before this proc call.
+ */
+/obj/screen/proc/fill_rect(x1, y1, x2, y2)
+ if(assigned_map)
+ screen_loc = "[assigned_map]:[x1],[y1] to [x2],[y2]"
+ else
+ screen_loc = "[x1],[y1] to [x2],[y2]"
+
+/**
+ * Registers screen obj with the client, which makes it visible on the
+ * assigned map, and becomes a part of the assigned map's lifecycle.
+ */
+/client/proc/register_map_obj(obj/screen/screen_obj)
+ if(!screen_obj.assigned_map)
+ CRASH("Can't register [screen_obj] without 'assigned_map' property.")
+ if(!screen_maps[screen_obj.assigned_map])
+ screen_maps[screen_obj.assigned_map] = list()
+ // NOTE: Possibly an expensive operation
+ var/list/screen_map = screen_maps[screen_obj.assigned_map]
+ if(!screen_map.Find(screen_obj))
+ screen_map += screen_obj
+ if(!screen.Find(screen_obj))
+ screen += screen_obj
+
+/**
+ * Clears the map of registered screen objects.
+ *
+ * Not really needed most of the time, as the client's screen list gets reset
+ * on relog. any of the buttons are going to get caught by garbage collection
+ * anyway. they're effectively qdel'd.
+ */
+/client/proc/clear_map(map_name)
+ if(!map_name || !(map_name in screen_maps))
+ return FALSE
+ for(var/obj/screen/screen_obj in screen_maps[map_name])
+ screen_maps[map_name] -= screen_obj
+ if(screen_obj.del_on_map_removal)
+ qdel(screen_obj)
+ screen_maps -= map_name
+
+/**
+ * Clears all the maps of registered screen objects.
+ */
+/client/proc/clear_all_maps()
+ for(var/map_name in screen_maps)
+ clear_map(map_name)
+
+/**
+ * Creates a popup window with a basic map element in it, without any
+ * further initialization.
+ *
+ * Ratio is how many pixels by how many pixels (keep it simple).
+ *
+ * Returns a map name.
+ */
+/client/proc/create_popup(name, ratiox = 100, ratioy = 100)
+ winclone(src, "popupwindow", name)
+ var/list/winparams = list()
+ winparams["size"] = "[ratiox]x[ratioy]"
+ winparams["on-close"] = "handle-popup-close [name]"
+ winset(src, "[name]", list2params(winparams))
+ winshow(src, "[name]", 1)
+
+ var/list/params = list()
+ params["parent"] = "[name]"
+ params["type"] = "map"
+ params["size"] = "[ratiox]x[ratioy]"
+ params["anchor1"] = "0,0"
+ params["anchor2"] = "[ratiox],[ratioy]"
+ winset(src, "[name]_map", list2params(params))
+
+ return "[name]_map"
+
+/**
+ * Create the popup, and get it ready for generic use by giving
+ * it a background.
+ *
+ * Width and height are multiplied by 64 by default.
+ */
+/client/proc/setup_popup(popup_name, width = 9, height = 9, \
+ tilesize = 2, bg_icon)
+ if(!popup_name)
+ return
+ clear_map("[popup_name]_map")
+ var/x_value = world.icon_size * tilesize * width
+ var/y_value = world.icon_size * tilesize * height
+ var/map_name = create_popup(popup_name, x_value, y_value)
+
+ var/obj/screen/background/background = new
+ background.assigned_map = map_name
+ background.fill_rect(1, 1, width, height)
+ if(bg_icon)
+ background.icon_state = bg_icon
+ register_map_obj(background)
+
+ return map_name
+
+/**
+ * Closes a popup.
+ */
+/client/proc/close_popup(popup)
+ winshow(src, popup, 0)
+ handle_popup_close(popup)
+
+/**
+ * When the popup closes in any way (player or proc call) it calls this.
+ */
+/client/verb/handle_popup_close(window_id as text)
+ set hidden = TRUE
+ clear_map("[window_id]_map")
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index 1423720f8f6..9795975a090 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -483,7 +483,7 @@
var/list/cached_healthdoll_overlays = list() // List of icon states (strings) for overlays
/obj/screen/healthdoll/Click()
- if(ishuman(usr))
+ if(ishuman(usr) && !usr.is_dead())
var/mob/living/carbon/H = usr
H.check_self_for_injuries()
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index f88244c0cd8..4fd5b956df5 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -81,7 +81,7 @@
user.do_attack_animation(M)
. = M.attacked_by(src, user, def_zone)
- add_attack_logs(user, M, "Attacked with [name] (INTENT: [uppertext(user.a_intent)]) (DAMTYPE: [uppertext(damtype)])", (M.ckey && force > 0 && damtype != STAMINA) ? null : ATKLOG_ALMOSTALL)
+ add_attack_logs(user, M, "Attacked with [name] ([uppertext(user.a_intent)]) ([uppertext(damtype)])", (M.ckey && force > 0 && damtype != STAMINA) ? null : ATKLOG_ALMOSTALL)
add_fingerprint(user)
diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm
index 47857ee4fa1..4497f170a2d 100644
--- a/code/_onclick/other_mobs.dm
+++ b/code/_onclick/other_mobs.dm
@@ -17,6 +17,11 @@
if(A.attack_hulk(src))
return
+ if(buckled && isstructure(buckled))
+ var/obj/structure/S = buckled
+ if(S.prevents_buckled_mobs_attacking())
+ return
+
A.attack_hand(src)
/atom/proc/attack_hand(mob/user as mob)
diff --git a/code/_onclick/rig.dm b/code/_onclick/rig.dm
deleted file mode 100644
index ff55f35cb57..00000000000
--- a/code/_onclick/rig.dm
+++ /dev/null
@@ -1,84 +0,0 @@
-
-#define MIDDLE_CLICK 0
-#define ALT_CLICK 1
-#define CTRL_CLICK 2
-#define MAX_HARDSUIT_CLICK_MODE 2
-
-/client
- var/hardsuit_click_mode = MIDDLE_CLICK
-
-/client/verb/toggle_hardsuit_mode()
- set name = "Toggle Hardsuit Activation Mode"
- set desc = "Switch between hardsuit activation modes."
- set category = "OOC"
-
- hardsuit_click_mode++
- if(hardsuit_click_mode > MAX_HARDSUIT_CLICK_MODE)
- hardsuit_click_mode = 0
-
- switch(hardsuit_click_mode)
- if(MIDDLE_CLICK)
- to_chat(src, "Hardsuit activation mode set to middle-click.")
- if(ALT_CLICK)
- to_chat(src, "Hardsuit activation mode set to alt-click.")
- if(CTRL_CLICK)
- to_chat(src, "Hardsuit activation mode set to control-click.")
- else
- // should never get here, but just in case:
- log_runtime(EXCEPTION("Bad hardsuit click mode: [hardsuit_click_mode] - expected 0 to [MAX_HARDSUIT_CLICK_MODE]"), src)
- to_chat(src, "Somehow you bugged the system. Setting your hardsuit mode to middle-click.")
- hardsuit_click_mode = MIDDLE_CLICK
-
-/mob/living/MiddleClickOn(atom/A)
- if(client && client.hardsuit_click_mode == MIDDLE_CLICK)
- if(HardsuitClickOn(A))
- return
- ..()
-
-/mob/living/AltClickOn(atom/A)
- if(client && client.hardsuit_click_mode == ALT_CLICK)
- if(HardsuitClickOn(A))
- return
- ..()
-
-/mob/living/CtrlClickOn(atom/A)
- if(client && client.hardsuit_click_mode == CTRL_CLICK)
- if(HardsuitClickOn(A))
- return
- ..()
-
-/mob/living/proc/can_use_rig()
- return 0
-
-/mob/living/carbon/human/can_use_rig()
- return 1
-
-/mob/living/carbon/brain/can_use_rig()
- return istype(loc, /obj/item/mmi)
-
-/mob/living/silicon/ai/can_use_rig()
- return istype(loc, /obj/item/aicard)
-
-/mob/living/silicon/pai/can_use_rig()
- return loc == card
-
-/mob/living/proc/HardsuitClickOn(var/atom/A, var/alert_ai = 0)
- if(!can_use_rig() || (next_move > world.time))
- return 0
- var/obj/item/rig/rig = get_rig()
- if(istype(rig) && !rig.offline && rig.selected_module)
- if(src != rig.wearer)
- if(rig.ai_can_move_suit(src, check_user_module = 1))
- message_admins("[key_name_admin(src)] is trying to force \the [key_name_admin(rig.wearer)] to use a hardsuit module.")
- else
- return 0
- rig.selected_module.engage(A, alert_ai)
- if(ismob(A)) // No instant mob attacking - though modules have their own cooldowns
- changeNext_move(CLICK_CD_MELEE)
- return 1
- return 0
-
-#undef MIDDLE_CLICK
-#undef ALT_CLICK
-#undef CTRL_CLICK
-#undef MAX_HARDSUIT_CLICK_MODE
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index f4f2f72b16b..756ce6f8527 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -61,7 +61,6 @@
var/usewhitelist = 0
var/mods_are_mentors = 0
var/load_jobs_from_txt = 0
- var/ToRban = 0
var/automute_on = 0 //enables automuting/spam prevention
var/jobs_have_minimal_access = 0 //determines whether jobs use minimal access or expanded access.
var/round_abandon_penalty_period = 30 MINUTES // Time from round start during which ghosting out is penalized
@@ -578,9 +577,6 @@
if("humans_need_surnames")
humans_need_surnames = 1
- if("tor_ban")
- ToRban = 1
-
if("automute_on")
automute_on = 1
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index 03e54df61c5..c9934b128cf 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -35,7 +35,7 @@
var/static/list/failure_strikes //How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out!
- var/offline_implications = "None" // What are the implications of this SS being offlined?
+ var/offline_implications = "None. No immediate action is needed." // What are the implications of this SS being offlined?
//Do not override
///datum/controller/subsystem/New()
diff --git a/code/controllers/subsystem/afk.dm b/code/controllers/subsystem/afk.dm
index 951a7fb1786..25ccda3349c 100644
--- a/code/controllers/subsystem/afk.dm
+++ b/code/controllers/subsystem/afk.dm
@@ -21,7 +21,8 @@ SUBSYSTEM_DEF(afk)
/datum/controller/subsystem/afk/fire()
var/list/toRemove = list()
- for(var/mob/living/carbon/human/H in GLOB.alive_mob_list)
+ for(var/thing in GLOB.human_list)
+ var/mob/living/carbon/human/H = thing
if(!H?.ckey) // Useless non ckey creatures
continue
diff --git a/code/controllers/subsystem/alarm.dm b/code/controllers/subsystem/alarm.dm
index 289adf6de40..3d91d763be3 100644
--- a/code/controllers/subsystem/alarm.dm
+++ b/code/controllers/subsystem/alarm.dm
@@ -1,31 +1,31 @@
-SUBSYSTEM_DEF(alarms)
- name = "Alarms"
- init_order = INIT_ORDER_ALARMS // 2
- offline_implications = "Alarms (Power, camera, fire, etc) will no longer be checked. No immediate action is needed."
- var/datum/alarm_handler/atmosphere/atmosphere_alarm = new()
- var/datum/alarm_handler/burglar/burglar_alarm = new()
- var/datum/alarm_handler/camera/camera_alarm = new()
- var/datum/alarm_handler/fire/fire_alarm = new()
- var/datum/alarm_handler/motion/motion_alarm = new()
- var/datum/alarm_handler/power/power_alarm = new()
- var/list/datum/alarm/all_handlers
+SUBSYSTEM_DEF(alarm)
+ name = "Alarm"
+ flags = SS_NO_INIT | SS_NO_FIRE
+ var/list/alarms = list("Motion" = list(), "Fire" = list(), "Atmosphere" = list(), "Power" = list(), "Camera" = list(), "Burglar" = list())
-/datum/controller/subsystem/alarms/Initialize(start_timeofday)
- all_handlers = list(SSalarms.atmosphere_alarm, SSalarms.burglar_alarm, SSalarms.camera_alarm, SSalarms.fire_alarm, SSalarms.motion_alarm, SSalarms.power_alarm)
- return ..()
+/datum/controller/subsystem/alarm/proc/triggerAlarm(class, area/A, list/O, obj/alarmsource)
+ var/list/L = alarms[class]
+ for(var/I in L)
+ if(I == A.name)
+ var/list/alarm = L[I]
+ var/list/sources = alarm[3]
+ if(!(alarmsource.UID() in sources))
+ sources += alarmsource.UID()
+ return TRUE
+ L[A.name] = list(get_area_name(A, TRUE), O, list(alarmsource.UID()))
+ SEND_SIGNAL(SSalarm, COMSIG_TRIGGERED_ALARM, class, A, O, alarmsource)
+ return TRUE
-/datum/controller/subsystem/alarms/fire()
- for(var/datum/alarm_handler/AH in all_handlers)
- AH.process()
+/datum/controller/subsystem/alarm/proc/cancelAlarm(class, area/A, obj/origin)
+ var/list/L = alarms[class]
+ var/cleared = FALSE
+ for(var/I in L)
+ if(I == A.name)
+ var/list/alarm = L[I]
+ var/list/srcs = alarm[3]
+ srcs -= origin.UID()
+ if(!length(srcs))
+ cleared = TRUE
+ L -= I
-/datum/controller/subsystem/alarms/proc/active_alarms()
- var/list/all_alarms = new ()
- for(var/datum/alarm_handler/AH in all_handlers)
- var/list/alarms = AH.alarms
- all_alarms += alarms
-
- return all_alarms
-
-/datum/controller/subsystem/alarms/proc/number_of_active_alarms()
- var/list/alarms = active_alarms()
- return alarms.len
+ SEND_SIGNAL(SSalarm, COMSIG_CANCELLED_ALARM, class, A, origin, cleared)
diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm
deleted file mode 100644
index 4eb468a0952..00000000000
--- a/code/controllers/subsystem/chat.dm
+++ /dev/null
@@ -1,67 +0,0 @@
-SUBSYSTEM_DEF(chat)
- name = "Chat"
- flags = SS_TICKER|SS_NO_INIT
- wait = 1
- priority = FIRE_PRIORITY_CHAT
- init_order = INIT_ORDER_CHAT
- offline_implications = "Chat messages will no longer be cleanly queued. No immediate action is needed."
-
- var/list/payload = list()
-
-
-/datum/controller/subsystem/chat/fire()
- for(var/i in payload)
- var/client/C = i
- if(C)
- C << output(payload[C], "browseroutput:output")
- payload -= C
-
- if(MC_TICK_CHECK)
- return
-
-
-/datum/controller/subsystem/chat/proc/queue(target, message, flag)
- if(!target || !message)
- return
-
- if(!istext(message))
- stack_trace("to_chat called with invalid input type")
- return
-
- if(target == world)
- target = GLOB.clients
-
- //Some macros remain in the string even after parsing and fuck up the eventual output
- message = replacetext(message, "\improper", "")
- message = replacetext(message, "\proper", "")
- message += " "
-
-
- //url_encode it TWICE, this way any UTF-8 characters are able to be decoded by the Javascript.
- //Do the double-encoding here to save nanoseconds
- var/twiceEncoded = url_encode(url_encode(message))
-
- if(islist(target))
- for(var/I in target)
- var/client/C = CLIENT_FROM_VAR(I) //Grab us a client if possible
-
- if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file.
- continue
-
- if(!C.chatOutput.loaded) //Client still loading, put their messages in a queue
- C.chatOutput.messageQueue += message
- continue
-
- payload[C] += twiceEncoded
-
- else
- var/client/C = CLIENT_FROM_VAR(target) //Grab us a client if possible
-
- if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file.
- return
-
- if(!C.chatOutput.loaded) //Client still loading, put their messages in a queue
- C.chatOutput.messageQueue += message
- return
-
- payload[C] += twiceEncoded
diff --git a/code/controllers/subsystem/cleanup.dm b/code/controllers/subsystem/cleanup.dm
new file mode 100644
index 00000000000..f5962c45547
--- /dev/null
+++ b/code/controllers/subsystem/cleanup.dm
@@ -0,0 +1,44 @@
+/**
+ * # Cleanup Subsystem
+ *
+ * For now, all it does is periodically clean the supplied global lists of any null values they may contain.
+ *
+ * Why is this important?
+ *
+ * Sometimes, these lists can gain nulls due to errors.
+ * For example, when a dead player trasitions from the `dead_mob_list` to the `alive_mob_list`, a null value may get stuck in the dead mob list.
+ * This can cause issues when other code tries to do things with the values in the list, but are instead met with null values.
+ * These problems are incredibly hard to track down and fix, so this subsystem is a solution to that.
+ */
+SUBSYSTEM_DEF(cleanup)
+ name = "Null cleanup"
+ wait = 30 SECONDS
+ flags = SS_POST_FIRE_TIMING
+ priority = FIRE_PRIORITY_CLEANUP
+ init_order = INIT_ORDER_CLEANUP
+ runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
+ offline_implications = "Certain global lists will no longer be cleared of nulls, which may result in runtimes. No immediate action is needed."
+ /// A list of global lists we want the subsystem to clean.
+ var/list/lists_to_clean
+
+/datum/controller/subsystem/cleanup/Initialize(start_timeofday)
+ // If you want this subsystem to clean out nulls from a specific list, add it here.
+ lists_to_clean = list(
+ GLOB.clients = "clients",
+ GLOB.player_list = "player_list",
+ GLOB.mob_list = "mob_list",
+ GLOB.alive_mob_list = "alive_mob_list",
+ GLOB.dead_mob_list = "dead_mob_list",
+ GLOB.human_list = "human_list",
+ GLOB.carbon_list = "carbon_list"
+ )
+ return ..()
+
+/datum/controller/subsystem/cleanup/fire(resumed)
+ for(var/L in lists_to_clean)
+ var/list/_list = L
+ var/prev_length = length(_list)
+ listclearnulls(_list)
+
+ if(length(_list) < prev_length)
+ stack_trace("Found a null value in GLOB.[lists_to_clean[_list]]!")
diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm
index 1a6e073c808..4b2ed4a71fd 100644
--- a/code/controllers/subsystem/events.dm
+++ b/code/controllers/subsystem/events.dm
@@ -40,7 +40,7 @@ SUBSYSTEM_DEF(events)
var/datum/event_container/EC = event_containers[i]
EC.process()
-/datum/controller/subsystem/events/proc/event_complete(var/datum/event/E)
+/datum/controller/subsystem/events/proc/event_complete(datum/event/E)
if(!E.event_meta) // datum/event is used here and there for random reasons, maintaining "backwards compatibility"
log_debug("Event of '[E.type]' with missing meta-data has completed.")
return
@@ -65,11 +65,11 @@ SUBSYSTEM_DEF(events)
log_debug("Event '[EM.name]' has completed at [station_time_timestamp()].")
-/datum/controller/subsystem/events/proc/delay_events(var/severity, var/delay)
+/datum/controller/subsystem/events/proc/delay_events(severity, delay)
var/datum/event_container/EC = event_containers[severity]
EC.next_event_time += delay
-/datum/controller/subsystem/events/proc/Interact(var/mob/living/user)
+/datum/controller/subsystem/events/proc/Interact(mob/living/user)
var/html = GetInteractWindow()
@@ -113,7 +113,7 @@ SUBSYSTEM_DEF(events)
html += "[EM.name] | "
html += "[EM.weight] | "
html += "[EM.min_weight] | "
- html += "[EM.max_weight] | "
+ html += "[EM.max_weight == INFINITY ? "No max" : EM.max_weight] | "
html += "[EM.one_shot] | "
html += "[EM.enabled] | "
html += "[EM.get_weight(number_active_with_role())] | "
@@ -212,38 +212,38 @@ SUBSYSTEM_DEF(events)
if(href_list["toggle_report"])
report_at_round_end = !report_at_round_end
- admin_log_and_message_admins("has [report_at_round_end ? "enabled" : "disabled"] the round end event report.")
+ log_and_message_admins("has [report_at_round_end ? "enabled" : "disabled"] the round end event report.")
else if(href_list["dec_timer"])
var/datum/event_container/EC = locate(href_list["event"])
var/decrease = (60 * RaiseToPower(10, text2num(href_list["dec_timer"])))
EC.next_event_time -= decrease
- admin_log_and_message_admins("decreased timer for [GLOB.severity_to_string[EC.severity]] events by [decrease/600] minute(s).")
+ log_and_message_admins("decreased timer for [GLOB.severity_to_string[EC.severity]] events by [decrease/600] minute(s).")
else if(href_list["inc_timer"])
var/datum/event_container/EC = locate(href_list["event"])
var/increase = (60 * RaiseToPower(10, text2num(href_list["inc_timer"])))
EC.next_event_time += increase
- admin_log_and_message_admins("increased timer for [GLOB.severity_to_string[EC.severity]] events by [increase/600] minute(s).")
+ log_and_message_admins("increased timer for [GLOB.severity_to_string[EC.severity]] events by [increase/600] minute(s).")
else if(href_list["select_event"])
var/datum/event_container/EC = locate(href_list["select_event"])
var/datum/event_meta/EM = EC.SelectEvent()
if(EM)
- admin_log_and_message_admins("has queued the [GLOB.severity_to_string[EC.severity]] event '[EM.name]'.")
+ log_and_message_admins("has queued the [GLOB.severity_to_string[EC.severity]] event '[EM.name]'.")
else if(href_list["pause"])
var/datum/event_container/EC = locate(href_list["pause"])
EC.delayed = !EC.delayed
- admin_log_and_message_admins("has [EC.delayed ? "paused" : "resumed"] countdown for [GLOB.severity_to_string[EC.severity]] events.")
+ log_and_message_admins("has [EC.delayed ? "paused" : "resumed"] countdown for [GLOB.severity_to_string[EC.severity]] events.")
else if(href_list["interval"])
var/delay = input("Enter delay modifier. A value less than one means events fire more often, higher than one less often.", "Set Interval Modifier") as num|null
if(delay && delay > 0)
var/datum/event_container/EC = locate(href_list["interval"])
EC.delay_modifier = delay
- admin_log_and_message_admins("has set the interval modifier for [GLOB.severity_to_string[EC.severity]] events to [EC.delay_modifier].")
+ log_and_message_admins("has set the interval modifier for [GLOB.severity_to_string[EC.severity]] events to [EC.delay_modifier].")
else if(href_list["stop"])
if(alert("Stopping an event may have unintended side-effects. Continue?","Stopping Event!","Yes","No") != "Yes")
return
var/datum/event/E = locate(href_list["stop"])
var/datum/event_meta/EM = E.event_meta
- admin_log_and_message_admins("has stopped the [GLOB.severity_to_string[EM.severity]] event '[EM.name]'.")
+ log_and_message_admins("has stopped the [GLOB.severity_to_string[EM.severity]] event '[EM.name]'.")
E.kill()
else if(href_list["view_events"])
selected_event_container = locate(href_list["view_events"])
@@ -265,23 +265,23 @@ SUBSYSTEM_DEF(events)
var/datum/event_meta/EM = locate(href_list["set_weight"])
EM.weight = weight
if(EM != new_event)
- admin_log_and_message_admins("has changed the weight of the [GLOB.severity_to_string[EM.severity]] event '[EM.name]' to [EM.weight].")
+ log_and_message_admins("has changed the weight of the [GLOB.severity_to_string[EM.severity]] event '[EM.name]' to [EM.weight].")
else if(href_list["toggle_oneshot"])
var/datum/event_meta/EM = locate(href_list["toggle_oneshot"])
EM.one_shot = !EM.one_shot
if(EM != new_event)
- admin_log_and_message_admins("has [EM.one_shot ? "set" : "unset"] the oneshot flag for the [GLOB.severity_to_string[EM.severity]] event '[EM.name]'.")
+ log_and_message_admins("has [EM.one_shot ? "set" : "unset"] the oneshot flag for the [GLOB.severity_to_string[EM.severity]] event '[EM.name]'.")
else if(href_list["toggle_enabled"])
var/datum/event_meta/EM = locate(href_list["toggle_enabled"])
EM.enabled = !EM.enabled
- admin_log_and_message_admins("has [EM.enabled ? "enabled" : "disabled"] the [GLOB.severity_to_string[EM.severity]] event '[EM.name]'.")
+ log_and_message_admins("has [EM.enabled ? "enabled" : "disabled"] the [GLOB.severity_to_string[EM.severity]] event '[EM.name]'.")
else if(href_list["remove"])
if(alert("This will remove the event from rotation. Continue?","Removing Event!","Yes","No") != "Yes")
return
var/datum/event_meta/EM = locate(href_list["remove"])
var/datum/event_container/EC = locate(href_list["EC"])
EC.available_events -= EM
- admin_log_and_message_admins("has removed the [GLOB.severity_to_string[EM.severity]] event '[EM.name]'.")
+ log_and_message_admins("has removed the [GLOB.severity_to_string[EM.severity]] event '[EM.name]'.")
else if(href_list["add"])
if(!new_event.name || !new_event.event_type)
return
@@ -289,12 +289,12 @@ SUBSYSTEM_DEF(events)
return
new_event.severity = selected_event_container.severity
selected_event_container.available_events += new_event
- admin_log_and_message_admins("has added \a [GLOB.severity_to_string[new_event.severity]] event '[new_event.name]' of type [new_event.event_type] with weight [new_event.weight].")
+ log_and_message_admins("has added \a [GLOB.severity_to_string[new_event.severity]] event '[new_event.name]' of type [new_event.event_type] with weight [new_event.weight].")
new_event = new
else if(href_list["clear"])
var/datum/event_container/EC = locate(href_list["clear"])
if(EC.next_event)
- admin_log_and_message_admins("has dequeued the [GLOB.severity_to_string[EC.severity]] event '[EC.next_event.name]'.")
+ log_and_message_admins("has dequeued the [GLOB.severity_to_string[EC.severity]] event '[EC.next_event.name]'.")
EC.next_event = null
Interact(usr)
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index 619508544a6..e50c372584a 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -4,7 +4,7 @@ SUBSYSTEM_DEF(garbage)
wait = 2 SECONDS
flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
- init_order = INIT_ORDER_GARBAGE
+ init_order = INIT_ORDER_GARBAGE // Why does this have an init order if it has SS_NO_INIT?
offline_implications = "Garbage collection is no longer functional, and objects will not be qdel'd. Immediate server restart recommended."
var/list/collection_timeout = list(2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level
diff --git a/code/controllers/subsystem/ghost_spawns.dm b/code/controllers/subsystem/ghost_spawns.dm
new file mode 100644
index 00000000000..d8a4f29e20d
--- /dev/null
+++ b/code/controllers/subsystem/ghost_spawns.dm
@@ -0,0 +1,276 @@
+SUBSYSTEM_DEF(ghost_spawns)
+ name = "Ghost Spawns"
+ init_order = INIT_ORDER_EVENTS
+ flags = SS_BACKGROUND
+ wait = 1 SECONDS
+ runlevels = RUNLEVEL_GAME
+ offline_implications = "Ghosts will no longer be able to respawn as event mobs (Blob, etc..). Shuttle call recommended."
+
+ /// List of polls currently ongoing, to be checked on next fire()
+ var/list/datum/candidate_poll/currently_polling
+ /// Whether there are active polls or not
+ var/polls_active = FALSE
+ /// Number of polls performed since the start
+ var/total_polls = 0
+ /// The poll that's closest to finishing
+ var/datum/candidate_poll/next_poll_to_finish
+
+/datum/controller/subsystem/ghost_spawns/fire()
+ if(!polls_active)
+ return
+ if(!currently_polling) // if polls_active is TRUE then this shouldn't happen, but still..
+ currently_polling = list()
+
+ for(var/poll in currently_polling)
+ var/datum/candidate_poll/P = poll
+ if(P.time_left() <= 0)
+ polling_finished(P)
+
+/**
+ * Polls for candidates with a question and a preview of the role
+ *
+ * This proc replaces /proc/pollCandidates.
+ * Should NEVER be used in a proc that has waitfor set to FALSE/0 (due to #define UNTIL)
+ * Arguments:
+ * * question - The question to ask to potential candidates
+ * * role - The role to poll for. Should be a ROLE_x enum. If set, potential candidates who aren't eligible will be ignored
+ * * antag_age_check - Whether to filter out potential candidates who don't have an old enough account
+ * * poll_time - How long to poll for in deciseconds
+ * * ignore_respawnability - Whether to ignore the player's respawnability
+ * * min_hours - The amount of hours needed for a potential candidate to be eligible
+ * * flash_window - Whether the poll should flash a potential candidate's game window
+ * * check_antaghud - Whether to filter out potential candidates who enabled AntagHUD
+ * * source - The atom, atom prototype, icon or mutable appearance to display as an icon in the alert
+ * * role_cleanname - The name override to display to clients
+ */
+/datum/controller/subsystem/ghost_spawns/proc/poll_candidates(question = "Would you like to play a special role?", role, antag_age_check = FALSE, poll_time = 30 SECONDS, ignore_respawnability = FALSE, min_hours = 0, flash_window = TRUE, check_antaghud = TRUE, source, role_cleanname)
+ log_debug("Polling candidates [role ? "for [role_cleanname || get_roletext(role)]" : "\"[question]\""] for [poll_time / 10] seconds")
+
+ // Start firing
+ polls_active = TRUE
+ total_polls++
+
+ var/datum/candidate_poll/P = new(role, question, poll_time)
+ LAZYADD(currently_polling, P)
+
+ // We're the poll closest to completion
+ if(!next_poll_to_finish || poll_time < next_poll_to_finish.time_left())
+ next_poll_to_finish = P
+
+ var/category = "[P.hash]_notify_action"
+
+ for(var/mob/dead/observer/M in (ignore_respawnability ? GLOB.player_list : GLOB.respawnable_list))
+ if(!is_eligible(M, role, antag_age_check, role, min_hours, check_antaghud))
+ continue
+
+ SEND_SOUND(M, 'sound/misc/notice2.ogg')
+ if(flash_window)
+ window_flash(M.client)
+
+ // If we somehow send two polls for the same mob type, but with a duration on the second one shorter than the time left on the first one,
+ // we need to keep the first one's timeout rather than use the shorter one
+ var/obj/screen/alert/notify_action/current_alert = LAZYACCESS(M.alerts, category)
+ var/alert_time = poll_time
+ var/alert_poll = P
+ if(current_alert && current_alert.timeout > (world.time + poll_time - world.tick_lag))
+ alert_time = current_alert.timeout - world.time + world.tick_lag
+ alert_poll = current_alert.poll
+
+ // Send them an on-screen alert
+ var/obj/screen/alert/notify_action/A = M.throw_alert(category, /obj/screen/alert/notify_action, timeout_override = alert_time, no_anim = TRUE)
+ if(!A)
+ continue
+
+ A.icon = ui_style2icon(M.client?.prefs.UI_style)
+ A.name = "Looking for candidates"
+ A.desc = "[question]\n\n(expires in [poll_time / 10] seconds)"
+ A.show_time_left = TRUE
+ A.poll = alert_poll
+
+ // Sign up inheritance and stacking
+ var/inherited_sign_up = FALSE
+ var/num_stack = 1
+ for(var/existing_poll in currently_polling)
+ var/datum/candidate_poll/P2 = existing_poll
+ if(P != P2 && P.hash == P2.hash)
+ // If there's already a poll for an identical mob type ongoing and the client is signed up for it, sign them up for this one
+ if(!inherited_sign_up && (M in P2.signed_up) && P.sign_up(M, TRUE))
+ A.display_signed_up()
+ inherited_sign_up = TRUE
+ // This number is used to display the number of polls the alert regroups
+ num_stack++
+ if(num_stack > 1)
+ A.display_stacks(num_stack)
+
+ // Image to display
+ var/image/I
+ if(source)
+ if(!ispath(source))
+ var/atom/S = source
+ var/old_layer = S.layer
+ var/old_plane = S.plane
+
+ S.layer = FLOAT_LAYER
+ S.plane = FLOAT_PLANE
+ A.overlays += S
+ S.layer = old_layer
+ S.plane = old_plane
+ else
+ I = image(source, layer = FLOAT_LAYER, dir = SOUTH)
+ else
+ // Just use a generic image
+ I = image('icons/effects/effects.dmi', icon_state = "static", layer = FLOAT_LAYER, dir = SOUTH)
+
+ if(I)
+ I.layer = FLOAT_LAYER
+ I.plane = FLOAT_PLANE
+ A.overlays += I
+
+ // Chat message
+ var/act_jump = ""
+ if(isatom(source))
+ act_jump = "\[Teleport]"
+ var/act_signup = "\[Sign Up]"
+ to_chat(M, "Now looking for candidates [role ? "to play as \an [role_cleanname || get_roletext(role)]" : "\"[question]\""]. [act_jump] [act_signup]")
+
+ // Start processing it so it updates visually the timer
+ START_PROCESSING(SSprocessing, A)
+ A.process()
+
+ // Sleep until the time is up
+ UNTIL(P.finished)
+ return P.signed_up
+
+/**
+ * Returns whether an observer is eligible to be an event mob
+ *
+ * Arguments:
+ * * M - The mob to check eligibility
+ * * role - The role to check eligibility for. Checks 1. the client has enabled the role 2. the account's age for this role if antag_age_check is TRUE
+ * * antag_age_check - Whether to check the account's age or not for the given role.
+ * * role_text - The role's clean text. Used for checking job bans to determine eligibility
+ * * min_hours - The amount of minimum hours the client needs before being eligible
+ * * check_antaghud - Whether to consider a client who enabled AntagHUD ineligible or not
+ */
+/datum/controller/subsystem/ghost_spawns/proc/is_eligible(mob/M, role, antag_age_check, role_text, min_hours, check_antaghud)
+ . = FALSE
+ if(!M.key || !M.client)
+ return
+ if(role)
+ if(!(role in M.client.prefs.be_special))
+ return
+ if(antag_age_check)
+ if(!player_old_enough_antag(M.client, role))
+ return
+ if(role_text)
+ if(jobban_isbanned(M, role_text) || jobban_isbanned(M, "Syndicate"))
+ return
+ if(config.use_exp_restrictions && min_hours)
+ if(M.client.get_exp_type_num(EXP_TYPE_LIVING) < min_hours * 60)
+ return
+ if(check_antaghud && cannotPossess(M))
+ return
+
+ return TRUE
+
+/**
+ * Called by the subsystem when a poll's timer runs out
+ *
+ * Can be called manually to finish a poll prematurely
+ * Arguments:
+ * * P - The poll to finish
+ */
+/datum/controller/subsystem/ghost_spawns/proc/polling_finished(datum/candidate_poll/P)
+ // Trim players who aren't eligible anymore
+ var/len_pre_trim = length(P.signed_up)
+ P.trim_candidates()
+ log_debug("Candidate poll [P.role ? "for [get_roletext(P.role)]" : "\"[P.question]\""] finished. [len_pre_trim] players signed up, [length(P.signed_up)] after trimming")
+
+ P.finished = TRUE
+ currently_polling -= P
+
+ // Determine which is the next poll closest the completion or "disable" firing if there's none
+ if(!length(currently_polling))
+ polls_active = FALSE
+ next_poll_to_finish = null
+ else if(P == next_poll_to_finish)
+ next_poll_to_finish = null
+ for(var/poll in currently_polling)
+ var/datum/candidate_poll/P2 = poll
+ if(!next_poll_to_finish || P2.time_left() < next_poll_to_finish.time_left())
+ next_poll_to_finish = P2
+
+/datum/controller/subsystem/ghost_spawns/stat_entry(msg)
+ msg += "Active: [length(currently_polling)] | Total: [total_polls]"
+ if(next_poll_to_finish)
+ msg += " | Next: [DisplayTimeText(next_poll_to_finish.time_left())] ([length(next_poll_to_finish.signed_up)] candidates)"
+ ..(msg)
+
+// The datum that describes one instance of candidate polling
+/datum/candidate_poll
+ var/role // The role the poll is for
+ var/question // The question asked to observers
+ var/duration // The duration of the poll
+ var/list/mob/dead/observer/signed_up // The players who signed up to this poll
+ var/time_started // The world.time at which the poll was created
+ var/finished = FALSE // Whether the polling is finished
+ var/hash // Used to categorize in the alerts system
+
+/datum/candidate_poll/New(polled_role, polled_question, poll_duration)
+ role = polled_role
+ question = polled_question
+ duration = poll_duration
+ signed_up = list()
+ time_started = world.time
+ hash = copytext(md5("[question]_[role ? role : "0"]"), 1, 7)
+ return ..()
+
+/**
+ * Attempts to sign a (controlled) mob up
+ *
+ * Will fail if the mob is already signed up or the poll's timer ran out.
+ * Does not check for eligibility
+ * Arguments:
+ * * M - The (controlled) mob to sign up
+ * * silent - Whether no messages should appear or not. If not TRUE, signing up to this poll will also sign the mob up for identical polls
+ */
+/datum/candidate_poll/proc/sign_up(mob/dead/observer/M, silent = FALSE)
+ . = FALSE
+ if(!istype(M) || !M.key || !M.client)
+ return
+ if(M in signed_up)
+ if(!silent)
+ to_chat(M, "You have already signed up for this!")
+ return
+ if(time_left() <= 0)
+ if(!silent)
+ to_chat(M, "Sorry, you were too late for the consideration!")
+ SEND_SOUND(M, 'sound/machines/buzz-sigh.ogg')
+ return
+
+ signed_up += M
+ if(!silent)
+ to_chat(M, "You have signed up for this role! A candidate will be picked randomly soon..")
+ // Sign them up for any other polls with the same mob type
+ for(var/existing_poll in SSghost_spawns.currently_polling)
+ var/datum/candidate_poll/P = existing_poll
+ if(src != P && hash == P.hash && !(M in P.signed_up))
+ P.sign_up(M, TRUE)
+
+ return TRUE
+
+/**
+ * Deletes any candidates who may have disconnected from the list
+ */
+/datum/candidate_poll/proc/trim_candidates()
+ listclearnulls(signed_up)
+ for(var/mob in signed_up)
+ var/mob/M = mob
+ if(!M.key || !M.client)
+ signed_up -= M
+
+/**
+ * Returns the time left for a poll
+ */
+/datum/candidate_poll/proc/time_left()
+ return duration - (world.time - time_started)
diff --git a/code/controllers/subsystem/jobs.dm b/code/controllers/subsystem/jobs.dm
index ef89317d0f5..729c4dbe67b 100644
--- a/code/controllers/subsystem/jobs.dm
+++ b/code/controllers/subsystem/jobs.dm
@@ -27,7 +27,7 @@ SUBSYSTEM_DEF(jobs)
/datum/controller/subsystem/jobs/fire()
if(!config.sql_enabled || !config.use_exp_tracking)
return
- update_exp(5,0)
+ INVOKE_ASYNC(GLOBAL_PROC, /.proc/update_exp, 5, 0)
/datum/controller/subsystem/jobs/proc/SetupOccupations(var/list/faction = list("Station"))
occupations = list()
@@ -620,20 +620,32 @@ SUBSYSTEM_DEF(jobs)
var/mob/M = tgtcard.getPlayer()
for(var/datum/job/job in occupations)
if(tgtcard.assignment && tgtcard.assignment == job.title)
- jobs_to_formats[job.title] = "disabled" // the job they already have is pre-selected
+ jobs_to_formats[job.title] = "green" // the job they already have is pre-selected
+ else if(tgtcard.assignment == "Demoted" || tgtcard.assignment == "Terminated")
+ jobs_to_formats[job.title] = "grey"
else if(!job.would_accept_job_transfer_from_player(M))
- jobs_to_formats[job.title] = "linkDiscourage" // jobs which are karma-locked and not unlocked for this player are discouraged
+ jobs_to_formats[job.title] = "grey" // jobs which are karma-locked and not unlocked for this player are discouraged
else if((job.title in GLOB.command_positions) && istype(M) && M.client && job.available_in_playtime(M.client))
- jobs_to_formats[job.title] = "linkDiscourage" // command jobs which are playtime-locked and not unlocked for this player are discouraged
+ jobs_to_formats[job.title] = "grey" // command jobs which are playtime-locked and not unlocked for this player are discouraged
else if(job.total_positions && !job.current_positions && job.title != "Civilian")
- jobs_to_formats[job.title] = "linkEncourage" // jobs with nobody doing them at all are encouraged
+ jobs_to_formats[job.title] = "teal" // jobs with nobody doing them at all are encouraged
else if(job.total_positions >= 0 && job.current_positions >= job.total_positions)
- jobs_to_formats[job.title] = "linkDiscourage" // jobs that are full (no free positions) are discouraged
+ jobs_to_formats[job.title] = "grey" // jobs that are full (no free positions) are discouraged
+ if(tgtcard.assignment == "Demoted" || tgtcard.assignment == "Terminated")
+ jobs_to_formats["Custom"] = "grey"
return jobs_to_formats
-/datum/controller/subsystem/jobs/proc/log_job_transfer(transferee, oldvalue, newvalue, whodidit)
- id_change_records["[id_change_counter]"] = list("transferee" = transferee, "oldvalue" = oldvalue, "newvalue" = newvalue, "whodidit" = whodidit, "timestamp" = station_time_timestamp())
+
+/datum/controller/subsystem/jobs/proc/log_job_transfer(transferee, oldvalue, newvalue, whodidit, reason)
+ id_change_records["[id_change_counter]"] = list(
+ "transferee" = transferee,
+ "oldvalue" = oldvalue,
+ "newvalue" = newvalue,
+ "whodidit" = whodidit,
+ "timestamp" = station_time_timestamp(),
+ "reason" = reason
+ )
id_change_counter++
/datum/controller/subsystem/jobs/proc/slot_job_transfer(oldtitle, newtitle)
@@ -644,43 +656,54 @@ SUBSYSTEM_DEF(jobs)
oldjobdatum.current_positions--
newjobdatum.current_positions++
+/datum/controller/subsystem/jobs/proc/notify_dept_head(jobtitle, antext)
+ // Used to notify the department head of jobtitle X that their employee was brigged, demoted or terminated
+ if(!jobtitle || !antext)
+ return
+ var/datum/job/tgt_job = GetJob(jobtitle)
+ if(!tgt_job)
+ return
+ if(!tgt_job.department_head[1])
+ return
+ var/boss_title = tgt_job.department_head[1]
+ var/obj/item/pda/target_pda
+ for(var/obj/item/pda/check_pda in GLOB.PDAs)
+ if(check_pda.ownrank == boss_title)
+ target_pda = check_pda
+ break
+ if(!target_pda)
+ return
+ var/datum/data/pda/app/messenger/PM = target_pda.find_program(/datum/data/pda/app/messenger)
+ if(PM && PM.can_receive())
+ PM.notify("Automated Notification: \"[antext]\" (Unable to Reply)", 0) // the 0 means don't make the PDA flash
-/datum/controller/subsystem/jobs/proc/fetch_transfer_record_html(var/centcom)
- var/record_html = ""
+/datum/controller/subsystem/jobs/proc/notify_by_name(target_name, antext)
+ // Used to notify a specific crew member based on their real_name
+ if(!target_name || !antext)
+ return
+ var/obj/item/pda/target_pda
+ for(var/obj/item/pda/check_pda in GLOB.PDAs)
+ if(check_pda.owner == target_name)
+ target_pda = check_pda
+ break
+ if(!target_pda)
+ return
+ var/datum/data/pda/app/messenger/PM = target_pda.find_program(/datum/data/pda/app/messenger)
+ if(PM && PM.can_receive())
+ PM.notify("Automated Notification: \"[antext]\" (Unable to Reply)", 0) // the 0 means don't make the PDA flash
- var/table_headers = list("Crewman", "Old Rank", "New Rank", "Authorized By", "Time")
- var/hidden_fields = list("deletedby")
- if(centcom)
- table_headers += "Deleted By"
- record_html += ""
- for(var/thisheader in table_headers)
- record_html += "| [thisheader] | "
- record_html += " "
-
- var/visible_record_count = 0
+/datum/controller/subsystem/jobs/proc/format_job_change_records(centcom)
+ var/list/formatted = list()
for(var/thisid in id_change_records)
var/thisrecord = id_change_records[thisid]
-
if(thisrecord["deletedby"] && !centcom)
continue
-
- record_html += ""
+ var/list/newlist = list()
for(var/lkey in thisrecord)
- if(lkey in hidden_fields)
- if(centcom)
- record_html += "| [thisrecord[lkey]] | "
- else
- continue
- else
- record_html += "[thisrecord[lkey]] | "
- record_html += " "
- visible_record_count++
+ newlist[lkey] = thisrecord[lkey]
+ formatted.Add(list(newlist))
+ return formatted
- record_html += " "
-
- if(!visible_record_count)
- return "No records on file yet."
- return record_html
/datum/controller/subsystem/jobs/proc/delete_log_records(sourceuser, delete_all)
. = 0
diff --git a/code/controllers/subsystem/nano_mob_hunter.dm b/code/controllers/subsystem/nano_mob_hunter.dm
index 19b57f59a70..b8a5d09e0b5 100644
--- a/code/controllers/subsystem/nano_mob_hunter.dm
+++ b/code/controllers/subsystem/nano_mob_hunter.dm
@@ -2,6 +2,7 @@ SUBSYSTEM_DEF(mob_hunt)
name = "Nano-Mob Hunter GO Server"
init_order = INIT_ORDER_NANOMOB
priority = FIRE_PRIORITY_NANOMOB // Low priority, no need for MC_TICK_CHECK due to extremely low performance impact.
+ flags = SS_NO_INIT
offline_implications = "Nano-Mob Hunter will no longer spawn mobs. No immediate action is needed."
var/max_normal_spawns = 15 //change this to adjust the number of normal spawns that can exist at one time. trapped spawns (from traitors) don't count towards this
var/list/normal_spawns = list()
diff --git a/code/controllers/subsystem/parallax.dm b/code/controllers/subsystem/parallax.dm
index 9135f62b03f..1e3c8aca0d9 100644
--- a/code/controllers/subsystem/parallax.dm
+++ b/code/controllers/subsystem/parallax.dm
@@ -34,7 +34,7 @@ SUBSYSTEM_DEF(parallax)
for (A; isloc(A.loc) && !isturf(A.loc); A = A.loc);
if(A != C.movingmob)
- if(C.movingmob != null)
+ if(C.movingmob != null && C.movingmob.client_mobs_in_contents)
C.movingmob.client_mobs_in_contents -= C.mob
UNSETEMPTY(C.movingmob.client_mobs_in_contents)
LAZYINITLIST(A.client_mobs_in_contents)
diff --git a/code/controllers/subsystem/dcs.dm b/code/controllers/subsystem/processing/dcs.dm
similarity index 90%
rename from code/controllers/subsystem/dcs.dm
rename to code/controllers/subsystem/processing/dcs.dm
index 09dea24071f..a223f4676f6 100644
--- a/code/controllers/subsystem/dcs.dm
+++ b/code/controllers/subsystem/processing/dcs.dm
@@ -3,6 +3,8 @@ PROCESSING_SUBSYSTEM_DEF(dcs)
flags = SS_NO_INIT
var/list/elements_by_type = list()
+ // Update this if you add in components which actually use this as a processor
+ offline_implications = "This SS doesnt actually process anything yet. No immediate action is needed."
/datum/controller/subsystem/processing/dcs/Recover()
comp_lookup = SSdcs.comp_lookup
diff --git a/code/controllers/subsystem/processing/fastprocess.dm b/code/controllers/subsystem/processing/fastprocess.dm
index 9622e021469..37761ca8d60 100644
--- a/code/controllers/subsystem/processing/fastprocess.dm
+++ b/code/controllers/subsystem/processing/fastprocess.dm
@@ -4,3 +4,4 @@ PROCESSING_SUBSYSTEM_DEF(fastprocess)
name = "Fast Processing"
wait = 2
stat_tag = "FP"
+ offline_implications = "Objects using the 'Fast Processing' processor will no longer process. Shuttle call recommended."
diff --git a/code/controllers/subsystem/processing/instruments.dm b/code/controllers/subsystem/processing/instruments.dm
new file mode 100644
index 00000000000..3d571d2a13d
--- /dev/null
+++ b/code/controllers/subsystem/processing/instruments.dm
@@ -0,0 +1,86 @@
+PROCESSING_SUBSYSTEM_DEF(instruments)
+ name = "Instruments"
+ init_order = INIT_ORDER_INSTRUMENTS
+ wait = 1
+ flags = SS_TICKER|SS_BACKGROUND|SS_KEEP_TIMING
+ offline_implications = "Instruments will no longer play. No immediate action is needed."
+
+ /// List of all instrument data, associative id = datum
+ var/list/datum/instrument/instrument_data
+ /// List of all song datums.
+ var/list/datum/song/songs
+ /// Max lines in songs
+ var/musician_maxlines = 600
+ /// Max characters per line in songs
+ var/musician_maxlinechars = 300
+ /// Deciseconds between hearchecks. Too high and instruments seem to lag when people are moving around in terms of who can hear it. Too low and the server lags from this.
+ var/musician_hearcheck_mindelay = 5
+ /// Maximum instrument channels total instruments are allowed to use. This is so you don't have instruments deadlocking all sound channels.
+ var/max_instrument_channels = MAX_INSTRUMENT_CHANNELS
+ /// Current number of channels allocated for instruments
+ var/current_instrument_channels = 0
+ /// Single cached list for synthesizer instrument ids, so you don't have to have a new list with every synthesizer.
+ var/list/synthesizer_instrument_ids
+
+/datum/controller/subsystem/processing/instruments/Initialize()
+ initialize_instrument_data()
+ synthesizer_instrument_ids = get_allowed_instrument_ids()
+ return ..()
+
+/**
+ * Initializes all instrument datums
+ */
+/datum/controller/subsystem/processing/instruments/proc/initialize_instrument_data()
+ instrument_data = list()
+ for(var/path in subtypesof(/datum/instrument))
+ var/datum/instrument/I = path
+ if(initial(I.abstract_type) == path)
+ continue
+ I = new path
+ I.Initialize()
+ if(!I.id)
+ qdel(I)
+ continue
+ else
+ instrument_data[I.id] = I
+ CHECK_TICK
+
+/**
+ * Reserves a sound channel for a given instrument datum
+ *
+ * Arguments:
+ * * I - The instrument datum
+ */
+/datum/controller/subsystem/processing/instruments/proc/reserve_instrument_channel(datum/instrument/I)
+ if(current_instrument_channels > max_instrument_channels)
+ return
+ . = SSsounds.reserve_sound_channel(I)
+ if(!isnull(.))
+ current_instrument_channels++
+
+/**
+ * Called when a datum/song is created
+ *
+ * Arguments:
+ * * S - The created datum/song
+ */
+/datum/controller/subsystem/processing/instruments/proc/on_song_new(datum/song/S)
+ LAZYADD(songs, S)
+
+/**
+ * Called when a datum/song is deleted
+ *
+ * Arguments:
+ * * S - The deleted datum/song
+ */
+/datum/controller/subsystem/processing/instruments/proc/on_song_del(datum/song/S)
+ LAZYREMOVE(songs, S)
+
+/**
+ * Returns the instrument datum at the given ID or path
+ *
+ * Arguments:
+ * * id_or_path - The ID or path of the instrument
+ */
+/datum/controller/subsystem/processing/instruments/proc/get_instrument(id_or_path)
+ return instrument_data["[id_or_path]"]
diff --git a/code/controllers/subsystem/processing/obj.dm b/code/controllers/subsystem/processing/obj.dm
index 26021fb267a..2a05c04af58 100644
--- a/code/controllers/subsystem/processing/obj.dm
+++ b/code/controllers/subsystem/processing/obj.dm
@@ -3,3 +3,4 @@ PROCESSING_SUBSYSTEM_DEF(obj)
priority = FIRE_PRIORITY_OBJ
flags = SS_NO_INIT
wait = 20
+ offline_implications = "Objects using the 'Objects' processor will no longer process. Shuttle call recommended."
diff --git a/code/controllers/subsystem/processing/processing.dm b/code/controllers/subsystem/processing/processing.dm
index a8bc823bbbe..5302314589d 100644
--- a/code/controllers/subsystem/processing/processing.dm
+++ b/code/controllers/subsystem/processing/processing.dm
@@ -9,6 +9,7 @@ SUBSYSTEM_DEF(processing)
var/stat_tag = "P" //Used for logging
var/list/processing = list()
var/list/currentrun = list()
+ offline_implications = "Objects using the default processor will no longer process. Shuttle call recommended."
/datum/controller/subsystem/processing/stat_entry()
..("[stat_tag]:[processing.len]")
diff --git a/code/controllers/subsystem/shuttles.dm b/code/controllers/subsystem/shuttles.dm
index 247d71d05b8..c86f988bcd4 100644
--- a/code/controllers/subsystem/shuttles.dm
+++ b/code/controllers/subsystem/shuttles.dm
@@ -14,9 +14,9 @@ SUBSYSTEM_DEF(shuttle)
//emergency shuttle stuff
var/obj/docking_port/mobile/emergency/emergency
var/obj/docking_port/mobile/emergency/backup/backup_shuttle
- var/emergencyCallTime = 6000 //time taken for emergency shuttle to reach the station when called (in deciseconds)
- var/emergencyDockTime = 1800 //time taken for emergency shuttle to leave again once it has docked (in deciseconds)
- var/emergencyEscapeTime = 1200 //time taken for emergency shuttle to reach a safe distance after leaving station (in deciseconds)
+ var/emergencyCallTime = SHUTTLE_CALLTIME //time taken for emergency shuttle to reach the station when called (in deciseconds)
+ var/emergencyDockTime = SHUTTLE_DOCKTIME //time taken for emergency shuttle to leave again once it has docked (in deciseconds)
+ var/emergencyEscapeTime = SHUTTLE_ESCAPETIME //time taken for emergency shuttle to reach a safe distance after leaving station (in deciseconds)
var/emergency_sec_level_time = 0 // time sec level was last raised to red or higher
var/area/emergencyLastCallLoc
var/emergencyNoEscape
@@ -31,7 +31,7 @@ SUBSYSTEM_DEF(shuttle)
var/points_per_intel = 250 //points gained per intel returned
var/points_per_plasma = 5 //points gained per plasma returned
var/points_per_design = 25 //points gained per research design returned
- var/centcom_message = "" //Remarks from Centcom on how well you checked the last order.
+ var/centcom_message = null //Remarks from Centcom on how well you checked the last order.
var/list/discoveredPlants = list() //Typepaths for unusual plants we've already sent CentComm, associated with their potencies
var/list/techLevels = list()
var/list/researchDesigns = list()
@@ -61,6 +61,8 @@ SUBSYSTEM_DEF(shuttle)
supply_packs["[P.type]"] = P
initial_move()
+ centcom_message = "---[station_time_timestamp()]--- Remember to stamp and send back the supply manifests. "
+
return ..()
/datum/controller/subsystem/shuttle/stat_entry(msg)
@@ -93,6 +95,11 @@ SUBSYSTEM_DEF(shuttle)
return S
WARNING("couldn't find dock with id: [id]")
+/datum/controller/subsystem/shuttle/proc/secondsToRefuel()
+ var/elapsed = world.time - SSticker.round_start_time
+ var/remaining = round((config.shuttle_refuel_delay - elapsed) / 10)
+ return remaining > 0 ? remaining : 0
+
/datum/controller/subsystem/shuttle/proc/requestEvac(mob/user, call_reason)
if(!emergency)
WARNING("requestEvac(): There is no emergency shuttle, but the shuttle was called. Using the backup shuttle instead.")
@@ -107,7 +114,7 @@ SUBSYSTEM_DEF(shuttle)
return
emergency = backup_shuttle
- if(world.time - SSticker.round_start_time < config.shuttle_refuel_delay)
+ if(secondsToRefuel())
to_chat(user, "The emergency shuttle is refueling. Please wait another [abs(round(((world.time - SSticker.round_start_time) - config.shuttle_refuel_delay)/600))] minutes before trying again.")
return
@@ -131,7 +138,7 @@ SUBSYSTEM_DEF(shuttle)
call_reason = trim(html_encode(call_reason))
if(length(call_reason) < CALL_SHUTTLE_REASON_LENGTH)
- to_chat(user, "You must provide a reason.")
+ to_chat(user, "Reason is too short. [CALL_SHUTTLE_REASON_LENGTH] character minimum.")
return
var/area/signal_origin = get_area(user)
@@ -192,7 +199,7 @@ SUBSYSTEM_DEF(shuttle)
var/obj/machinery/computer/communications/C = thing
if(C.stat & BROKEN)
continue
- else if(istype(thing, /datum/computer_file/program/comm) || istype(thing, /obj/item/circuitboard/communications))
+ else if(istype(thing, /obj/item/circuitboard/communications))
continue
var/turf/T = get_turf(thing)
@@ -247,7 +254,7 @@ SUBSYSTEM_DEF(shuttle)
/datum/controller/subsystem/shuttle/proc/generateSupplyOrder(packId, _orderedby, _orderedbyRank, _comment, _crates)
if(!packId)
return
- var/datum/supply_packs/P = supply_packs["[packId]"]
+ var/datum/supply_packs/P = locateUID(packId)
if(!P)
return
diff --git a/code/controllers/subsystem/sounds.dm b/code/controllers/subsystem/sounds.dm
new file mode 100644
index 00000000000..33d97fcfe04
--- /dev/null
+++ b/code/controllers/subsystem/sounds.dm
@@ -0,0 +1,165 @@
+#define DATUMLESS "NO_DATUM"
+
+SUBSYSTEM_DEF(sounds)
+ name = "Sounds"
+ init_order = INIT_ORDER_SOUNDS
+ flags = SS_NO_FIRE
+ offline_implications = "Sounds may not play correctly. Shuttle call recommended."
+
+ var/using_channels_max = CHANNEL_HIGHEST_AVAILABLE // BYOND max channels
+ /// Amount of channels to reserve for random usage rather than reservations being allowed to reserve all channels. Also a nice safeguard for when someone screws up.
+ var/random_channels_min = 50
+ // Hey uh these two needs to be initialized fast because the whole "things get deleted before init" thing.
+ /// Assoc list, "[channel]" = either the datum using it or TRUE for an unsafe-reserved (datumless reservation) channel
+ var/list/using_channels
+ /// Assoc list datum = list(channel1, channel2, ...) for what channels something reserved.
+ var/list/using_channels_by_datum
+ // Special datastructure for fast channel management
+ /// List of all channels as numbers
+ var/list/channel_list
+ /// Associative list of all reserved channels associated to their position. "[channel_number]" = index as number
+ var/list/reserved_channels
+ /// lower iteration position - Incremented and looped to get "random" sound channels for normal sounds. The channel at this index is returned when asking for a random channel.
+ var/channel_random_low
+ /// higher reserve position - decremented and incremented to reserve sound channels, anything above this is reserved. The channel at this index is the highest unreserved channel.
+ var/channel_reserve_high
+
+/datum/controller/subsystem/sounds/Initialize()
+ setup_available_channels()
+ return ..()
+
+/**
+ * Sets up all available sound channels
+ */
+/datum/controller/subsystem/sounds/proc/setup_available_channels()
+ channel_list = list()
+ reserved_channels = list()
+ using_channels = list()
+ using_channels_by_datum = list()
+ for(var/i in 1 to using_channels_max)
+ channel_list += i
+ channel_random_low = 1
+ channel_reserve_high = length(channel_list)
+
+/**
+ * Removes a channel from using list
+ *
+ * Arguments:
+ * * channel - The channel number
+ */
+/datum/controller/subsystem/sounds/proc/free_sound_channel(channel)
+ var/text_channel = num2text(channel)
+ var/using = using_channels[text_channel]
+ using_channels -= text_channel
+ if(!using) // datum channel
+ using_channels_by_datum[using] -= channel
+ if(!length(using_channels_by_datum[using]))
+ using_channels_by_datum -= using
+ free_channel(channel)
+
+/**
+ * Frees all the channels a datum is using
+ *
+ * Arguments:
+ * * D - The datum
+ */
+/datum/controller/subsystem/sounds/proc/free_datum_channels(datum/D)
+ var/list/L = using_channels_by_datum[D]
+ if(!L)
+ return
+ for(var/channel in L)
+ using_channels -= num2text(channel)
+ free_channel(channel)
+ using_channels_by_datum -= D
+
+/**
+ * Frees all datumless channels
+ */
+/datum/controller/subsystem/sounds/proc/free_datumless_channels()
+ free_datum_channels(DATUMLESS)
+
+/**
+ * NO AUTOMATIC CLEANUP - If you use this, you better manually free it later!
+ *
+ * Returns an integer for channel
+ */
+/datum/controller/subsystem/sounds/proc/reserve_sound_channel_datumless()
+ . = reserve_channel()
+ if(!.) // oh no..
+ return FALSE
+ var/text_channel = num2text(.)
+ using_channels[text_channel] = DATUMLESS
+ LAZYADD(using_channels_by_datum[DATUMLESS], .)
+
+/**
+ * Reserves a channel for a datum. Automatic cleanup only when the datum is deleted.
+ *
+ * Returns an integer for channel
+ * Arguments:
+ * * D - The datum
+ */
+/datum/controller/subsystem/sounds/proc/reserve_sound_channel(datum/D)
+ if(!D) // i don't like typechecks but someone will fuck it up
+ CRASH("Attempted to reserve sound channel without datum using the managed proc.")
+ . = reserve_channel()
+ if(!.)
+ return FALSE
+ var/text_channel = num2text(.)
+ using_channels[text_channel] = D
+ LAZYADD(using_channels_by_datum[D], .)
+
+/**
+ * Reserves a channel and updates the datastructure. Private proc.
+ */
+/datum/controller/subsystem/sounds/proc/reserve_channel()
+ PRIVATE_PROC(TRUE)
+ if(channel_reserve_high <= random_channels_min) // out of channels
+ return
+ var/channel = channel_list[channel_reserve_high]
+ reserved_channels[num2text(channel)] = channel_reserve_high--
+ return channel
+
+/**
+ * Frees a channel and updates the datastructure. Private proc.
+ */
+/datum/controller/subsystem/sounds/proc/free_channel(number)
+ PRIVATE_PROC(TRUE)
+ var/text_channel = num2text(number)
+ var/index = reserved_channels[text_channel]
+ if(!index)
+ CRASH("Attempted to (internally) free a channel that wasn't reserved.")
+ reserved_channels -= text_channel
+ // push reserve index up, which makes it now on a channel that is reserved
+ channel_reserve_high++
+ // swap the reserved channel with the unreserved channel so the reserve index is now on an unoccupied channel and the freed channel is next to be used.
+ channel_list.Swap(channel_reserve_high, index)
+ // now, an existing reserved channel will likely (exception: unreserving last reserved channel) be at index
+ // get it, and update position.
+ var/text_reserved = num2text(channel_list[index])
+ if(!reserved_channels[text_reserved]) // if it isn't already reserved make sure we don't accidently mistakenly put it on reserved list!
+ return
+ reserved_channels[text_reserved] = index
+
+/**
+ * Random available channel, returns text
+ */
+/datum/controller/subsystem/sounds/proc/random_available_channel_text()
+ if(channel_random_low > channel_reserve_high)
+ channel_random_low = 1
+ . = "[channel_list[channel_random_low++]]"
+
+/**
+ * Random available channel, returns number
+ */
+/datum/controller/subsystem/sounds/proc/random_available_channel()
+ if(channel_random_low > channel_reserve_high)
+ channel_random_low = 1
+ . = channel_list[channel_random_low++]
+
+/**
+ * How many channels we have left
+ */
+/datum/controller/subsystem/sounds/proc/available_channels_left()
+ return length(channel_list) - random_channels_min
+
+#undef DATUMLESS
diff --git a/code/controllers/subsystem/tgui.dm b/code/controllers/subsystem/tgui.dm
index c4240021137..5c4c6ec6ff3 100644
--- a/code/controllers/subsystem/tgui.dm
+++ b/code/controllers/subsystem/tgui.dm
@@ -126,6 +126,8 @@ SUBSYSTEM_DEF(tgui)
* return int The number of UIs closed.
**/
/datum/controller/subsystem/tgui/proc/close_uis(datum/src_object)
+ if(!src_object.unique_datum_id) // First check if the datum has an UID set
+ return 0
var/src_object_key = "[src_object.UID()]"
if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
return 0 // Couldn't find any UIs for this object.
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 5d0a53c221e..3a1ba7595c0 100644
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -61,10 +61,12 @@ SUBSYSTEM_DEF(ticker)
if(GAME_STATE_STARTUP)
// This is ran as soon as the MC starts firing, and should only run ONCE, unless startup fails
round_start_time = world.time + (config.pregame_timestart * 10)
- to_chat(world, "Welcome to the pre-game lobby!")
+ to_chat(world, "Welcome to the pre-game lobby!")
to_chat(world, "Please, setup your character and select ready. Game will start in [config.pregame_timestart] seconds")
current_state = GAME_STATE_PREGAME
fire() // TG says this is a good idea
+ for(var/mob/new_player/N in GLOB.player_list)
+ N.new_player_panel_proc() // to enable the observe option
if(GAME_STATE_PREGAME)
if(!SSticker.ticker_going) // This has to be referenced like this, and I dont know why. If you dont put SSticker. it will break
return
@@ -217,11 +219,11 @@ SUBSYSTEM_DEF(ticker)
for(var/obj/effect/landmark/spacepod/random/R in L)
qdel(R)
- to_chat(world, "Enjoy the game!")
+ to_chat(world, "Enjoy the game!")
world << sound('sound/AI/welcome.ogg')// Skie
if(SSholiday.holidays)
- to_chat(world, "and...")
+ to_chat(world, "and...")
for(var/holidayname in SSholiday.holidays)
var/datum/holiday/holiday = SSholiday.holidays[holidayname]
to_chat(world, "[holiday.greet()]")
diff --git a/code/controllers/subsystem/tickets/mentor_tickets.dm b/code/controllers/subsystem/tickets/mentor_tickets.dm
index 76c7ed2dc28..706a498aa64 100644
--- a/code/controllers/subsystem/tickets/mentor_tickets.dm
+++ b/code/controllers/subsystem/tickets/mentor_tickets.dm
@@ -6,16 +6,29 @@ GLOBAL_REAL(SSmentor_tickets, /datum/controller/subsystem/tickets/mentor_tickets
/datum/controller/subsystem/tickets/mentor_tickets
name = "Mentor Tickets"
+ offline_implications = "Mentor tickets will no longer be marked as stale. No immediate action is needed."
ticket_system_name = "Mentor Tickets"
ticket_name = "Mentor Ticket"
span_class = "mentorhelp"
+ anchor_link_extra = ";is_mhelp=1"
+ ticket_help_type = "Mentorhelp"
+ ticket_help_span = "mentorhelp"
+ other_ticket_name = "Admin"
+ other_ticket_permission = R_ADMIN
close_rights = R_MENTOR | R_ADMIN
-
-/datum/controller/subsystem/tickets/mentor_tickets/message_staff(var/msg)
- message_mentorTicket(msg)
+ rights_needed = R_MENTOR | R_ADMIN | R_MOD
/datum/controller/subsystem/tickets/mentor_tickets/Initialize()
close_messages = list("- [ticket_name] Closed -",
"Please try to be as descriptive as possible in mentor helps. Mentors do not know the full situation you're in and need more information to give you a helpful response.",
"Your [ticket_name] has now been closed.")
return ..()
+
+/datum/controller/subsystem/tickets/mentor_tickets/message_staff(msg, prefix_type = NONE, important = FALSE)
+ message_mentorTicket(msg, important)
+
+/datum/controller/subsystem/tickets/mentor_tickets/create_other_system_ticket(datum/ticket/T)
+ SStickets.newTicket(get_client_by_ckey(T.client_ckey), T.content, T.raw_title)
+
+/datum/controller/subsystem/tickets/mentor_tickets/autoRespond(N)
+ return
diff --git a/code/controllers/subsystem/tickets/tickets.dm b/code/controllers/subsystem/tickets/tickets.dm
index 3aa0a8846c3..30c4c13166c 100644
--- a/code/controllers/subsystem/tickets/tickets.dm
+++ b/code/controllers/subsystem/tickets/tickets.dm
@@ -10,25 +10,40 @@
#define TICKET_RESOLVED 3
#define TICKET_STALE 4
+#define TICKET_STAFF_MESSAGE_ADMIN_CHANNEL 1
+#define TICKET_STAFF_MESSAGE_PREFIX 2
+
SUBSYSTEM_DEF(tickets)
name = "Admin Tickets"
+ init_order = INIT_ORDER_TICKETS
+ wait = 300
+ priority = FIRE_PRIORITY_TICKETS
+ offline_implications = "Admin tickets will no longer be marked as stale. No immediate action is needed."
+ flags = SS_BACKGROUND
+
var/span_class = "adminticket"
var/ticket_system_name = "Admin Tickets"
var/ticket_name = "Admin Ticket"
var/close_rights = R_ADMIN
+ var/rights_needed = R_ADMIN | R_MOD
+
+ /// Text that will be added to the anchor link
+ var/anchor_link_extra = ""
+
+ var/ticket_help_type = "Adminhelp"
+ var/ticket_help_span = "adminhelp"
+ /// The name of the other ticket type to convert to
+ var/other_ticket_name = "Mentor"
+ /// Which permission to look for when seeing if there is staff available for the other ticket type
+ var/other_ticket_permission = R_MENTOR
var/list/close_messages
- init_order = INIT_ORDER_TICKETS
- wait = 300
- priority = FIRE_PRIORITY_TICKETS
-
- flags = SS_BACKGROUND
-
var/list/allTickets = list() //make it here because someone might ahelp before the system has initialized
var/ticketCounter = 1
/datum/controller/subsystem/tickets/Initialize()
- close_messages = list("- [ticket_name] Rejected! -",
+ if(!close_messages)
+ close_messages = list("- [ticket_name] Rejected! -",
"Please try to be calm, clear, and descriptive in admin helps, do not assume the staff member has seen any related events, and clearly state the names of anybody you are reporting. If you asked a question, please ensure it was clear what you were asking.",
"Your [ticket_name] has now been closed.")
return ..()
@@ -70,24 +85,58 @@ SUBSYSTEM_DEF(tickets)
var/datum/ticket/T = i
resolveTicket(T.ticketNum)
+/**
+ * Will either make a new ticket using the given text or will add the text to an existing ticket.
+ * Staff will get a message
+ * Arguments:
+ * C - The client who requests help
+ * text - The text the client send
+ */
+/datum/controller/subsystem/tickets/proc/newHelpRequest(client/C, text)
+ var/ticketNum // Holder for the ticket number
+ var/datum/ticket/T
+ // Get the open ticket assigned to the client and add a response. If no open tickets then make a new one
+ if((T = checkForOpenTicket(C)))
+ ticketNum = T.ticketNum
+ T.addResponse(C, text)
+ T.setCooldownPeriod()
+ to_chat(C.mob, "Your [ticket_name] #[ticketNum] remains open! Visit \"My tickets\" under the Admin Tab to view it.")
+ var/url_message = makeUrlMessage(C, text, ticketNum)
+ message_staff(url_message, NONE, TRUE)
+ else
+ newTicket(C, text, text)
+
+/**
+ * Will add the URLs usable by staff to the message and return it
+ * Arguments:
+ * C - The client who send the message
+ * msg - The raw message
+ * ticketNum - Which ticket number the ticket has
+ */
+/datum/controller/subsystem/tickets/proc/makeUrlMessage(client/C, msg, ticketNum)
+ var/list/L = list()
+ L += "[ticket_help_type]: [key_name(C, TRUE, ticket_help_type)] "
+ L += "([ADMIN_QUE(C.mob,"?")]) ([ADMIN_PP(C.mob,"PP")]) ([ADMIN_VV(C.mob,"VV")]) ([ADMIN_TP(C.mob,"TP")]) ([ADMIN_SM(C.mob,"SM")]) "
+ L += "([admin_jump_link(C.mob)]) (TICKET) "
+ L += "[isAI(C.mob) ? "(CL)" : ""] (TAKE) "
+ L += "(RESOLVE) (AUTO) "
+ L += "(CONVERT) : [msg]"
+ return L.Join()
+
//Open a new ticket and populate details then add to the list of open tickets
/datum/controller/subsystem/tickets/proc/newTicket(client/C, passedContent, title)
if(!C || !passedContent)
return
- //Check if the user has an open ticket already within the cooldown period, if so we don't create a new one and re-set the cooldown period
- var/datum/ticket/existingTicket = checkForOpenTicket(C)
- if(existingTicket)
- existingTicket.setCooldownPeriod()
- to_chat(C.mob, "Your [ticket_name] #[existingTicket.ticketNum] remains open! Visit \"My tickets\" under the Admin Tab to view it.")
- return
-
if(!title)
title = passedContent
- var/datum/ticket/T = new(title, passedContent, getTicketCounterAndInc())
+ var/new_ticket_num = getTicketCounterAndInc()
+ var/url_title = makeUrlMessage(C, title, new_ticket_num)
+
+ var/datum/ticket/T = new(url_title, title, passedContent, new_ticket_num)
allTickets += T
- T.clientName = C
+ T.client_ckey = C.ckey
T.locationSent = C.mob.loc.name
T.mobControlled = C.mob
@@ -96,6 +145,8 @@ SUBSYSTEM_DEF(tickets)
var/ticket_open_sound = sound('sound/effects/adminticketopen.ogg')
SEND_SOUND(C, ticket_open_sound)
+ message_staff(url_title, NONE, TRUE)
+
//Set ticket state with key N to open
/datum/controller/subsystem/tickets/proc/openTicket(N)
var/datum/ticket/T = allTickets[N]
@@ -113,9 +164,39 @@ SUBSYSTEM_DEF(tickets)
to_chat_safe(returnClient(N), "Your [ticket_name] has now been resolved.")
return TRUE
+/datum/controller/subsystem/tickets/proc/convert_to_other_ticket(ticketId)
+ if(!check_rights(rights_needed))
+ return
+ if(alert("Are you sure to convert this ticket to an '[other_ticket_name]' ticket?",,"Yes","No") != "Yes")
+ return
+ if(!other_ticket_system_staff_check())
+ return
+ var/datum/ticket/T = allTickets[ticketId]
+ convert_ticket(T)
+
+/datum/controller/subsystem/tickets/proc/other_ticket_system_staff_check()
+ var/list/staff = staff_countup(other_ticket_permission)
+ if(!staff[1])
+ if(alert("No active staff online to answer the ticket. Are you sure you want to convert the ticket?",, "No", "Yes") != "Yes")
+ return FALSE
+ return TRUE
+
+/datum/controller/subsystem/tickets/proc/convert_ticket(datum/ticket/T)
+ T.ticketState = TICKET_CLOSED
+ var/client/C = usr.client
+ var/client/owner = get_client_by_ckey(T.client_ckey)
+ to_chat_safe(owner, list("[key_name_hidden(C)] has converted your ticket to a [other_ticket_name] ticket.",\
+ "Be sure to use the correct type of help next time!"))
+ message_staff("[C] has converted ticket number [T.ticketNum] to a [other_ticket_name] ticket.")
+ log_game("[C] has converted ticket number [T.ticketNum] to a [other_ticket_name] ticket.")
+ create_other_system_ticket(T)
+
+/datum/controller/subsystem/tickets/proc/create_other_system_ticket(datum/ticket/T)
+ var/client/C = get_client_by_ckey(T.client_ckey)
+ SSmentor_tickets.newTicket(C, T.content, T.raw_title)
/datum/controller/subsystem/tickets/proc/autoRespond(N)
- if(!check_rights(R_ADMIN|R_MOD))
+ if(!check_rights(rights_needed))
return
var/datum/ticket/T = allTickets[N]
@@ -130,6 +211,7 @@ SUBSYSTEM_DEF(tickets)
"Already Resolved" = "The problem has been resolved already.",
"Mentorhelp" = "Please redirect your question to Mentorhelp, as they are better experienced with these types of questions.",
"Happens Again" = "Thanks, let us know if it continues to happen.",
+ "Github Issue Report" = "To report a bug, please go to our Github page. Then go to 'Issues'. Then 'New Issue'. Then fill out the report form. If the report would reveal current-round information, file it after the round ends.",
"Clear Cache" = "To fix a blank screen, go to the 'Special Verbs' tab and press 'Reload UI Resources'. If that fails, clear your BYOND cache (instructions provided with 'Reload UI Resources'). If that still fails, please adminhelp again, stating you have already done the following." ,
"IC Issue" = "This is an In Character (IC) issue and will not be handled by admins. You could speak to Security, Internal Affairs, a Departmental Head, Nanotrasen Representetive, or any other relevant authority currently on station.",
"Reject" = "Reject",
@@ -142,7 +224,7 @@ SUBSYSTEM_DEF(tickets)
sorted_responses += key
var/message_key = input("Select an autoresponse. This will mark the ticket as resolved.", "Autoresponse") as null|anything in sortTim(sorted_responses, /proc/cmp_text_asc) //use sortTim and cmp_text_asc to sort alphabetically
-
+ var/client/ticket_owner = get_client_by_ckey(T.client_ckey)
switch(message_key)
if(null) //they cancelled
T.staffAssigned = initial(T.staffAssigned) //if they cancel we dont need to hold this ticket anymore
@@ -154,16 +236,19 @@ SUBSYSTEM_DEF(tickets)
C.man_up(returnClient(N))
T.lastStaffResponse = "Autoresponse: [message_key]"
resolveTicket(N)
- message_staff("[C] has auto responded to [T.clientName]\'s adminhelp with: [message_key] ")
- log_game("[C] has auto responded to [T.clientName]\'s adminhelp with: [response_phrases[message_key]]")
+ message_staff("[C] has auto responded to [ticket_owner]\'s adminhelp with: [message_key] ")
+ log_game("[C] has auto responded to [ticket_owner]\'s adminhelp with: [response_phrases[message_key]]")
+ if("Mentorhelp")
+ convert_ticket(T)
else
var/msg_sound = sound('sound/effects/adminhelp.ogg')
SEND_SOUND(returnClient(N), msg_sound)
- to_chat(returnClient(N), "[key_name_hidden(C)] is autoresponding with: [response_phrases[message_key]]")//for this we want the full value of whatever key this is to tell the player so we do response_phrases[message_key]
- message_staff("[C] has auto responded to [T.clientName]\'s adminhelp with: [message_key] ") //we want to use the short named keys for this instead of the full sentence which is why we just do message_key
+ to_chat_safe(returnClient(N), "[key_name_hidden(C)] is autoresponding with: [response_phrases[message_key]]")//for this we want the full value of whatever key this is to tell the player so we do response_phrases[message_key]
+ message_staff("[C] has auto responded to [ticket_owner]\'s adminhelp with: [message_key] ") //we want to use the short named keys for this instead of the full sentence which is why we just do message_key
T.lastStaffResponse = "Autoresponse: [message_key]"
resolveTicket(N)
- log_game("[C] has auto responded to [T.clientName]\'s adminhelp with: [response_phrases[message_key]]")
+ log_game("[C] has auto responded to [ticket_owner]\'s adminhelp with: [response_phrases[message_key]]")
+
//Set ticket state with key N to closed
/datum/controller/subsystem/tickets/proc/closeTicket(N)
var/datum/ticket/T = allTickets[N]
@@ -176,7 +261,7 @@ SUBSYSTEM_DEF(tickets)
//Check if the user already has a ticket open and within the cooldown period.
/datum/controller/subsystem/tickets/proc/checkForOpenTicket(client/C)
for(var/datum/ticket/T in allTickets)
- if(T.clientName == C && T.ticketState == TICKET_OPEN && (T.ticketCooldown > world.time))
+ if(T.client_ckey == C.ckey && T.ticketState == TICKET_OPEN && (T.ticketCooldown > world.time))
return T
return FALSE
@@ -184,7 +269,7 @@ SUBSYSTEM_DEF(tickets)
/datum/controller/subsystem/tickets/proc/checkForTicket(client/C)
var/list/tickets = list()
for(var/datum/ticket/T in allTickets)
- if(T.clientName == C && (T.ticketState == TICKET_OPEN || T.ticketState == TICKET_STALE))
+ if(T.client_ckey == C.ckey && (T.ticketState == TICKET_OPEN || T.ticketState == TICKET_STALE))
tickets += T
if(tickets.len)
return tickets
@@ -193,7 +278,7 @@ SUBSYSTEM_DEF(tickets)
//return the client of a ticket number
/datum/controller/subsystem/tickets/proc/returnClient(N)
var/datum/ticket/T = allTickets[N]
- return T.clientName
+ return get_client_by_ckey(T.client_ckey)
/datum/controller/subsystem/tickets/proc/assignStaffToTicket(client/C, N)
var/datum/ticket/T = allTickets[N]
@@ -206,9 +291,11 @@ SUBSYSTEM_DEF(tickets)
/datum/ticket
var/ticketNum // Ticket number
- var/clientName // Client which opened the ticket
+ /// ckey of the client who opened the ticket
+ var/client_ckey
var/timeOpened // Time the ticket was opened
var/title //The initial message with links
+ var/raw_title // The title without URLs added
var/list/content // content of the staff help
var/lastStaffResponse // Last staff member who responded
var/lastResponseTime // When the staff last responded
@@ -219,8 +306,9 @@ SUBSYSTEM_DEF(tickets)
var/ticketCooldown // Cooldown before allowing the user to open another ticket.
var/client/staffAssigned // Staff member who has assigned themselves to this ticket
-/datum/ticket/New(tit, cont, num)
+/datum/ticket/New(tit, raw_tit, cont, num)
title = tit
+ raw_title = raw_tit
content = list()
content += cont
timeOpened = worldtime2text()
@@ -341,7 +429,7 @@ UI STUFF
dat += "Ticket #[T.ticketNum]"
- dat += "[T.clientName] / [T.mobControlled] opened this [ticket_name] at [T.timeOpened] at location [T.locationSent]"
+ dat += "[T.client_ckey] / [T.mobControlled] opened this [ticket_name] at [T.timeOpened] at location [T.locationSent]"
dat += "Ticket Status: [status]"
dat += ""
dat += "| [T.title] | "
@@ -351,7 +439,7 @@ UI STUFF
dat += "| [T.content[i]] | "
dat += "
"
- dat += "Re-Open[check_rights(R_ADMIN|R_MOD, 0) ? "Auto": ""]Resolve
"
+ dat += "Re-Open[check_rights(rights_needed, 0) ? "Auto": ""]Resolve
"
if(!T.staffAssigned)
dat += "No staff member assigned to this [ticket_name] - Take Ticket "
@@ -366,6 +454,7 @@ UI STUFF
dat += "
"
dat += "Close Ticket"
+ dat += "Convert Ticket"
var/datum/browser/popup = new(user, "[ticket_system_name]detail", "[ticket_system_name] #[T.ticketNum]", 1000, 600)
popup.set_content(dat)
@@ -398,9 +487,21 @@ UI STUFF
to_chat(target, text)
return TRUE
-//Sends a message to the designated staff
-/datum/controller/subsystem/tickets/proc/message_staff(var/msg, var/alt = FALSE)
- message_adminTicket(msg, alt)
+/**
+ * Sends a message to the designated staff
+ * Arguments:
+ * msg - The message being send
+ * alt - If an alternative prefix should be used or not. Defaults to TICKET_STAFF_MESSAGE_PREFIX
+ * important - If the message is important. If TRUE it will ignore the CHAT_NO_TICKETLOGS preferences,
+ send a sound and flash the window. Defaults to FALSE
+ */
+/datum/controller/subsystem/tickets/proc/message_staff(msg, prefix_type = TICKET_STAFF_MESSAGE_PREFIX, important = FALSE)
+ switch(prefix_type)
+ if(TICKET_STAFF_MESSAGE_ADMIN_CHANNEL)
+ msg = "ADMIN TICKET: [msg]"
+ if(TICKET_STAFF_MESSAGE_PREFIX)
+ msg = "ADMIN TICKET: [msg]"
+ message_adminTicket(msg, important)
/datum/controller/subsystem/tickets/Topic(href, href_list)
@@ -448,7 +549,6 @@ UI STUFF
if(closeTicket(indexNum))
showDetailUI(usr, indexNum)
-
if(href_list["detailreopen"])
var/indexNum = text2num(href_list["detailreopen"])
if(openTicket(indexNum))
@@ -468,6 +568,10 @@ UI STUFF
var/indexNum = text2num(href_list["autorespond"])
autoRespond(indexNum)
+ if(href_list["convert_ticket"])
+ var/indexNum = text2num(href_list["convert_ticket"])
+ convert_to_other_ticket(indexNum)
+
if(href_list["resolveall"])
if(ticket_system_name == "Mentor Tickets")
usr.client.resolveAllMentorTickets()
@@ -479,7 +583,7 @@ UI STUFF
if(span_class == "mentorhelp")
message_staff("[usr.client] / ([usr]) has taken [ticket_name] number [index]")
else
- message_staff("[usr.client] / ([usr]) has taken [ticket_name] number [index]", TRUE)
+ message_staff("[usr.client] / ([usr]) has taken [ticket_name] number [index]", TICKET_STAFF_MESSAGE_ADMIN_CHANNEL)
to_chat_safe(returnClient(index), "Your [ticket_name] is being handled by [usr.client].")
/datum/controller/subsystem/tickets/proc/unassignTicket(index)
@@ -490,4 +594,7 @@ UI STUFF
if(span_class == "mentorhelp")
message_staff("[usr.client] / ([usr]) has unassigned [ticket_name] number [index]")
else
- message_staff("[usr.client] / ([usr]) has unassigned [ticket_name] number [index]", TRUE)
+ message_staff("[usr.client] / ([usr]) has unassigned [ticket_name] number [index]", TICKET_STAFF_MESSAGE_ADMIN_CHANNEL)
+
+#undef TICKET_STAFF_MESSAGE_ADMIN_CHANNEL
+#undef TICKET_STAFF_MESSAGE_PREFIX
diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm
index b0110258da7..0b91e6b3467 100644
--- a/code/controllers/subsystem/vote.dm
+++ b/code/controllers/subsystem/vote.dm
@@ -367,7 +367,7 @@ SUBSYSTEM_DEF(vote)
var/votedesc = capitalize(mode)
if(mode == "custom")
votedesc += " ([question])"
- admin_log_and_message_admins("cancelled the running [votedesc] vote.")
+ log_and_message_admins("cancelled the running [votedesc] vote.")
reset()
if("toggle_restart")
if(admin)
diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm
index 3eaed85c303..8d02529ae6b 100644
--- a/code/controllers/verbs.dm
+++ b/code/controllers/verbs.dm
@@ -7,7 +7,7 @@
set name = "Restart Controller"
set desc = "Restart one of the various periodic loop controllers for the game (be careful!)"
- if(!holder)
+ if(!check_rights(R_DEBUG))
return
switch(controller)
if("Master")
@@ -20,13 +20,14 @@
message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.")
/client/proc/debug_controller(controller in list("failsafe", "Master", "Ticker", "Air", "Jobs", "Sun", "Radio", "Configuration", "pAI",
- "Cameras", "Garbage", "Event", "Alarm", "Nano", "Vote", "Fires",
+ "Cameras", "Garbage", "Event", "Nano", "Vote", "Fires",
"Mob", "NPC Pool", "Shuttle", "Timer", "Weather", "Space", "Mob Hunt Server","Input"))
set category = "Debug"
set name = "Debug Controller"
set desc = "Debug the various periodic loop controllers for the game (be careful!)"
- if(!holder) return
+ if(!check_rights(R_DEBUG))
+ return
switch(controller)
if("failsafe")
debug_variables(Failsafe)
@@ -64,9 +65,6 @@
if("Event")
debug_variables(SSevents)
feedback_add_details("admin_verb","DEvent")
- if("Alarm")
- debug_variables(SSalarms)
- feedback_add_details("admin_verb", "DAlarm")
if("Nano")
debug_variables(SSnanoui)
feedback_add_details("admin_verb","DNano")
diff --git a/code/datums/action.dm b/code/datums/action.dm
index c1802da47c1..84702757e86 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -162,6 +162,14 @@
/datum/action/item_action/print_report
name = "Print Report"
+/datum/action/item_action/print_forensic_report
+ name = "Print Report"
+ button_icon_state = "scanner_print"
+ use_itemicon = FALSE
+
+/datum/action/item_action/clear_records
+ name = "Clear Scanner Records"
+
/datum/action/item_action/toggle_gunlight
name = "Toggle Gunlight"
@@ -190,9 +198,6 @@
/datum/action/item_action/toggle_mister
name = "Toggle Mister"
-/datum/action/item_action/toggle_headphones
- name = "Toggle Headphones"
-
/datum/action/item_action/toggle_helmet_light
name = "Toggle Helmet Light"
@@ -232,19 +237,6 @@
button.name = name
..()
-/datum/action/item_action/synthswitch
- name = "Change Synthesizer Instrument"
- desc = "Change the type of instrument your synthesizer is playing as."
-
-/datum/action/item_action/synthswitch/Trigger()
- if(istype(target, /obj/item/instrument/piano_synth))
- var/obj/item/instrument/piano_synth/synth = target
- var/chosen = input("Choose the type of instrument you want to use", "Instrument Selection", "piano") as null|anything in synth.insTypes
- if(!synth.insTypes[chosen])
- return
- return synth.changeInstrument(chosen)
- return ..()
-
/datum/action/item_action/vortex_recall
name = "Vortex Recall"
desc = "Recall yourself, and anyone nearby, to an attuned hierophant beacon at any time. If the beacon is still attached, will detach it."
@@ -257,6 +249,9 @@
return 0
return ..()
+/datum/action/item_action/change_headphones_song
+ name = "Change Headphones Song"
+
/datum/action/item_action/toggle
/datum/action/item_action/toggle/New(Target)
@@ -463,6 +458,7 @@
/datum/action/spell_action
check_flags = 0
background_icon_state = "bg_spell"
+ var/recharge_text_color = "#FFFFFF"
/datum/action/spell_action/New(Target)
..()
@@ -500,6 +496,18 @@
return spell.can_cast(owner)
return 0
+/datum/action/spell_action/UpdateButtonIcon()
+ if(button && !(. = ..()))
+ var/obj/effect/proc_holder/spell/S = target
+ if(!istype(S))
+ return
+ var/progress = S.get_availability_percentage()
+ var/col_val_high = 72 * progress + 128
+ var/col_val_low = 200 * progress
+ button.maptext = "[round_down(progress * 100)]% "
+ button.color = rgb(col_val_high, col_val_low, col_val_low, col_val_high)
+ else
+ button.maptext = null
/*
/datum/action/spell_action/alien
diff --git a/code/datums/beam.dm b/code/datums/beam.dm
index 19935bda130..3b5c9d2f5fb 100644
--- a/code/datums/beam.dm
+++ b/code/datums/beam.dm
@@ -131,6 +131,5 @@
/atom/proc/Beam(atom/BeamTarget,icon_state="b_beam",icon='icons/effects/beam.dmi',time=50, maxdistance=10,beam_type=/obj/effect/ebeam,beam_sleep_time=3)
var/datum/beam/newbeam = new(src,BeamTarget,icon,icon_state,time,maxdistance,beam_type,beam_sleep_time)
- spawn(0)
- newbeam.Start()
+ INVOKE_ASYNC(newbeam, /datum/beam.proc/Start)
return newbeam
diff --git a/code/datums/cache/air_alarm.dm b/code/datums/cache/air_alarm.dm
index 2edc0792a34..fd9e529d70a 100644
--- a/code/datums/cache/air_alarm.dm
+++ b/code/datums/cache/air_alarm.dm
@@ -1,3 +1,5 @@
+#define AIR_ALARM_DATA_CACHE_DURATION 10 SECONDS
+
GLOBAL_DATUM_INIT(air_alarm_repository, /datum/repository/air_alarm, new())
/datum/repository/air_alarm/proc/air_alarm_data(var/list/monitored_alarms, var/refresh = 0, var/obj/machinery/alarm/passed_alarm)
@@ -8,7 +10,7 @@ GLOBAL_DATUM_INIT(air_alarm_repository, /datum/repository/air_alarm, new())
cache_entry = new/datum/cache_entry
cache_data = cache_entry
- if(!refresh)
+ if(!refresh && cache_entry.timestamp + AIR_ALARM_DATA_CACHE_DURATION > world.time)
return cache_entry.data
if(SSticker && SSticker.current_state < GAME_STATE_PLAYING && istype(passed_alarm)) // Generating the list for the first time as the game hasn't started - no need to run through the machines list everything every time
@@ -29,3 +31,5 @@ GLOBAL_DATUM_INIT(air_alarm_repository, /datum/repository/air_alarm, new())
/datum/repository/air_alarm/proc/update_cache(var/obj/machinery/alarm/alarm)
return air_alarm_data(refresh = 1, passed_alarm = alarm)
+
+#undef AIR_ALARM_DATA_CACHE_DURATION
diff --git a/code/datums/cache/crew.dm b/code/datums/cache/crew.dm
index f92bab9cf7a..84308dd4262 100644
--- a/code/datums/cache/crew.dm
+++ b/code/datums/cache/crew.dm
@@ -1,5 +1,8 @@
GLOBAL_DATUM_INIT(crew_repository, /datum/repository/crew, new())
+/datum/repository/crew
+ var/static/list/bold_jobs
+
/datum/repository/crew/New()
cache_data = list()
..()
@@ -18,6 +21,13 @@ GLOBAL_DATUM_INIT(crew_repository, /datum/repository/crew, new())
if(world.time < cache_entry.timestamp)
return cache_entry.data
+ // Initialize the jobs here because in New(), GLOB.command_positions may not be inited yet
+ if(!bold_jobs)
+ bold_jobs = list()
+ bold_jobs += GLOB.command_positions
+ bold_jobs += get_all_centcom_jobs()
+ bold_jobs += list("Nanotrasen Representative", "Blueshield", "Magistrate")
+
for(var/thing in GLOB.human_list)
var/mob/living/carbon/human/H = thing
var/obj/item/clothing/under/C = H.w_uniform
@@ -32,11 +42,13 @@ GLOBAL_DATUM_INIT(crew_repository, /datum/repository/crew, new())
crewmemberData["name"] = H.get_authentification_name(if_no_id="Unknown")
crewmemberData["rank"] = H.get_authentification_rank(if_no_id="Unknown", if_no_job="No Job")
crewmemberData["assignment"] = H.get_assignment(if_no_id="Unknown", if_no_job="No Job")
+ crewmemberData["is_command"] = (crewmemberData["assignment"] in bold_jobs)
if(C.sensor_mode >= SUIT_SENSOR_BINARY)
- crewmemberData["dead"] = H.stat > UNCONSCIOUS
+ crewmemberData["dead"] = H.stat == DEAD
if(C.sensor_mode >= SUIT_SENSOR_VITAL)
+ crewmemberData["stat"] = H.stat
crewmemberData["oxy"] = round(H.getOxyLoss(), 1)
crewmemberData["tox"] = round(H.getToxLoss(), 1)
crewmemberData["fire"] = round(H.getFireLoss(), 1)
diff --git a/code/datums/cache/powermonitor.dm b/code/datums/cache/powermonitor.dm
index 606dd500eda..a11e9b76a41 100644
--- a/code/datums/cache/powermonitor.dm
+++ b/code/datums/cache/powermonitor.dm
@@ -13,7 +13,7 @@ GLOBAL_DATUM_INIT(powermonitor_repository, /datum/repository/powermonitor, new()
for(var/obj/machinery/computer/monitor/pMon in GLOB.power_monitors)
if( !(pMon.stat & (NOPOWER|BROKEN)) && !pMon.is_secret_monitor )
- pMonData[++pMonData.len] = list ("Name" = pMon.name, "ref" = "\ref[pMon]")
+ pMonData[++pMonData.len] = list ("Name" = pMon.name, "uid" = "[pMon.UID()]")
cache_entry.timestamp = world.time //+ 30 SECONDS
cache_entry.data = pMonData
diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm
index 3bd740973a3..fc781b288a9 100644
--- a/code/datums/components/caltrop.dm
+++ b/code/datums/components/caltrop.dm
@@ -42,7 +42,7 @@
if(!(flags & CALTROP_BYPASS_SHOES) && (H.shoes || feetCover))
return
- if((H.flying) || H.buckled)
+ if(H.flying || H.floating || H.buckled)
return
var/damage = rand(min_damage, max_damage)
diff --git a/code/datums/components/ducttape.dm b/code/datums/components/ducttape.dm
index 49931db8edc..b41066d375d 100644
--- a/code/datums/components/ducttape.dm
+++ b/code/datums/components/ducttape.dm
@@ -47,23 +47,30 @@
return
if(!isturf(target))
return
- if(!user.unEquip(I))
- return
var/turf/source_turf = get_turf(I)
var/turf/target_turf = target
- var/list/clickparams = params2list(params)
- var/x_offset = text2num(clickparams["icon-x"]) - 16
- var/y_offset = text2num(clickparams["icon-y"]) - 16
+ var/x_offset
+ var/y_offset
if(target_turf != get_turf(I)) //Trying to stick it on a wall, don't move it to the actual wall or you can move the item through it. Instead set the pixels as appropriate
var/target_direction = get_dir(source_turf, target_turf)//The direction we clicked
+ // Snowflake diagonal handling
+ if(target_direction in GLOB.diagonals)
+ to_chat(user, "You cant reach [target_turf].")
+ return
if(target_direction & EAST)
- x_offset += 32
+ x_offset = 16
+ y_offset = rand(-12, 12)
else if(target_direction & WEST)
- x_offset -= 32
- if(target_direction & NORTH)
- y_offset += 32
+ x_offset = -16
+ y_offset = rand(-12, 12)
+ else if(target_direction & NORTH)
+ x_offset = rand(-12, 12)
+ y_offset = 16
else if(target_direction & SOUTH)
- y_offset -= 32
+ x_offset = rand(-12, 12)
+ y_offset = -16
+ if(!user.unEquip(I))
+ return
to_chat(user, "You stick [I] to [target_turf].")
I.pixel_x = x_offset
I.pixel_y = y_offset
diff --git a/code/datums/components/proximity_monitor.dm b/code/datums/components/proximity_monitor.dm
new file mode 100644
index 00000000000..cf5de38b387
--- /dev/null
+++ b/code/datums/components/proximity_monitor.dm
@@ -0,0 +1,139 @@
+/**
+ * # Proximity monitor component
+ *
+ * Attaching this component to an atom means that the atom will be able to detect mobs/objs moving within a 1 tile of it.
+ *
+ * The component creates several `obj/effect/abstract/proximity_checker` objects, which follow the parent atom around, always making sure it's at the center.
+ * When something crosses one of these `proximiy_checker`s, the parent has the `HasProximity()` proc called on it, with the crossing mob/obj as the argument.
+ */
+/datum/component/proximity_monitor
+ var/atom/owner
+ /// A list of currently created `/obj/effect/abstract/proximity_checker`s in use with this component.
+ var/list/proximity_checkers
+
+/datum/component/proximity_monitor/Initialize()
+ . = ..()
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ owner = parent
+ create_prox_checkers()
+
+/datum/component/proximity_monitor/Destroy(force, silent)
+ QDEL_LIST(proximity_checkers)
+ owner = null
+ return ..()
+
+/datum/component/proximity_monitor/RegisterWithParent()
+ . = ..()
+ if(ismovable(parent))
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/HandleMove)
+
+/datum/component/proximity_monitor/UnregisterFromParent()
+ . = ..()
+ if(ismovable(parent))
+ UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
+
+/**
+ * Called when the `parent` receives the `COMSIG_MOVABLE_MOVED` signal, which occurs when it `Move()`s
+ *
+ * Code is only ran when there is no `Dir`, which occurs when the parent is teleported, gets placed into a storage item, dropped, or picked up.
+ * Normal movement, for example moving 1 tile to the west, is handled by the `proximity_checker` objects.
+ *
+ * Arguments:
+ * * source - this will be the `parent`
+ * * OldLoc - the location the parent just moved from
+ * * Dir - the direction the parent just moved in
+ * * forced - if we were forced to move
+ */
+/datum/component/proximity_monitor/proc/HandleMove(datum/source, atom/OldLoc, Dir, forced)
+ if(!Dir) // No dir means the parent teleported, or moved in a non-standard way like getting placed into disposals, onto a table, dropped, picked up, etc.
+ recenter_prox_checkers()
+
+/**
+ * Called in Initialize(). Generates a set of `/obj/effect/abstract/proximity_checker` objects around the parent, and registers signals to them.
+ */
+/datum/component/proximity_monitor/proc/create_prox_checkers()
+ proximity_checkers = list()
+ for(var/turf/T in range(1, get_turf(parent)))
+ var/obj/effect/abstract/proximity_checker/P = new(T, parent)
+ proximity_checkers += P
+ // Basic movement for the proximity_checker objects. The objects will move 1 tile in the direction the parent just moved.
+ P.RegisterSignal(parent, COMSIG_MOVABLE_MOVED, /obj/effect/abstract/proximity_checker/.proc/HandleMove)
+
+/**
+ * Re-centers all of the parent's `proximity_checker`s around its current location.
+ */
+/datum/component/proximity_monitor/proc/recenter_prox_checkers()
+ var/list/prox_checkers = owner.get_all_adjacent_turfs()
+ for(var/checker in proximity_checkers)
+ var/obj/effect/abstract/proximity_checker/P = checker
+ P.loc = pick_n_take(prox_checkers)
+
+/**
+ * # Proximity checker abstract object
+ *
+ * Inteded for use with the proximity checker component (/datum/component/proximity_monitor).
+ * Whenever a movable atom crosses this object, it calls `HasProximity()` on the object which is listening for proximity (`hasprox_receiver`).
+ */
+/obj/effect/abstract/proximity_checker
+ name = "Proximity checker"
+ /// Whether or not the proximity checker is listening for things crossing it.
+ var/active
+ /// The linked atom which has the proximity_monitor component, and will recieve the `HasProximity()` calls.
+ var/atom/hasprox_receiver
+
+// If this object is initialized without a `_hasprox_receiver` arg, it is qdel'd.
+/obj/effect/abstract/proximity_checker/Initialize(mapload, atom/_hasprox_receiver)
+ if(_hasprox_receiver)
+ hasprox_receiver = _hasprox_receiver
+ RegisterSignal(hasprox_receiver, COMSIG_PARENT_QDELETING, .proc/OnParentDeletion)
+ if(isturf(hasprox_receiver.loc)) // if the reciever is inside a locker/crate/etc, they don't detect proximity
+ active = TRUE
+ else
+ stack_trace("/obj/effect/abstract/proximity_checker created without a receiver")
+ return INITIALIZE_HINT_QDEL
+ return ..()
+
+/obj/effect/abstract/proximity_checker/Destroy()
+ hasprox_receiver = null
+ return ..()
+
+/**
+ * Called when the `hasprox_receiver` receives the `COMSIG_PARENT_QDELETING` signal. When the receiver is deleted, so is this object.
+ *
+ * Arugments:
+ * * source - this will be the `hasprox_receiver`
+ * * force - the force flag taken from the qdel proc currently running on `hasprox_receiver`
+ */
+/obj/effect/abstract/proximity_checker/proc/OnParentDeletion(datum/source, force = FALSE)
+ qdel(src)
+
+/**
+ * Something crossed over the proximity_checker. Notify the `hasprox_receiver` it has proximity with something. Only fires if the checker is `active`.
+ */
+/obj/effect/abstract/proximity_checker/Crossed(atom/movable/AM, oldloc)
+ set waitfor = FALSE
+ if(active)
+ hasprox_receiver.HasProximity(AM)
+
+/**
+ * Moves the proximity_checker 1 tile in the `Dir` direction.
+ *
+ * If `Dir` is null it will be recentered around the receiver via the `recenter_prox_checkers()` proc.
+ * If the new location of the receiver is NOT a turf, set `active` to FALSE, so that it does not receive proximity calls.
+ * If the new location of the receiver IS a turf, set `active` to TRUE, so that it can receive proximity calls again.
+ *
+ * Arguments:
+ * * source - this will be the `hasprox_receiver`
+ * * OldLoc - the location the `hasprox_receiver` just moved from
+ * * Dir - the direction the `hasprox_receiver` just moved in
+ * * forced - if we were forced to move
+ */
+/obj/effect/abstract/proximity_checker/proc/HandleMove(datum/source, atom/OldLoc, Dir, forced)
+ if(Dir)
+ loc = get_step(src, Dir) // Basic movement 1 tile in some direction.
+ return
+ if(!isturf(hasprox_receiver.loc))
+ active = FALSE // Receiver shouldn't detect proximity while picked up, in a backpack, closet, etc.
+ else
+ active = TRUE // Receiver can detect proximity again because it's on a turf.
diff --git a/code/datums/components/slippery.dm b/code/datums/components/slippery.dm
index 88e51209afb..a7b81856468 100644
--- a/code/datums/components/slippery.dm
+++ b/code/datums/components/slippery.dm
@@ -51,6 +51,6 @@
Additionally calls the parent's `after_slip()` proc on the `victim`.
*/
/datum/component/slippery/proc/Slip(datum/source, mob/living/carbon/human/victim)
- if(istype(victim) && prob(slip_chance) && victim.slip(description, stun, weaken, slip_tiles, walking_is_safe, slip_always, slip_verb))
+ if(istype(victim) && !victim.flying && prob(slip_chance) && victim.slip(description, stun, weaken, slip_tiles, walking_is_safe, slip_always, slip_verb))
var/atom/movable/owner = parent
owner.after_slip(victim)
diff --git a/code/datums/components/spooky.dm b/code/datums/components/spooky.dm
new file mode 100644
index 00000000000..f5ee9c94666
--- /dev/null
+++ b/code/datums/components/spooky.dm
@@ -0,0 +1,58 @@
+/datum/component/spooky
+ var/too_spooky = TRUE //will it spawn a new instrument?
+
+/datum/component/spooky/Initialize()
+ RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/spectral_attack)
+
+/datum/component/spooky/proc/spectral_attack(datum/source, mob/living/carbon/C, mob/user)
+ if(ishuman(user)) //this weapon wasn't meant for mortals.
+ var/mob/living/carbon/human/U = user
+ if(!istype(U.dna.species, /datum/species/skeleton))
+ U.adjustStaminaLoss(35) //Extra Damage
+ U.Jitter(35)
+ U.stuttering = 20
+ if(U.getStaminaLoss() > 95)
+ to_chat(U, "Your ears weren't meant for this spectral sound.")
+ spectral_change(U)
+ return
+
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ if(istype(H.dna.species, /datum/species/skeleton))
+ return //undeads are unaffected by the spook-pocalypse.
+ C.Jitter(35)
+ C.stuttering = 20
+ if(!istype(H.dna.species, /datum/species/diona) && !istype(H.dna.species, /datum/species/machine) && !istype(H.dna.species, /datum/species/slime) && !istype(H.dna.species, /datum/species/golem) && !istype(H.dna.species, /datum/species/plasmaman))
+ C.adjustStaminaLoss(25) //boneless humanoids don't lose the will to live
+ to_chat(C, "DOOT")
+ spectral_change(H)
+
+ else //the sound will spook monkeys.
+ C.Jitter(15)
+ C.stuttering = 20
+
+/datum/component/spooky/proc/spectral_change(mob/living/carbon/human/H, mob/user)
+ if((H.getStaminaLoss() > 95) && (!istype(H.dna.species, /datum/species/diona) && !istype(H.dna.species, /datum/species/machine) && !istype(H.dna.species, /datum/species/slime) && !istype(H.dna.species, /datum/species/golem) && !istype(H.dna.species, /datum/species/plasmaman) && !istype(H.dna.species, /datum/species/skeleton)))
+ H.Stun(20)
+ H.set_species(/datum/species/skeleton)
+ H.visible_message("[H] has given up on life as a mortal.")
+ var/T = get_turf(H)
+ if(too_spooky)
+ if(prob(30))
+ new/obj/item/instrument/saxophone/spectral(T)
+ else if(prob(30))
+ new/obj/item/instrument/trumpet/spectral(T)
+ else if(prob(30))
+ new/obj/item/instrument/trombone/spectral(T)
+ else
+ to_chat(H, "The spooky gods forgot to ship your instrument. Better luck next unlife.")
+ to_chat(H, "You are the spooky skeleton!")
+ to_chat(H, "A new life and identity has begun. Help your fellow skeletons into bringing out the spooky-pocalypse. You haven't forgotten your past life, and are still beholden to past loyalties.")
+ change_name(H) //time for a new name!
+
+/datum/component/spooky/proc/change_name(mob/living/carbon/human/H)
+ var/t = stripped_input(H, "Enter your new skeleton name", H.real_name, null, MAX_NAME_LEN)
+ if(!t)
+ t = "spooky skeleton"
+ H.real_name = t
+ H.name = t
diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm
index 4c55ad2ad82..f79439f2b3b 100644
--- a/code/datums/components/squeak.dm
+++ b/code/datums/components/squeak.dm
@@ -67,6 +67,14 @@
var/obj/item/projectile/P = AM
if(P.original != parent)
return
+ if(ismob(AM))
+ var/mob/M = AM
+ if(M.flying)
+ return
+ if(isliving(AM))
+ var/mob/living/L = M
+ if(L.floating)
+ return
var/atom/current_parent = parent
if(isturf(current_parent.loc))
play_squeak()
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index 0e98179e2f9..1042d9a8ddd 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -35,12 +35,18 @@
var/rank = t.fields["rank"]
var/real_rank = t.fields["real_rank"]
if(OOC)
- var/active = 0
- for(var/mob/M in GLOB.player_list)
- if(M.real_name == name && M.client && M.client.inactivity <= 10 * 60 * 10)
- active = 1
+ var/activetext = "Inactive"
+ for(var/thing in GLOB.human_list)
+ var/mob/living/carbon/human/H = thing
+ if(H.real_name != name)
+ continue
+ if(H.client && H.client.inactivity <= 6000)
+ activetext = "Active"
break
- isactive[name] = active ? "Active" : "Inactive"
+ if(isLivingSSD(H))
+ activetext = "SSD"
+ break
+ isactive[name] = activetext
else
isactive[name] = t.fields["p_stat"]
var/department = 0
@@ -278,8 +284,8 @@ GLOBAL_VAR_INIT(record_id_num, 1001)
G.fields["sex"] = capitalize(H.gender)
G.fields["species"] = H.dna.species.name
G.fields["photo"] = get_id_photo(H)
- G.fields["photo-south"] = "'data:image/png;base64,[icon2base64(icon(G.fields["photo"], dir = SOUTH))]'"
- G.fields["photo-west"] = "'data:image/png;base64,[icon2base64(icon(G.fields["photo"], dir = WEST))]'"
+ G.fields["photo-south"] = "data:image/png;base64,[icon2base64(icon(G.fields["photo"], dir = SOUTH))]"
+ G.fields["photo-west"] = "data:image/png;base64,[icon2base64(icon(G.fields["photo"], dir = WEST))]"
if(H.gen_record && !jobban_isbanned(H, "Records"))
G.fields["notes"] = H.gen_record
else
@@ -319,7 +325,7 @@ GLOBAL_VAR_INIT(record_id_num, 1001)
if(H.sec_record && !jobban_isbanned(H, "Records"))
S.fields["notes"] = H.sec_record
else
- S.fields["notes"] = "No notes."
+ S.fields["notes"] = "No notes found."
LAZYINITLIST(S.fields["comments"])
security += S
@@ -518,8 +524,8 @@ GLOBAL_VAR_INIT(record_id_num, 1001)
clothes_s = new /icon('icons/mob/uniform.dmi', "cargotech_s")
clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)
if("Shaft Miner")
- clothes_s = new /icon('icons/mob/uniform.dmi', "miner_s")
- clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)
+ clothes_s = new /icon('icons/mob/uniform.dmi', "explorer_s")
+ clothes_s.Blend(new /icon('icons/mob/feet.dmi', "explorer"), ICON_UNDERLAY)
if("Lawyer")
clothes_s = new /icon('icons/mob/uniform.dmi', "internalaffairs_s")
clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY)
diff --git a/code/datums/diseases/advance/advance.dm b/code/datums/diseases/advance/advance.dm
index 34c41c581f2..318dd176e45 100644
--- a/code/datums/diseases/advance/advance.dm
+++ b/code/datums/diseases/advance/advance.dm
@@ -395,8 +395,9 @@ GLOBAL_LIST_INIT(advance_cures, list(
for(var/datum/disease/advance/AD in GLOB.active_diseases)
AD.Refresh()
- for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list))
- if(!is_station_level(H.z))
+ for(var/thing in shuffle(GLOB.human_list))
+ var/mob/living/carbon/human/H = thing
+ if(H.stat == DEAD || !is_station_level(H.z))
continue
if(!H.HasDisease(D))
H.ForceContractDisease(D)
diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm
index f619e9aa9bd..3e3e1696e61 100644
--- a/code/datums/diseases/transformation.dm
+++ b/code/datums/diseases/transformation.dm
@@ -57,6 +57,9 @@
W.plane = initial(W.plane)
W.loc = affected_mob.loc
W.dropped(affected_mob)
+ if(isobj(affected_mob.loc))
+ var/obj/O = affected_mob.loc
+ O.force_eject_occupant()
var/mob/living/new_mob = new new_form(affected_mob.loc)
if(istype(new_mob))
new_mob.a_intent = "harm"
diff --git a/code/datums/dog_fashion.dm b/code/datums/dog_fashion.dm
index 3bf109eabd9..14705ae7375 100644
--- a/code/datums/dog_fashion.dm
+++ b/code/datums/dog_fashion.dm
@@ -191,6 +191,10 @@
..()
desc = "That's Definitely Not [M.real_name]."
+/datum/dog_fashion/head/cone
+ name = "REAL_NAME"
+ desc = "Omnicone's Chosen Champion"
+
/datum/dog_fashion/back/hardsuit
name = "Space Explorer REAL_NAME"
desc = "That's one small step for a corgi. One giant yap for corgikind."
@@ -200,3 +204,7 @@
D.mutations.Add(BREATHLESS)
D.atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
D.minbodytemp = 0
+
+/datum/dog_fashion/head/fried_vox_empty
+ name = "Colonel REAL_NAME"
+ desc = "Keep away from live vox."
diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index 4e20d2a675e..4609075ddd7 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -152,13 +152,11 @@
return doTeleport()
return 0
-/datum/teleport/instant //teleports when datum is created
-
- start(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
- if(..())
- if(teleport())
- return 1
- return 0
+/datum/teleport/instant/start(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
+ if(..())
+ if(teleport())
+ return 1
+ return 0
/datum/teleport/instant/science
diff --git a/code/datums/log_record.dm b/code/datums/log_record.dm
index 9b7d52b5390..2a659477b47 100644
--- a/code/datums/log_record.dm
+++ b/code/datums/log_record.dm
@@ -3,32 +3,51 @@
var/raw_time // When did this happen?
var/what // What happened
var/who // Who did it
- var/target // Who/what was targeted (can be a string)
- var/turf/where // Where did it happen
+ var/target // Who/what was targeted
+ var/where // Where did it happen
/datum/log_record/New(_log_type, _who, _what, _target, _where, _raw_time)
log_type = _log_type
-
- who = get_subject_text(_who)
+
+ who = get_subject_text(_who, _log_type)
what = _what
- target = get_subject_text(_target)
- if(!_where)
+ target = get_subject_text(_target, _log_type)
+ if(!istext(_where) && !isturf(_where))
_where = get_turf(_who)
- where = _where
+ if(isturf(_where))
+ var/turf/T = _where
+ where = ADMIN_COORDJMP(T)
+ else
+ where = _where
if(!_raw_time)
_raw_time = world.time
raw_time = _raw_time
-/datum/log_record/proc/get_subject_text(subject)
+/datum/log_record/proc/get_subject_text(subject, log_type)
if(ismob(subject) || isclient(subject) || istype(subject, /datum/mind))
- return key_name_admin(subject)
- if(isatom(subject))
+ . = key_name_admin(subject)
+ if(should_log_health(log_type) && isliving(subject))
+ . += get_health_string(subject)
+ else if(isatom(subject))
var/atom/A = subject
- return A.name
- if(istype(subject, /datum))
+ . = A.name
+ else if(istype(subject, /datum))
var/datum/D = subject
return D.type
- return subject
+ else
+ . = subject
+
+/datum/log_record/proc/get_health_string(var/mob/living/L)
+ var/OX = L.getOxyLoss() > 50 ? "[L.getOxyLoss()]" : L.getOxyLoss()
+ var/TX = L.getToxLoss() > 50 ? "[L.getToxLoss()]" : L.getToxLoss()
+ var/BU = L.getFireLoss() > 50 ? "[L.getFireLoss()]" : L.getFireLoss()
+ var/BR = L.getBruteLoss() > 50 ? "[L.getBruteLoss()]" : L.getBruteLoss()
+ return " ([L.health]: [OX] - [TX] - [BU] - [BR])"
+
+/datum/log_record/proc/should_log_health(log_type)
+ if(log_type == ATTACK_LOG || log_type == DEFENSE_LOG)
+ return TRUE
+ return FALSE
/proc/compare_log_record(datum/log_record/A, datum/log_record/B)
var/time_diff = A.raw_time - B.raw_time
diff --git a/code/datums/log_viewer.dm b/code/datums/log_viewer.dm
index 67eb3d21d86..c5a17507606 100644
--- a/code/datums/log_viewer.dm
+++ b/code/datums/log_viewer.dm
@@ -1,31 +1,45 @@
-#define ALL_LOGS list(ATTACK_LOG, DEFENSE_LOG, CONVERSION_LOG, SAY_LOG, EMOTE_LOG, MISC_LOG)
+#define UPDATE_CKEY_MOB(__ckey) var/mob/result = selected_ckeys_mobs[__ckey];\
+if(!result || result.ckey != __ckey){\
+ result = get_mob_by_ckey(__ckey);\
+ selected_ckeys_mobs[__ckey] = result;\
+}
+
+#define RECORD_WARN_LIMIT 1000
+#define RECORD_HARD_LIMIT 2500
/datum/log_viewer
var/time_from = 0
var/time_to = 4 HOURS // 4 Hours should be enough. INFINITY would screw the UI up
- var/list/selected_mobs = list() // The mobs in question
- var/list/selected_log_types = list() // The log types being searched for
-
+ var/list/selected_mobs = list() // The mobs in question.
+ var/list/selected_ckeys = list() // The ckeys selected to search for. Will show all mobs the ckey is attached to
+ var/list/mob/selected_ckeys_mobs = list()
+ var/list/selected_log_types = ALL_LOGS // The log types being searched for
var/list/log_records = list() // Found and sorted records
/datum/log_viewer/proc/clear_all()
selected_mobs.Cut()
- selected_log_types.Cut()
+ selected_log_types = ALL_LOGS
+ selected_ckeys.Cut()
+ selected_ckeys_mobs.Cut()
time_from = initial(time_from)
time_to = initial(time_to)
log_records.Cut()
return
-/datum/log_viewer/proc/search()
+/datum/log_viewer/proc/search(user)
log_records.Cut() // Empty the old results
var/list/invalid_mobs = list()
+ var/list/ckeys = selected_ckeys.Copy()
for(var/i in selected_mobs)
var/mob/M = i
- if(!M || QDELETED(M))
+ if(!M || QDELETED(M) || !M.last_known_ckey)
invalid_mobs |= M
continue
+ ckeys |= M.last_known_ckey
+
+ for(var/ckey in ckeys)
for(var/log_type in selected_log_types)
- var/list/logs = M.logs[log_type]
+ var/list/logs = GLOB.logging.get_logs_by_type(ckey, log_type)
var/len_logs = length(logs)
if(len_logs)
var/start_index = get_earliest_log_index(logs)
@@ -36,8 +50,8 @@
continue
log_records.Add(logs.Copy(start_index, end_index + 1))
- if(invalid_mobs.len)
- to_chat(usr, "The search criteria contained invalid mobs. They have been removed from the criteria.")
+ if(length(invalid_mobs))
+ to_chat(user, "The search criteria contained invalid mobs. They have been removed from the criteria.")
for(var/i in invalid_mobs)
selected_mobs -= i // Cleanup
@@ -91,9 +105,23 @@
return start
return 0
-/datum/log_viewer/proc/add_mob(mob/user, mob/M)
+/datum/log_viewer/proc/add_mobs(list/mob/mobs)
+ if(!length(mobs))
+ return
+ for(var/i in mobs)
+ add_mob(usr, i, FALSE)
+
+/datum/log_viewer/proc/add_ckey(mob/user, ckey)
+ if(!user || !ckey)
+ return
+ selected_ckeys |= ckey
+ UPDATE_CKEY_MOB(ckey)
+ show_ui(user)
+
+/datum/log_viewer/proc/add_mob(mob/user, mob/M, show_the_ui = TRUE)
if(!M || !user)
return
+
selected_mobs |= M
show_ui(user)
@@ -102,9 +130,9 @@
var/all_log_types = ALL_LOGS
var/trStyleTop = "border-top:2px solid; border-bottom:2px solid; padding-top: 5px; padding-bottom: 5px;"
var/trStyle = "border-top:1px solid; border-bottom:1px solid; padding-top: 5px; padding-bottom: 5px;"
- var/dat
- dat += ""
- dat += ""
+ var/list/dat = list()
+ dat += " "
+ dat += " "
dat += " Time Search Range: [gameTimestamp(wtime = time_from)]"
dat += " To: [gameTimestamp(wtime = time_to)]"
dat += " "
@@ -115,20 +143,26 @@
if(QDELETED(M))
selected_mobs -= i
continue
- dat += " [M.name]"
+ dat += " [get_display_name(M)]"
dat += " Add Mob"
dat += " Clear All Mobs"
dat += " "
+ dat += " Ckeys being used:"
+ for(var/ckey in selected_ckeys)
+ dat += " [get_ckey_name(ckey)]"
+ dat += " Add ckey"
+ dat += " Clear All ckeys"
+ dat += " "
+
dat += " Log Types:"
- for(var/i in all_log_types)
- var/log_type = i
+ for(var/log_type in all_log_types)
var/enabled = (log_type in selected_log_types)
var/text
var/style
if(enabled)
text = " [log_type]"
- style = "background: [get_logtype_color(i)]"
+ style = "background: [get_logtype_color(log_type)]"
else
text = log_type
@@ -142,9 +176,9 @@
// Search results
var/tdStyleTime = "width:80px; text-align:center;"
var/tdStyleType = "width:80px; text-align:center;"
- var/tdStyleWho = "width:300px; text-align:center;"
+ var/tdStyleWho = "width:400px; text-align:center;"
var/tdStyleWhere = "width:150px; text-align:center;"
- dat += " "
+ dat += " "
dat += " "
dat += "| When | Type | Who | What | Target | Where | "
for(var/i in log_records)
@@ -153,13 +187,12 @@
dat +="| [time] | [L.log_type] | \
[L.who] | [L.what] | \
- [L.target] | [ADMIN_COORDJMP(L.where)] | "
-
+ [L.target] | [L.where] | "
dat += " "
dat += " "
- var/datum/browser/popup = new(user, "Log viewer", "Log viewer", 1400, 600)
- popup.set_content(dat)
+ var/datum/browser/popup = new(user, "Log Viewer", "Log Viewer", 1500, 600)
+ popup.set_content(dat.Join())
popup.open()
/datum/log_viewer/Topic(href, href_list)
@@ -188,6 +221,19 @@
return
if(href_list["search"])
search(usr)
+ var/records_len = length(log_records)
+ if(records_len > RECORD_WARN_LIMIT)
+ var/datum/log_record/last_record = log_records[RECORD_WARN_LIMIT]
+ var/last_time = gameTimestamp(wtime = last_record.raw_time - 9.99)
+ var/answer = alert(usr, "More than [RECORD_WARN_LIMIT] records were found. continuing will take a long time. This won't cause much lag for the server. Time at the [RECORD_WARN_LIMIT]th record '[last_time]'", "Warning", "Continue", "Limit to [RECORD_WARN_LIMIT]", "Cancel")
+ if(answer == "Limit to [RECORD_WARN_LIMIT]")
+ log_records.Cut(RECORD_WARN_LIMIT)
+ else if(answer == "Cancel")
+ log_records.Cut()
+ else
+ if(records_len > RECORD_HARD_LIMIT)
+ to_chat(usr, " Record limit reached. Limiting to [RECORD_HARD_LIMIT].")
+ log_records.Cut(RECORD_HARD_LIMIT)
show_ui(usr)
return
if(href_list["clear_all"])
@@ -198,17 +244,31 @@
selected_mobs.Cut()
show_ui(usr)
return
+ if(href_list["clear_ckeys"])
+ selected_ckeys.Cut()
+ selected_ckeys_mobs.Cut()
+ show_ui(usr)
+ return
if(href_list["add_mob"])
var/list/mobs = getpois(TRUE, TRUE)
var/datum/async_input/A = input_autocomplete_async(usr, "Please, select a mob: ", mobs)
A.on_close(CALLBACK(src, .proc/add_mob, usr))
return
+ if(href_list["add_ckey"])
+ var/list/ckeys = GLOB.logging.get_ckeys_logged()
+ var/datum/async_input/A = input_autocomplete_async(usr, "Please, select a ckey: ", ckeys)
+ A.on_close(CALLBACK(src, .proc/add_ckey, usr))
+ return
if(href_list["remove_mob"])
var/mob/M = locate(href_list["remove_mob"])
if(M)
selected_mobs -= M
show_ui(usr)
return
+ if(href_list["remove_ckey"])
+ selected_ckeys -= href_list["remove_ckey"]
+ show_ui(usr)
+ return
if(href_list["toggle_log_type"])
var/log_type = href_list["toggle_log_type"]
if(log_type in selected_log_types)
@@ -232,4 +292,28 @@
return "deepskyblue"
if(MISC_LOG)
return "gray"
+ if(DEADCHAT_LOG)
+ return "#cc00c6"
+ if(OOC_LOG)
+ return "#002eb8"
+ if(LOOC_LOG)
+ return "#6699CC"
return "slategray"
+
+/datum/log_viewer/proc/get_display_name(mob/M)
+ var/name = M.name
+ if(M.name != M.real_name)
+ name = "[name] ([M.real_name])"
+ if(isobserver(M))
+ name = "[name] (DEAD)"
+ return "\[[M.last_known_ckey]\] [name]"
+
+/datum/log_viewer/proc/get_ckey_name(ckey)
+ UPDATE_CKEY_MOB(ckey)
+ var/mob/M = selected_ckeys_mobs[ckey]
+
+ return get_display_name(M)
+
+#undef UPDATE_CKEY_MOB
+#undef RECORD_WARN_LIMIT
+#undef RECORD_HARD_LIMIT
diff --git a/code/datums/logging.dm b/code/datums/logging.dm
new file mode 100644
index 00000000000..bac26610e5e
--- /dev/null
+++ b/code/datums/logging.dm
@@ -0,0 +1,47 @@
+/datum/logging
+ var/list/datum/log_record/logs = list() // Assoc list of assoc lists (ckey, (log_type, list/logs))
+
+/datum/logging/proc/add_log(ckey, datum/log_record/log)
+ if(!ckey)
+ log_debug("GLOB.logging.add_log called with an invalid ckey")
+ return
+
+ if(!logs[ckey])
+ logs[ckey] = list()
+
+ var/list/log_types_list = logs[ckey]
+
+ if(!log_types_list[log.log_type])
+ log_types_list[log.log_type] = list()
+
+ var/list/datum/log_record/log_records = log_types_list[log.log_type]
+ log_records.Add(log)
+
+/datum/logging/proc/get_ckeys_logged()
+ var/list/ckeys = list()
+ for(var/ckey in logs)
+ ckeys.Add(ckey)
+ return ckeys
+
+/* Returns the logs of a given ckey and log_type
+ * If no logs exist it will return an empty list
+*/
+/datum/logging/proc/get_logs_by_type(ckey, log_type)
+ if(!ckey)
+ log_debug("GLOB.logging.get_logs_by_type called with an invalid ckey")
+ return
+ if(!log_type || !(log_type in ALL_LOGS))
+ log_debug("GLOB.logging.get_logs_by_type called with an invalid log_type '[log_type]'")
+ return
+
+ var/list/log_types_list = logs[ckey]
+ // Check if logs exist for the ckey
+ if(!length(log_types_list))
+ return list()
+
+ var/list/datum/log_record/log_records = log_types_list[log_type]
+
+ // Check if logs exist for this type
+ if(!log_records)
+ return list()
+ return log_records
diff --git a/code/datums/looping_sounds/looping_sound.dm b/code/datums/looping_sounds/looping_sound.dm
index f44a87bdd7a..006e92c305c 100644
--- a/code/datums/looping_sounds/looping_sound.dm
+++ b/code/datums/looping_sounds/looping_sound.dm
@@ -71,7 +71,7 @@
var/list/atoms_cache = output_atoms
var/sound/S = sound(soundfile)
if(direct)
- S.channel = open_sound_channel()
+ S.channel = SSsounds.random_available_channel()
S.volume = volume
for(var/i in 1 to atoms_cache.len)
var/atom/thing = atoms_cache[i]
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 2f22e58d37b..cc8b2dd5e97 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -35,6 +35,7 @@
var/list/restricted_roles = list()
var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button.
+ var/datum/martial_art/martial_art
var/role_alt_title
@@ -100,15 +101,8 @@
current.mind = null
leave_all_huds() //leave all the huds in the old body, so it won't get huds if somebody else enters it
- for(var/log_type in current.logs) // Copy the old logs
- var/list/logs = current.logs[log_type]
- if(new_character.logs[log_type])
- new_character.logs[log_type] += logs.Copy() // Append the old ones
- new_character.logs[log_type] = sortTim(new_character.logs[log_type], /proc/compare_log_record) // Sort them on time
- else
- new_character.logs[log_type] = logs.Copy() // Just copy them
-
SSnanoui.user_transferred(current, new_character)
+ SStgui.on_transfer(current, new_character)
if(new_character.mind) //remove any mind currently in our new body's mind variable
new_character.mind.current = null
@@ -119,6 +113,11 @@
A.on_body_transfer(old_current, current)
transfer_antag_huds(hud_to_transfer) //inherit the antag HUD
transfer_actions(new_character)
+ if(martial_art)
+ if(martial_art.temporary)
+ martial_art.remove(current)
+ else
+ martial_art.teach(current)
if(active)
new_character.key = key //now transfer the key to link the client to our new body
@@ -922,7 +921,7 @@
log_admin("[key_name(usr)] has equipped [key_name(current)] as a wizard")
message_admins("[key_name_admin(usr)] has equipped [key_name_admin(current)] as a wizard")
if("name")
- SSticker.mode.name_wizard(current)
+ INVOKE_ASYNC(SSticker.mode, /datum/game_mode/wizard.proc/name_wizard, current)
log_admin("[key_name(usr)] has allowed wizard [key_name(current)] to name themselves")
message_admins("[key_name_admin(usr)] has allowed wizard [key_name_admin(current)] to name themselves")
if("autoobjectives")
@@ -1110,8 +1109,8 @@
special_role = null
to_chat(current," Your infernal link has been severed! You are no longer a devil!")
RemoveSpell(/obj/effect/proc_holder/spell/targeted/infernal_jaunt)
- RemoveSpell(/obj/effect/proc_holder/spell/fireball/hellish)
- RemoveSpell(/obj/effect/proc_holder/spell/targeted/summon_contract)
+ RemoveSpell(/obj/effect/proc_holder/spell/targeted/click/fireball/hellish)
+ RemoveSpell(/obj/effect/proc_holder/spell/targeted/click/summon_contract)
RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork)
RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/greater)
RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/ascended)
@@ -1419,9 +1418,10 @@
return A
/datum/mind/proc/announce_objectives()
- to_chat(current, " Your current objectives:")
- for(var/line in splittext(gen_objective_text(), " "))
- to_chat(current, line)
+ if(current)
+ to_chat(current, " Your current objectives:")
+ for(var/line in splittext(gen_objective_text(), " "))
+ to_chat(current, line)
/datum/mind/proc/find_syndicate_uplink()
var/list/L = current.get_contents()
@@ -1508,7 +1508,7 @@
SSticker.mode.equip_wizard(current)
for(var/obj/item/spellbook/S in current.contents)
S.op = 0
- SSticker.mode.name_wizard(current)
+ INVOKE_ASYNC(SSticker.mode, /datum/game_mode/wizard.proc/name_wizard, current)
SSticker.mode.forge_wizard_objectives(src)
SSticker.mode.greet_wizard(src)
SSticker.mode.update_wiz_icons_added(src)
diff --git a/code/datums/outfits/outfit_admin.dm b/code/datums/outfits/outfit_admin.dm
index 14bc959fbb1..d5be5f50f35 100644
--- a/code/datums/outfits/outfit_admin.dm
+++ b/code/datums/outfits/outfit_admin.dm
@@ -222,10 +222,10 @@
name = "NT Undercover Operative"
// Disguised NT special forces, sent to quietly eliminate or keep tabs on people in high positions (e.g: captain)
- uniform = /obj/item/clothing/under/color/black
+ uniform = /obj/item/clothing/under/color/random
back = /obj/item/storage/backpack
belt = /obj/item/storage/belt/utility/full/multitool
- gloves = /obj/item/clothing/gloves/combat
+ gloves = /obj/item/clothing/gloves/color/yellow
shoes = /obj/item/clothing/shoes/chameleon/noslip
l_ear = /obj/item/radio/headset/centcom
id = /obj/item/card/id
@@ -242,7 +242,7 @@
/obj/item/organ/internal/cyberimp/eyes/shield,
/obj/item/organ/internal/cyberimp/eyes/hud/security,
/obj/item/organ/internal/cyberimp/eyes/xray,
- /obj/item/organ/internal/cyberimp/brain/anti_stun,
+ /obj/item/organ/internal/cyberimp/brain/anti_stun/hardened,
/obj/item/organ/internal/cyberimp/chest/nutriment/plus,
/obj/item/organ/internal/cyberimp/arm/combat/centcom
)
diff --git a/code/datums/pipe_datums.dm b/code/datums/pipe_datums.dm
index 83b77425447..8d3b0807f9c 100644
--- a/code/datums/pipe_datums.dm
+++ b/code/datums/pipe_datums.dm
@@ -351,16 +351,6 @@ GLOBAL_LIST_EMPTY(rpd_pipe_list) //Some pipes we don't want to be dispensable
pipe_id = PIPE_CIRCULATOR
pipe_icon = "circ"
-/datum/pipes/atmospheric/omni_filter
- pipe_name = "omni filter"
- pipe_id = PIPE_OMNI_FILTER
- pipe_icon = "omni_filter"
-
-/datum/pipes/atmospheric/omni_mixer
- pipe_name = "omni mixer"
- pipe_id = PIPE_OMNI_MIXER
- pipe_icon = "omni_mixer"
-
/datum/pipes/atmospheric/insulated
pipe_name = "insulated pipe"
pipe_id = PIPE_INSULATED_STRAIGHT
diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm
index f826b6ff0d2..2143fb5ad5c 100644
--- a/code/datums/ruins/lavaland.dm
+++ b/code/datums/ruins/lavaland.dm
@@ -16,7 +16,7 @@
name = "Biodome Winter"
id = "biodome-winter"
description = "For those getaways where you want to get back to nature, but you don't want to leave the fortified military compound where you spend your days. \
- Includes a unique(*) laser pistol display case, and the recently introduced I.C.E(tm)."
+ Includes the recently introduced I.C.E(tm)."
suffix = "lavaland_biodome_winter.dmm"
/datum/map_template/ruin/lavaland/biodome/clown
@@ -42,7 +42,7 @@
cost = 10
allow_duplicates = FALSE
-datum/map_template/ruin/lavaland/ash_walker
+/datum/map_template/ruin/lavaland/ash_walker
name = "Ash Walker Nest"
id = "ash-walker"
description = "A race of unbreathing lizards live here, that run faster than a human can, worship a broken dead city, and are capable of reproducing by something involving tentacles? \
diff --git a/code/datums/ruins/space.dm b/code/datums/ruins/space.dm
index b81587440d4..3541be89983 100644
--- a/code/datums/ruins/space.dm
+++ b/code/datums/ruins/space.dm
@@ -175,6 +175,7 @@
description = "The crew of a space station awaken one hundred years after a crisis. Awaking to a derelict space station on the verge of collapse, and a hostile force of invading \
hivebots. Can the surviving crew overcome the odds and survive and rebuild, or will the cold embrace of the stars become their new home?"
cost = 2
+ allow_duplicates = FALSE
/datum/map_template/ruin/space/wizardcrash
id = "wizardcrash"
@@ -247,3 +248,29 @@
allow_duplicates = FALSE // I dont even want to think about what happens if you have 2 shuttles with the same ID. Likely scary stuff.
always_place = TRUE // Its designed to make exploring other space ruins more accessible
cost = 0 // Force spawned so shouldnt have a cost
+
+/datum/map_template/ruin/space/syndiecakesfactory
+ id = "Syndiecakes Factory"
+ suffix = "syndiecakesfactory.dmm"
+ name = "Syndicakes Factory"
+ description = "Syndicate used to get funds selling corgi cakes produced here. Was it hit by meteors or by a Nanotrasen comando?"
+ allow_duplicates = FALSE
+ cost = 2 //telecomms + multiple mobs
+
+/datum/map_template/ruin/space/debris1
+ id = "debris1"
+ suffix = "debris1.dmm"
+ name = "Debris field 1"
+ description = "A bunch of metal chunks, wires and space waste"
+
+/datum/map_template/ruin/space/debris2
+ id = "debris2"
+ suffix = "debris2.dmm"
+ name = "Debris field 2"
+ description = "A bunch of metal chunks, wires and space waste that used to be some kind of secure storage facility"
+
+/datum/map_template/ruin/space/debris3
+ id = "debris3"
+ suffix = "debris3.dmm"
+ name = "Debris field 3"
+ description = "A bunch of metal chunks, wires and space waste. It used to be an arcade."
diff --git a/code/datums/shuttles.dm b/code/datums/shuttles.dm
index 53517d5dbc9..dacc08dee31 100644
--- a/code/datums/shuttles.dm
+++ b/code/datums/shuttles.dm
@@ -9,8 +9,9 @@
var/admin_notes
/datum/map_template/shuttle/New()
- shuttle_id = "[port_id]_[suffix]"
- mappath = "[prefix][shuttle_id].dmm"
+ if(port_id && suffix)
+ shuttle_id = "[port_id]_[suffix]"
+ mappath = "[prefix][shuttle_id].dmm"
. = ..()
/datum/map_template/shuttle/emergency
@@ -132,3 +133,8 @@
suffix = "admin"
name = "NTV Argos"
description = "Default Admin ship. An older ship used for special operations."
+
+/datum/map_template/shuttle/admin/armory
+ suffix = "armory"
+ name = "NRV Sparta"
+ description = "Armory Shuttle, with plenty of guns to hand out and some general supplies."
diff --git a/code/datums/spawners_menu.dm b/code/datums/spawners_menu.dm
index a651edbc79c..a196a60fcc5 100644
--- a/code/datums/spawners_menu.dm
+++ b/code/datums/spawners_menu.dm
@@ -6,44 +6,47 @@
qdel(src)
owner = new_owner
-/datum/spawners_menu/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = FALSE, datum/topic_state/state = GLOB.ghost_state, datum/nanoui/master_ui = null)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/datum/spawners_menu/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui_state/state = GLOB.tgui_observer_state, datum/tgui/master_ui = null)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "spawners_menu.tmpl", "Spawners Menu", 700, 600, master_ui, state = state)
+ ui = new(user, src, ui_key, "SpawnersMenu", "Spawners Menu", 700, 600, master_ui, state = state)
ui.open()
-/datum/spawners_menu/ui_data(mob/user)
+/datum/spawners_menu/tgui_data(mob/user)
var/list/data = list()
data["spawners"] = list()
for(var/spawner in GLOB.mob_spawners)
var/list/this = list()
this["name"] = spawner
this["desc"] = ""
+ this["important_info"] = ""
+ this["fluff"] = ""
this["uids"] = list()
- for(var/spawner_obj in GLOB.mob_spawners[spawner])
+ for(var/spawner_obj in GLOB.mob_spawners[spawner])//each spawner can contain multiple actual spawners, we use only one desc/info
this["uids"] += "\ref[spawner_obj]"
- if(!this["desc"])
+ if(!this["desc"]) //haven't set descriptions yet
if(istype(spawner_obj, /obj/effect/mob_spawn))
var/obj/effect/mob_spawn/MS = spawner_obj
- this["desc"] = MS.flavour_text
+ this["desc"] = MS.description
+ this["important_info"] = MS.important_info
+ this["fluff"] = MS.flavour_text
else
var/obj/O = spawner_obj
this["desc"] = O.desc
this["amount_left"] = LAZYLEN(GLOB.mob_spawners[spawner])
data["spawners"] += list(this)
-
return data
-/datum/spawners_menu/Topic(href, href_list)
+/datum/spawners_menu/tgui_act(action, params)
if(..())
- return 1
- var/spawners = replacetext(href_list["uid"], ",", ";")
+ return
+ var/spawners = replacetext(params["ID"], ",", ";")
var/list/possible_spawners = params2list(spawners)
var/obj/effect/mob_spawn/MS = locate(pick(possible_spawners))
if(!MS || !istype(MS))
log_runtime(EXCEPTION("A ghost tried to interact with an invalid spawner, or the spawner didn't exist."))
return
- switch(href_list["action"])
+ switch(action)
if("jump")
owner.forceMove(get_turf(MS))
. = TRUE
diff --git a/code/datums/spell.dm b/code/datums/spell.dm
index 3855dbcfac3..abb9fdc37c9 100644
--- a/code/datums/spell.dm
+++ b/code/datums/spell.dm
@@ -24,6 +24,20 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
user.face_atom(A)
return FALSE
+/datum/click_intercept/proc_holder
+ var/obj/effect/proc_holder/spell
+
+/datum/click_intercept/proc_holder/New(client/C, obj/effect/proc_holder/spell_to_cast)
+ . = ..()
+ spell = spell_to_cast
+
+/datum/click_intercept/proc_holder/InterceptClickOn(user, params, atom/object)
+ spell.InterceptClickOn(user, params, object)
+
+/datum/click_intercept/proc_holder/quit()
+ spell.remove_ranged_ability(spell.ranged_ability_user)
+ return ..()
+
/obj/effect/proc_holder/proc/add_ranged_ability(mob/living/user, var/msg)
if(!user || !user.client)
return
@@ -32,7 +46,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
user.ranged_ability.remove_ranged_ability(user)
user.ranged_ability = src
ranged_ability_user = user
- user.client.click_intercept = user.ranged_ability
+ user.client.click_intercept = new /datum/click_intercept/proc_holder(user.client, user.ranged_ability)
add_mousepointer(user.client)
active = TRUE
if(msg)
@@ -48,15 +62,17 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
C.mouse_pointer_icon = initial(C.mouse_pointer_icon)
/obj/effect/proc_holder/proc/remove_ranged_ability(mob/living/user, var/msg)
- if(!user || !user.client || (user.ranged_ability && user.ranged_ability != src)) //To avoid removing the wrong ability
+ if(!user || (user.ranged_ability && user.ranged_ability != src)) //To avoid removing the wrong ability
return
user.ranged_ability = null
ranged_ability_user = null
- user.client.click_intercept = null
- remove_mousepointer(user.client)
active = FALSE
- if(msg)
- to_chat(user, msg)
+ if(user.client)
+ qdel(user.client.click_intercept)
+ user.client.click_intercept = null
+ remove_mousepointer(user.client)
+ if(msg)
+ to_chat(user, msg)
update_icon()
/obj/effect/proc_holder/spell
@@ -114,10 +130,14 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
var/sound = null //The sound the spell makes when it is cast
-/obj/effect/proc_holder/spell/proc/cast_check(skipcharge = 0, mob/living/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell
- if(((!user.mind) || !(src in user.mind.spell_list)) && !(src in user.mob_spell_list))
- to_chat(user, " You shouldn't have this spell! Something's wrong.")
- return 0
+/* Checks if the user can cast the spell
+ * @param charge_check If the proc should do the cooldown check
+ * @param start_recharge If the proc should set the cooldown
+ * @param user The caster of the spell
+*/
+/obj/effect/proc_holder/spell/proc/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell
+ if(!can_cast(user, charge_check, TRUE))
+ return FALSE
if(ishuman(user))
var/mob/living/carbon/human/caster = user
@@ -126,49 +146,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
caster.reset_perspective(0)
return 0
- if(is_admin_level(user.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel
- return 0
-
- if(!skipcharge)
- switch(charge_type)
- if("recharge")
- if(charge_counter < charge_max)
- to_chat(user, still_recharging_msg)
- return 0
- if("charges")
- if(!charge_counter)
- to_chat(user, " [name] has no charges left.")
- return 0
-
- if(!ghost)
- if(user.stat && !stat_allowed)
- to_chat(user, " You can't cast this spell while incapacitated.")
- return 0
- if(ishuman(user) && (invocation_type == "whisper" || invocation_type == "shout") && user.is_muzzled())
- to_chat(user, "Mmmf mrrfff!")
- return 0
-
- var/obj/effect/proc_holder/spell/noclothes/clothes_spell = locate() in (user.mob_spell_list | (user.mind ? user.mind.spell_list : list()))
- if((ishuman(user) && clothes_req) && !istype(clothes_spell))//clothes check
- var/mob/living/carbon/human/H = user
- var/obj/item/clothing/robe = H.wear_suit
- var/obj/item/clothing/hat = H.head
- var/obj/item/clothing/shoes = H.shoes
- if(!robe || !hat || !shoes)
- to_chat(user, " Your outfit isn't complete, you should put on your robe and wizard hat, as well as sandals.")
- return 0
- if(!robe.magical || !hat.magical || !shoes.magical)
- to_chat(user, " Your outfit isn't magical enough, you should put on your robe and wizard hat, as well as your sandals.")
- return 0
- else if(!ishuman(user))
- if(clothes_req || human_req)
- to_chat(user, " This spell can only be cast by humans!")
- return 0
- if(nonabstract_req && (isbrain(user) || ispAI(user)))
- to_chat(user, " This spell can only be cast by physical beings!")
- return 0
-
- if(!skipcharge)
+ if(start_recharge)
switch(charge_type)
if("recharge")
charge_counter = 0 //doesn't start recharging until the targets selecting ends
@@ -230,12 +208,12 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
/obj/effect/proc_holder/spell/process()
charge_counter += 2
+ if(action)
+ action.UpdateButtonIcon()
if(charge_counter < charge_max)
return
STOP_PROCESSING(SSfastprocess, src)
charge_counter = charge_max
- if(action)
- action.UpdateButtonIcon()
/obj/effect/proc_holder/spell/proc/perform(list/targets, recharge = 1, mob/user = usr, make_attack_logs = TRUE) //if recharge is started is important for the trigger spells
before_cast(targets)
@@ -339,6 +317,19 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
target.vars[type] += amount //I bear no responsibility for the runtimes that'll happen if you try to adjust non-numeric or even non-existant vars
return
+/obj/effect/proc_holder/spell/proc/get_availability_percentage()
+ switch(charge_type)
+ if("recharge")
+ if(charge_counter == 0)
+ return 0
+ return charge_counter / charge_max
+ if("charges")
+ if(charge_counter)
+ return 1
+ return 0
+ if("holdervar")
+ return 1
+
/obj/effect/proc_holder/spell/targeted //can mean aoe for mobs (limited/unlimited number) or one target mob
var/max_targets = 1 //leave 0 for unlimited targets in range, 1 for one selectable target in range, more for limited number of casts (can all target one guy, depends on target_ignore_prev) in range
var/target_ignore_prev = 1 //only important if max_targets > 1, affects if the spell can be cast multiple times at one person from one cast
@@ -429,6 +420,100 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
return
+/obj/effect/proc_holder/spell/targeted/click
+ var/click_radius = 1 // How big the radius around the clicked atom is to find a suitable target. -1 is only the selected atom is considered
+ var/selection_activated_message = " Click on a target to cast the spell."
+ var/selection_deactivated_message = " You choose to not cast this spell."
+ var/allowed_type = /mob/living // Which type the targets have to be
+ var/auto_target_single = TRUE // If the spell should auto select a target if only one is found
+
+/obj/effect/proc_holder/spell/targeted/click/Click()
+ var/mob/living/user = usr
+ if(!istype(user))
+ return
+
+ if(active)
+ remove_ranged_ability(user, selection_deactivated_message)
+ else
+ if(cast_check(TRUE, FALSE, user))
+ if(auto_target_single && attempt_auto_target(user))
+ return
+
+ add_ranged_ability(user, selection_activated_message)
+ else
+ to_chat(user, " [src] is not ready to be used yet.")
+
+/obj/effect/proc_holder/spell/targeted/click/proc/attempt_auto_target(mob/user)
+ var/atom/target
+ for(var/atom/A in view_or_range(range, user, selection_type))
+ if(valid_target(A, user))
+ if(target)
+ return FALSE // Two targets found. ABORT
+ target = A
+
+ if(target && cast_check(TRUE, TRUE, user)) // Singular target found. Cast it instantly
+ to_chat(user, " Only one target found. Casting [src] on [target]!")
+ perform(list(target), user = user)
+ return TRUE
+ return FALSE
+
+/obj/effect/proc_holder/spell/targeted/click/InterceptClickOn(mob/living/user, params, atom/A)
+ if(..() || !cast_check(TRUE, TRUE, user))
+ remove_ranged_ability(user)
+ revert_cast(user)
+ return TRUE
+
+ var/list/targets = list()
+ if(valid_target(A, user))
+ targets.Add(A)
+
+ if((!max_targets || max_targets > targets.len) && click_radius >= 0)
+ var/list/found_others = list()
+ for(var/atom/target in range(click_radius, A))
+ if(valid_target(target, user))
+ found_others |= target
+ if(!max_targets)
+ targets.Add(found_others)
+ else
+ if(max_targets <= found_others.len + targets.len)
+ targets.Add(found_others)
+ else
+ switch(random_target_priority) //Add in the rest
+ if(TARGET_RANDOM)
+ while(targets.len < max_targets && found_others.len) // Add the others
+ targets.Add(pick_n_take(found_others))
+ if(TARGET_CLOSEST)
+ var/list/distances = list()
+ for(var/target in found_others)
+ distances[target] = get_dist(user, target)
+ sortTim(distances, /proc/cmp_numeric_asc, TRUE) // Sort on distance
+ for(var/target in distances)
+ targets.Add(target)
+ if(targets.len >= max_targets)
+ break
+
+
+ if(!targets.len)
+ to_chat(user, " No suitable target found.")
+ revert_cast(user)
+ return FALSE
+
+ perform(targets, user = user)
+ remove_ranged_ability(user)
+ return TRUE
+
+/* Checks if a target is valid
+ * Should not include to_chats or other types of messages since this is used often on tons of targets.
+ * @param target The target to check
+ * @param user The user of the spell
+*/
+/obj/effect/proc_holder/spell/targeted/click/proc/valid_target(target, user)
+ return istype(target, allowed_type) && (include_user || target != user) && \
+ (target in view_or_range(range, user, selection_type))
+
+/obj/effect/proc_holder/spell/targeted/click/choose_targets(mob/living/user, atom/A) // Not used
+ return
+
/obj/effect/proc_holder/spell/aoe_turf/choose_targets(mob/user = usr)
var/list/targets = list()
@@ -444,6 +529,11 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
return
+// Normally, AoE spells will generate an attack log for every turf they loop over, while searching for targets.
+// With this override, all /aoe_turf type spells will only generate 1 log, saying that the user has cast the spell.
+/obj/effect/proc_holder/spell/aoe_turf/perform(list/targets, recharge, mob/user, make_attack_logs)
+ add_attack_logs(user, null, "Cast the AoE spell [name]", ATKLOG_ALL)
+ return ..(targets, recharge, user, FALSE)
/obj/effect/proc_holder/spell/targeted/proc/los_check(mob/A,mob/B)
//Checks for obstacles from A to B
@@ -457,30 +547,39 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
qdel(dummy)
return 1
-/obj/effect/proc_holder/spell/proc/can_cast(mob/user = usr)
+/obj/effect/proc_holder/spell/proc/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE)
if(((!user.mind) || !(src in user.mind.spell_list)) && !(src in user.mob_spell_list))
+ if(show_message)
+ to_chat(user, " You shouldn't have this spell! Something's wrong.")
return 0
if(is_admin_level(user.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel
return 0
- switch(charge_type)
- if("recharge")
- if(charge_counter < charge_max)
- return 0
- if("charges")
- if(!charge_counter)
- return 0
-
- if(user.stat && !stat_allowed)
- return 0
+ if(charge_check)
+ switch(charge_type)
+ if("recharge")
+ if(charge_counter < charge_max)
+ if(show_message)
+ to_chat(user, still_recharging_msg)
+ return 0
+ if("charges")
+ if(!charge_counter)
+ if(show_message)
+ to_chat(user, " [name] has no charges left.")
+ return 0
+ if(!ghost)
+ if(user.stat && !stat_allowed)
+ if(show_message)
+ to_chat(user, " You can't cast this spell while incapacitated.")
+ return 0
+ if(ishuman(user) && (invocation_type == "whisper" || invocation_type == "shout") && user.is_muzzled())
+ if(show_message)
+ to_chat(user, "Mmmf mrrfff!")
+ return 0
if(ishuman(user))
var/mob/living/carbon/human/H = user
-
- if((invocation_type == "whisper" || invocation_type == "shout") && H.is_muzzled())
- return 0
-
var/clothcheck = locate(/obj/effect/proc_holder/spell/noclothes) in user.mob_spell_list
var/clothcheck2 = user.mind && (locate(/obj/effect/proc_holder/spell/noclothes) in user.mind.spell_list)
if(clothes_req && !clothcheck && !clothcheck2) //clothes check
@@ -488,12 +587,20 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
var/obj/item/clothing/hat = H.head
var/obj/item/clothing/shoes = H.shoes
if(!robe || !hat || !shoes)
+ if(show_message)
+ to_chat(user, " Your outfit isn't complete, you should put on your robe and wizard hat, as well as sandals.")
return 0
if(!robe.magical || !hat.magical || !shoes.magical)
+ if(show_message)
+ to_chat(user, " Your outfit isn't magical enough, you should put on your robe and wizard hat, as well as your sandals.")
return 0
else
if(clothes_req || human_req)
+ if(show_message)
+ to_chat(user, " This spell can only be cast by humans!")
return 0
if(nonabstract_req && (isbrain(user) || ispAI(user)))
+ if(show_message)
+ to_chat(user, " This spell can only be cast by physical beings!")
return 0
return 1
diff --git a/code/datums/spells/area_teleport.dm b/code/datums/spells/area_teleport.dm
index 0d24a984014..5986da3a412 100644
--- a/code/datums/spells/area_teleport.dm
+++ b/code/datums/spells/area_teleport.dm
@@ -11,7 +11,7 @@
/obj/effect/proc_holder/spell/targeted/area_teleport/perform(list/targets, recharge = 1, mob/living/user = usr)
var/thearea = before_cast(targets)
- if(!thearea || !cast_check(1))
+ if(!thearea || !cast_check(TRUE, FALSE, user))
revert_cast()
return
invocation(thearea)
diff --git a/code/datums/spells/chaplain.dm b/code/datums/spells/chaplain.dm
index bdb7bb3f551..f16a2a3fbac 100644
--- a/code/datums/spells/chaplain.dm
+++ b/code/datums/spells/chaplain.dm
@@ -1,25 +1,31 @@
-/obj/effect/proc_holder/spell/targeted/chaplain_bless
+/obj/effect/proc_holder/spell/targeted/click/chaplain_bless
name = "Bless"
desc = "Blesses a single person."
school = "transmutation"
charge_max = 60
- clothes_req = 0
+ clothes_req = FALSE
invocation = "none"
invocation_type = "none"
max_targets = 1
- include_user = 0
- humans_only = 1
-
+ include_user = FALSE
+ allowed_type = /mob/living/carbon/human
+ selection_activated_message = " You prepare a blessing. Click on a target to start blessing."
+ selection_deactivated_message = " The crew will be blessed another time."
range = 1
+ click_radius = -1 // Only precision clicking
cooldown_min = 20
action_icon_state = "shield"
+/obj/effect/proc_holder/spell/targeted/click/chaplain_bless/valid_target(mob/living/carbon/human/target, user)
+ if(!..())
+ return FALSE
-/obj/effect/proc_holder/spell/targeted/chaplain_bless/cast(list/targets, mob/living/user = usr, distanceoverride)
+ return target.mind && target.ckey && !target.stat
+/obj/effect/proc_holder/spell/targeted/click/chaplain_bless/cast(list/targets, mob/living/user = usr)
if(!istype(user))
to_chat(user, "Somehow, you are not a living mob. This should never happen. Report this bug.")
revert_cast()
@@ -35,32 +41,7 @@
revert_cast()
return
- var/mob/living/carbon/human/target = targets[range]
-
- if(!istype(target))
- to_chat(user, "No target.")
- revert_cast()
- return
-
- if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it.
- to_chat(user, "[target] is too far away!")
- revert_cast()
- return
-
- if(!target.mind)
- to_chat(user, "[target] appears to be catatonic. Your blessing would have no effect.")
- revert_cast()
- return
-
- if(!target.ckey)
- to_chat(user, "[target] appears to be too out of it to benefit from this.")
- revert_cast()
- return
-
- if(target.stat == DEAD)
- to_chat(user, "[target] is already dead. There is no point.")
- revert_cast()
- return
+ var/mob/living/carbon/human/target = targets[1]
spawn(0) // allows cast to complete even if recipient ignores the prompt
if(alert(target, "[user] wants to bless you, in the name of [user.p_their()] religion. Accept?", "Accept Blessing?", "Yes", "No") == "Yes") // prevents forced conversions
diff --git a/code/datums/spells/devil.dm b/code/datums/spells/devil.dm
index 27b2fcb8851..f9e075bdf81 100644
--- a/code/datums/spells/devil.dm
+++ b/code/datums/spells/devil.dm
@@ -21,13 +21,18 @@
action_background_icon_state = "bg_demon"
-/obj/effect/proc_holder/spell/targeted/summon_contract
+/obj/effect/proc_holder/spell/targeted/click/summon_contract
name = "Summon infernal contract"
desc = "Skip making a contract by hand, just do it by magic."
invocation_type = "whisper"
invocation = "Just sign on the dotted line."
- include_user = 0
+ selection_activated_message = " You prepare a detailed contract. Click on a target to summon the contract in his hands."
+ selection_deactivated_message = " You archive the contract for later use."
+ include_user = FALSE
range = 5
+ auto_target_single = FALSE // Prevent an accidental contract from summoning
+ click_radius = -1 // Precision clicking required
+ allowed_type = /mob/living/carbon
clothes_req = FALSE
school = "conjuration"
charge_max = 150
@@ -35,8 +40,9 @@
action_icon_state = "spell_default"
action_background_icon_state = "bg_demon"
-/obj/effect/proc_holder/spell/targeted/summon_contract/cast(list/targets, mob/user = usr)
- for(var/mob/living/carbon/C in targets)
+/obj/effect/proc_holder/spell/targeted/click/summon_contract/cast(list/targets, mob/user = usr)
+ for(var/target in targets)
+ var/mob/living/carbon/C = target
if(C.mind && user.mind)
if(C.stat == DEAD)
if(user.drop_item())
@@ -63,7 +69,7 @@
to_chat(user," [C] seems to not be sentient. You are unable to summon a contract for them.")
-/obj/effect/proc_holder/spell/fireball/hellish
+/obj/effect/proc_holder/spell/targeted/click/fireball/hellish
name = "Hellfire"
desc = "This spell launches hellfire at the target."
school = "evocation"
@@ -74,8 +80,8 @@
fireball_type = /obj/item/projectile/magic/fireball/infernal
action_background_icon_state = "bg_demon"
-/obj/effect/proc_holder/spell/fireball/hellish/cast(list/targets, mob/living/user = usr)
- msg_admin_attack("[key_name_admin(usr)] has fired a fireball.", ATKLOG_FEW)
+/obj/effect/proc_holder/spell/targeted/click/fireball/hellish/cast(list/targets, mob/living/user = usr)
+ add_attack_logs(user, targets, "has fired a Hellfire ball", ATKLOG_FEW)
.=..()
diff --git a/code/datums/spells/horsemask.dm b/code/datums/spells/horsemask.dm
index 0140fbdf951..d5f686fb763 100644
--- a/code/datums/spells/horsemask.dm
+++ b/code/datums/spells/horsemask.dm
@@ -1,39 +1,31 @@
-/obj/effect/proc_holder/spell/targeted/horsemask
+/obj/effect/proc_holder/spell/targeted/click/horsemask
name = "Curse of the Horseman"
desc = "This spell triggers a curse on a target, causing them to wield an unremovable horse head mask. They will speak like a horse! Any masks they are wearing will be disintegrated. This spell does not require robes."
school = "transmutation"
charge_type = "recharge"
charge_max = 150
charge_counter = 0
- clothes_req = 0
- stat_allowed = 0
+ clothes_req = FALSE
+ stat_allowed = FALSE
invocation = "KN'A FTAGHU, PUCK 'BTHNK!"
invocation_type = "shout"
range = 7
cooldown_min = 30 //30 deciseconds reduction per rank
selection_type = "range"
+ selection_activated_message = " You start to quietly neigh an incantation. Click on or near a target to cast the spell."
+ selection_deactivated_message = " You stop neighing to yourself."
+ allowed_type = /mob/living/carbon/human
+
action_icon_state = "barn"
sound = 'sound/magic/HorseHead_curse.ogg'
-/obj/effect/proc_holder/spell/targeted/horsemask/cast(list/targets, mob/user = usr)
+/obj/effect/proc_holder/spell/targeted/click/horsemask/cast(list/targets, mob/user = usr)
if(!targets.len)
to_chat(user, " No target found in range.")
return
- var/mob/living/carbon/target = targets[1]
-
- if(!target)
- return
-
-
- if(!ishuman(target))
- to_chat(user, " It'd be stupid to curse [target] with a horse's head!")
- return
-
- if(!(target in oview(range)))//If they are not in overview after selection.
- to_chat(user, " They are too far away!")
- return
+ var/mob/living/carbon/human/target = targets[1]
var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead
magichead.flags |= NODROP | DROPDEL //curses!
diff --git a/code/datums/spells/knock.dm b/code/datums/spells/knock.dm
index 88f5eec9474..e4c652ae1d3 100644
--- a/code/datums/spells/knock.dm
+++ b/code/datums/spells/knock.dm
@@ -13,11 +13,6 @@
action_icon_state = "knock"
sound = 'sound/magic/knock.ogg'
-// Knock doesn't need to generate an attack log for every turf, set `make_attack_logs` to FALSE and just create a custom one.
-/obj/effect/proc_holder/spell/aoe_turf/knock/perform(list/targets, recharge, mob/user)
- add_attack_logs(user, user, "cast the spell [name]", ATKLOG_ALL)
- return ..(targets, recharge, user, make_attack_logs = FALSE)
-
/obj/effect/proc_holder/spell/aoe_turf/knock/cast(list/targets, mob/user = usr)
for(var/turf/T in targets)
for(var/obj/machinery/door/door in T.contents)
diff --git a/code/datums/spells/lightning.dm b/code/datums/spells/lightning.dm
index e7821aed6c2..20cd8dccdf5 100644
--- a/code/datums/spells/lightning.dm
+++ b/code/datums/spells/lightning.dm
@@ -25,10 +25,10 @@
/obj/effect/proc_holder/spell/targeted/lightning/Click()
if(!ready && start_time == 0)
- if(cast_check())
+ if(cast_check(TRUE, FALSE, usr))
StartChargeup()
else
- if(ready && cast_check(skipcharge=1))
+ if(ready && cast_check(TRUE, TRUE, usr))
choose_targets()
return 1
@@ -44,7 +44,7 @@
if(ready)
Discharge()
-obj/effect/proc_holder/spell/targeted/lightning/proc/Reset(mob/user = usr)
+/obj/effect/proc_holder/spell/targeted/lightning/proc/Reset(mob/user = usr)
ready = 0
start_time = 0
if(halo)
diff --git a/code/datums/spells/magnet.dm b/code/datums/spells/magnet.dm
index fea6780085c..155a3cb0b45 100644
--- a/code/datums/spells/magnet.dm
+++ b/code/datums/spells/magnet.dm
@@ -20,10 +20,10 @@
/obj/effect/proc_holder/spell/targeted/magnet/Click()
if(!ready && start_time == 0)
- if(cast_check())
+ if(cast_check(TRUE, FALSE, usr))
StartChargeup()
else
- if(ready && cast_check(skipcharge=1))
+ if(ready && cast_check(TRUE, TRUE, usr))
choose_targets()
return 1
@@ -39,7 +39,7 @@
if(ready)
Discharge()
-obj/effect/proc_holder/spell/targeted/magnet/proc/Reset(mob/user = usr)
+/obj/effect/proc_holder/spell/targeted/magnet/proc/Reset(mob/user = usr)
ready = 0
energy = 0
start_time = 0
diff --git a/code/datums/spells/mind_transfer.dm b/code/datums/spells/mind_transfer.dm
index 3d9031a8e03..e4924ee1c2f 100644
--- a/code/datums/spells/mind_transfer.dm
+++ b/code/datums/spells/mind_transfer.dm
@@ -1,4 +1,4 @@
-/obj/effect/proc_holder/spell/targeted/mind_transfer
+/obj/effect/proc_holder/spell/targeted/click/mind_transfer
name = "Mind Transfer"
desc = "This spell allows the user to switch bodies with a target."
@@ -8,33 +8,29 @@
invocation = "GIN'YU CAPAN"
invocation_type = "whisper"
range = 1
+ click_radius = 0 // Still gotta be pretty accurate
+ selection_activated_message = " You prepare to transfer your mind. Click on a target to cast the spell."
+ selection_deactivated_message = " You decide that your current form is good enough."
cooldown_min = 200 //100 deciseconds reduction per rank
var/list/protected_roles = list("Wizard","Changeling","Cultist") //which roles are immune to the spell
var/paralysis_amount_caster = 20 //how much the caster is paralysed for after the spell
var/paralysis_amount_victim = 20 //how much the victim is paralysed for after the spell
action_icon_state = "mindswap"
+/obj/effect/proc_holder/spell/targeted/click/mind_transfer/valid_target(mob/living/target, user)
+ if(!..())
+ return FALSE
+ return target.stat != DEAD && target.key && target.mind
+
/*
Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do.
Make sure spells that are removed from spell_list are actually removed and deleted when mind transfering.
Also, you never added distance checking after target is selected. I've went ahead and did that.
*/
-/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets, mob/user = usr, distanceoverride)
+/obj/effect/proc_holder/spell/targeted/click/mind_transfer/cast(list/targets, mob/user = usr)
var/mob/living/target = targets[range]
- if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it.
- to_chat(user, "They are too far away!")
- return
-
- if(target.stat == DEAD)
- to_chat(user, "You don't particularly want to be dead.")
- return
-
- if(!target.key || !target.mind)
- to_chat(user, "[target.p_they(TRUE)] appear[target.p_s()] to be catatonic. Not even magic can affect [target.p_their()] vacant mind.")
- return
-
if(user.suiciding)
to_chat(user, " You're killing yourself! You can't concentrate enough to do this!")
return
diff --git a/code/datums/spells/wizard.dm b/code/datums/spells/wizard.dm
index 94383f1b1e2..a99655222a4 100644
--- a/code/datums/spells/wizard.dm
+++ b/code/datums/spells/wizard.dm
@@ -308,76 +308,50 @@
duration = 300
sound = 'sound/magic/blind.ogg'
-/obj/effect/proc_holder/spell/fireball
+/obj/effect/proc_holder/spell/targeted/click/fireball
name = "Fireball"
desc = "This spell fires a fireball at a target and does not require wizard garb."
school = "evocation"
charge_max = 60
- clothes_req = 0
+ clothes_req = FALSE
invocation = "ONI SOMA"
invocation_type = "shout"
+ auto_target_single = FALSE // Having this true won't ever find a single target and is just lost processing power
range = 20
cooldown_min = 20 //10 deciseconds reduction per rank
+
+ click_radius = -1
+ selection_activated_message = " Your prepare to cast your fireball spell! Left-click to cast at a target!"
+ selection_deactivated_message = " You extinguish your fireball...for now."
+ allowed_type = /atom // FIRE AT EVERYTHING
+
var/fireball_type = /obj/item/projectile/magic/fireball
action_icon_state = "fireball0"
sound = 'sound/magic/fireball.ogg'
active = FALSE
-/obj/effect/proc_holder/spell/fireball/Click()
- var/mob/living/user = usr
- if(!istype(user))
- return
-
- var/msg
-
- if(!can_cast(user))
- msg = " You can no longer cast Fireball."
- remove_ranged_ability(user, msg)
- return
-
- if(active)
- msg = " You extinguish your fireball...for now."
- remove_ranged_ability(user, msg)
- else
- msg = " Your prepare to cast your fireball spell! Left-click to cast at a target!"
- add_ranged_ability(user, msg)
-
-/obj/effect/proc_holder/spell/fireball/update_icon()
+/obj/effect/proc_holder/spell/targeted/click/fireball/update_icon()
if(!action)
return
action.button_icon_state = "fireball[active]"
action.UpdateButtonIcon()
-/obj/effect/proc_holder/spell/fireball/InterceptClickOn(mob/living/user, params, atom/target)
- if(..())
- return FALSE
-
- if(!cast_check(0, user))
- remove_ranged_ability(user)
- return FALSE
-
- var/list/targets = list(target)
- perform(targets, user = user)
-
- return TRUE
-
-/obj/effect/proc_holder/spell/fireball/cast(list/targets, mob/living/user = usr)
+/obj/effect/proc_holder/spell/targeted/click/fireball/cast(list/targets, mob/living/user = usr)
var/target = targets[1] //There is only ever one target for fireball
var/turf/T = user.loc
var/turf/U = get_step(user, user.dir) // Get the tile infront of the move, based on their direction
if(!isturf(U) || !isturf(T))
- return 0
+ return FALSE
var/obj/item/projectile/magic/fireball/FB = new fireball_type(user.loc)
FB.current = get_turf(user)
FB.preparePixelProjectile(target, get_turf(target), user)
FB.fire()
user.newtonian_move(get_dir(U, T))
- remove_ranged_ability(user)
- return 1
+ return TRUE
/obj/effect/proc_holder/spell/aoe_turf/repulse
name = "Repulse"
diff --git a/code/datums/supplypacks.dm b/code/datums/supplypacks.dm
index a18a9d74f28..98272825e2f 100644
--- a/code/datums/supplypacks.dm
+++ b/code/datums/supplypacks.dm
@@ -55,6 +55,8 @@ GLOBAL_LIST_INIT(all_supply_groups, list(SUPPLY_EMERGENCY,SUPPLY_SECURITY,SUPPLY
var/list/announce_beacons = list() // Particular beacons that we'll notify the relevant department when we reach
var/special = FALSE //Event/Station Goals/Admin enabled packs
var/special_enabled = FALSE
+ /// List of names for being done in TGUI
+ var/list/tgui_manifest = list()
/datum/supply_packs/New()
@@ -63,8 +65,12 @@ GLOBAL_LIST_INIT(all_supply_groups, list(SUPPLY_EMERGENCY,SUPPLY_SECURITY,SUPPLY
if(!path) continue
var/atom/movable/AM = path
manifest += " [initial(AM.name)]"
+ // Add the name to the UI manifest
+ tgui_manifest += "[initial(AM.name)]"
manifest += ""
+
+
////// Use the sections to keep things tidy please /Malkevin
//////////////////////////////////////////////////////////////////////////////
@@ -344,6 +350,14 @@ GLOBAL_LIST_INIT(all_supply_groups, list(SUPPLY_EMERGENCY,SUPPLY_SECURITY,SUPPLY
cost = 15
containername = "tactical armor crate"
+/datum/supply_packs/security/armory/webbing
+ name = "Webbing Crate"
+ contains = list(/obj/item/storage/belt/security/webbing,
+ /obj/item/storage/belt/security/webbing,
+ /obj/item/storage/belt/security/webbing)
+ cost = 15
+ containername = "tactical webbing crate"
+
/datum/supply_packs/security/armory/swat
name = "SWAT gear crate"
contains = list(/obj/item/clothing/head/helmet/swat,
@@ -633,7 +647,6 @@ GLOBAL_LIST_INIT(all_supply_groups, list(SUPPLY_EMERGENCY,SUPPLY_SECURITY,SUPPLY
contains = list(/obj/machinery/power/emitter,
/obj/machinery/power/emitter)
cost = 10
- containertype = /obj/structure/closet/crate/secure
containername = "emitter crate"
access = ACCESS_CE
containertype = /obj/structure/closet/crate/secure/engineering
@@ -717,7 +730,7 @@ GLOBAL_LIST_INIT(all_supply_groups, list(SUPPLY_EMERGENCY,SUPPLY_SECURITY,SUPPLY
name = "Supermatter Shard Crate"
contains = list(/obj/machinery/power/supermatter_shard)
cost = 50 //So cargo thinks twice before killing themselves with it
- containertype = /obj/structure/closet/crate/secure
+ containertype = /obj/structure/closet/crate/secure/engineering
containername = "supermatter shard crate"
access = ACCESS_CE
@@ -728,7 +741,7 @@ GLOBAL_LIST_INIT(all_supply_groups, list(SUPPLY_EMERGENCY,SUPPLY_SECURITY,SUPPLY
/obj/item/pipe/circulator,
/obj/item/pipe/circulator)
cost = 25
- containertype = /obj/structure/closet/crate/secure
+ containertype = /obj/structure/closet/crate/secure/engineering
containername = "thermo-electric generator crate"
access = ACCESS_CE
announce_beacons = list("Engineering" = list("Chief Engineer's Desk", "Atmospherics"))
@@ -981,12 +994,6 @@ GLOBAL_LIST_INIT(all_supply_groups, list(SUPPLY_EMERGENCY,SUPPLY_SECURITY,SUPPLY
containername = "shield generators crate"
access = ACCESS_TELEPORTER
-/datum/supply_packs/science/modularpc
- name = "Deluxe Silicate Selections restocking unit"
- cost = 15
- contains = list(/obj/item/vending_refill/modularpc)
- containername = "computer supply crate"
-
/datum/supply_packs/science/transfer_valves
name = "Tank Transfer Valves Crate"
contains = list(/obj/item/transfer_valve,
@@ -1174,11 +1181,10 @@ GLOBAL_LIST_INIT(all_supply_groups, list(SUPPLY_EMERGENCY,SUPPLY_SECURITY,SUPPLY
containername = "fox crate"
/datum/supply_packs/organic/butterfly
- name = "Butterflies Crate"
+ name = "Butterfly Crate"
cost = 50
containertype = /obj/structure/closet/critter/butterfly
- containername = "butterflies crate"
- contraband = 1
+ containername = "butterfly crate"
/datum/supply_packs/organic/deer
name = "Deer Crate"
@@ -1767,6 +1773,16 @@ GLOBAL_LIST_INIT(all_supply_groups, list(SUPPLY_EMERGENCY,SUPPLY_SECURITY,SUPPLY
cost = 20
containername = "polo supply crate"
+/datum/supply_packs/misc/boxing //For non log spamming cargo brawls!
+ name = "Boxing Supply Crate"
+ // 4 boxing gloves
+ contains = list(/obj/item/clothing/gloves/boxing/blue,
+ /obj/item/clothing/gloves/boxing/green,
+ /obj/item/clothing/gloves/boxing/yellow,
+ /obj/item/clothing/gloves/boxing)
+ cost = 15
+ containername = "boxing supply crate"
+
///////////// Station Goals
/datum/supply_packs/misc/station_goal
diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm
index 4814e27380b..389b21dd6d1 100644
--- a/code/datums/uplink_item.dm
+++ b/code/datums/uplink_item.dm
@@ -59,10 +59,6 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
return uplink_items
-/datum/nano_item_lists
- var/list/items_nano
- var/list/items_reference
-
// You can change the order of the list by putting datums before/after one another OR
// you can use the last variable to make sure it appears last, well have the category appear last.
@@ -412,9 +408,9 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
/datum/uplink_item/jobspecific/energizedfireaxe
name = "Energized Fire Axe"
- desc = "A fire axe with a massive electrical charge built into it. It can release this charge on its first victim and will be rather plain after that."
+ desc = "A fire axe with a massive energy charge built into it. Upon striking someone while charged it will throw them backwards while stunning them briefly, but will take some time to charge up again. It is also much sharper than a regular axe and can pierce light armor."
reference = "EFA"
- item = /obj/item/twohanded/energizedfireaxe
+ item = /obj/item/twohanded/fireaxe/energized
cost = 10
job = list("Life Support Specialist")
@@ -736,6 +732,14 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 12 // normally 18
gamemodes = list(/datum/game_mode/nuclear)
+/datum/uplink_item/ammo/bulldog_XLmagsbag
+ name = "Bulldog - 12g XL Magazine Duffel Bag"
+ desc = "A duffel bag containing three 16 round drum magazines(Slug, Buckshot, Dragon's Breath)."
+ reference = "12XLDB"
+ item = /obj/item/storage/backpack/duffel/syndie/ammo/shotgunXLmags
+ cost = 12 // normally 18
+ gamemodes = list(/datum/game_mode/nuclear)
+
/datum/uplink_item/ammo/smg
name = "C-20r - .45 Magazine"
desc = "An additional 20-round .45 magazine for use in the C-20r submachine gun. These bullets pack a lot of punch that can knock most targets down, but do limited overall damage."
@@ -1175,7 +1179,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
/datum/uplink_item/stealthy_tools/camera_bug
name = "Camera Bug"
- desc = "Enables you to view all cameras on the network and track a target. Bugging cameras allows you to disable them remotely."
+ desc = "Enables you to view all cameras on the network to track a target."
reference = "CB"
item = /obj/item/camera_bug
cost = 1
@@ -1555,11 +1559,11 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 10
/datum/uplink_item/cyber_implants/antistun
- name = "CNS Rebooter Implant"
- desc = "This implant will help you get back up on your feet faster after being stunned. \
+ name = "Hardened CNS Rebooter Implant"
+ desc = "This implant will help you get back up on your feet faster after being stunned. It is invulnerable to EMPs. \
Comes with an automated implanting tool."
reference = "CIAS"
- item = /obj/item/organ/internal/cyberimp/brain/anti_stun
+ item = /obj/item/organ/internal/cyberimp/brain/anti_stun/hardened
cost = 12
/datum/uplink_item/cyber_implants/reviver
@@ -1725,8 +1729,8 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
U.purchase_log += " [bicon(C)]"
for(var/item in bought_items)
- new item(C)
- U.purchase_log += " [bicon(item)]"
+ var/obj/purchased = new item(C)
+ U.purchase_log += " [bicon(purchased)]"
log_game("[key_name(usr)] purchased a surplus crate with [jointext(itemlog, ", ")]")
/datum/uplink_item/bundles_TC/telecrystal
diff --git a/code/datums/weather/weather_types/floor_is_lava.dm b/code/datums/weather/weather_types/floor_is_lava.dm
index 9f98a481dbf..0233e0aee71 100644
--- a/code/datums/weather/weather_types/floor_is_lava.dm
+++ b/code/datums/weather/weather_types/floor_is_lava.dm
@@ -35,6 +35,8 @@
return
if(!L.client) //Only sentient people are going along with it!
return
+ if(L.flying)
+ return
L.adjustFireLoss(3)
/datum/weather/floor_is_lava/fake
diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm
index dcde08d7ec1..ff7a0169c15 100644
--- a/code/datums/wires/airlock.dm
+++ b/code/datums/wires/airlock.dm
@@ -1,67 +1,29 @@
// Wires for airlocks
/datum/wires/airlock/secure
- random = 1
+ randomize = TRUE
/datum/wires/airlock
holder_type = /obj/machinery/door/airlock
- wire_count = 12
- window_x = 410
- window_y = 570
+ wire_count = 12 // 10 actual, 2 duds.
+ proper_name = "Airlock"
+ window_x = 400
+ window_y = 101
-#define AIRLOCK_WIRE_IDSCAN 1
-#define AIRLOCK_WIRE_MAIN_POWER1 2
-#define AIRLOCK_WIRE_DOOR_BOLTS 4
-#define AIRLOCK_WIRE_BACKUP_POWER1 8
-#define AIRLOCK_WIRE_OPEN_DOOR 16
-#define AIRLOCK_WIRE_AI_CONTROL 32
-#define AIRLOCK_WIRE_ELECTRIFY 64
-#define AIRLOCK_WIRE_SAFETY 128
-#define AIRLOCK_WIRE_SPEED 256
-#define AIRLOCK_WIRE_LIGHT 512
+/datum/wires/airlock/New(atom/_holder)
+ wires = list(
+ WIRE_IDSCAN, WIRE_MAIN_POWER1, WIRE_DOOR_BOLTS, WIRE_BACKUP_POWER1, WIRE_OPEN_DOOR,
+ WIRE_AI_CONTROL, WIRE_ELECTRIFY, WIRE_SAFETY, WIRE_SPEED, WIRE_BOLT_LIGHT
+ )
+ return ..()
-/datum/wires/airlock/GetWireName(index)
- switch(index)
- if(AIRLOCK_WIRE_IDSCAN)
- return "ID Scan"
-
- if(AIRLOCK_WIRE_MAIN_POWER1)
- return "Primary Power"
-
- if(AIRLOCK_WIRE_DOOR_BOLTS)
- return "Door Bolts"
-
- if(AIRLOCK_WIRE_BACKUP_POWER1)
- return "Primary Backup Power"
-
- if(AIRLOCK_WIRE_OPEN_DOOR)
- return "Door State"
-
- if(AIRLOCK_WIRE_AI_CONTROL)
- return "AI Control"
-
- if(AIRLOCK_WIRE_ELECTRIFY)
- return "Electrification"
-
- if(AIRLOCK_WIRE_ELECTRIFY)
- return "Door Safeties"
-
- if(AIRLOCK_WIRE_ELECTRIFY)
- return "Door Timing"
-
- if(AIRLOCK_WIRE_ELECTRIFY)
- return "Bolt Lights"
-
-/datum/wires/airlock/CanUse(mob/living/L)
+/datum/wires/airlock/interactable(mob/user)
var/obj/machinery/door/airlock/A = holder
- if(iscarbon(L))
- if(A.Adjacent(L))
- if(A.isElectrified())
- if(A.shock(L, 100))
- return 0
+ if(iscarbon(user) && A.Adjacent(user) && A.isElectrified() && A.shock(user, 100))
+ return FALSE
if(A.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
/datum/wires/airlock/get_status()
. = ..()
@@ -70,21 +32,20 @@
. += "The door bolts [A.locked ? "have fallen!" : "look up."]"
. += "The door bolt lights are [(A.lights && haspower) ? "on." : "off!"]"
- . += "The test light is [haspower ? "on." : "off!"]"
- . += "The 'AI control allowed' light is [(A.aiControlDisabled == 0 && !A.emagged && haspower) ? "on" : "off"]."
+ . += "The test light is [haspower ? "on." : "off!"]"
+ . += "The 'AI control allowed' light is [(A.aiControlDisabled == AICONTROLDISABLED_OFF && !A.emagged && haspower) ? "on" : "off"]."
. += "The 'Check Wiring' light is [(A.safe == 0 && haspower) ? "on" : "off"]."
. += "The 'Check Timing Mechanism' light is [(A.normalspeed == 0 && haspower) ? "on" : "off"]."
. += "The emergency lights are [(A.emergency && haspower) ? "on" : "off"]."
-/datum/wires/airlock/UpdateCut(index, mended)
-
+/datum/wires/airlock/on_cut(wire, mend)
var/obj/machinery/door/airlock/A = holder
- switch(index)
- if(AIRLOCK_WIRE_IDSCAN)
- A.aiDisabledIdScanner = !mended
- if(AIRLOCK_WIRE_MAIN_POWER1)
+ switch(wire)
+ if(WIRE_IDSCAN)
+ A.aiDisabledIdScanner = !mend
+ if(WIRE_MAIN_POWER1)
- if(!mended)
+ if(!mend)
//Cutting either one disables the main door power, but unless backup power is also cut, the backup power re-powers the door in 10 seconds. While unpowered, the door may be crowbarred open, but bolts-raising will not work. Cutting these wires may electocute the user.
A.loseMainPower()
A.shock(usr, 50)
@@ -92,9 +53,9 @@
A.regainMainPower()
A.shock(usr, 50)
- if(AIRLOCK_WIRE_BACKUP_POWER1)
+ if(WIRE_BACKUP_POWER1)
- if(!mended)
+ if(!mend)
//Cutting either one disables the backup door power (allowing it to be crowbarred open, but disabling bolts-raising), but may electocute the user.
A.loseBackupPower()
A.shock(usr, 50)
@@ -102,66 +63,66 @@
A.regainBackupPower()
A.shock(usr, 50)
- if(AIRLOCK_WIRE_DOOR_BOLTS)
+ if(WIRE_DOOR_BOLTS)
- if(!mended)
+ if(!mend)
//Cutting this wire also drops the door bolts, and mending it does not raise them. (This is what happens now, except there are a lot more wires going to door bolts at present)
A.lock(1)
A.update_icon()
- if(AIRLOCK_WIRE_AI_CONTROL)
+ if(WIRE_AI_CONTROL)
- if(!mended)
+ if(!mend)
//one wire for AI control. Cutting this prevents the AI from controlling the door unless it has hacked the door through the power connection (which takes about a minute). If both main and backup power are cut, as well as this wire, then the AI cannot operate or hack the door at all.
- //aiControlDisabled: If 1, AI control is disabled until the AI hacks back in and disables the lock. If 2, the AI has bypassed the lock. If -1, the control is enabled but the AI had bypassed it earlier, so if it is disabled again the AI would have no trouble getting back in.
- if(A.aiControlDisabled == 0)
- A.aiControlDisabled = 1
- else if(A.aiControlDisabled == -1)
- A.aiControlDisabled = 2
+ //aiControlDisabled: see explanation in code\__DEFINES\construction.dm#32
+ if(A.aiControlDisabled == AICONTROLDISABLED_OFF)
+ A.aiControlDisabled = AICONTROLDISABLED_ON
+ else if(A.aiControlDisabled == AICONTROLDISABLED_PERMA)
+ A.aiControlDisabled = AICONTROLDISABLED_BYPASS
else
- if(A.aiControlDisabled == 1)
- A.aiControlDisabled = 0
- else if(A.aiControlDisabled == 2)
- A.aiControlDisabled = -1
+ if(A.aiControlDisabled == AICONTROLDISABLED_ON)
+ A.aiControlDisabled = AICONTROLDISABLED_OFF
+ else if(A.aiControlDisabled == AICONTROLDISABLED_BYPASS)
+ A.aiControlDisabled = AICONTROLDISABLED_PERMA
- if(AIRLOCK_WIRE_ELECTRIFY)
- if(!mended)
+ if(WIRE_ELECTRIFY)
+ if(!mend)
//Cutting this wire electrifies the door, so that the next person to touch the door without insulated gloves gets electrocuted.
A.electrify(-1)
else
A.electrify(0)
return // Don't update the dialog.
- if(AIRLOCK_WIRE_SAFETY)
- A.safe = mended
+ if(WIRE_SAFETY)
+ A.safe = mend
- if(AIRLOCK_WIRE_SPEED)
- A.autoclose = mended
- if(mended)
+ if(WIRE_SPEED)
+ A.autoclose = mend
+ if(mend)
if(!A.density)
- spawn(0)
- A.close()
+ INVOKE_ASYNC(A, /obj/machinery/door/airlock/.proc/close)
- if(AIRLOCK_WIRE_LIGHT)
- A.lights = mended
+ if(WIRE_BOLT_LIGHT)
+ A.lights = mend
A.update_icon()
..()
-/datum/wires/airlock/UpdatePulsed(index)
-
+/datum/wires/airlock/on_pulse(wire)
var/obj/machinery/door/airlock/A = holder
- switch(index)
- if(AIRLOCK_WIRE_IDSCAN)
+ switch(wire)
+ if(WIRE_IDSCAN)
//Sending a pulse through flashes the red light on the door (if the door has power).
if(A.arePowerSystemsOn() && A.density)
A.do_animate("deny")
if(A.emergency)
A.emergency = 0
A.update_icon()
- if(AIRLOCK_WIRE_MAIN_POWER1)
+
+ if(WIRE_MAIN_POWER1)
//Sending a pulse through either one causes a breaker to trip, disabling the door for 10 seconds if backup power is connected, or 1 minute if not (or until backup power comes back on, whichever is shorter).
A.loseMainPower()
- if(AIRLOCK_WIRE_DOOR_BOLTS)
+
+ if(WIRE_DOOR_BOLTS)
//one wire for door bolts. Sending a pulse through this drops door bolts if they're not down (whether power's on or not),
//raises them if they are down (only if power's on)
if(!A.locked)
@@ -170,45 +131,41 @@
else if(A.unlock())
A.audible_message(" You hear a click from the bottom of the door.", hearing_distance = 1)
- if(AIRLOCK_WIRE_BACKUP_POWER1)
+ if(WIRE_BACKUP_POWER1)
//two wires for backup power. Sending a pulse through either one causes a breaker to trip, but this does not disable it unless main power is down too (in which case it is disabled for 1 minute or however long it takes main power to come back, whichever is shorter).
A.loseBackupPower()
- if(AIRLOCK_WIRE_AI_CONTROL)
- if(A.aiControlDisabled == 0)
- A.aiControlDisabled = 1
- else if(A.aiControlDisabled == -1)
- A.aiControlDisabled = 2
- spawn(10)
- if(A)
- if(A.aiControlDisabled == 1)
- A.aiControlDisabled = 0
- else if(A.aiControlDisabled == 2)
- A.aiControlDisabled = -1
+ if(WIRE_AI_CONTROL)
+ if(A.aiControlDisabled == AICONTROLDISABLED_OFF)
+ A.aiControlDisabled = AICONTROLDISABLED_ON
+ else if(A.aiControlDisabled == AICONTROLDISABLED_PERMA)
+ A.aiControlDisabled = AICONTROLDISABLED_BYPASS
- if(AIRLOCK_WIRE_ELECTRIFY)
+ addtimer(CALLBACK(A, /obj/machinery/door/airlock/.proc/ai_control_callback), 1 SECONDS)
+
+ if(WIRE_ELECTRIFY)
//one wire for electrifying the door. Sending a pulse through this electrifies the door for 30 seconds.
A.electrify(30)
- if(AIRLOCK_WIRE_OPEN_DOOR)
+
+ if(WIRE_OPEN_DOOR)
//tries to open the door without ID
//will succeed only if the ID wire is cut or the door requires no access and it's not emagged
if(A.emagged) return
if(!A.requiresID() || A.check_access(null))
- spawn(0)
- if(A.density)
- A.open()
- else
- A.close()
- if(AIRLOCK_WIRE_SAFETY)
+ if(A.density)
+ INVOKE_ASYNC(A, /obj/machinery/door/airlock/.proc/open)
+ else
+ INVOKE_ASYNC(A, /obj/machinery/door/airlock/.proc/close)
+
+ if(WIRE_SAFETY)
A.safe = !A.safe
if(!A.density)
- spawn(0)
- A.close()
+ INVOKE_ASYNC(A, /obj/machinery/door/airlock/.proc/close)
- if(AIRLOCK_WIRE_SPEED)
+ if(WIRE_SPEED)
A.normalspeed = !A.normalspeed
- if(AIRLOCK_WIRE_LIGHT)
+ if(WIRE_BOLT_LIGHT)
A.lights = !A.lights
A.update_icon()
diff --git a/code/datums/wires/alarm.dm b/code/datums/wires/alarm.dm
index f6b49b0dd99..cd13c9fe1d8 100644
--- a/code/datums/wires/alarm.dm
+++ b/code/datums/wires/alarm.dm
@@ -2,35 +2,22 @@
/datum/wires/alarm
holder_type = /obj/machinery/alarm
wire_count = 5
+ window_x = 385
+ window_y = 90
+ proper_name = "Air alarm"
-#define AALARM_WIRE_IDSCAN 1
-#define AALARM_WIRE_POWER 2
-#define AALARM_WIRE_SYPHON 4
-#define AALARM_WIRE_AI_CONTROL 8
-#define AALARM_WIRE_AALARM 16
+/datum/wires/alarm/New(atom/_holder)
+ wires = list(
+ WIRE_IDSCAN , WIRE_MAIN_POWER1 , WIRE_SYPHON,
+ WIRE_AI_CONTROL, WIRE_AALARM
+ )
+ return ..()
-/datum/wires/alarm/GetWireName(index)
- switch(index)
- if(AALARM_WIRE_IDSCAN)
- return "ID Scan"
-
- if(AALARM_WIRE_POWER)
- return "Power"
-
- if(AALARM_WIRE_SYPHON)
- return "Syphon"
-
- if(AALARM_WIRE_AI_CONTROL)
- return "AI Control"
-
- if(AALARM_WIRE_AALARM)
- return "Atmospherics Alarm"
-
-/datum/wires/alarm/CanUse(mob/living/L)
+/datum/wires/alarm/interactable(mob/user)
var/obj/machinery/alarm/A = holder
if(A.wiresexposed)
- return 1
- return 0
+ return TRUE
+ return FALSE
/datum/wires/alarm/get_status()
. = ..()
@@ -39,75 +26,60 @@
. += "The Air Alarm is [(A.shorted || (A.stat & (NOPOWER|BROKEN))) ? "offline." : "working properly!"]"
. += "The 'AI control allowed' light is [A.aidisabled ? "off" : "on"]."
-/datum/wires/alarm/UpdateCut(index, mended)
+/datum/wires/alarm/on_cut(wire, mend)
var/obj/machinery/alarm/A = holder
- switch(index)
- if(AALARM_WIRE_IDSCAN)
- if(!mended)
+ switch(wire)
+ if(WIRE_IDSCAN)
+ if(!mend)
A.locked = 1
-// to_chat(world, "Idscan wire cut")
- if(AALARM_WIRE_POWER)
+ if(WIRE_MAIN_POWER1)
A.shock(usr, 50)
- A.shorted = !mended
+ A.shorted = !mend
A.update_icon()
-// to_chat(world, "Power wire cut")
- if(AALARM_WIRE_AI_CONTROL)
- A.aidisabled = !mended
-// to_chat(world, "AI Control Wire Cut")
+ if(WIRE_AI_CONTROL)
+ A.aidisabled = !mend
- if(AALARM_WIRE_SYPHON)
- if(!mended)
+ if(WIRE_SYPHON)
+ if(!mend)
A.mode = 3 // AALARM_MODE_PANIC
A.apply_mode()
-// to_chat(world, "Syphon Wire Cut")
- if(AALARM_WIRE_AALARM)
- if(A.alarm_area.atmosalert(2, A))
- A.post_alert(2)
+ if(WIRE_AALARM)
+ if(A.alarm_area.atmosalert(ATMOS_ALARM_DANGER, A))
+ A.post_alert(ATMOS_ALARM_DANGER)
A.update_icon()
..()
-/datum/wires/alarm/UpdatePulsed(index)
+/datum/wires/alarm/on_pulse(wire)
var/obj/machinery/alarm/A = holder
- switch(index)
- if(AALARM_WIRE_IDSCAN)
+ switch(wire)
+ if(WIRE_IDSCAN)
A.locked = !A.locked
-// to_chat(world, "Idscan wire pulsed")
- if(AALARM_WIRE_POWER)
-// to_chat(world, "Power wire pulsed")
- if(A.shorted == 0)
- A.shorted = 1
+ if(WIRE_MAIN_POWER1)
+ if(!A.shorted)
+ A.shorted = TRUE
A.update_icon()
+ addtimer(CALLBACK(A, /obj/machinery/alarm/.proc/unshort_callback), 120 SECONDS)
- spawn(12000)
- if(A.shorted == 1)
- A.shorted = 0
- A.update_icon()
-
-
- if(AALARM_WIRE_AI_CONTROL)
-// to_chat(world, "AI Control wire pulsed")
- if(A.aidisabled == 0)
- A.aidisabled = 1
+ if(WIRE_AI_CONTROL)
+ if(!A.aidisabled)
+ A.aidisabled = TRUE
A.updateDialog()
- spawn(100)
- if(A.aidisabled == 1)
- A.aidisabled = 0
+ addtimer(CALLBACK(A, /obj/machinery/alarm/.proc/enable_ai_control_callback), 10 SECONDS)
- if(AALARM_WIRE_SYPHON)
-// to_chat(world, "Syphon wire pulsed")
+
+ if(WIRE_SYPHON)
if(A.mode == 1) // AALARM_MODE_SCRUB
A.mode = 3 // AALARM_MODE_PANIC
else
A.mode = 1 // AALARM_MODE_SCRUB
A.apply_mode()
- if(AALARM_WIRE_AALARM)
-// to_chat(world, "Aalarm wire pulsed")
- if(A.alarm_area.atmosalert(0, A))
- A.post_alert(0)
+ if(WIRE_AALARM)
+ if(A.alarm_area.atmosalert(ATMOS_ALARM_NONE, A))
+ A.post_alert(ATMOS_ALARM_NONE)
A.update_icon()
..()
diff --git a/code/datums/wires/apc.dm b/code/datums/wires/apc.dm
index b1076c8e29b..49931d8fa31 100644
--- a/code/datums/wires/apc.dm
+++ b/code/datums/wires/apc.dm
@@ -1,25 +1,13 @@
/datum/wires/apc
holder_type = /obj/machinery/power/apc
wire_count = 4
+ proper_name = "APC"
+ window_x = 355
+ window_y = 97
-#define APC_WIRE_IDSCAN 1
-#define APC_WIRE_MAIN_POWER1 2
-#define APC_WIRE_MAIN_POWER2 4
-#define APC_WIRE_AI_CONTROL 8
-
-/datum/wires/apc/GetWireName(index)
- switch(index)
- if(APC_WIRE_IDSCAN)
- return "ID Scan"
-
- if(APC_WIRE_MAIN_POWER1)
- return "Primary Power"
-
- if(APC_WIRE_MAIN_POWER2)
- return "Secondary Power"
-
- if(APC_WIRE_AI_CONTROL)
- return "AI Control"
+/datum/wires/apc/New(atom/_holder)
+ wires = list(WIRE_IDSCAN, WIRE_MAIN_POWER1, WIRE_MAIN_POWER2, WIRE_AI_CONTROL)
+ return ..()
/datum/wires/apc/get_status()
. = ..()
@@ -29,66 +17,53 @@
. += "The 'AI control allowed' light is [A.aidisabled ? "off" : "on"]."
-/datum/wires/apc/CanUse(mob/living/L)
+/datum/wires/apc/interactable(mob/user)
var/obj/machinery/power/apc/A = holder
if(A.panel_open && !A.opened)
return TRUE
return FALSE
-/datum/wires/apc/UpdatePulsed(index)
+/datum/wires/apc/on_pulse(wire)
var/obj/machinery/power/apc/A = holder
- switch(index)
+ switch(wire)
+ if(WIRE_IDSCAN)
+ A.locked = FALSE
+ addtimer(CALLBACK(A, /obj/machinery/power/apc/.proc/relock_callback), 30 SECONDS)
- if(APC_WIRE_IDSCAN)
- A.locked = 0
- spawn(300)
- if(A)
- A.locked = 1
- A.updateDialog()
+ if(WIRE_MAIN_POWER1, WIRE_MAIN_POWER2)
+ if(!A.shorted)
+ A.shorted = TRUE
+ addtimer(CALLBACK(A, /obj/machinery/power/apc/.proc/check_main_power_callback), 120 SECONDS)
- if(APC_WIRE_MAIN_POWER1, APC_WIRE_MAIN_POWER2)
- if(A.shorted == 0)
- A.shorted = 1
- spawn(1200)
- if(A && !IsIndexCut(APC_WIRE_MAIN_POWER1) && !IsIndexCut(APC_WIRE_MAIN_POWER2))
- A.shorted = 0
- A.updateDialog()
-
- if(APC_WIRE_AI_CONTROL)
- if(A.aidisabled == 0)
- A.aidisabled = 1
-
- spawn(10)
- if(A && !IsIndexCut(APC_WIRE_AI_CONTROL))
- A.aidisabled = 0
- A.updateDialog()
+ if(WIRE_AI_CONTROL)
+ if(!A.aidisabled)
+ A.aidisabled = TRUE
+ addtimer(CALLBACK(A, /obj/machinery/power/apc/.proc/check_ai_control_callback), 1 SECONDS)
..()
-/datum/wires/apc/UpdateCut(index, mended)
+/datum/wires/apc/on_cut(wire, mend)
var/obj/machinery/power/apc/A = holder
- switch(index)
- if(APC_WIRE_MAIN_POWER1, APC_WIRE_MAIN_POWER2)
-
- if(!mended)
+ switch(wire)
+ if(WIRE_MAIN_POWER1, WIRE_MAIN_POWER2)
+ if(!mend)
A.shock(usr, 50)
- A.shorted = 1
+ A.shorted = TRUE
- else if(!IsIndexCut(APC_WIRE_MAIN_POWER1) && !IsIndexCut(APC_WIRE_MAIN_POWER2))
- A.shorted = 0
+ else if(!is_cut(WIRE_MAIN_POWER1) && !is_cut(WIRE_MAIN_POWER2))
+ A.shorted = FALSE
A.shock(usr, 50)
- if(APC_WIRE_AI_CONTROL)
-
- if(!mended)
- if(A.aidisabled == 0)
- A.aidisabled = 1
+ if(WIRE_AI_CONTROL)
+ if(!mend)
+ if(!A.aidisabled)
+ A.aidisabled = TRUE
else
- if(A.aidisabled == 1)
- A.aidisabled = 0
+ if(A.aidisabled)
+ A.aidisabled = FALSE
..()
diff --git a/code/datums/wires/autolathe.dm b/code/datums/wires/autolathe.dm
index c1413abd0cc..0d2aab5a81f 100644
--- a/code/datums/wires/autolathe.dm
+++ b/code/datums/wires/autolathe.dm
@@ -1,21 +1,13 @@
/datum/wires/autolathe
holder_type = /obj/machinery/autolathe
wire_count = 10
+ proper_name = "Autolathe"
+ window_x = 340
+ window_y = 55
-#define AUTOLATHE_HACK_WIRE 1
-#define AUTOLATHE_SHOCK_WIRE 2
-#define AUTOLATHE_DISABLE_WIRE 4
-
-/datum/wires/autolathe/GetWireName(index)
- switch(index)
- if(AUTOLATHE_HACK_WIRE)
- return "Hack"
-
- if(AUTOLATHE_SHOCK_WIRE)
- return "Shock"
-
- if(AUTOLATHE_DISABLE_WIRE)
- return "Disable"
+/datum/wires/autolathe/New(atom/_holder)
+ wires = list(WIRE_AUTOLATHE_HACK, WIRE_ELECTRIFY, WIRE_AUTOLATHE_DISABLE)
+ return ..()
/datum/wires/autolathe/get_status()
. = ..()
@@ -24,51 +16,38 @@
. += "The green light is [A.shocked ? "off" : "on"]."
. += "The blue light is [A.hacked ? "off" : "on"]."
-/datum/wires/autolathe/CanUse()
+/datum/wires/autolathe/interactable(mob/user)
var/obj/machinery/autolathe/A = holder
+ if(iscarbon(user) && A.Adjacent(user) && A.shocked && A.shock(user, 100))
+ return FALSE
if(A.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
-/datum/wires/autolathe/UpdateCut(index, mended)
+/datum/wires/autolathe/on_cut(wire, mend)
var/obj/machinery/autolathe/A = holder
- switch(index)
- if(AUTOLATHE_HACK_WIRE)
- A.adjust_hacked(!mended)
- if(AUTOLATHE_SHOCK_WIRE)
- A.shocked = !mended
- if(AUTOLATHE_DISABLE_WIRE)
- A.disabled = !mended
+ switch(wire)
+ if(WIRE_AUTOLATHE_HACK)
+ A.adjust_hacked(!mend)
+ if(WIRE_ELECTRIFY)
+ A.shocked = !mend
+ if(WIRE_AUTOLATHE_DISABLE)
+ A.disabled = !mend
..()
-/datum/wires/autolathe/UpdatePulsed(index)
- if(IsIndexCut(index))
+/datum/wires/autolathe/on_pulse(wire)
+ if(is_cut(wire))
return
var/obj/machinery/autolathe/A = holder
- switch(index)
- if(AUTOLATHE_HACK_WIRE)
+ switch(wire)
+ if(WIRE_AUTOLATHE_HACK)
A.adjust_hacked(!A.hacked)
- updateUIs()
- spawn(50)
- if(A && !IsIndexCut(index))
- A.adjust_hacked(0)
- updateUIs()
- if(AUTOLATHE_SHOCK_WIRE)
- A.shocked = !A.shocked
- updateUIs()
- spawn(50)
- if(A && !IsIndexCut(index))
- A.shocked = 0
- updateUIs()
- if(AUTOLATHE_DISABLE_WIRE)
- A.disabled = !A.disabled
- updateUIs()
- spawn(50)
- if(A && !IsIndexCut(index))
- A.disabled = 0
- updateUIs()
+ addtimer(CALLBACK(A, /obj/machinery/autolathe/.proc/check_hacked_callback), 5 SECONDS)
-/datum/wires/autolathe/proc/updateUIs()
- SSnanoui.update_uis(src)
- if(holder)
- SSnanoui.update_uis(holder)
+ if(WIRE_ELECTRIFY)
+ A.shocked = !A.shocked
+ addtimer(CALLBACK(A, /obj/machinery/autolathe/.proc/check_electrified_callback), 5 SECONDS)
+
+ if(WIRE_AUTOLATHE_DISABLE)
+ A.disabled = !A.disabled
+ addtimer(CALLBACK(A, /obj/machinery/autolathe/.proc/check_disabled_callback), 5 SECONDS)
diff --git a/code/datums/wires/camera.dm b/code/datums/wires/camera.dm
index a6b1549fbe1..39e0cb2c3e1 100644
--- a/code/datums/wires/camera.dm
+++ b/code/datums/wires/camera.dm
@@ -1,9 +1,15 @@
// Wires for cameras.
/datum/wires/camera
- random = 0
holder_type = /obj/machinery/camera
wire_count = 2
+ proper_name = "Camera"
+ window_x = 350
+ window_y = 95
+
+/datum/wires/camera/New(atom/_holder)
+ wires = list(WIRE_FOCUS, WIRE_MAIN_POWER1)
+ return ..()
/datum/wires/camera/get_status()
. = ..()
@@ -11,51 +17,40 @@
. += "The focus light is [(C.view_range == initial(C.view_range)) ? "on" : "off"]."
. += "The power link light is [C.can_use() ? "on" : "off"]."
-/datum/wires/camera/CanUse(mob/living/L)
+/datum/wires/camera/interactable(mob/user)
var/obj/machinery/camera/C = holder
if(!C.panel_open)
return FALSE
return TRUE
-#define CAMERA_WIRE_FOCUS 1
-#define CAMERA_WIRE_POWER 2
-
-/datum/wires/camera/GetWireName(index)
- switch(index)
- if(CAMERA_WIRE_FOCUS)
- return "Focus"
-
- if(CAMERA_WIRE_POWER)
- return "Power"
-
-/datum/wires/camera/UpdateCut(index, mended)
+/datum/wires/camera/on_cut(wire, mend)
var/obj/machinery/camera/C = holder
- switch(index)
- if(CAMERA_WIRE_FOCUS)
- var/range = (mended ? initial(C.view_range) : C.short_range)
+ switch(wire)
+ if(WIRE_FOCUS)
+ var/range = (mend ? initial(C.view_range) : C.short_range)
C.setViewRange(range)
- if(CAMERA_WIRE_POWER)
- if(C.status && !mended || !C.status && mended)
+ if(WIRE_MAIN_POWER1)
+ if(C.status && !mend || !C.status && mend)
C.toggle_cam(usr, TRUE)
C.obj_integrity = C.max_integrity //this is a pretty simplistic way to heal the camera, but there's no reason for this to be complex.
..()
-/datum/wires/camera/UpdatePulsed(index)
+/datum/wires/camera/on_pulse(wire)
var/obj/machinery/camera/C = holder
- if(IsIndexCut(index))
+ if(is_cut(wire))
return
- switch(index)
- if(CAMERA_WIRE_FOCUS)
+ switch(wire)
+ if(WIRE_FOCUS)
var/new_range = (C.view_range == initial(C.view_range) ? C.short_range : initial(C.view_range))
C.setViewRange(new_range)
- if(CAMERA_WIRE_POWER)
+ if(WIRE_MAIN_POWER1)
C.toggle_cam(null) // Deactivate the camera
..()
/datum/wires/camera/proc/CanDeconstruct()
- if(IsIndexCut(CAMERA_WIRE_POWER) && IsIndexCut(CAMERA_WIRE_FOCUS))
+ if(is_cut(WIRE_MAIN_POWER1) && is_cut(WIRE_FOCUS))
return TRUE
else
return FALSE
diff --git a/code/datums/wires/explosive.dm b/code/datums/wires/explosive.dm
index 47fd627d597..988aa0f37a8 100644
--- a/code/datums/wires/explosive.dm
+++ b/code/datums/wires/explosive.dm
@@ -1,36 +1,36 @@
/datum/wires/explosive
wire_count = 1
+ proper_name = "Explosive"
+ window_x = 320
+ window_y = 50
-#define WIRE_EXPLODE 1
-
-/datum/wires/explosive/GetWireName(index)
- switch(index)
- if(WIRE_EXPLODE)
- return "Explode"
+/datum/wires/explosive/New(atom/_holder)
+ wires = list(WIRE_EXPLODE)
+ return ..()
/datum/wires/explosive/proc/explode()
return
-/datum/wires/explosive/UpdatePulsed(index)
- switch(index)
+/datum/wires/explosive/on_pulse(wire)
+ switch(wire)
if(WIRE_EXPLODE)
explode()
..()
-/datum/wires/explosive/UpdateCut(index, mended)
- switch(index)
+/datum/wires/explosive/on_cut(wire, mend)
+ switch(wire)
if(WIRE_EXPLODE)
- if(!mended)
+ if(!mend)
explode()
..()
/datum/wires/explosive/gibtonite
holder_type = /obj/item/twohanded/required/gibtonite
-/datum/wires/explosive/gibtonite/CanUse(mob/L)
- return 1
+/datum/wires/explosive/gibtonite/interactable(mob/user)
+ return TRUE
-/datum/wires/explosive/gibtonite/UpdateCut(index, mended)
+/datum/wires/explosive/gibtonite/on_cut(wire, mend)
return
/datum/wires/explosive/gibtonite/explode()
diff --git a/code/datums/wires/mulebot.dm b/code/datums/wires/mulebot.dm
index 988b520854f..cb13b0d6718 100644
--- a/code/datums/wires/mulebot.dm
+++ b/code/datums/wires/mulebot.dm
@@ -1,90 +1,35 @@
/datum/wires/mulebot
- random = 1
+ randomize = TRUE
holder_type = /mob/living/simple_animal/bot/mulebot
wire_count = 10
- window_x = 410
+ proper_name = "Mulebot"
+ window_x = 370
+ window_y = -12
-#define MULEBOT_WIRE_POWER1 1 // power connections
-#define MULEBOT_WIRE_POWER2 2
-#define MULEBOT_WIRE_AVOIDANCE 4 // mob avoidance
-#define MULEBOT_WIRE_LOADCHECK 8 // load checking (non-crate)
-#define MULEBOT_WIRE_MOTOR1 16 // motor wires
-#define MULEBOT_WIRE_MOTOR2 32 //
-#define MULEBOT_WIRE_REMOTE_RX 64 // remote recv functions
-#define MULEBOT_WIRE_REMOTE_TX 128 // remote trans status
-#define MULEBOT_WIRE_BEACON_RX 256 // beacon ping recv
+/datum/wires/mulebot/New(atom/_holder)
+ wires = list(
+ WIRE_MAIN_POWER1, WIRE_MAIN_POWER2, WIRE_MOB_AVOIDANCE,
+ WIRE_LOADCHECK, WIRE_MOTOR1, WIRE_MOTOR2,
+ WIRE_REMOTE_RX, WIRE_REMOTE_TX, WIRE_BEACON_RX
+ )
+ return ..()
-/datum/wires/mulebot/GetWireName(index)
- switch(index)
- if(MULEBOT_WIRE_POWER1)
- return "Primary Power"
-
- if(MULEBOT_WIRE_POWER2)
- return "Secondary Power"
-
- if(MULEBOT_WIRE_AVOIDANCE)
- return "Mob Avoidance"
-
- if(MULEBOT_WIRE_LOADCHECK)
- return "Load Checking"
-
- if(MULEBOT_WIRE_MOTOR1)
- return "Primary Motor"
-
- if(MULEBOT_WIRE_MOTOR2)
- return "Secondary Motor"
-
- if(MULEBOT_WIRE_REMOTE_RX)
- return "Remote Signal Receiver"
-
- if(MULEBOT_WIRE_REMOTE_TX)
- return "Remote Signal Sender"
-
- if(MULEBOT_WIRE_BEACON_RX)
- return "Navigation Beacon Receiver"
-
-/datum/wires/mulebot/CanUse(mob/living/L)
+/datum/wires/mulebot/interactable(mob/user)
var/mob/living/simple_animal/bot/mulebot/M = holder
if(M.open)
- return 1
- return 0
+ return TRUE
+ return FALSE
-/datum/wires/mulebot/UpdatePulsed(index)
- switch(index)
- if(MULEBOT_WIRE_POWER1, MULEBOT_WIRE_POWER2)
+/datum/wires/mulebot/on_pulse(wire)
+ switch(wire)
+ if(WIRE_MAIN_POWER1, WIRE_MAIN_POWER2)
holder.visible_message(" [bicon(holder)] The charge light flickers.")
- if(MULEBOT_WIRE_AVOIDANCE)
+ if(WIRE_MOB_AVOIDANCE)
holder.visible_message(" [bicon(holder)] The external warning lights flash briefly.")
- if(MULEBOT_WIRE_LOADCHECK)
+ if(WIRE_LOADCHECK)
holder.visible_message(" [bicon(holder)] The load platform clunks.")
- if(MULEBOT_WIRE_MOTOR1, MULEBOT_WIRE_MOTOR2)
+ if(WIRE_MOTOR1, WIRE_MOTOR2)
holder.visible_message(" [bicon(holder)] The drive motor whines briefly.")
else
holder.visible_message(" [bicon(holder)] You hear a radio crackle.")
..()
-
-// HELPER PROCS
-
-/datum/wires/mulebot/proc/Motor1()
- return !(wires_status & MULEBOT_WIRE_MOTOR1)
-
-/datum/wires/mulebot/proc/Motor2()
- return !(wires_status & MULEBOT_WIRE_MOTOR2)
-
-/datum/wires/mulebot/proc/HasPower()
- return !(wires_status & MULEBOT_WIRE_POWER1) && !(wires_status & MULEBOT_WIRE_POWER2)
-
-/datum/wires/mulebot/proc/LoadCheck()
- return !(wires_status & MULEBOT_WIRE_LOADCHECK)
-
-/datum/wires/mulebot/proc/MobAvoid()
- return !(wires_status & MULEBOT_WIRE_AVOIDANCE)
-
-/datum/wires/mulebot/proc/RemoteTX()
- return !(wires_status & MULEBOT_WIRE_REMOTE_TX)
-
-/datum/wires/mulebot/proc/RemoteRX()
- return !(wires_status & MULEBOT_WIRE_REMOTE_RX)
-
-/datum/wires/mulebot/proc/BeaconRX()
- return !(wires_status & MULEBOT_WIRE_BEACON_RX)
diff --git a/code/datums/wires/nuclearbomb.dm b/code/datums/wires/nuclearbomb.dm
index 3b8ff5db098..51918c02ff4 100644
--- a/code/datums/wires/nuclearbomb.dm
+++ b/code/datums/wires/nuclearbomb.dm
@@ -1,28 +1,20 @@
/datum/wires/nuclearbomb
holder_type = /obj/machinery/nuclearbomb
- random = 1
- wire_count = 7
+ randomize = TRUE
+ wire_count = 7 // 3 actual, 4 duds.
+ proper_name = "Nuclear bomb"
+ window_x = 345
+ window_y = 75
-#define NUCLEARBOMB_WIRE_LIGHT 1
-#define NUCLEARBOMB_WIRE_TIMING 2
-#define NUCLEARBOMB_WIRE_SAFETY 4
+/datum/wires/nuclearbomb/New(atom/_holder)
+ wires = list(WIRE_BOMB_LIGHT, WIRE_BOMB_TIMING, WIRE_BOMB_SAFETY)
+ return ..()
-/datum/wires/nuclearbomb/GetWireName(index)
- switch(index)
- if(NUCLEARBOMB_WIRE_LIGHT)
- return "Bomb Light"
-
- if(NUCLEARBOMB_WIRE_TIMING)
- return "Bomb Timing"
-
- if(NUCLEARBOMB_WIRE_SAFETY)
- return "Bomb Safety"
-
-/datum/wires/nuclearbomb/CanUse(mob/living/L)
+/datum/wires/nuclearbomb/interactable(mob/user)
var/obj/machinery/nuclearbomb/N = holder
if(N.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
/datum/wires/nuclearbomb/get_status()
. = ..()
@@ -31,52 +23,36 @@
. += "The device is is [N.safety ? "quiet" : "whirring"]."
. += "The lights are [N.lighthack ? "static" : "functional"]."
-/datum/wires/nuclearbomb/UpdatePulsed(index)
+/datum/wires/nuclearbomb/on_pulse(wire)
var/obj/machinery/nuclearbomb/N = holder
- switch(index)
- if(NUCLEARBOMB_WIRE_LIGHT)
+ switch(wire)
+ if(WIRE_BOMB_LIGHT)
N.lighthack = !N.lighthack
- updateUIs()
- spawn(100)
- N.lighthack = !N.lighthack
- updateUIs()
- if(NUCLEARBOMB_WIRE_TIMING)
+ addtimer(CALLBACK(N, /obj/machinery/nuclearbomb/.proc/reset_lighthack_callback), 10 SECONDS)
+
+ if(WIRE_BOMB_TIMING)
if(N.timing)
message_admins("[key_name_admin(usr)] pulsed a nuclear bomb's detonation wire, causing it to explode ( JMP)")
N.explode()
- if(NUCLEARBOMB_WIRE_SAFETY)
- N.safety = !N.safety
- updateUIs()
- spawn(100)
- N.safety = !N.safety
- if(N.safety == 1)
- if(!N.is_syndicate)
- set_security_level(N.previous_level)
- N.visible_message(" The [N] quiets down.")
- if(!N.lighthack)
- if(N.icon_state == "nuclearbomb2")
- N.icon_state = "nuclearbomb1"
- else
- N.visible_message(" The [N] emits a quiet whirling noise!")
- updateUIs()
-/datum/wires/nuclearbomb/UpdateCut(index, mended)
+ if(WIRE_BOMB_SAFETY)
+ N.safety = !N.safety
+ addtimer(CALLBACK(N, /obj/machinery/nuclearbomb/.proc/reset_safety_callback), 10 SECONDS)
+
+/datum/wires/nuclearbomb/on_cut(wire, mend)
var/obj/machinery/nuclearbomb/N = holder
- switch(index)
- if(NUCLEARBOMB_WIRE_SAFETY)
+ switch(wire)
+ if(WIRE_BOMB_SAFETY)
if(N.timing)
message_admins("[key_name_admin(usr)] cut a nuclear bomb's timing wire, causing it to explode ( JMP)")
N.explode()
- if(NUCLEARBOMB_WIRE_TIMING)
+
+ if(WIRE_BOMB_TIMING)
if(!N.lighthack)
if(N.icon_state == "nuclearbomb2")
N.icon_state = "nuclearbomb1"
N.timing = 0
- GLOB.bomb_set = 0
- if(NUCLEARBOMB_WIRE_LIGHT)
- N.lighthack = !N.lighthack
+ GLOB.bomb_set = FALSE
-/datum/wires/nuclearbomb/proc/updateUIs()
- SSnanoui.update_uis(src)
- if(holder)
- SSnanoui.update_uis(holder)
+ if(WIRE_BOMB_LIGHT)
+ N.lighthack = !N.lighthack
diff --git a/code/datums/wires/particle_accelerator.dm b/code/datums/wires/particle_accelerator.dm
index d6e68a79cfb..e285f4cf898 100644
--- a/code/datums/wires/particle_accelerator.dm
+++ b/code/datums/wires/particle_accelerator.dm
@@ -1,67 +1,52 @@
/datum/wires/particle_acc/control_box
wire_count = 5
holder_type = /obj/machinery/particle_accelerator/control_box
+ proper_name = "Particle accelerator control"
+ window_x = 361
+ window_y = 22
-#define PARTICLE_TOGGLE_WIRE 1 // Toggles whether the PA is on or not.
-#define PARTICLE_STRENGTH_WIRE 2 // Determines the strength of the PA.
-#define PARTICLE_INTERFACE_WIRE 4 // Determines the interface showing up.
-#define PARTICLE_LIMIT_POWER_WIRE 8 // Determines how strong the PA can be.
+/datum/wires/particle_acc/control_box/New(atom/_holder)
+ wires = list(WIRE_PARTICLE_POWER, WIRE_PARTICLE_STRENGTH, WIRE_PARTICLE_INTERFACE, WIRE_PARTICLE_POWER_LIMIT)
+ return ..()
-/datum/wires/particle_acc/control_box/GetWireName(index)
- switch(index)
- if(PARTICLE_TOGGLE_WIRE)
- return "Power Toggle"
-
- if(PARTICLE_STRENGTH_WIRE)
- return "Strength"
-
- if(PARTICLE_INTERFACE_WIRE)
- return "Interface"
-
- if(PARTICLE_LIMIT_POWER_WIRE)
- return "Maximum Power"
-
-/datum/wires/particle_acc/control_box/CanUse(mob/living/L)
+/datum/wires/particle_acc/control_box/interactable(mob/user)
var/obj/machinery/particle_accelerator/control_box/C = holder
if(C.construction_state == 2)
- return 1
- return 0
+ return TRUE
+ return FALSE
-/datum/wires/particle_acc/control_box/UpdatePulsed(index)
+/datum/wires/particle_acc/control_box/on_pulse(wire)
var/obj/machinery/particle_accelerator/control_box/C = holder
- switch(index)
-
- if(PARTICLE_TOGGLE_WIRE)
+ switch(wire)
+ if(WIRE_PARTICLE_POWER)
C.toggle_power()
- if(PARTICLE_STRENGTH_WIRE)
+ if(WIRE_PARTICLE_STRENGTH)
C.add_strength()
- if(PARTICLE_INTERFACE_WIRE)
+ if(WIRE_PARTICLE_INTERFACE)
C.interface_control = !C.interface_control
- if(PARTICLE_LIMIT_POWER_WIRE)
+ if(WIRE_PARTICLE_POWER_LIMIT)
C.visible_message("[bicon(C)] [C] makes a large whirring noise.")
..()
-/datum/wires/particle_acc/control_box/UpdateCut(index, mended)
+/datum/wires/particle_acc/control_box/on_cut(wire, mend)
var/obj/machinery/particle_accelerator/control_box/C = holder
- switch(index)
-
- if(PARTICLE_TOGGLE_WIRE)
- if(C.active == !mended)
+ switch(wire)
+ if(WIRE_PARTICLE_POWER)
+ if(C.active == !mend)
C.toggle_power()
- if(PARTICLE_STRENGTH_WIRE)
-
- for(var/i = 1; i < 3; i++)
+ if(WIRE_PARTICLE_STRENGTH)
+ for(var/i in 1 to 2)
C.remove_strength()
- if(PARTICLE_INTERFACE_WIRE)
- C.interface_control = mended
+ if(WIRE_PARTICLE_INTERFACE)
+ C.interface_control = mend
- if(PARTICLE_LIMIT_POWER_WIRE)
- C.strength_upper_limit = (mended ? 2 : 3)
+ if(WIRE_PARTICLE_POWER_LIMIT)
+ C.strength_upper_limit = (mend ? 2 : 3)
if(C.strength_upper_limit < C.strength)
C.remove_strength()
..()
diff --git a/code/datums/wires/radio.dm b/code/datums/wires/radio.dm
index 90a72935c53..1efee71692d 100644
--- a/code/datums/wires/radio.dm
+++ b/code/datums/wires/radio.dm
@@ -1,52 +1,44 @@
/datum/wires/radio
holder_type = /obj/item/radio
wire_count = 3
+ proper_name = "Radio"
+ window_x = 330
+ window_y = 37
-#define RADIO_WIRE_SIGNAL 1
-#define RADIO_WIRE_RECEIVE 2
-#define RADIO_WIRE_TRANSMIT 4
+/datum/wires/radio/New(atom/_holder)
+ wires = list(WIRE_RADIO_SIGNAL, WIRE_RADIO_RECEIVER, WIRE_RADIO_TRANSMIT)
+ return ..()
-/datum/wires/radio/GetWireName(index)
- switch(index)
- if(RADIO_WIRE_SIGNAL)
- return "Signal"
-
- if(RADIO_WIRE_RECEIVE)
- return "Receiver"
-
- if(RADIO_WIRE_TRANSMIT)
- return "Transmitter"
-
-/datum/wires/radio/CanUse(mob/living/L)
+/datum/wires/radio/interactable(mob/user)
var/obj/item/radio/R = holder
if(R.b_stat)
- return 1
- return 0
+ return TRUE
+ return FALSE
-/datum/wires/radio/UpdatePulsed(index)
+/datum/wires/radio/on_pulse(wire)
var/obj/item/radio/R = holder
- switch(index)
- if(RADIO_WIRE_SIGNAL)
- R.listening = !R.listening && !IsIndexCut(RADIO_WIRE_RECEIVE)
- R.broadcasting = R.listening && !IsIndexCut(RADIO_WIRE_TRANSMIT)
+ switch(wire)
+ if(WIRE_RADIO_SIGNAL)
+ R.listening = !R.listening && !is_cut(WIRE_RADIO_RECEIVER)
+ R.broadcasting = R.listening && !is_cut(WIRE_RADIO_TRANSMIT)
- if(RADIO_WIRE_RECEIVE)
- R.listening = !R.listening && !IsIndexCut(RADIO_WIRE_SIGNAL)
+ if(WIRE_RADIO_RECEIVER)
+ R.listening = !R.listening && !is_cut(WIRE_RADIO_SIGNAL)
- if(RADIO_WIRE_TRANSMIT)
- R.broadcasting = !R.broadcasting && !IsIndexCut(RADIO_WIRE_SIGNAL)
+ if(WIRE_RADIO_TRANSMIT)
+ R.broadcasting = !R.broadcasting && !is_cut(WIRE_RADIO_SIGNAL)
..()
-/datum/wires/radio/UpdateCut(index, mended)
+/datum/wires/radio/on_cut(wire, mend)
var/obj/item/radio/R = holder
- switch(index)
- if(RADIO_WIRE_SIGNAL)
- R.listening = mended && !IsIndexCut(RADIO_WIRE_RECEIVE)
- R.broadcasting = mended && !IsIndexCut(RADIO_WIRE_TRANSMIT)
+ switch(wire)
+ if(WIRE_RADIO_SIGNAL)
+ R.listening = mend && !is_cut(WIRE_RADIO_RECEIVER)
+ R.broadcasting = mend && !is_cut(WIRE_RADIO_TRANSMIT)
- if(RADIO_WIRE_RECEIVE)
- R.listening = mended && !IsIndexCut(RADIO_WIRE_SIGNAL)
+ if(WIRE_RADIO_RECEIVER)
+ R.listening = mend && !is_cut(WIRE_RADIO_SIGNAL)
- if(RADIO_WIRE_TRANSMIT)
- R.broadcasting = mended && !IsIndexCut(RADIO_WIRE_SIGNAL)
+ if(WIRE_RADIO_TRANSMIT)
+ R.broadcasting = mend && !is_cut(WIRE_RADIO_SIGNAL)
..()
diff --git a/code/datums/wires/robot.dm b/code/datums/wires/robot.dm
index 04a9509cfe2..927980c57fa 100644
--- a/code/datums/wires/robot.dm
+++ b/code/datums/wires/robot.dm
@@ -1,32 +1,14 @@
/datum/wires/robot
- random = 1
+ randomize = TRUE
holder_type = /mob/living/silicon/robot
wire_count = 5
+ window_x = 340
+ window_y = 106
+ proper_name = "Cyborg"
-// /vg/ ordering
-
-#define BORG_WIRE_MAIN_POWER 1 // The power wires do nothing whyyyyyyyyyyyyy
-#define BORG_WIRE_LOCKED_DOWN 2
-#define BORG_WIRE_CAMERA 4
-#define BORG_WIRE_AI_CONTROL 8 // Not used on MoMMIs
-#define BORG_WIRE_LAWCHECK 16 // Not used on MoMMIs
-
-/datum/wires/robot/GetWireName(index)
- switch(index)
- if(BORG_WIRE_MAIN_POWER)
- return "Main Power"
-
- if(BORG_WIRE_LOCKED_DOWN)
- return "Lockdown"
-
- if(BORG_WIRE_CAMERA)
- return "Camera"
-
- if(BORG_WIRE_AI_CONTROL)
- return "AI Control"
-
- if(BORG_WIRE_LAWCHECK)
- return "Law Check"
+/datum/wires/robot/New(atom/_holder)
+ wires = list(WIRE_AI_CONTROL, WIRE_BORG_CAMERA, WIRE_BORG_LAWCHECK, WIRE_BORG_LOCKED)
+ return ..()
/datum/wires/robot/get_status()
. = ..()
@@ -36,70 +18,53 @@
. += "The Camera light is [(R.camera && R.camera.status == 1) ? "on" : "off"]."
. += "The lockdown light is [R.lockcharge ? "on" : "off"]."
-/datum/wires/robot/UpdateCut(index, mended)
-
+/datum/wires/robot/on_cut(wire, mend)
var/mob/living/silicon/robot/R = holder
- switch(index)
- if(BORG_WIRE_LAWCHECK) //Cut the law wire, and the borg will no longer receive law updates from its AI
- if(!mended)
- if(R.lawupdate == 1)
+ switch(wire)
+ if(WIRE_BORG_LAWCHECK) //Cut the law wire, and the borg will no longer receive law updates from its AI
+ if(!mend)
+ if(R.lawupdate)
to_chat(R, "LawSync protocol engaged.")
+ R.lawsync()
R.show_laws()
else
- if(R.lawupdate == 0 && !R.emagged)
- R.lawupdate = 1
+ if(!R.lawupdate && !R.emagged)
+ R.lawupdate = TRUE
- if(BORG_WIRE_AI_CONTROL) //Cut the AI wire to reset AI control
- if(!mended)
+ if(WIRE_AI_CONTROL) //Cut the AI wire to reset AI control
+ if(!mend)
if(R.connected_ai)
R.disconnect_from_ai()
- if(BORG_WIRE_CAMERA)
+ if(WIRE_BORG_CAMERA)
if(!isnull(R.camera) && !R.scrambledcodes)
- R.camera.status = mended
+ R.camera.status = mend
R.camera.toggle_cam(usr, 0) // Will kick anyone who is watching the Cyborg's camera.
- if(BORG_WIRE_LAWCHECK) //Forces a law update if the borg is set to receive them. Since an update would happen when the borg checks its laws anyway, not much use, but eh
- if(R.lawupdate)
- R.lawsync()
-
- if(BORG_WIRE_LOCKED_DOWN)
- R.SetLockdown(!mended)
+ if(WIRE_BORG_LOCKED)
+ R.SetLockdown(!mend)
..()
-/datum/wires/robot/UpdatePulsed(index)
-
+/datum/wires/robot/on_pulse(wire)
var/mob/living/silicon/robot/R = holder
- switch(index)
- if(BORG_WIRE_AI_CONTROL) //pulse the AI wire to make the borg reselect an AI
+ switch(wire)
+ if(WIRE_AI_CONTROL) //pulse the AI wire to make the borg reselect an AI
if(!R.emagged)
R.connect_to_ai(select_active_ai())
- if(BORG_WIRE_CAMERA)
+ if(WIRE_BORG_CAMERA)
if(!isnull(R.camera) && R.camera.can_use() && !R.scrambledcodes)
R.camera.toggle_cam(usr, 0) // Kick anyone watching the Cyborg's camera, doesn't display you disconnecting the camera.
R.visible_message("[R]'s camera lense focuses loudly.")
to_chat(R, "Your camera lense focuses loudly.")
- if(BORG_WIRE_LOCKED_DOWN)
+ if(WIRE_BORG_LOCKED)
R.SetLockdown(!R.lockcharge) // Toggle
..()
-/datum/wires/robot/CanUse(mob/living/L)
+/datum/wires/robot/interactable(mob/user)
var/mob/living/silicon/robot/R = holder
if(R.wiresexposed)
- return 1
- return 0
-
-/datum/wires/robot/proc/IsCameraCut()
- return wires_status & BORG_WIRE_CAMERA
-
-/datum/wires/robot/proc/LockedCut()
- return wires_status & BORG_WIRE_LOCKED_DOWN
-
-/datum/wires/robot/proc/CanLawCheck()
- return wires_status & BORG_WIRE_LAWCHECK
-
-/datum/wires/robot/proc/AIHasControl()
- return wires_status & BORG_WIRE_AI_CONTROL
+ return TRUE
+ return FALSE
diff --git a/code/datums/wires/smartfridge.dm b/code/datums/wires/smartfridge.dm
index a65377ead1a..3955b1849b0 100644
--- a/code/datums/wires/smartfridge.dm
+++ b/code/datums/wires/smartfridge.dm
@@ -1,35 +1,26 @@
/datum/wires/smartfridge
holder_type = /obj/machinery/smartfridge
wire_count = 3
+ proper_name = "Smartfridge"
+ window_x = 340
+ window_y = 103
+
+/datum/wires/smartfridge/New(atom/_holder)
+ wires = list(WIRE_ELECTRIFY, WIRE_IDSCAN, WIRE_THROW_ITEM)
+ return ..()
/datum/wires/smartfridge/secure
- random = 1
- wire_count = 4
+ randomize = TRUE
+ wire_count = 4 // 3 actual, 1 dud.
+ window_y = 97
-#define SMARTFRIDGE_WIRE_ELECTRIFY 1
-#define SMARTFRIDGE_WIRE_THROW 2
-#define SMARTFRIDGE_WIRE_IDSCAN 4
-
-/datum/wires/smartfridge/GetWireName(index)
- switch(index)
- if(SMARTFRIDGE_WIRE_ELECTRIFY)
- return "Electrification"
-
- if(SMARTFRIDGE_WIRE_THROW)
- return "Item Throw"
-
- if(SMARTFRIDGE_WIRE_IDSCAN)
- return "ID Scan"
-
-/datum/wires/smartfridge/CanUse(mob/living/L)
+/datum/wires/smartfridge/interactable(mob/user)
var/obj/machinery/smartfridge/S = holder
- if(!issilicon(L))
- if(S.seconds_electrified)
- if(S.shock(L, 100))
- return 0
+ if(iscarbon(user) && S.Adjacent(user) && S.seconds_electrified && S.shock(user, 100))
+ return FALSE
if(S.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
/datum/wires/smartfridge/get_status()
. = ..()
@@ -38,27 +29,27 @@
. += "The red light is [S.shoot_inventory ? "off" : "blinking"]."
. += "A [S.scan_id ? "purple" : "yellow"] light is on."
-/datum/wires/smartfridge/UpdatePulsed(index)
+/datum/wires/smartfridge/on_pulse(wire)
var/obj/machinery/smartfridge/S = holder
- switch(index)
- if(SMARTFRIDGE_WIRE_THROW)
+ switch(wire)
+ if(WIRE_THROW_ITEM)
S.shoot_inventory = !S.shoot_inventory
- if(SMARTFRIDGE_WIRE_ELECTRIFY)
+ if(WIRE_ELECTRIFY)
S.seconds_electrified = 30
- if(SMARTFRIDGE_WIRE_IDSCAN)
+ if(WIRE_IDSCAN)
S.scan_id = !S.scan_id
..()
-/datum/wires/smartfridge/UpdateCut(index, mended)
+/datum/wires/smartfridge/on_cut(wire, mend)
var/obj/machinery/smartfridge/S = holder
- switch(index)
- if(SMARTFRIDGE_WIRE_THROW)
- S.shoot_inventory = !mended
- if(SMARTFRIDGE_WIRE_ELECTRIFY)
- if(mended)
+ switch(wire)
+ if(WIRE_THROW_ITEM)
+ S.shoot_inventory = !mend
+ if(WIRE_ELECTRIFY)
+ if(mend)
S.seconds_electrified = 0
else
S.seconds_electrified = -1
- if(SMARTFRIDGE_WIRE_IDSCAN)
- S.scan_id = 1
+ if(WIRE_IDSCAN)
+ S.scan_id = TRUE
..()
diff --git a/code/datums/wires/suitstorage.dm b/code/datums/wires/suitstorage.dm
index 175b36e6081..ec651002747 100644
--- a/code/datums/wires/suitstorage.dm
+++ b/code/datums/wires/suitstorage.dm
@@ -1,24 +1,13 @@
/datum/wires/suitstorage
holder_type = /obj/machinery/suit_storage_unit
wire_count = 8
+ proper_name = "Suit storage unit"
+ window_x = 350
+ window_y = 85
-#define SSU_WIRE_ID 1
-#define SSU_WIRE_SHOCK 2
-#define SSU_WIRE_SAFETY 4
-#define SSU_WIRE_UV 8
-
-
-/datum/wires/suitstorage/GetWireName(index)
- switch(index)
- if(SSU_WIRE_ID)
- return "ID lock"
- if(SSU_WIRE_SHOCK)
- return "Shock wire"
- if(SSU_WIRE_SAFETY)
- return "Safety wire"
- if(SSU_WIRE_UV)
- return "UV wire"
-
+/datum/wires/suitstorage/New(atom/_holder)
+ wires = list(WIRE_IDSCAN, WIRE_ELECTRIFY, WIRE_SAFETY, WIRE_SSU_UV)
+ return ..()
/datum/wires/suitstorage/get_status()
. = ..()
@@ -28,42 +17,48 @@
. += "The green light is [A.shocked ? "on" : "off"]."
. += "The UV display shows [A.uv_super ? "15 nm" : "185 nm"]."
-datum/wires/suitstorage/CanUse()
+/datum/wires/suitstorage/interactable(mob/user)
var/obj/machinery/suit_storage_unit/A = holder
+ if(iscarbon(user) && A.Adjacent(user) && A.shocked)
+ return A.shock(user, 100)
if(A.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
-/datum/wires/suitstorage/UpdateCut(index, mended)
+/datum/wires/suitstorage/on_cut(wire, mend)
var/obj/machinery/suit_storage_unit/A = holder
- switch(index)
- if(SSU_WIRE_ID)
- A.secure = mended
- if(SSU_WIRE_SAFETY)
- A.safeties = mended
- if(SSU_WIRE_SHOCK)
- A.shocked = !mended
+ switch(wire)
+ if(WIRE_IDSCAN)
+ A.secure = mend
+
+ if(WIRE_SAFETY)
+ A.safeties = mend
+
+ if(WIRE_ELECTRIFY)
+ A.shocked = !mend
A.shock(usr, 50)
- if(SSU_WIRE_UV)
- A.uv_super = !mended
+
+ if(WIRE_SSU_UV)
+ A.uv_super = !mend
..()
-datum/wires/suitstorage/UpdatePulsed(index)
+/datum/wires/suitstorage/on_pulse(wire)
var/obj/machinery/suit_storage_unit/A = holder
- if(IsIndexCut(index))
+ if(is_cut(wire))
return
- switch(index)
- if(SSU_WIRE_ID)
+ switch(wire)
+ if(WIRE_IDSCAN)
A.secure = !A.secure
- if(SSU_WIRE_SAFETY)
+
+ if(WIRE_SAFETY)
A.safeties = !A.safeties
- if(SSU_WIRE_SHOCK)
+
+ if(WIRE_ELECTRIFY)
A.shocked = !A.shocked
if(A.shocked)
A.shock(usr, 100)
- spawn(50)
- if(A && !IsIndexCut(index))
- A.shocked = FALSE
- if(SSU_WIRE_UV)
+ addtimer(CALLBACK(A, /obj/machinery/suit_storage_unit/.proc/check_electrified_callback), 5 SECONDS)
+
+ if(WIRE_SSU_UV)
A.uv_super = !A.uv_super
..()
diff --git a/code/datums/wires/syndicatebomb.dm b/code/datums/wires/syndicatebomb.dm
index 430b5ce88aa..bd996f72fcc 100644
--- a/code/datums/wires/syndicatebomb.dm
+++ b/code/datums/wires/syndicatebomb.dm
@@ -1,47 +1,31 @@
/datum/wires/syndicatebomb
- random = TRUE
+ randomize = TRUE
holder_type = /obj/machinery/syndicatebomb
wire_count = 5
+ proper_name = "Syndicate bomb"
+ window_x = 320
+ window_y = 22
-#define BOMB_WIRE_BOOM 1 // Explodes if pulsed or cut while active, defuses a bomb that isn't active on cut
-#define BOMB_WIRE_UNBOLT 2 // Unbolts the bomb if cut, hint on pulsed
-#define BOMB_WIRE_DELAY 4 // Raises the timer on pulse, does nothing on cut
-#define BOMB_WIRE_PROCEED 8 // Lowers the timer, explodes if cut while the bomb is active
-#define BOMB_WIRE_ACTIVATE 16 // Will start a bombs timer if pulsed, will hint if pulsed while already active, will stop a timer a bomb on cut
+/datum/wires/syndicatebomb/New(atom/_holder)
+ wires = list(WIRE_BOMB_DELAY, WIRE_EXPLODE, WIRE_BOMB_UNBOLT,WIRE_BOMB_PROCEED, WIRE_BOMB_ACTIVATE)
+ return ..()
-/datum/wires/syndicatebomb/GetWireName(index)
- switch(index)
- if(BOMB_WIRE_BOOM)
- return "Explode"
-
- if(BOMB_WIRE_UNBOLT)
- return "Unbolt"
-
- if(BOMB_WIRE_DELAY)
- return "Delay"
-
- if(BOMB_WIRE_PROCEED)
- return "Proceed"
-
- if(BOMB_WIRE_ACTIVATE)
- return "Activate"
-
-/datum/wires/syndicatebomb/CanUse(mob/living/L)
+/datum/wires/syndicatebomb/interactable(mob/user)
var/obj/machinery/syndicatebomb/P = holder
if(P.open_panel)
return TRUE
return FALSE
-/datum/wires/syndicatebomb/UpdatePulsed(index)
+/datum/wires/syndicatebomb/on_pulse(wire)
var/obj/machinery/syndicatebomb/B = holder
- switch(index)
- if(BOMB_WIRE_BOOM)
+ switch(wire)
+ if(WIRE_EXPLODE)
if(B.active)
holder.visible_message(" [bicon(B)] An alarm sounds! It's go-")
B.explode_now = TRUE
- if(BOMB_WIRE_UNBOLT)
+ if(WIRE_BOMB_UNBOLT)
holder.visible_message(" [bicon(holder)] The bolts spin in place for a moment.")
- if(BOMB_WIRE_DELAY)
+ if(WIRE_BOMB_DELAY)
if(B.delayedbig)
holder.visible_message(" [bicon(B)] The bomb has already been delayed.")
else
@@ -49,7 +33,7 @@
playsound(B, 'sound/machines/chime.ogg', 30, 1)
B.detonation_timer += 300
B.delayedbig = TRUE
- if(BOMB_WIRE_PROCEED)
+ if(WIRE_BOMB_PROCEED)
holder.visible_message(" [bicon(B)] The bomb buzzes ominously!")
playsound(B, 'sound/machines/buzz-sigh.ogg', 30, 1)
var/seconds = B.seconds_remaining()
@@ -59,7 +43,7 @@
B.detonation_timer -= 100
else if(seconds >= 11) // Both to prevent negative timers and to have a little mercy.
B.detonation_timer = world.time + 100
- if(BOMB_WIRE_ACTIVATE)
+ if(WIRE_BOMB_ACTIVATE)
if(!B.active && !B.defused)
holder.visible_message(" [bicon(B)] You hear the bomb start ticking!")
B.activate()
@@ -72,11 +56,11 @@
B.delayedlittle = TRUE
..()
-/datum/wires/syndicatebomb/UpdateCut(index, mended)
+/datum/wires/syndicatebomb/on_cut(wire, mend)
var/obj/machinery/syndicatebomb/B = holder
- switch(index)
- if(BOMB_WIRE_BOOM)
- if(mended)
+ switch(wire)
+ if(WIRE_EXPLODE)
+ if(mend)
B.defused = FALSE // Cutting and mending all the wires of an inactive bomb will thus cure any sabotage.
else
if(B.active)
@@ -84,17 +68,17 @@
B.explode_now = TRUE
else
B.defused = TRUE
- if(BOMB_WIRE_UNBOLT)
- if(!mended && B.anchored)
+ if(WIRE_BOMB_UNBOLT)
+ if(!mend && B.anchored)
holder.visible_message(" [bicon(B)] The bolts lift out of the ground!")
playsound(B, 'sound/effects/stealthoff.ogg', 30, 1)
B.anchored = FALSE
- if(BOMB_WIRE_PROCEED)
- if(!mended && B.active)
+ if(WIRE_BOMB_PROCEED)
+ if(!mend && B.active)
holder.visible_message(" [bicon(B)] An alarm sounds! It's go-")
B.explode_now = TRUE
- if(BOMB_WIRE_ACTIVATE)
- if(!mended && B.active)
+ if(WIRE_BOMB_ACTIVATE)
+ if(!mend && B.active)
holder.visible_message(" [bicon(B)] The timer stops! The bomb has been defused!")
B.defused = TRUE
B.update_icon()
diff --git a/code/datums/wires/tesla_coil.dm b/code/datums/wires/tesla_coil.dm
index bc513c6c6c0..f4dbf39017a 100644
--- a/code/datums/wires/tesla_coil.dm
+++ b/code/datums/wires/tesla_coil.dm
@@ -1,23 +1,23 @@
/datum/wires/tesla_coil
wire_count = 1
holder_type = /obj/machinery/power/tesla_coil
+ proper_name = "Tesla coil"
+ window_x = 320
+ window_y = 50
-#define TESLACOIL_WIRE_ZAP 1
+/datum/wires/tesla_coil/New(atom/_holder)
+ wires = list(WIRE_TESLACOIL_ZAP)
+ return ..()
-/datum/wires/tesla_coil/GetWireName(index)
- switch(index)
- if(TESLACOIL_WIRE_ZAP)
- return "Zap"
-
-/datum/wires/tesla_coil/CanUse(mob/living/L)
+/datum/wires/tesla_coil/interactable(mob/user)
var/obj/machinery/power/tesla_coil/T = holder
if(T && T.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
-/datum/wires/tesla_coil/UpdatePulsed(index)
+/datum/wires/tesla_coil/on_pulse(wire)
var/obj/machinery/power/tesla_coil/T = holder
- switch(index)
- if(TESLACOIL_WIRE_ZAP)
+ switch(wire)
+ if(WIRE_TESLACOIL_ZAP)
T.zap()
..()
diff --git a/code/datums/wires/vending.dm b/code/datums/wires/vending.dm
index c9e6032afee..299e18d815a 100644
--- a/code/datums/wires/vending.dm
+++ b/code/datums/wires/vending.dm
@@ -1,35 +1,21 @@
/datum/wires/vending
holder_type = /obj/machinery/vending
wire_count = 4
+ window_y = 112
+ window_x = 350
+ proper_name = "Vending machine"
-#define VENDING_WIRE_THROW 1
-#define VENDING_WIRE_CONTRABAND 2
-#define VENDING_WIRE_ELECTRIFY 4
-#define VENDING_WIRE_IDSCAN 8
+/datum/wires/vending/New(atom/_holder)
+ wires = list(WIRE_THROW_ITEM, WIRE_IDSCAN, WIRE_ELECTRIFY, WIRE_CONTRABAND)
+ return ..()
-/datum/wires/vending/GetWireName(index)
- switch(index)
- if(VENDING_WIRE_THROW)
- return "Item Throw"
-
- if(VENDING_WIRE_CONTRABAND)
- return "Contraband"
-
- if(VENDING_WIRE_ELECTRIFY)
- return "Electrification"
-
- if(VENDING_WIRE_IDSCAN)
- return "ID Scan"
-
-/datum/wires/vending/CanUse(mob/living/L)
+/datum/wires/vending/interactable(mob/user)
var/obj/machinery/vending/V = holder
- if(!istype(L, /mob/living/silicon))
- if(V.seconds_electrified)
- if(V.shock(L, 100))
- return 0
+ if(!istype(user, /mob/living/silicon) && V.seconds_electrified && V.shock(user, 100))
+ return FALSE
if(V.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
/datum/wires/vending/get_status()
. = ..()
@@ -39,31 +25,31 @@
. += "The green light is [V.extended_inventory ? "on" : "off"]."
. += "A [V.scan_id ? "purple" : "yellow"] light is on."
-/datum/wires/vending/UpdatePulsed(index)
+/datum/wires/vending/on_pulse(wire)
var/obj/machinery/vending/V = holder
- switch(index)
- if(VENDING_WIRE_THROW)
+ switch(wire)
+ if(WIRE_THROW_ITEM)
V.shoot_inventory = !V.shoot_inventory
- if(VENDING_WIRE_CONTRABAND)
+ if(WIRE_CONTRABAND)
V.extended_inventory = !V.extended_inventory
- if(VENDING_WIRE_ELECTRIFY)
+ if(WIRE_ELECTRIFY)
V.seconds_electrified = 30
- if(VENDING_WIRE_IDSCAN)
+ if(WIRE_IDSCAN)
V.scan_id = !V.scan_id
..()
-/datum/wires/vending/UpdateCut(index, mended)
+/datum/wires/vending/on_cut(wire, mend)
var/obj/machinery/vending/V = holder
- switch(index)
- if(VENDING_WIRE_THROW)
- V.shoot_inventory = !mended
- if(VENDING_WIRE_CONTRABAND)
+ switch(wire)
+ if(WIRE_THROW_ITEM)
+ V.shoot_inventory = !mend
+ if(WIRE_CONTRABAND)
V.extended_inventory = FALSE
- if(VENDING_WIRE_ELECTRIFY)
- if(mended)
+ if(WIRE_ELECTRIFY)
+ if(mend)
V.seconds_electrified = 0
else
V.seconds_electrified = -1
- if(VENDING_WIRE_IDSCAN)
- V.scan_id = 1
+ if(WIRE_IDSCAN)
+ V.scan_id = TRUE
..()
diff --git a/code/datums/wires/wires.dm b/code/datums/wires/wires.dm
index 09c874ce8a8..d2c092211ec 100644
--- a/code/datums/wires/wires.dm
+++ b/code/datums/wires/wires.dm
@@ -1,331 +1,456 @@
-// Wire datums. Created by Giacomand.
-// Was created to replace a horrible case of copy and pasted code with no care for maintability.
-// Goodbye Door wires, Cyborg wires, Vending Machine wires, Autolathe wires
-// Protolathe wires, APC wires and Camera wires!
-
-#define MAX_FLAG 65535
-
-GLOBAL_LIST_EMPTY(same_wires)
-// 12 colours, if you're adding more than 12 wires then add more colours here
-GLOBAL_LIST_INIT(wireColours, list("red", "blue", "green", "black", "orange", "brown", "gold", "gray", "cyan", "navy", "purple", "pink"))
/datum/wires
+ /// TRUE if the wires will be different every time a new wire datum is created.
+ var/randomize = FALSE
+ /// The atom the wires belong too. For example: an airlock.
+ var/atom/holder
+ /// The holder type; used to make sure that the holder is the correct type.
+ var/holder_type
+ /// The display name for the TGUI window. For example, given the var is "APC"...
+ /// When the TGUI window is opened, "wires" will be appended to it's title, and it would become "APC wires".
+ var/proper_name = "Unknown"
+ /// The total number of wires that our holder atom has.
+ var/wire_count = NONE
+ /// A list of all wires. For a list of valid wires defines that can go here, see `code/__DEFINES/wires.dm`
+ var/list/wires
+ /// A list of all cut wires. The same values that can go into `wires` will get added and removed from this list.
+ var/list/cut_wires
+ /// An associative list with the wire color as the key, and the wire define as the value.
+ var/list/colors
+ /// An associative list of signalers attached to the wires. The wire color is the key, and the signaler object reference is the value.
+ var/list/assemblies
+ /// The width of the wire TGUI window.
+ var/window_x = 300
+ /// The height of the wire TGUI window. Will get longer as needed, based on the `wire_count`.
+ var/window_y = 100
- var/random = 0 // Will the wires be different for every single instance.
- var/atom/holder = null // The holder
- var/holder_type = null // The holder type; used to make sure that the holder is the correct type.
- var/wire_count = 0 // Max is 16
- var/wires_status = 0 // BITFLAG OF WIRES
-
- var/list/wires = list()
- var/list/signallers = list()
-
- var/table_options = " align='center'"
- var/row_options1 = " width='80px'"
- var/row_options2 = " width='260px'"
- var/window_x = 370
- var/window_y = 470
-
-/datum/wires/New(atom/holder)
+/datum/wires/New(atom/_holder)
..()
- src.holder = holder
- if(!istype(holder, holder_type))
+ if(!istype(_holder, holder_type))
CRASH("Our holder is null/the wrong type!")
- // Generate new wires
- if(random)
- GenerateWires()
- // Get the same wires
+ holder = _holder
+ cut_wires = list()
+ colors = list()
+ assemblies = list()
+
+ // Add in the appropriate amount of dud wires.
+ var/wire_len = length(wires)
+ if(wire_len < wire_count) // If the amount of "real" wires is less than the total we're suppose to have...
+ add_duds(wire_count - wire_len) // Add in the appropriate amount of duds to reach `wire_count`.
+
+ // If the randomize is true, we need to generate a new set of wires and ignore any wire color directories.
+ if(randomize)
+ randomize()
+ return
+
+ if(!GLOB.wire_color_directory[holder_type])
+ randomize()
+ GLOB.wire_color_directory[holder_type] = colors
else
- // We don't have any wires to copy yet, generate some and then copy it.
- if(!GLOB.same_wires[holder_type])
- GenerateWires()
- GLOB.same_wires[holder_type] = src.wires.Copy()
- else
- var/list/wires = GLOB.same_wires[holder_type]
- src.wires = wires // Reference the wires list.
+ colors = GLOB.wire_color_directory[holder_type]
/datum/wires/Destroy()
holder = null
+ for(var/color in colors)
+ detach_assembly(color)
return ..()
-/datum/wires/proc/GenerateWires()
- var/list/colours_to_pick = GLOB.wireColours.Copy() // Get a copy, not a reference.
- var/list/indexes_to_pick = list()
- //Generate our indexes
- for(var/i = 1; i < MAX_FLAG && i < (1 << wire_count); i += i)
- indexes_to_pick += i
- colours_to_pick.len = wire_count // Downsize it to our specifications.
+/**
+ * Randomly generates a new set of wires. and corresponding colors from the given pool. Assigns the information as an associative list, to `colors`.
+ *
+ * In the `colors` list, the name of the color is the key, and the wire is the value.
+ * For example: `colors["red"] = WIRE_ELECTRIFY`. This will look like `list("red" = WIRE_ELECTRIFY)` internally.
+ */
+/datum/wires/proc/randomize()
+ var/static/list/possible_colors = list("red", "blue", "green", "silver", "orange", "brown", "gold", "white", "cyan", "magenta", "purple", "pink")
+ var/list/my_possible_colors = possible_colors.Copy()
- while(colours_to_pick.len && indexes_to_pick.len)
- // Pick and remove a colour
- var/colour = pick_n_take(colours_to_pick)
-
- // Pick and remove an index
- var/index = pick_n_take(indexes_to_pick)
-
- src.wires[colour] = index
- //wires = shuffle(wires)
-
-/datum/wires/proc/get_status()
- return list()
+ for(var/wire in shuffle(wires))
+ colors[pick_n_take(my_possible_colors)] = wire
+/**
+ * Proc called when the user attempts to interact with wires UI.
+ *
+ * Checks if the user exists, is a mob, the wires are attached to something (`holder`) and makes sure `interactable(user)` returns TRUE.
+ * If all the checks succeed, open the TGUI interface for the user.
+ *
+ * Arugments:
+ * * user - the mob trying to interact with the wires.
+ */
/datum/wires/proc/Interact(mob/user)
- if(user && istype(user) && holder && CanUse(user))
- ui_interact(user)
+ if(user && istype(user) && holder && interactable(user))
+ tgui_interact(user)
-/datum/wires/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/**
+ * Base proc, intended to be overriden. Wire datum specific checks you want to run before the TGUI is shown to the user should go here.
+ */
+/datum/wires/proc/interactable(mob/user)
+ return TRUE
+
+/// Users will be interacting with our holder object and not the wire datum directly, therefore we need to return the holder.
+/datum/wires/tgui_host()
+ return holder
+
+/datum/wires/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_physical_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "wires.tmpl", holder.name, window_x, window_y)
+ ui = new(user, src, ui_key, "Wires", "[proper_name] wires", window_x, window_y + wire_count * 30, master_ui, state)
ui.open()
-/datum/wires/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.physical_state)
- var/data[0]
- var/list/replace_colours = null
+/datum/wires/tgui_data(mob/user)
+ var/list/data = list()
+ var/list/replace_colors
+
if(ishuman(user))
var/mob/living/carbon/human/H = user
var/obj/item/organ/internal/eyes/eyes = H.get_int_organ(/obj/item/organ/internal/eyes)
- if(eyes && (COLOURBLIND in H.mutations))
- replace_colours = eyes.replace_colours
+ if(eyes && (COLOURBLIND in H.mutations)) // Check if the human has colorblindness.
+ replace_colors = eyes.replace_colours // Get the colorblind replacement colors list.
+ var/list/wires_list = list()
- var/list/W[0]
- for(var/colour in wires)
- var/new_colour = colour
- var/colour_name = colour
- if(colour in replace_colours)
- new_colour = replace_colours[colour]
- if(new_colour in LIST_REPLACE_RENAME)
- colour_name = LIST_REPLACE_RENAME[new_colour]
+ for(var/color in colors)
+ var/replaced_color = color
+ var/color_name = color
+
+ if(color in replace_colors) // If this color is one that needs to be replaced using the colorblindness list.
+ replaced_color = replace_colors[color]
+ if(replaced_color in LIST_COLOR_RENAME) // If its an ugly written color name like "darkolivegreen", rename it to something like "dark green".
+ color_name = LIST_COLOR_RENAME[replaced_color]
else
- colour_name = new_colour
- else
- new_colour = colour
- colour_name = new_colour
- W[++W.len] = list("colour_name" = capitalize(colour_name), "seen_colour" = capitalize(new_colour),"colour" = capitalize(colour), "cut" = IsColourCut(colour), "index" = can_see_wire_index(user) ? GetWireName(GetIndex(colour)) : null, "attached" = IsAttached(colour))
+ color_name = replaced_color // Else just keep the normal color name
- if(W.len > 0)
- data["wires"] = W
+ wires_list += list(list(
+ "seen_color" = replaced_color, // The color of the wire that the mob will see. This will be the same as `color` if the user is NOT colorblind.
+ "color_name" = color_name, // The wire's name. This will be the same as `color` if the user is NOT colorblind.
+ "color" = color, // The "real" color of the wire. No replacements.
+ "wire" = can_see_wire_info(user) && !is_dud_color(color) ? get_wire(color) : null, // Wire define information like "Contraband" or "Door Bolts".
+ "cut" = is_color_cut(color), // Whether the wire is cut or not. Used to display "cut" or "mend".
+ "attached" = is_attached(color) // Whether or not a signaler is attached to this wire.
+ ))
+ data["wires"] = wires_list
+ // Get the information shown at the bottom of wire TGUI window, such as "The red light is blinking", etc.
+ // If the user is colorblind, we need to replace these colors as well.
var/list/status = get_status()
- if(replace_colours)
- var/i
- for(i=1, i<=status.len, i++)
- for(var/colour in replace_colours)
- var/new_colour = replace_colours[colour]
- if(new_colour in LIST_REPLACE_RENAME)
- new_colour = LIST_REPLACE_RENAME[new_colour]
- if(findtext(status[i],colour))
- status[i] = replacetext(status[i],colour,new_colour)
- break
- data["status_len"] = status.len
- data["status"] = status
+ if(replace_colors)
+ var/i
+ for(i in 1 to length(status))
+ for(var/color in replace_colors)
+ var/new_color = replace_colors[color]
+ if(new_color in LIST_COLOR_RENAME)
+ new_color = LIST_COLOR_RENAME[new_color]
+ if(findtext(status[i], color))
+ status[i] = replacetext(status[i], color, new_color)
+ break
+
+ data["status"] = status
return data
-/datum/wires/nano_host()
- return holder
+/datum/wires/tgui_act(action, list/params)
+ if(..())
+ return
-/datum/wires/proc/can_see_wire_index(mob/user)
+ var/mob/user = usr
+ if(!interactable(user))
+ return
+
+ 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(!istype(I, /obj/item/wirecutters) && !user.can_admin_interact())
+ to_chat(user, " You need wirecutters!")
+ return
+
+ if(istype(I))
+ playsound(holder, I.usesound, 20, 1)
+ cut_color(color)
+ return TRUE
+
+ // Pulse a wire.
+ if("pulse")
+ if(!istype(I, /obj/item/multitool) && !user.can_admin_interact())
+ 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/assembly/signaler))
+ to_chat(user, " You need a remote signaller!")
+ return
+
+ if(user.drop_item())
+ attach_assembly(color, I)
+ return TRUE
+ else
+ to_chat(user, " [user.get_active_hand()] 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)
if(user.can_admin_interact())
return TRUE
else if(istype(user.get_active_hand(), /obj/item/multitool))
var/obj/item/multitool/M = user.get_active_hand()
if(M.shows_wire_information)
return TRUE
-
return FALSE
-/datum/wires/Topic(href, href_list)
- if(..())
- return 1
- var/mob/L = usr
- if(CanUse(L) && href_list["action"])
- var/obj/item/I = L.get_active_hand()
- var/colour = lowertext(href_list["wire"])
- holder.add_hiddenprint(L)
- switch(href_list["action"])
- if("cut") // Toggles the cut/mend status
- if(istype(I, /obj/item/wirecutters) || L.can_admin_interact())
- if(istype(I))
- playsound(holder, I.usesound, 20, 1)
- CutWireColour(colour)
- else
- to_chat(L, " You need wirecutters!")
- if("pulse")
- if(istype(I, /obj/item/multitool) || L.can_admin_interact())
- playsound(holder, 'sound/weapons/empty.ogg', 20, 1)
- PulseColour(colour)
- else
- to_chat(L, " You need a multitool!")
- if("attach")
- if(IsAttached(colour))
- var/obj/item/O = Detach(colour)
- if(O)
- L.put_in_hands(O)
- else
- if(istype(I, /obj/item/assembly/signaler))
- if(L.drop_item())
- Attach(colour, I)
- else
- to_chat(L, " [L.get_active_hand()] is stuck to your hand!")
- else
- to_chat(L, " You need a remote signaller!")
+/**
+ * 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()
- SSnanoui.update_uis(src)
- return 1
+/**
+ * Clears the `colors` list, and randomizes it to a new set of color-to-wire relations.
+ */
+/datum/wires/proc/shuffle_wires()
+ colors.Cut()
+ randomize()
-//
-// Overridable Procs
-//
+/**
+ * Repairs all cut wires.
+ */
+/datum/wires/proc/repair()
+ cut_wires.Cut()
-// Called when wires cut/mended.
-/datum/wires/proc/UpdateCut(index, mended)
- if(holder)
- SSnanoui.update_uis(holder)
+/**
+ * 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
-// Called when wire pulsed. Add code here.
-/datum/wires/proc/UpdatePulsed(index)
- if(holder)
- SSnanoui.update_uis(holder)
+/**
+ * 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)
-/datum/wires/proc/CanUse(mob/L)
- return 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))
-/datum/wires/CanUseTopic(mob/user, datum/topic_state/state)
- if(!CanUse(user))
- return STATUS_CLOSE
- return ..()
+/**
+ * Gets the wire associated with the color passed in.
+ *
+ * Arugments:
+ * * color - a wire color.
+ */
+/datum/wires/proc/get_wire(color)
+ return colors[color]
-// Example of use:
-/*
+/**
+ * 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)
-#define NAME_WIRE_BOLTED 1
-#define NAME_WIRE_SHOCKED 2
-#define NAME_WIRE_SAFETY 4
-#define NAME_WIRE_POWER 8
+/**
+ * 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))
-/datum/wires/door/UpdateCut(var/index, var/mended)
- var/obj/machinery/door/airlock/A = holder
- switch(index)
- if(NAME_WIRE_BOLTED)
- if(!mended)
- A.bolt()
- if(NAME_WIRE_SHOCKED)
- A.shock()
- if(NAME_WIRE_SAFETY)
- A.safety()
+/**
+ * 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))
-*/
-
-
-//
-// Helper Procs
-//
-
-/datum/wires/proc/PulseColour(colour)
- PulseIndex(GetIndex(colour))
-
-/datum/wires/proc/PulseIndex(index)
- if(IsIndexCut(index))
- return
- UpdatePulsed(index)
-
-/datum/wires/proc/GetIndex(colour)
- if(wires[colour])
- var/index = wires[colour]
- return index
+/**
+ * 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)
-/datum/wires/proc/GetWireName(index)
+/**
+ * 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))
+
+/**
+ * Cuts a random wire.
+ */
+/datum/wires/proc/cut_random()
+ cut(wires[rand(1, length(wires))])
+
+/**
+ * Cuts all wires.
+ */
+/datum/wires/proc/cut_all()
+ for(var/wire in wires)
+ cut(wire)
+
+/**
+ * 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
-//
-// Is Index/Colour Cut procs
-//
+/**
+ * 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/IsColourCut(colour)
- var/index = GetIndex(colour)
- return IsIndexCut(index)
+/**
+ * Pulses the wire associated with the given color.
+ *
+ * Arugments:
+ * * wire - a wire color.
+ */
+/datum/wires/proc/pulse_color(color)
+ pulse(get_wire(color))
-/datum/wires/proc/IsIndexCut(index)
- return (index & wires_status)
+/**
+ * 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
-//
-// Signaller Procs
-//
+/**
+ * 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/assembly/signaler/S)
+ for(var/color in assemblies)
+ if(S == assemblies[color])
+ pulse_color(color)
+ return TRUE
-/datum/wires/proc/IsAttached(colour)
- if(signallers[colour])
- return 1
- return 0
+/**
+ * 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/assembly/signaler/S)
+ if(S && istype(S) && !is_attached(color))
+ assemblies[color] = S
+ S.forceMove(holder)
+ S.connected = src
+ return S
-/datum/wires/proc/GetAttached(colour)
- if(signallers[colour])
- return signallers[colour]
+/**
+ * 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/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(colour, obj/item/assembly/signaler/S)
- if(colour && S)
- if(!IsAttached(colour))
- signallers[colour] = S
- S.loc = holder
- S.connected = src
- return S
-
-/datum/wires/proc/Detach(colour)
- if(colour)
- var/obj/item/assembly/signaler/S = GetAttached(colour)
- if(S)
- signallers -= colour
- S.connected = null
- S.loc = holder.loc
- return S
-
-
-/datum/wires/proc/Pulse(obj/item/assembly/signaler/S)
-
- for(var/colour in signallers)
- if(S == signallers[colour])
- PulseColour(colour)
- break
-
-
-//
-// Cut Wire Colour/Index procs
-//
-
-/datum/wires/proc/CutWireColour(colour)
- var/index = GetIndex(colour)
- CutWireIndex(index)
-
-/datum/wires/proc/CutWireIndex(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/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
-
-//
-//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/defines/procs/AStar.dm b/code/defines/procs/AStar.dm
index 1f8c83f5f04..1d6fa5619e2 100644
--- a/code/defines/procs/AStar.dm
+++ b/code/defines/procs/AStar.dm
@@ -29,15 +29,15 @@ Actual Adjacent procs :
//////////////////////
//A* nodes variables
-/PathNode
+/datum/pathnode
var/turf/source //turf associated with the PathNode
- var/PathNode/prevNode //link to the parent PathNode
+ var/datum/pathnode/prevNode //link to the parent PathNode
var/f //A* Node weight (f = g + h)
var/g //A* movement cost variable
var/h //A* heuristic variable
var/nt //count the number of Nodes traversed
-/PathNode/New(s,p,pg,ph,pnt)
+/datum/pathnode/New(s,p,pg,ph,pnt)
source = s
prevNode = p
g = pg
@@ -46,7 +46,7 @@ Actual Adjacent procs :
source.PNode = src
nt = pnt
-/PathNode/proc/calc_f()
+/datum/pathnode/proc/calc_f()
f = g + h
//////////////////////
@@ -54,11 +54,11 @@ Actual Adjacent procs :
//////////////////////
//the weighting function, used in the A* algorithm
-/proc/PathWeightCompare(PathNode/a, PathNode/b)
+/proc/PathWeightCompare(datum/pathnode/a, datum/pathnode/b)
return a.f - b.f
//reversed so that the Heap is a MinHeap rather than a MaxHeap
-/proc/HeapPathWeightCompare(PathNode/a, PathNode/b)
+/proc/HeapPathWeightCompare(datum/pathnode/a, datum/pathnode/b)
return b.f - a.f
//wrapper that returns an empty list if A* failed to find a path
@@ -82,13 +82,13 @@ Actual Adjacent procs :
return 0
maxnodedepth = maxnodes //no need to consider path longer than maxnodes
- var/Heap/open = new /Heap(/proc/HeapPathWeightCompare) //the open list
+ var/datum/heap/open = new /datum/heap(/proc/HeapPathWeightCompare) //the open list
var/list/closed = new() //the closed list
var/list/path = null //the returned path, if any
- var/PathNode/cur //current processed turf
+ var/datum/pathnode/cur //current processed turf
//initialization
- open.Insert(new /PathNode(start,null,0,call(start,dist)(end),0))
+ open.Insert(new /datum/pathnode(start,null,0,call(start,dist)(end),0))
//then run the main loop
while(!open.IsEmpty() && !path)
@@ -125,7 +125,7 @@ Actual Adjacent procs :
var/newg = cur.g + call(cur.source,dist)(T)
if(!T.PNode) //is not already in open list, so add it
- open.Insert(new /PathNode(T,cur,newg,call(T,dist)(end),cur.nt+1))
+ open.Insert(new /datum/pathnode(T,cur,newg,call(T,dist)(end),cur.nt+1))
else //is already in open list, check if it's a better way from the current turf
if(newg < T.PNode.g)
T.PNode.prevNode = cur
@@ -137,7 +137,7 @@ Actual Adjacent procs :
}
//cleaning after us
- for(var/PathNode/PN in open.L)
+ for(var/datum/pathnode/PN in open.L)
PN.source.PNode = null
for(var/turf/T in closed)
T.PNode = null
diff --git a/code/defines/procs/admin.dm b/code/defines/procs/admin.dm
index fb8ca665906..fa5556b4e40 100644
--- a/code/defines/procs/admin.dm
+++ b/code/defines/procs/admin.dm
@@ -42,11 +42,11 @@
if(key)
if(C && C.holder && C.holder.fakekey && !include_name)
if(include_link)
- . += " "
+ . += ""
. += "Administrator"
else
if(include_link && C)
- . += ""
+ . += ""
. += key
if(include_link)
@@ -83,10 +83,6 @@
var/message = "[key_name(whom, 0)][isAntag(whom) ? "(ANTAG)" : ""][isLivingSSD(whom) ? "(SSD!)": ""]"
return message
-/proc/log_and_message_admins(var/message as text)
+/proc/log_and_message_admins(message)
log_admin("[key_name(usr)] " + message)
message_admins("[key_name_admin(usr)] " + message)
-
-/proc/admin_log_and_message_admins(var/message as text)
- log_admin("[key_name(usr)] " + message)
- message_admins("[key_name_admin(usr)] " + message, 1)
diff --git a/code/defines/procs/dbcore.dm b/code/defines/procs/dbcore.dm
index 90a5b0db40c..945ab8af23a 100644
--- a/code/defines/procs/dbcore.dm
+++ b/code/defines/procs/dbcore.dm
@@ -29,7 +29,7 @@
#define BLOB 14
// TODO: Investigate more recent type additions and see if I can handle them. - Nadrew
-DBConnection
+/DBConnection
var/_db_con // This variable contains a reference to the actual database connection.
var/dbi // This variable is a string containing the DBI MySQL requires.
var/user // This variable contains the username data.
@@ -39,14 +39,14 @@ DBConnection
var/server = ""
var/port = 3306
-DBConnection/New(dbi_handler,username,password_handler,cursor_handler)
+/DBConnection/New(dbi_handler,username,password_handler,cursor_handler)
src.dbi = dbi_handler
src.user = username
src.password = password_handler
src.default_cursor = cursor_handler
_db_con = _dm_db_new_con()
-DBConnection/proc/Connect(dbi_handler=src.dbi,user_handler=src.user,password_handler=src.password,cursor_handler)
+/DBConnection/proc/Connect(dbi_handler=src.dbi,user_handler=src.user,password_handler=src.password,cursor_handler)
if(!config.sql_enabled)
return 0
if(!src) return 0
@@ -54,24 +54,24 @@ DBConnection/proc/Connect(dbi_handler=src.dbi,user_handler=src.user,password_han
if(!cursor_handler) cursor_handler = Default_Cursor
return _dm_db_connect(_db_con,dbi_handler,user_handler,password_handler,cursor_handler,null)
-DBConnection/proc/Disconnect() return _dm_db_close(_db_con)
+/DBConnection/proc/Disconnect() return _dm_db_close(_db_con)
-DBConnection/proc/IsConnected()
+/DBConnection/proc/IsConnected()
if(!config.sql_enabled) return 0
var/success = _dm_db_is_connected(_db_con)
return success
-DBConnection/proc/Quote(str) return _dm_db_quote(_db_con,str)
+/DBConnection/proc/Quote(str) return _dm_db_quote(_db_con,str)
-DBConnection/proc/ErrorMsg() return _dm_db_error_msg(_db_con)
-DBConnection/proc/SelectDB(database_name,dbi)
+/DBConnection/proc/ErrorMsg() return _dm_db_error_msg(_db_con)
+/DBConnection/proc/SelectDB(database_name,dbi)
if(IsConnected()) Disconnect()
//return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[DB_SERVER]:[DB_PORT]"]",user,password)
return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[sqladdress]:[sqlport]"]",user,password)
-DBConnection/proc/NewQuery(sql_query,cursor_handler=src.default_cursor) return new/DBQuery(sql_query,src,cursor_handler)
+/DBConnection/proc/NewQuery(sql_query,cursor_handler=src.default_cursor) return new/DBQuery(sql_query,src,cursor_handler)
-DBQuery/New(sql_query,DBConnection/connection_handler,cursor_handler)
+/DBQuery/New(sql_query,DBConnection/connection_handler,cursor_handler)
if(IsAdminAdvancedProcCall())
to_chat(usr, "DB query blocked: Advanced ProcCall detected.")
message_admins("[key_name(usr)] attempted to create a DB query via advanced proc-call")
@@ -83,12 +83,12 @@ DBQuery/New(sql_query,DBConnection/connection_handler,cursor_handler)
_db_query = _dm_db_new_query()
return ..()
-DBQuery/CanProcCall()
+/DBQuery/CanProcCall()
// dont even try it
return FALSE
-DBQuery
+/DBQuery
var/sql // The sql query being executed.
var/default_cursor
var/list/columns //list of DB Columns populated by Columns()
@@ -98,26 +98,26 @@ DBQuery
var/DBConnection/db_connection
var/_db_query
-DBQuery/proc/Connect(DBConnection/connection_handler) src.db_connection = connection_handler
+/DBQuery/proc/Connect(DBConnection/connection_handler) src.db_connection = connection_handler
-DBQuery/proc/Execute(sql_query=src.sql,cursor_handler=default_cursor)
+/DBQuery/proc/Execute(sql_query=src.sql,cursor_handler=default_cursor)
Close()
return _dm_db_execute(_db_query,sql_query,db_connection._db_con,cursor_handler,null)
-DBQuery/proc/NextRow() return _dm_db_next_row(_db_query,item,conversions)
+/DBQuery/proc/NextRow() return _dm_db_next_row(_db_query,item,conversions)
-DBQuery/proc/RowsAffected() return _dm_db_rows_affected(_db_query)
+/DBQuery/proc/RowsAffected() return _dm_db_rows_affected(_db_query)
-DBQuery/proc/RowCount() return _dm_db_row_count(_db_query)
+/DBQuery/proc/RowCount() return _dm_db_row_count(_db_query)
-DBQuery/proc/ErrorMsg() return _dm_db_error_msg(_db_query)
+/DBQuery/proc/ErrorMsg() return _dm_db_error_msg(_db_query)
-DBQuery/proc/Columns()
+/DBQuery/proc/Columns()
if(!columns)
columns = _dm_db_columns(_db_query,/DBColumn)
return columns
-DBQuery/proc/GetRowData()
+/DBQuery/proc/GetRowData()
var/list/columns = Columns()
var/list/results
if(columns.len)
@@ -128,23 +128,23 @@ DBQuery/proc/GetRowData()
results[C] = src.item[(cur_col.position+1)]
return results
-DBQuery/proc/Close()
+/DBQuery/proc/Close()
item.len = 0
columns = null
conversions = null
return _dm_db_close(_db_query)
-DBQuery/proc/Quote(str)
+/DBQuery/proc/Quote(str)
return db_connection.Quote(str)
-DBQuery/proc/SetConversion(column,conversion)
+/DBQuery/proc/SetConversion(column,conversion)
if(istext(column)) column = columns.Find(column)
if(!conversions) conversions = new/list(column)
else if(conversions.len < column) conversions.len = column
conversions[column] = conversion
-DBColumn
+/DBColumn
var/name
var/table
var/position //1-based index into item data
@@ -153,7 +153,7 @@ DBColumn
var/length
var/max_length
-DBColumn/New(name_handler,table_handler,position_handler,type_handler,flag_handler,length_handler,max_length_handler)
+/DBColumn/New(name_handler,table_handler,position_handler,type_handler,flag_handler,length_handler,max_length_handler)
src.name = name_handler
src.table = table_handler
src.position = position_handler
@@ -164,7 +164,7 @@ DBColumn/New(name_handler,table_handler,position_handler,type_handler,flag_handl
return ..()
-DBColumn/proc/SqlTypeName(type_handler=src.sql_type)
+/DBColumn/proc/SqlTypeName(type_handler=src.sql_type)
switch(type_handler)
if(TINYINT) return "TINYINT"
if(SMALLINT) return "SMALLINT"
diff --git a/code/defines/vox_sounds.dm b/code/defines/vox_sounds.dm
index 1d7bb9753a0..5b44f80895b 100644
--- a/code/defines/vox_sounds.dm
+++ b/code/defines/vox_sounds.dm
@@ -1,7 +1,16 @@
// List is required to compile the resources into the game when it loads.
// Dynamically loading it has bad results with sounds overtaking each other, even with the wait variable.
-GLOBAL_LIST_INIT(vox_sounds, list("," = 'sound/vox_fem/,.ogg',
+GLOBAL_LIST_INIT(vox_alerts, list(
+"bizwarn" = 'sound/vox_fem/bizwarn.ogg',
+"bloop" = 'sound/vox_fem/bloop.ogg',
+"buzwarn" = 'sound/vox_fem/buzwarn.ogg',
+"dadeda" = 'sound/vox_fem/dadeda.ogg',
+"deeoo" = 'sound/vox_fem/deeoo.ogg'
+))
+
+GLOBAL_LIST_INIT(vox_sounds, list(
+"," = 'sound/vox_fem/,.ogg',
"." = 'sound/vox_fem/..ogg',
"a" = 'sound/vox_fem/a.ogg',
"abortions" = 'sound/vox_fem/abortions.ogg',
diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm
index c33e7609685..9a333d268f3 100644
--- a/code/game/area/Space Station 13 areas.dm
+++ b/code/game/area/Space Station 13 areas.dm
@@ -50,10 +50,10 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/space/atmosalert()
return
-/area/space/fire_alert()
+/area/space/firealert(obj/source)
return
-/area/space/fire_reset()
+/area/space/firereset(obj/source)
return
/area/space/readyalert()
diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm
index 8f65ac634f4..abfcb7210b0 100644
--- a/code/game/area/ai_monitored.dm
+++ b/code/game/area/ai_monitored.dm
@@ -1,24 +1,31 @@
/area/ai_monitored
name = "AI Monitored Area"
- var/obj/machinery/camera/motioncamera = null
+ var/list/motioncameras = list()
+ var/list/motionTargets = list()
-
-/area/ai_monitored/LateInitialize()
+/area/ai_monitored/Initialize(mapload)
. = ..()
- // locate and store the motioncamera
- for(var/obj/machinery/camera/M in src)
- if(M.isMotion())
- motioncamera = M
- M.area_motion = src
- break
+ if(mapload)
+ for(var/obj/machinery/camera/M in src)
+ if(M.isMotion())
+ motioncameras.Add(M)
+ M.AddComponent(/datum/component/proximity_monitor)
+ M.set_area_motion(src)
/area/ai_monitored/Entered(atom/movable/O)
..()
- if(ismob(O) && motioncamera)
- motioncamera.newTarget(O)
+ if(ismob(O) && length(motioncameras))
+ for(var/X in motioncameras)
+ var/obj/machinery/camera/cam = X
+ cam.newTarget(O)
+ return
/area/ai_monitored/Exited(atom/movable/O)
- if(ismob(O) && motioncamera)
- motioncamera.lostTarget(O)
+ ..()
+ if(ismob(O) && length(motioncameras))
+ for(var/X in motioncameras)
+ var/obj/machinery/camera/cam = X
+ cam.lostTargetRef(O.UID())
+ return
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index da80a6e775c..aec3ea58f01 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -57,6 +57,11 @@
var/list/ambientsounds = GENERIC_SOUNDS
+ var/list/firedoors
+ var/list/cameras
+ var/list/firealarms
+ var/firedoors_last_closed_on = 0
+
var/fast_despawn = FALSE
var/can_get_auto_cryod = TRUE
var/hide_attacklogs = FALSE // For areas such as thunderdome, lavaland syndiebase, etc which generate a lot of spammy attacklogs. Reduces log priority.
@@ -125,35 +130,6 @@
cameras += C
return cameras
-
-/area/proc/atmosalert(danger_level, var/alarm_source, var/force = FALSE)
- if(report_alerts)
- if(danger_level == ATMOS_ALARM_NONE)
- SSalarms.atmosphere_alarm.clearAlarm(src, alarm_source)
- else
- SSalarms.atmosphere_alarm.triggerAlarm(src, alarm_source, severity = danger_level)
-
- //Check all the alarms before lowering atmosalm. Raising is perfectly fine. If force = 1 we don't care.
- for(var/obj/machinery/alarm/AA in src)
- if(!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted && AA.report_danger_level && !force)
- danger_level = max(danger_level, AA.danger_level)
-
- if(danger_level != atmosalm)
- if(danger_level < ATMOS_ALARM_WARNING && atmosalm >= ATMOS_ALARM_WARNING)
- //closing the doors on red and opening on green provides a bit of hysteresis that will hopefully prevent fire doors from opening and closing repeatedly due to noise
- air_doors_open()
- else if(danger_level >= ATMOS_ALARM_DANGER && atmosalm < ATMOS_ALARM_DANGER)
- air_doors_close()
-
- atmosalm = danger_level
- for(var/obj/machinery/alarm/AA in src)
- AA.update_icon()
-
- GLOB.air_alarm_repository.update_cache(src)
- return 1
- GLOB.air_alarm_repository.update_cache(src)
- return 0
-
/area/proc/air_doors_close()
if(!air_doors_activated)
air_doors_activated = TRUE
@@ -179,44 +155,151 @@
D.open()
-/area/proc/fire_alert()
- if(!fire)
- fire = 1 //used for firedoor checks
- updateicon()
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- air_doors_close()
+/area/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
-/area/proc/fire_reset()
- if(fire)
- fire = 0 //used for firedoor checks
- updateicon()
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- air_doors_open()
+/**
+ * Generate a power alert for this area
+ *
+ * Sends to all ai players, alert consoles, drones and alarm monitor programs in the world
+ */
+/area/proc/poweralert(state, obj/source)
+ if(state != poweralm)
+ poweralm = state
+ if(istype(source)) //Only report power alarms on the z-level where the source is located.
+ for(var/thing in cameras)
+ var/obj/machinery/camera/C = locateUID(thing)
+ if(!QDELETED(C) && is_station_level(C.z))
+ if(state)
+ C.network -= "Power Alarms"
+ else
+ C.network |= "Power Alarms"
- return
+ if(state)
+ SSalarm.cancelAlarm("Power", src, source)
+ else
+ SSalarm.triggerAlarm("Power", src, cameras, source)
-/area/proc/burglaralert(var/obj/trigger)
- if(always_unpowered == 1) //no burglar alarms in space/asteroid
+/**
+ * Generate an atmospheric alert for this area
+ *
+ * Sends to all ai players, alert consoles, drones and alarm monitor programs in the world
+ */
+/area/proc/atmosalert(danger_level, obj/source)
+ if(danger_level != atmosalm)
+ if(danger_level == ATMOS_ALARM_DANGER)
+
+ for(var/thing in cameras)
+ var/obj/machinery/camera/C = locateUID(thing)
+ if(!QDELETED(C) && is_station_level(C.z))
+ C.network |= "Atmosphere Alarms"
+
+
+ SSalarm.triggerAlarm("Atmosphere", src, cameras, source)
+
+ else if(atmosalm == ATMOS_ALARM_DANGER)
+ for(var/thing in cameras)
+ var/obj/machinery/camera/C = locateUID(thing)
+ if(!QDELETED(C) && is_station_level(C.z))
+ C.network -= "Atmosphere Alarms"
+
+ SSalarm.cancelAlarm("Atmosphere", src, source)
+
+ atmosalm = danger_level
+ return TRUE
+ return FALSE
+
+/**
+ * Try to close all the firedoors in the area
+ */
+/area/proc/ModifyFiredoors(opening)
+ if(firedoors)
+ firedoors_last_closed_on = world.time
+ for(var/FD in firedoors)
+ var/obj/machinery/door/firedoor/D = FD
+ var/cont = !D.welded
+ if(cont && opening) //don't open if adjacent area is on fire
+ for(var/I in D.affecting_areas)
+ var/area/A = I
+ if(A.fire)
+ cont = FALSE
+ break
+ if(cont && D.is_operational())
+ if(D.operating)
+ D.nextstate = opening ? FD_OPEN : FD_CLOSED
+ else if(!(D.density ^ opening))
+ INVOKE_ASYNC(D, (opening ? /obj/machinery/door/firedoor.proc/open : /obj/machinery/door/firedoor.proc/close))
+
+/**
+ * Generate a firealarm alert for this area
+ *
+ * Sends to all ai players, alert consoles, drones and alarm monitor programs in the world
+ *
+ * Also starts the area processing on SSobj
+ */
+/area/proc/firealert(obj/source)
+ if(always_unpowered) //no fire alarms in space/asteroid
return
- //Trigger alarm effect
- set_fire_alarm_effect()
+ if(!fire)
+ set_fire_alarm_effect()
+ ModifyFiredoors(FALSE)
+ for(var/item in firealarms)
+ var/obj/machinery/firealarm/F = item
+ F.update_icon()
- //Lockdown airlocks
- for(var/obj/machinery/door/airlock/A in src)
- spawn(0)
- A.close()
- if(A.density)
- A.lock()
+ for(var/thing in cameras)
+ var/obj/machinery/camera/C = locateUID(thing)
+ if(!QDELETED(C) && is_station_level(C.z))
+ C.network |= "Fire Alarms"
- SSalarms.burglar_alarm.triggerAlarm(src, trigger)
- spawn(600)
- SSalarms.burglar_alarm.clearAlarm(src, trigger)
+ SSalarm.triggerAlarm("Fire", src, cameras, source)
-/area/proc/set_fire_alarm_effect()
- fire = 1
- updateicon()
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ START_PROCESSING(SSobj, src)
+
+/**
+ * Reset the firealarm alert for this area
+ *
+ * resets the alert sent to all ai players, alert consoles, drones and alarm monitor programs
+ * in the world
+ *
+ * Also cycles the icons of all firealarms and deregisters the area from processing on SSOBJ
+ */
+/area/proc/firereset(obj/source)
+ if(fire)
+ unset_fire_alarm_effects()
+ ModifyFiredoors(TRUE)
+ for(var/item in firealarms)
+ var/obj/machinery/firealarm/F = item
+ F.update_icon()
+
+ for(var/thing in cameras)
+ var/obj/machinery/camera/C = locateUID(thing)
+ if(!QDELETED(C) && is_station_level(C.z))
+ C.network -= "Fire Alarms"
+
+ SSalarm.cancelAlarm("Fire", src, source)
+
+ STOP_PROCESSING(SSobj, src)
+
+/**
+ * If 100 ticks has elapsed, toggle all the firedoors closed again
+ */
+/area/process()
+ if(firedoors_last_closed_on + 100 < world.time) //every 10 seconds
+ ModifyFiredoors(FALSE)
+
+/**
+ * Close and lock a door passed into this proc
+ *
+ * Does this need to exist on area? probably not
+ */
+/area/proc/close_and_lock_door(obj/machinery/door/DOOR)
+ set waitfor = FALSE
+ DOOR.close()
+ if(DOOR.density)
+ DOOR.lock()
/area/proc/readyalert()
if(!eject)
@@ -240,13 +323,62 @@
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
updateicon()
+/**
+ * Raise a burglar alert for this area
+ *
+ * Close and locks all doors in the area and alerts silicon mobs of a break in
+ *
+ * Alarm auto resets after 600 ticks
+ */
+/area/proc/burglaralert(obj/trigger)
+ if(always_unpowered) //no burglar alarms in space/asteroid
+ return
+
+ //Trigger alarm effect
+ set_fire_alarm_effect()
+ //Lockdown airlocks
+ for(var/obj/machinery/door/DOOR in src)
+ close_and_lock_door(DOOR)
+
+ if(SSalarm.triggerAlarm("Burglar", src, cameras, trigger))
+ //Cancel silicon alert after 1 minute
+ addtimer(CALLBACK(SSalarm, /datum/controller/subsystem/alarm.proc/cancelAlarm, "Burglar", src, trigger), 600)
+
+/**
+ * Trigger the fire alarm visual affects in an area
+ *
+ * Updates the fire light on fire alarms in the area and sets all lights to emergency mode
+ */
+/area/proc/set_fire_alarm_effect()
+ fire = TRUE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ for(var/alarm in firealarms)
+ var/obj/machinery/firealarm/F = alarm
+ F.update_fire_light(fire)
+ for(var/obj/machinery/light/L in src)
+ L.update()
+
+/**
+ * unset the fire alarm visual affects in an area
+ *
+ * Updates the fire light on fire alarms in the area and sets all lights to emergency mode
+ */
+/area/proc/unset_fire_alarm_effects()
+ fire = FALSE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ for(var/alarm in firealarms)
+ var/obj/machinery/firealarm/F = alarm
+ F.update_fire_light(fire)
+ for(var/obj/machinery/light/L in src)
+ L.update()
+
/area/proc/updateicon()
- if((fire || eject || party) && (!requires_power||power_environ))//If it doesn't require power, can still activate this proc.
- if(fire && !eject && !party)
+ if((eject || party) && (!requires_power||power_environ))//If it doesn't require power, can still activate this proc.
+ if(!eject && !party)
icon_state = "red"
- else if(!fire && eject && !party)
+ else if(eject && !party)
icon_state = "red"
- else if(party && !fire && !eject)
+ else if(party && !eject)
icon_state = "party"
else
icon_state = "blue-red"
@@ -288,11 +420,15 @@
/area/space/powered(chan) //Nope.avi
return 0
-// called when power status changes
-
+/**
+ * Called when the area power status changes
+ *
+ * Updates the area icon, calls power change on all machines in the area, and sends the `COMSIG_AREA_POWER_CHANGE` signal.
+ */
/area/proc/power_change()
for(var/obj/machinery/M in src) // for each machine in the area
M.power_change() // reverify power status (to update icons etc.)
+ SEND_SIGNAL(src, COMSIG_AREA_POWER_CHANGE)
updateicon()
/area/proc/usage(var/chan)
@@ -438,7 +574,7 @@
/area/proc/prison_break()
for(var/obj/machinery/power/apc/temp_apc in src)
- temp_apc.overload_lighting(70)
+ INVOKE_ASYNC(temp_apc, /obj/machinery/power/apc.proc/overload_lighting, 70)
for(var/obj/machinery/door/airlock/temp_airlock in src)
temp_airlock.prison_open()
for(var/obj/machinery/door/window/temp_windoor in src)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 776a2bccd12..73dcd14c7c8 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -231,7 +231,7 @@
/atom/proc/on_reagent_change()
return
-/atom/proc/Bumped(AM as mob|obj)
+/atom/proc/Bumped(atom/movable/AM)
return
/// Convenience proc to see if a container is open for chemistry handling
@@ -257,7 +257,7 @@
/atom/proc/CheckExit()
return TRUE
-/atom/proc/HasProximity(atom/movable/AM as mob|obj)
+/atom/proc/HasProximity(atom/movable/AM)
return
/atom/proc/emp_act(severity)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index ab5ff19462a..d463c85eaf6 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -61,7 +61,7 @@
/atom/movable/proc/get_cell()
return
-/atom/movable/proc/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE)
+/atom/movable/proc/start_pulling(atom/movable/AM, state, force = pull_force, show_message = FALSE)
if(QDELETED(AM))
return FALSE
if(!(AM.can_be_pulled(src, state, force)))
@@ -87,7 +87,7 @@
if(ismob(AM))
var/mob/M = AM
add_attack_logs(src, M, "passively grabbed", ATKLOG_ALMOSTALL)
- if(!supress_message)
+ if(show_message)
visible_message("[src] has grabbed [M] passively!")
return TRUE
@@ -120,12 +120,18 @@
if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move.
pulledby.stop_pulling()
-/atom/movable/proc/can_be_pulled(user, grab_state, force)
+/atom/movable/proc/can_be_pulled(user, grab_state, force, show_message = FALSE)
if(src == user || !isturf(loc))
return FALSE
- if(anchored || throwing)
+ if(anchored || move_resist == INFINITY)
+ if(show_message)
+ to_chat(user, "[src] appears to be anchored to the ground!")
+ return FALSE
+ if(throwing)
return FALSE
if(force < (move_resist * MOVE_FORCE_PULL_RATIO))
+ if(show_message)
+ to_chat(user, "[src] is too heavy to pull!")
return FALSE
return TRUE
@@ -506,6 +512,7 @@
return //don't do an animation if attacking self
var/pixel_x_diff = 0
var/pixel_y_diff = 0
+
var/direction = get_dir(src, A)
if(direction & NORTH)
pixel_y_diff = 8
@@ -525,7 +532,8 @@
if(visual_effect_icon)
I = image('icons/effects/effects.dmi', A, visual_effect_icon, A.layer + 0.1)
else if(used_item)
- I = image(used_item.icon, A, used_item.icon_state, A.layer + 0.1)
+ I = image(icon = used_item, loc = A, layer = A.layer + 0.1)
+ I.plane = GAME_PLANE
// Scale the icon.
I.transform *= 0.75
@@ -567,3 +575,6 @@
/atom/movable/proc/portal_destroyed(obj/effect/portal/P)
return
+
+/atom/movable/proc/decompile_act(obj/item/matter_decompiler/C, mob/user) // For drones to decompile mobs and objs. See drone for an example.
+ return FALSE
diff --git a/code/game/dna/dna_modifier.dm b/code/game/dna/dna_modifier.dm
index fb0ccff32cb..3158cf90303 100644
--- a/code/game/dna/dna_modifier.dm
+++ b/code/game/dna/dna_modifier.dm
@@ -7,6 +7,11 @@
#define NEGATE_MUTATION_THRESHOLD 30 // Occupants with over ## percent radiation threshold will not gain mutations
+#define PAGE_UI "ui"
+#define PAGE_SE "se"
+#define PAGE_BUFFER "buffer"
+#define PAGE_REJUVENATORS "rejuvenators"
+
//list("data" = null, "owner" = null, "label" = null, "type" = null, "ue" = 0),
/datum/dna2/record
var/datum/dna/dna = null
@@ -161,6 +166,7 @@
occupant = usr
icon_state = "scanner_occupied"
add_fingerprint(usr)
+ SStgui.update_uis(src)
/obj/machinery/dna_scannernew/MouseDrop_T(atom/movable/O, mob/user)
if(!istype(O))
@@ -214,6 +220,7 @@
return
beaker = I
+ SStgui.update_uis(src)
I.forceMove(src)
user.visible_message("[user] adds \a [I] to \the [src]!", "You add \a [I] to \the [src]!")
return
@@ -260,6 +267,7 @@
M.forceMove(src)
occupant = M
icon_state = "scanner_occupied"
+ SStgui.update_uis(src)
// search for ghosts, if the corpse is empty and the scanner is connected to a cloner
if(locate(/obj/machinery/computer/cloning, get_step(src, NORTH)) \
@@ -281,6 +289,10 @@
occupant.forceMove(loc)
occupant = null
icon_state = "scanner_open"
+ SStgui.update_uis(src)
+
+/obj/machinery/dna_scannernew/force_eject_occupant()
+ go_out(null, TRUE)
/obj/machinery/dna_scannernew/ex_act(severity)
if(occupant)
@@ -293,6 +305,7 @@
occupant = null
updateUsrDialog()
update_icon()
+ SStgui.update_uis(src)
// Checks if occupants can be irradiated/mutated - prevents exploits where wearing full rad protection would still let you gain mutations
/obj/machinery/dna_scannernew/proc/radiation_check()
@@ -330,12 +343,11 @@
var/injector_ready = FALSE //Quick fix for issue 286 (screwdriver the screen twice to restore injector) -Pete
var/obj/machinery/dna_scannernew/connected = null
var/obj/item/disk/data/disk = null
- var/selected_menu_key = null
+ var/selected_menu_key = PAGE_UI
anchored = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 10
active_power_usage = 400
- var/waiting_for_user_input = 0 // Fix for #274 (Mash create block injector without answering dialog to make unlimited injectors) - N3X
/obj/machinery/computer/scan_consolenew/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/disk/data)) //INSERT SOME diskS
@@ -344,7 +356,7 @@
I.forceMove(src)
disk = I
to_chat(user, "You insert [I].")
- SSnanoui.update_uis(src) // update all UIs attached to src()
+ SStgui.update_uis(src)
return
else
return ..()
@@ -396,35 +408,18 @@
if(stat & (NOPOWER|BROKEN))
return
- ui_interact(user)
+ tgui_interact(user)
- /**
- * The ui_interact proc is used to open and update Nano UIs
- * If ui_interact is not used then the UI will not update correctly
- * ui_interact is currently defined for /atom/movable
- *
- * @param user /mob The mob who is interacting with this ui
- * @param ui_key string A string key to use for this ui. Allows for multiple unique uis on one obj/mob (defaut value "main")
- * @param ui /datum/nanoui This parameter is passed by the nanoui process() proc when updating an open ui
- *
- * @return nothing
- */
-/obj/machinery/computer/scan_consolenew/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1)
+/obj/machinery/computer/scan_consolenew/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
if(user == connected.occupant)
return
- // update the ui if it exists, returns null if no ui is passed/found
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- // the ui does not exist, so we'll create a new() one
- // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "dna_modifier.tmpl", "DNA Modifier Console", 660, 700)
- // open the new ui window
+ ui = new(user, src, ui_key, "DNAModifier", name, 660, 700, master_ui, state)
ui.open()
- // auto update every Master Controller tick
- ui.set_auto_update(1)
-/obj/machinery/computer/scan_consolenew/ui_data(mob/user, datum/topic_state/state)
+/obj/machinery/computer/scan_consolenew/tgui_data(mob/user)
var/data[0]
data["selectedMenuKey"] = selected_menu_key
data["locked"] = connected.locked
@@ -498,9 +493,12 @@
for(var/datum/reagent/R in connected.beaker.reagents.reagent_list)
data["beakerVolume"] += R.volume
+ // Transfer modal information if there is one
+ data["modal"] = tgui_modal_data(src)
+
return data
-/obj/machinery/computer/scan_consolenew/Topic(href, href_list)
+/obj/machinery/computer/scan_consolenew/tgui_act(action, params)
if(..())
return FALSE // don't update uis
if(!istype(usr.loc, /turf))
@@ -509,405 +507,350 @@
return FALSE // don't update uis
if(irradiating) // Make sure that it isn't already irradiating someone...
return FALSE // don't update uis
+ if(stat & (NOPOWER|BROKEN))
+ return
add_fingerprint(usr)
- if(href_list["selectMenuKey"])
- selected_menu_key = href_list["selectMenuKey"]
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["toggleLock"])
- if((connected && connected.occupant))
- connected.locked = !(connected.locked)
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["pulseRadiation"])
- irradiating = radiation_duration
- var/lock_state = connected.locked
- connected.locked = TRUE //lock it
- SSnanoui.update_uis(src) // update all UIs attached to src
-
- sleep(10 * radiation_duration) // sleep for radiation_duration seconds
-
- irradiating = 0
- connected.locked = lock_state
-
- if(!connected.occupant)
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- var/radiation = (((radiation_intensity * 3) + radiation_duration * 3) / connected.damage_coeff)
- connected.occupant.apply_effect(radiation, IRRADIATE, 0)
- if(connected.radiation_check())
- return TRUE
-
- if(prob(95))
- if(prob(75))
- randmutb(connected.occupant)
- else
- randmuti(connected.occupant)
- else
- if(prob(95))
- randmutg(connected.occupant)
- else
- randmuti(connected.occupant)
-
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["radiationDuration"])
- if(text2num(href_list["radiationDuration"]) > 0)
- if(radiation_duration < 20)
- radiation_duration += 2
- else
- if(radiation_duration > 2)
- radiation_duration -= 2
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["radiationIntensity"])
- if(text2num(href_list["radiationIntensity"]) > 0)
- if(radiation_intensity < 10)
- radiation_intensity++
- else
- if(radiation_intensity > 1)
- radiation_intensity--
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- ////////////////////////////////////////////////////////
-
- if(href_list["changeUITarget"] && text2num(href_list["changeUITarget"]) > 0)
- if(selected_ui_target < 15)
- selected_ui_target++
- selected_ui_target_hex = selected_ui_target
- switch(selected_ui_target)
- if(10)
- selected_ui_target_hex = "A"
- if(11)
- selected_ui_target_hex = "B"
- if(12)
- selected_ui_target_hex = "C"
- if(13)
- selected_ui_target_hex = "D"
- if(14)
- selected_ui_target_hex = "E"
- if(15)
- selected_ui_target_hex = "F"
- else
- selected_ui_target = 0
- selected_ui_target_hex = 0
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["changeUITarget"] && text2num(href_list["changeUITarget"]) < 1)
- if(selected_ui_target > 0)
- selected_ui_target--
- selected_ui_target_hex = selected_ui_target
- switch(selected_ui_target)
- if(10)
- selected_ui_target_hex = "A"
- if(11)
- selected_ui_target_hex = "B"
- if(12)
- selected_ui_target_hex = "C"
- if(13)
- selected_ui_target_hex = "D"
- if(14)
- selected_ui_target_hex = "E"
- else
- selected_ui_target = 15
- selected_ui_target_hex = "F"
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["selectUIBlock"] && href_list["selectUISubblock"]) // This chunk of code updates selected block / sub-block based on click
- var/select_block = text2num(href_list["selectUIBlock"])
- var/select_subblock = text2num(href_list["selectUISubblock"])
- if((select_block <= DNA_UI_LENGTH) && (select_block >= 1))
- selected_ui_block = select_block
- if((select_subblock <= DNA_BLOCK_SIZE) && (select_subblock >= 1))
- selected_ui_subblock = select_subblock
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["pulseUIRadiation"])
- var/block = connected.occupant.dna.GetUISubBlock(selected_ui_block, selected_ui_subblock)
-
- irradiating = radiation_duration
- var/lock_state = connected.locked
- connected.locked = TRUE //lock it
- SSnanoui.update_uis(src) // update all UIs attached to src
-
- sleep(10 * radiation_duration) // sleep for radiation_duration seconds
-
- irradiating = 0
- connected.locked = lock_state
-
- if(!connected.occupant)
- return TRUE
-
- if(prob((80 + (radiation_duration / 2))))
- var/radiation = (radiation_intensity + radiation_duration)
- connected.occupant.apply_effect(radiation,IRRADIATE,0)
-
- if(connected.radiation_check())
- return TRUE
-
- block = miniscrambletarget(num2text(selected_ui_target), radiation_intensity, radiation_duration)
- connected.occupant.dna.SetUISubBlock(selected_ui_block, selected_ui_subblock, block)
- connected.occupant.UpdateAppearance()
- else
- var/radiation = ((radiation_intensity * 2) + radiation_duration)
- connected.occupant.apply_effect(radiation, IRRADIATE, 0)
- if(connected.radiation_check())
- return TRUE
-
- if(prob(20 + radiation_intensity))
- randmutb(connected.occupant)
- domutcheck(connected.occupant, connected)
- else
- randmuti(connected.occupant)
- connected.occupant.UpdateAppearance()
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- ////////////////////////////////////////////////////////
-
- if(href_list["injectRejuvenators"])
- if(!connected.occupant)
- return FALSE
- var/inject_amount = round(text2num(href_list["injectRejuvenators"]), 5) // round to nearest 5
- if(inject_amount < 0) // Since the user can actually type the commands himself, some sanity checking
- inject_amount = 0
- if(inject_amount > 50)
- inject_amount = 50
- connected.beaker.reagents.trans_to(connected.occupant, inject_amount)
- connected.beaker.reagents.reaction(connected.occupant)
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- ////////////////////////////////////////////////////////
-
- if(href_list["selectSEBlock"] && href_list["selectSESubblock"]) // This chunk of code updates selected block / sub-block based on click (se stands for strutural enzymes)
- var/select_block = text2num(href_list["selectSEBlock"])
- var/select_subblock = text2num(href_list["selectSESubblock"])
- if((select_block <= DNA_SE_LENGTH) && (select_block >= 1))
- selected_se_block = select_block
- if((select_subblock <= DNA_BLOCK_SIZE) && (select_subblock >= 1))
- selected_se_subblock = select_subblock
- //testing("User selected block [selected_se_block] (sent [select_block]), subblock [selected_se_subblock] (sent [select_block]).")
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["pulseSERadiation"])
- var/block = connected.occupant.dna.GetSESubBlock(selected_se_block, selected_se_subblock)
- //var/original_block=block
- //testing("Irradiating SE block [selected_se_block]:[selected_se_subblock] ([block])...")
-
- irradiating = radiation_duration
- var/lock_state = connected.locked
- connected.locked = TRUE //lock it
- SSnanoui.update_uis(src) // update all UIs attached to src
-
- sleep(10 * radiation_duration) // sleep for radiation_duration seconds
-
- irradiating = 0
- connected.locked = lock_state
-
- if(connected.occupant)
- if(prob((80 + ((radiation_duration / 2) + (connected.precision_coeff ** 3)))))
- var/radiation = ((radiation_intensity + radiation_duration) / connected.damage_coeff)
- connected.occupant.apply_effect(radiation, IRRADIATE, 0)
-
- if(connected.radiation_check())
- return 1
-
- var/real_SE_block=selected_se_block
- block = miniscramble(block, radiation_intensity, radiation_duration)
- if(prob(20))
- if(selected_se_block > 1 && selected_se_block < DNA_SE_LENGTH/2)
- real_SE_block++
- else if(selected_se_block > DNA_SE_LENGTH/2 && selected_se_block < DNA_SE_LENGTH)
- real_SE_block--
-
- //testing("Irradiated SE block [real_SE_block]:[selected_se_subblock] ([original_block] now [block]) [(real_SE_block!=selected_se_block) ? "(SHIFTED)":""]!")
- connected.occupant.dna.SetSESubBlock(real_SE_block, selected_se_subblock, block)
- domutcheck(connected.occupant, connected)
- else
- var/radiation = (((radiation_intensity * 2) + radiation_duration) / connected.damage_coeff)
- connected.occupant.apply_effect(radiation, IRRADIATE, 0)
-
- if(connected.radiation_check())
- return 1
-
- if(prob(80 - radiation_duration))
- //testing("Random bad mut!")
- randmutb(connected.occupant)
- domutcheck(connected.occupant, connected)
- else
- randmuti(connected.occupant)
- //testing("Random identity mut!")
- connected.occupant.UpdateAppearance()
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["ejectBeaker"])
- if(connected.beaker)
- var/obj/item/reagent_containers/glass/B = connected.beaker
- B.forceMove(connected.loc)
- connected.beaker = null
+ if(tgui_act_modal(action, params))
return TRUE
- if(href_list["ejectOccupant"])
- connected.eject_occupant(usr)
- return TRUE
-
- // Transfer Buffer Management
- if(href_list["bufferOption"])
- var/bufferOption = href_list["bufferOption"]
-
- // These bufferOptions do not require a bufferId
- if(bufferOption == "wipeDisk")
- if((isnull(disk)) || (disk.read_only))
- //temphtml = "Invalid disk. Please try again."
- return FALSE
-
- disk.buf = null
- //temphtml = "Data saved."
- return TRUE
-
- if(bufferOption == "ejectDisk")
- if(!disk)
+ . = TRUE
+ switch(action)
+ if("selectMenuKey")
+ var/key = params["key"]
+ if(!(key in list(PAGE_UI, PAGE_SE, PAGE_BUFFER, PAGE_REJUVENATORS)))
return
- disk.forceMove(get_turf(src))
- disk = null
- return TRUE
-
- // All bufferOptions from here on require a bufferId
- if(!href_list["bufferId"])
- return FALSE
-
- var/bufferId = text2num(href_list["bufferId"])
-
- if(bufferId < 1 || bufferId > 3)
- return FALSE // Not a valid buffer id
-
- if(bufferOption == "saveUI")
- if(connected.occupant && connected.occupant.dna)
- var/datum/dna2/record/databuf = new
- databuf.types = DNA2_BUF_UI // DNA2_BUF_UE
- databuf.dna = connected.occupant.dna.Clone()
- if(ishuman(connected.occupant))
- databuf.dna.real_name=connected.occupant.name
- databuf.name = "Unique Identifier"
- buffers[bufferId] = databuf
- return TRUE
-
- if(bufferOption == "saveUIAndUE")
- if(connected.occupant && connected.occupant.dna)
- var/datum/dna2/record/databuf = new
- databuf.types = DNA2_BUF_UI|DNA2_BUF_UE
- databuf.dna = connected.occupant.dna.Clone()
- if(ishuman(connected.occupant))
- databuf.dna.real_name=connected.occupant.dna.real_name
- databuf.name = "Unique Identifier + Unique Enzymes"
- buffers[bufferId] = databuf
- return TRUE
-
- if(bufferOption == "saveSE")
- if(connected.occupant && connected.occupant.dna)
- var/datum/dna2/record/databuf = new
- databuf.types = DNA2_BUF_SE
- databuf.dna = connected.occupant.dna.Clone()
- if(ishuman(connected.occupant))
- databuf.dna.real_name = connected.occupant.dna.real_name
- databuf.name = "Structural Enzymes"
- buffers[bufferId] = databuf
- return TRUE
-
- if(bufferOption == "clear")
- buffers[bufferId] = new /datum/dna2/record()
- return TRUE
-
- if(bufferOption == "changeLabel")
- var/datum/dna2/record/buf = buffers[bufferId]
- var/text = sanitize(input(usr, "New Label:", "Edit Label", buf.name) as text|null)
- buf.name = text
- buffers[bufferId] = buf
- return TRUE
-
- if(bufferOption == "transfer")
- if(!connected.occupant || (NOCLONE in connected.occupant.mutations && connected.scan_level < 3) || !connected.occupant.dna)
- return TRUE
-
- irradiating = 2
+ selected_menu_key = key
+ if("toggleLock")
+ if(connected && connected.occupant)
+ connected.locked = !(connected.locked)
+ if("pulseRadiation")
+ irradiating = radiation_duration
var/lock_state = connected.locked
connected.locked = TRUE //lock it
- SSnanoui.update_uis(src) // update all UIs attached to src
- sleep(2 SECONDS)
+ SStgui.update_uis(src)
+ sleep(10 * radiation_duration) // sleep for radiation_duration seconds
irradiating = 0
connected.locked = lock_state
- var/radiation = (rand(20,50) / connected.damage_coeff)
+ if(!connected.occupant)
+ return
+
+ var/radiation = (((radiation_intensity * 3) + radiation_duration * 3) / connected.damage_coeff)
connected.occupant.apply_effect(radiation, IRRADIATE, 0)
-
if(connected.radiation_check())
- return TRUE
+ return
- var/datum/dna2/record/buf = buffers[bufferId]
-
- if((buf.types & DNA2_BUF_UI))
- if((buf.types & DNA2_BUF_UE))
- connected.occupant.real_name = buf.dna.real_name
- connected.occupant.name = buf.dna.real_name
- connected.occupant.UpdateAppearance(buf.dna.UI.Copy())
- else if(buf.types & DNA2_BUF_SE)
- connected.occupant.dna.SE = buf.dna.SE.Copy()
- connected.occupant.dna.UpdateSE()
- domutcheck(connected.occupant, connected)
- return TRUE
-
- if(bufferOption == "createInjector")
- if(injector_ready && !waiting_for_user_input)
-
- var/success = 1
- var/obj/item/dnainjector/I = new /obj/item/dnainjector
- var/datum/dna2/record/buf = buffers[bufferId]
- buf = buf.copy()
- if(href_list["createBlockInjector"])
- waiting_for_user_input=1
- var/list/selectedbuf
- if(buf.types & DNA2_BUF_SE)
- selectedbuf=buf.dna.SE
- else
- selectedbuf=buf.dna.UI
- var/blk = input(usr,"Select Block","Block") as null|anything in all_dna_blocks(selectedbuf)
- success = setInjectorBlock(I,blk,buf)
+ if(prob(95))
+ if(prob(75))
+ randmutb(connected.occupant)
else
- I.buf = buf
- waiting_for_user_input = 0
- if(success)
- I.forceMove(loc)
- I.name += " ([buf.name])"
- if(connected)
- I.damage_coeff = connected.damage_coeff
- injector_ready = FALSE
- spawn(300)
- injector_ready = TRUE
- return TRUE
+ randmuti(connected.occupant)
+ else
+ if(prob(95))
+ randmutg(connected.occupant)
+ else
+ randmuti(connected.occupant)
+ if("radiationDuration")
+ radiation_duration = clamp(text2num(params["value"]), 1, 20)
+ if("radiationIntensity")
+ radiation_intensity = clamp(text2num(params["value"]), 1, 10)
+ ////////////////////////////////////////////////////////
+ if("changeUITarget")
+ selected_ui_target = clamp(text2num(params["value"]), 1, 15)
+ selected_ui_target_hex = num2text(selected_ui_target, 1, 16)
+ if("selectUIBlock") // This chunk of code updates selected block / sub-block based on click
+ var/select_block = text2num(params["block"])
+ var/select_subblock = text2num(params["subblock"])
+ if(!select_block || !select_subblock)
+ return
- if(bufferOption == "loadDisk")
- if((isnull(disk)) || (!disk.buf))
- //temphtml = "Invalid disk. Please try again."
- return FALSE
+ selected_ui_block = clamp(select_block, 1, DNA_UI_LENGTH)
+ selected_ui_subblock = clamp(select_subblock, 1, DNA_BLOCK_SIZE)
+ if("pulseUIRadiation")
+ var/block = connected.occupant.dna.GetUISubBlock(selected_ui_block, selected_ui_subblock)
- buffers[bufferId] = disk.buf.copy()
- //temphtml = "Data loaded."
- return TRUE
+ irradiating = radiation_duration
+ var/lock_state = connected.locked
+ connected.locked = TRUE //lock it
- if(bufferOption == "saveDisk")
- if((isnull(disk)) || (disk.read_only))
- //temphtml = "Invalid disk. Please try again."
- return FALSE
+ SStgui.update_uis(src)
+ sleep(10 * radiation_duration) // sleep for radiation_duration seconds
- var/datum/dna2/record/buf = buffers[bufferId]
+ irradiating = 0
+ connected.locked = lock_state
- disk.buf = buf.copy()
- disk.name = "data disk - '[buf.dna.real_name]'"
- //temphtml = "Data saved."
- return TRUE
+ if(!connected.occupant)
+ return
+ if(prob((80 + (radiation_duration / 2))))
+ var/radiation = (radiation_intensity + radiation_duration)
+ connected.occupant.apply_effect(radiation,IRRADIATE,0)
+
+ if(connected.radiation_check())
+ return
+
+ block = miniscrambletarget(num2text(selected_ui_target), radiation_intensity, radiation_duration)
+ connected.occupant.dna.SetUISubBlock(selected_ui_block, selected_ui_subblock, block)
+ connected.occupant.UpdateAppearance()
+ else
+ var/radiation = ((radiation_intensity * 2) + radiation_duration)
+ connected.occupant.apply_effect(radiation, IRRADIATE, 0)
+ if(connected.radiation_check())
+ return
+
+ if(prob(20 + radiation_intensity))
+ randmutb(connected.occupant)
+ domutcheck(connected.occupant, connected)
+ else
+ randmuti(connected.occupant)
+ connected.occupant.UpdateAppearance()
+ ////////////////////////////////////////////////////////
+ if("injectRejuvenators")
+ if(!connected.occupant || !connected.beaker)
+ return
+ var/inject_amount = clamp(round(text2num(params["amount"]), 5), 0, 50) // round to nearest 5 and clamp to 0-50
+ if(!inject_amount)
+ return
+ connected.beaker.reagents.trans_to(connected.occupant, inject_amount)
+ connected.beaker.reagents.reaction(connected.occupant)
+ ////////////////////////////////////////////////////////
+ if("selectSEBlock") // This chunk of code updates selected block / sub-block based on click (se stands for strutural enzymes)
+ var/select_block = text2num(params["block"])
+ var/select_subblock = text2num(params["subblock"])
+ if(!select_block || !select_subblock)
+ return
+
+ selected_se_block = clamp(select_block, 1, DNA_SE_LENGTH)
+ selected_se_subblock = clamp(select_subblock, 1, DNA_BLOCK_SIZE)
+ if("pulseSERadiation")
+ var/block = connected.occupant.dna.GetSESubBlock(selected_se_block, selected_se_subblock)
+ //var/original_block=block
+ //testing("Irradiating SE block [selected_se_block]:[selected_se_subblock] ([block])...")
+
+ irradiating = radiation_duration
+ var/lock_state = connected.locked
+ connected.locked = TRUE //lock it
+
+ SStgui.update_uis(src)
+ sleep(10 * radiation_duration) // sleep for radiation_duration seconds
+
+ irradiating = 0
+ connected.locked = lock_state
+
+ if(connected.occupant)
+ if(prob((80 + ((radiation_duration / 2) + (connected.precision_coeff ** 3)))))
+ var/radiation = ((radiation_intensity + radiation_duration) / connected.damage_coeff)
+ connected.occupant.apply_effect(radiation, IRRADIATE, 0)
+
+ if(connected.radiation_check())
+ return 1
+
+ var/real_SE_block=selected_se_block
+ block = miniscramble(block, radiation_intensity, radiation_duration)
+ if(prob(20))
+ if(selected_se_block > 1 && selected_se_block < DNA_SE_LENGTH/2)
+ real_SE_block++
+ else if(selected_se_block > DNA_SE_LENGTH/2 && selected_se_block < DNA_SE_LENGTH)
+ real_SE_block--
+
+ //testing("Irradiated SE block [real_SE_block]:[selected_se_subblock] ([original_block] now [block]) [(real_SE_block!=selected_se_block) ? "(SHIFTED)":""]!")
+ connected.occupant.dna.SetSESubBlock(real_SE_block, selected_se_subblock, block)
+ domutcheck(connected.occupant, connected)
+ else
+ var/radiation = (((radiation_intensity * 2) + radiation_duration) / connected.damage_coeff)
+ connected.occupant.apply_effect(radiation, IRRADIATE, 0)
+
+ if(connected.radiation_check())
+ return
+
+ if(prob(80 - radiation_duration))
+ //testing("Random bad mut!")
+ randmutb(connected.occupant)
+ domutcheck(connected.occupant, connected)
+ else
+ randmuti(connected.occupant)
+ //testing("Random identity mut!")
+ connected.occupant.UpdateAppearance()
+ if("ejectBeaker")
+ if(connected.beaker)
+ var/obj/item/reagent_containers/glass/B = connected.beaker
+ B.forceMove(connected.loc)
+ connected.beaker = null
+ if("ejectOccupant")
+ connected.eject_occupant()
+ // Transfer Buffer Management
+ if("bufferOption")
+ var/bufferOption = params["option"]
+ var/bufferId = text2num(params["id"])
+ if(bufferId < 1 || bufferId > 3) // Not a valid buffer id
+ return
+
+ var/datum/dna2/record/buffer = buffers[bufferId]
+ switch(bufferOption)
+ if("saveUI")
+ if(connected.occupant && connected.occupant.dna)
+ var/datum/dna2/record/databuf = new
+ databuf.types = DNA2_BUF_UI // DNA2_BUF_UE
+ databuf.dna = connected.occupant.dna.Clone()
+ if(ishuman(connected.occupant))
+ databuf.dna.real_name=connected.occupant.name
+ databuf.name = "Unique Identifier"
+ buffers[bufferId] = databuf
+ if("saveUIAndUE")
+ if(connected.occupant && connected.occupant.dna)
+ var/datum/dna2/record/databuf = new
+ databuf.types = DNA2_BUF_UI|DNA2_BUF_UE
+ databuf.dna = connected.occupant.dna.Clone()
+ if(ishuman(connected.occupant))
+ databuf.dna.real_name=connected.occupant.dna.real_name
+ databuf.name = "Unique Identifier + Unique Enzymes"
+ buffers[bufferId] = databuf
+ if("saveSE")
+ if(connected.occupant && connected.occupant.dna)
+ var/datum/dna2/record/databuf = new
+ databuf.types = DNA2_BUF_SE
+ databuf.dna = connected.occupant.dna.Clone()
+ if(ishuman(connected.occupant))
+ databuf.dna.real_name = connected.occupant.dna.real_name
+ databuf.name = "Structural Enzymes"
+ buffers[bufferId] = databuf
+ if("clear")
+ buffers[bufferId] = new /datum/dna2/record()
+ if("changeLabel")
+ tgui_modal_input(src, "changeBufferLabel", "Please enter the new buffer label:", null, list("id" = bufferId), buffer.name, TGUI_MODAL_INPUT_MAX_LENGTH_NAME)
+ if("transfer")
+ if(!connected.occupant || (NOCLONE in connected.occupant.mutations && connected.scan_level < 3) || !connected.occupant.dna)
+ return
+
+ irradiating = 2
+ var/lock_state = connected.locked
+ connected.locked = TRUE //lock it
+
+ SStgui.update_uis(src)
+ sleep(2 SECONDS)
+
+ irradiating = 0
+ connected.locked = lock_state
+
+ var/radiation = (rand(20,50) / connected.damage_coeff)
+ connected.occupant.apply_effect(radiation, IRRADIATE, 0)
+
+ if(connected.radiation_check())
+ return
+
+ var/datum/dna2/record/buf = buffers[bufferId]
+
+ if((buf.types & DNA2_BUF_UI))
+ if((buf.types & DNA2_BUF_UE))
+ connected.occupant.real_name = buf.dna.real_name
+ connected.occupant.name = buf.dna.real_name
+ connected.occupant.UpdateAppearance(buf.dna.UI.Copy())
+ else if(buf.types & DNA2_BUF_SE)
+ connected.occupant.dna.SE = buf.dna.SE.Copy()
+ connected.occupant.dna.UpdateSE()
+ domutcheck(connected.occupant, connected)
+ if("createInjector")
+ if(!injector_ready)
+ return
+ if(text2num(params["block"]) > 0)
+ var/list/choices = all_dna_blocks((buffer.types & DNA2_BUF_SE) ? buffer.dna.SE : buffer.dna.UI)
+ tgui_modal_choice(src, "createInjectorBlock", "Please select the block to create an injector from:", null, list("id" = bufferId), null, choices)
+ else
+ create_injector(bufferId, TRUE)
+ if("loadDisk")
+ if(isnull(disk) || disk.read_only)
+ return
+ buffers[bufferId] = disk.buf.copy()
+ if("saveDisk")
+ if(isnull(disk) || disk.read_only)
+ return
+ var/datum/dna2/record/buf = buffers[bufferId]
+ disk.buf = buf.copy()
+ disk.name = "data disk - '[buf.dna.real_name]'"
+ if("wipeDisk")
+ if(isnull(disk) || disk.read_only)
+ return
+ disk.buf = null
+ if("ejectDisk")
+ if(!disk)
+ return
+ disk.forceMove(get_turf(src))
+ disk = null
+
+/**
+ * Creates a blank injector with the name of the buffer at the given buffer_id
+ *
+ * Arguments:
+ * * buffer_id - The ID of the buffer
+ * * copy_buffer - Whether the injector should copy the buffer contents
+ */
+/obj/machinery/computer/scan_consolenew/proc/create_injector(buffer_id, copy_buffer = FALSE)
+ if(buffer_id < 1 || buffer_id > length(buffers))
+ return
+
+ // Cooldown
+ injector_ready = FALSE
+ addtimer(CALLBACK(src, .proc/injector_cooldown_finish), 30 SECONDS)
+
+ // Create it
+ var/datum/dna2/record/buf = buffers[buffer_id]
+ var/obj/item/dnainjector/I = new()
+ I.forceMove(loc)
+ I.name += " ([buf.name])"
+ if(copy_buffer)
+ I.buf = buf.copy()
+ if(connected)
+ I.damage_coeff = connected.damage_coeff
+ return I
+
+/**
+ * Called when the injector creation cooldown finishes
+ */
+/obj/machinery/computer/scan_consolenew/proc/injector_cooldown_finish()
+ injector_ready = TRUE
+
+/**
+ * Called in tgui_act() to process modal actions
+ *
+ * Arguments:
+ * * action - The action passed by tgui
+ * * params - The params passed by tgui
+ */
+/obj/machinery/computer/scan_consolenew/proc/tgui_act_modal(action, params)
+ . = TRUE
+ var/id = params["id"] // The modal's ID
+ var/list/arguments = istext(params["arguments"]) ? json_decode(params["arguments"]) : params["arguments"]
+ switch(tgui_modal_act(src, action, params))
+ if(TGUI_MODAL_ANSWER)
+ var/answer = params["answer"]
+ switch(id)
+ if("createInjectorBlock")
+ var/buffer_id = text2num(arguments["id"])
+ if(buffer_id < 1 || buffer_id > length(buffers))
+ return
+ var/datum/dna2/record/buf = buffers[buffer_id]
+ var/obj/item/dnainjector/I = create_injector(buffer_id)
+ setInjectorBlock(I, answer, buf.copy())
+ if("changeBufferLabel")
+ var/buffer_id = text2num(arguments["id"])
+ if(buffer_id < 1 || buffer_id > length(buffers))
+ return
+ var/datum/dna2/record/buf = buffers[buffer_id]
+ buf.name = answer
+ buffers[buffer_id] = buf
+ else
+ return FALSE
+ else
+ return FALSE
+
+
+#undef PAGE_UI
+#undef PAGE_SE
+#undef PAGE_BUFFER
+#undef PAGE_REJUVENATORS
/////////////////////////// DNA MACHINES
diff --git a/code/game/dna/genes/disabilities.dm b/code/game/dna/genes/disabilities.dm
index 8b8a86009b9..a887959db48 100644
--- a/code/game/dna/genes/disabilities.dm
+++ b/code/game/dna/genes/disabilities.dm
@@ -249,14 +249,13 @@
block = GLOB.wingdingsblock
/datum/dna/gene/disability/wingdings/OnSay(mob/M, message)
- var/list/chars = string2charlist(message)
var/garbled_message = ""
- for(var/C in chars)
- if(C in GLOB.alphabet_uppercase)
+ for(var/i in 1 to length(message))
+ if(message[i] in GLOB.alphabet_uppercase)
garbled_message += pick(GLOB.alphabet_uppercase)
- else if(C in GLOB.alphabet)
+ else if(message[i] in GLOB.alphabet)
garbled_message += pick(GLOB.alphabet)
else
- garbled_message += C
+ garbled_message += message[i]
message = garbled_message
return message
diff --git a/code/game/dna/genes/goon_powers.dm b/code/game/dna/genes/goon_powers.dm
index 87bbdf97c5c..77eb58e84b7 100644
--- a/code/game/dna/genes/goon_powers.dm
+++ b/code/game/dna/genes/goon_powers.dm
@@ -123,13 +123,13 @@
instability = GENE_INSTABILITY_MODERATE
mutation = CRYO
- spelltype = /obj/effect/proc_holder/spell/targeted/cryokinesis
+ spelltype = /obj/effect/proc_holder/spell/targeted/click/cryokinesis
/datum/dna/gene/basic/grant_spell/cryo/New()
..()
block = GLOB.cryoblock
-/obj/effect/proc_holder/spell/targeted/cryokinesis
+/obj/effect/proc_holder/spell/targeted/click/cryokinesis
name = "Cryokinesis"
desc = "Drops the bodytemperature of another person."
panel = "Abilities"
@@ -137,45 +137,44 @@
charge_type = "recharge"
charge_max = 1200
- clothes_req = 0
- stat_allowed = 0
+ clothes_req = FALSE
+ stat_allowed = FALSE
+
+ click_radius = 0
+ auto_target_single = FALSE // Give the clueless geneticists a way out and to have them not target themselves
+ selection_activated_message = "Your mind grow cold. Click on a target to cast the spell."
+ selection_deactivated_message = "Your mind returns to normal."
+ allowed_type = /mob/living/carbon
invocation_type = "none"
range = 7
selection_type = "range"
- include_user = 1
+ include_user = TRUE
var/list/compatible_mobs = list(/mob/living/carbon/human)
action_icon_state = "genetic_cryo"
-/obj/effect/proc_holder/spell/targeted/cryokinesis/cast(list/targets, mob/user = usr)
- if(!targets.len)
- to_chat(user, "No target found in range.")
- return
+/obj/effect/proc_holder/spell/targeted/click/cryokinesis/cast(list/targets, mob/user = usr)
var/mob/living/carbon/C = targets[1]
- if(!iscarbon(C))
- to_chat(user, "This will only work on normal organic beings.")
- return
-
if(COLDRES in C.mutations)
C.visible_message("A cloud of fine ice crystals engulfs [C.name], but disappears almost instantly!")
return
- var/handle_suit = 0
+ var/handle_suit = FALSE
if(ishuman(C))
var/mob/living/carbon/human/H = C
if(istype(H.head, /obj/item/clothing/head/helmet/space))
if(istype(H.wear_suit, /obj/item/clothing/suit/space))
- handle_suit = 1
+ handle_suit = TRUE
if(H.internal)
H.visible_message("[user] sprays a cloud of fine ice crystals, engulfing [H]!",
"[user] sprays a cloud of fine ice crystals over your [H.head]'s visor.")
- add_attack_logs(user, C, "Cryokinesis")
else
H.visible_message("[user] sprays a cloud of fine ice crystals engulfing, [H]!",
"[user] sprays a cloud of fine ice crystals cover your [H.head]'s visor and make it into your air vents!.")
- add_attack_logs(user, C, "Cryokinesis")
+
H.bodytemperature = max(0, H.bodytemperature - 100)
+ add_attack_logs(user, C, "Cryokinesis")
if(!handle_suit)
C.bodytemperature = max(0, C.bodytemperature - 200)
C.ExtinguishMob()
@@ -454,7 +453,7 @@
name = "Polymorphism"
desc = "Enables the subject to reconfigure their appearance to mimic that of others."
- spelltype =/obj/effect/proc_holder/spell/targeted/polymorph
+ spelltype =/obj/effect/proc_holder/spell/targeted/click/polymorph
//cooldown = 1800
activation_messages = list("You don't feel entirely like yourself somehow.")
deactivation_messages = list("You feel secure in your identity.")
@@ -465,34 +464,36 @@
..()
block = GLOB.polymorphblock
-/obj/effect/proc_holder/spell/targeted/polymorph
+/obj/effect/proc_holder/spell/targeted/click/polymorph
name = "Polymorph"
desc = "Mimic the appearance of others!"
panel = "Abilities"
charge_max = 1800
- clothes_req = 0
- human_req = 1
- stat_allowed = 0
+ clothes_req = FALSE
+ stat_allowed = FALSE
+
+ click_radius = -1 // Precision required
+ auto_target_single = FALSE // Safety to not turn into monkey (420)
+ selection_activated_message = "You body becomes unstable. Click on a target to cast transform into them."
+ selection_deactivated_message = "Your body calms down again."
+ allowed_type = /mob/living/carbon/human
+
invocation_type = "none"
range = 1
selection_type = "range"
action_icon_state = "genetic_poly"
-/obj/effect/proc_holder/spell/targeted/polymorph/cast(list/targets, mob/user = usr)
- var/mob/living/M = targets[1]
- if(!ishuman(M))
- to_chat(usr, "You can only change your appearance to that of another human.")
- return
+/obj/effect/proc_holder/spell/targeted/click/polymorph/cast(list/targets, mob/user = usr)
+ var/mob/living/carbon/human/target = targets[1]
user.visible_message("[user]'s body shifts and contorts.")
spawn(10)
- if(M && user)
+ if(target && user)
playsound(user.loc, 'sound/goonstation/effects/gib.ogg', 50, 1)
var/mob/living/carbon/human/H = user
- var/mob/living/carbon/human/target = M
H.UpdateAppearance(target.dna.UI)
H.real_name = target.real_name
H.name = target.name
diff --git a/code/game/dna/genes/vg_powers.dm b/code/game/dna/genes/vg_powers.dm
index 9092448e398..e08d35b681d 100644
--- a/code/game/dna/genes/vg_powers.dm
+++ b/code/game/dna/genes/vg_powers.dm
@@ -214,25 +214,21 @@
/obj/effect/proc_holder/spell/targeted/remotetalk/choose_targets(mob/user = usr)
var/list/targets = new /list()
- var/list/validtargets = new /list()
- var/turf/T = get_turf(user)
- for(var/mob/living/M in range(14, T))
- if(M && M.mind)
- if(M == user)
- continue
- validtargets += M
+ var/list/validtargets = user.get_telepathic_targets()
- if(!validtargets.len)
+ if(!length(validtargets))
to_chat(user, "There are no valid targets!")
start_recharge()
return
- targets += input("Choose the target to talk to.", "Targeting") as null|mob in validtargets
+ var/target_name = input("Choose the target to talk to.", "Targeting") as null|anything in validtargets
- if(!targets.len || !targets[1]) //doesn't waste the spell
+ var/mob/living/target
+ if(!target_name || !(target = validtargets[target_name]))
revert_cast(user)
return
+ targets += target
perform(targets, user = user)
/obj/effect/proc_holder/spell/targeted/remotetalk/cast(list/targets, mob/user = usr)
@@ -249,7 +245,7 @@
target.show_message("You hear [user.real_name]'s voice: [say]")
else
target.show_message("You hear a voice that seems to echo around the room: [say]")
- user.show_message("You project your mind into [target.name]: [say]")
+ user.show_message("You project your mind into [(target in user.get_visible_mobs()) ? target.name : "the unknown entity"]: [say]")
for(var/mob/dead/observer/G in GLOB.player_list)
G.show_message("Telepathic message from [user] ([ghost_follow_link(user, ghost=G)]) to [target] ([ghost_follow_link(target, ghost=G)]): [say]")
@@ -266,26 +262,22 @@
var/list/available_targets = list()
/obj/effect/proc_holder/spell/targeted/mindscan/choose_targets(mob/user = usr)
- var/list/targets = new /list()
- var/list/validtargets = new /list()
- var/turf/T = get_turf(user)
- for(var/mob/living/M in range(14, T))
- if(M && M.mind)
- if(M == user)
- continue
- validtargets += M
+ var/list/targets = list()
+ var/list/validtargets = user.get_telepathic_targets()
- if(!validtargets.len)
+ if(!length(validtargets))
to_chat(user, "There are no valid targets!")
start_recharge()
return
- targets += input("Choose the target to listen to.", "Targeting") as null|mob in validtargets
+ var/target_name = input("Choose the target to listen to.", "Targeting") as null|anything in validtargets
- if(!targets.len || !targets[1]) //doesn't waste the spell
+ var/mob/living/target
+ if(!target_name || !(target = validtargets[target_name]))
revert_cast(user)
return
+ targets += target
perform(targets, user = user)
/obj/effect/proc_holder/spell/targeted/mindscan/cast(list/targets, mob/user = usr)
@@ -295,7 +287,7 @@
var/message = "You feel your mind expand briefly... (Click to send a message.)"
if(REMOTE_TALK in target.mutations)
message = "You feel [user.real_name] request a response from you... (Click here to project mind.)"
- user.show_message("You offer your mind to [target.name].")
+ user.show_message("You offer your mind to [(target in user.get_visible_mobs()) ? target.name : "the unknown entity"].")
target.show_message("[message]")
available_targets += target
addtimer(CALLBACK(src, .proc/removeAvailability, target), 100)
diff --git a/code/game/gamemodes/blob/blob_report.dm b/code/game/gamemodes/blob/blob_report.dm
index 9c7fb99fe81..93b84dbb54d 100644
--- a/code/game/gamemodes/blob/blob_report.dm
+++ b/code/game/gamemodes/blob/blob_report.dm
@@ -40,7 +40,7 @@
aiPlayer.set_zeroth_law(law)
to_chat(aiPlayer, "Laws Updated: [law]")
- print_command_report(intercepttext, interceptname)
+ print_command_report(intercepttext, interceptname, FALSE)
GLOB.event_announcement.Announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", 'sound/AI/commandreport.ogg', from = "[command_name()] Update")
/datum/station_state
diff --git a/code/game/gamemodes/blob/blobs/blob_mobs.dm b/code/game/gamemodes/blob/blobs/blob_mobs.dm
index 46b1972e565..34c717f11a8 100644
--- a/code/game/gamemodes/blob/blobs/blob_mobs.dm
+++ b/code/game/gamemodes/blob/blobs/blob_mobs.dm
@@ -7,6 +7,7 @@
/mob/living/simple_animal/hostile/blob
icon = 'icons/mob/blob.dmi'
pass_flags = PASSBLOB
+ status_flags = NONE //No throwing blobspores into deep space to despawn, or throwing blobbernaughts, which are bigger than you.
faction = list(ROLE_BLOB)
bubble_icon = "blob"
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
@@ -49,6 +50,7 @@
environment_smash = ENVIRONMENT_SMASH_STRUCTURES
attacktext = "hits"
attack_sound = 'sound/weapons/genhit1.ogg'
+ flying = TRUE
speak_emote = list("pulses")
var/obj/structure/blob/factory/factory = null
var/list/human_overlays = list()
diff --git a/code/game/gamemodes/blob/blobs/core.dm b/code/game/gamemodes/blob/blobs/core.dm
index be794b9092b..dc8d0f5aba2 100644
--- a/code/game/gamemodes/blob/blobs/core.dm
+++ b/code/game/gamemodes/blob/blobs/core.dm
@@ -17,12 +17,12 @@
START_PROCESSING(SSobj, src)
GLOB.poi_list |= src
adjustcolors(color) //so it atleast appears
- if(!overmind)
- create_overmind(new_overmind)
- if(overmind)
- adjustcolors(overmind.blob_reagent_datum.color)
if(offspring)
is_offspring = 1
+ if(overmind)
+ adjustcolors(overmind.blob_reagent_datum.color)
+ if(!overmind)
+ create_overmind(new_overmind)
point_rate = new_rate
..(loc, h)
@@ -104,10 +104,11 @@
var/mob/C = null
var/list/candidates = list()
if(!new_overmind)
+ // sendit
if(is_offspring)
- candidates = pollCandidates("Do you want to play as a blob offspring?", ROLE_BLOB, 1)
+ candidates = SSghost_spawns.poll_candidates("Do you want to play as a blob offspring?", ROLE_BLOB, TRUE, source = src)
else
- candidates = pollCandidates("Do you want to play as a blob?", ROLE_BLOB, 1)
+ candidates = SSghost_spawns.poll_candidates("Do you want to play as a blob?", ROLE_BLOB, TRUE, source = src)
if(length(candidates))
C = pick(candidates)
diff --git a/code/game/gamemodes/blob/powers.dm b/code/game/gamemodes/blob/powers.dm
index 6f2eed39499..350da7c7593 100644
--- a/code/game/gamemodes/blob/powers.dm
+++ b/code/game/gamemodes/blob/powers.dm
@@ -237,7 +237,7 @@
blobber.AIStatus = AI_OFF
blobber.LoseTarget()
spawn()
- var/list/candidates = pollCandidates("Do you want to play as a blobbernaut?", ROLE_BLOB, 1, 100)
+ var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a blobbernaut?", ROLE_BLOB, TRUE, 10 SECONDS, source = blobber)
if(candidates.len)
var/mob/C = pick(candidates)
if(C)
@@ -389,7 +389,7 @@
return
split_used = TRUE
- new /obj/structure/blob/core/ (get_turf(N), 200, null, blob_core.point_rate, "offspring")
+ new /obj/structure/blob/core/ (get_turf(N), 200, null, blob_core.point_rate, offspring = TRUE)
qdel(N)
if(SSticker && SSticker.mode.name == "blob")
diff --git a/code/game/gamemodes/changeling/evolution_menu.dm b/code/game/gamemodes/changeling/evolution_menu.dm
index 9c33ffaaca5..b3b4000bbf1 100644
--- a/code/game/gamemodes/changeling/evolution_menu.dm
+++ b/code/game/gamemodes/changeling/evolution_menu.dm
@@ -370,16 +370,11 @@ GLOBAL_LIST_EMPTY(sting_paths)
mind.changeling.purchasedpowers += path
path.on_purchase(src)
else //for respec
- var/datum/action/changeling/hivemind_upload/S1 = new
+ var/datum/action/changeling/hivemind_pick/S1 = new
if(!mind.changeling.has_sting(S1))
mind.changeling.purchasedpowers+=S1
S1.Grant(src)
- var/datum/action/changeling/hivemind_download/S2 = new
- if(!mind.changeling.has_sting(S2))
- mind.changeling.purchasedpowers+=S2
- S2.Grant(src)
-
var/mob/living/carbon/C = src //only carbons have dna now, so we have to typecaste
mind.changeling.absorbed_dna |= C.dna.Clone()
mind.changeling.trim_dna()
diff --git a/code/game/gamemodes/changeling/powers/hivemind.dm b/code/game/gamemodes/changeling/powers/hivemind.dm
index 98336608da4..18a5477fd3f 100644
--- a/code/game/gamemodes/changeling/powers/hivemind.dm
+++ b/code/game/gamemodes/changeling/powers/hivemind.dm
@@ -12,27 +12,36 @@
var/datum/changeling/changeling=user.mind.changeling
changeling.changeling_speak = 1
to_chat(user, "Use say \":g message\" to communicate with the other changelings.")
- var/datum/action/changeling/hivemind_upload/S1 = new
+ var/datum/action/changeling/hivemind_pick/S1 = new
if(!changeling.has_sting(S1))
changeling.purchasedpowers+=S1
S1.Grant(user)
- var/datum/action/changeling/hivemind_download/S2 = new
- if(!changeling.has_sting(S2))
- S2.Grant(user)
- changeling.purchasedpowers+=S2
return
// HIVE MIND UPLOAD/DOWNLOAD DNA
GLOBAL_LIST_EMPTY(hivemind_bank)
-/datum/action/changeling/hivemind_upload
+/datum/action/changeling/hivemind_pick
name = "Hive Channel DNA"
- desc = "Allows us to channel DNA in the airwaves to allow other changelings to absorb it. Costs 10 chemicals."
- button_icon_state = "hivemind_channel"
+ desc = "Allows us to upload or absorb DNA in the airwaves. Does not count towards absorb objectives. Costs 10 chemicals."
+ button_icon_state = "hive_absorb"
chemical_cost = 10
dna_cost = -1
-/datum/action/changeling/hivemind_upload/sting_action(var/mob/user)
+/datum/action/changeling/hivemind_pick/sting_action(mob/user)
+ var/datum/changeling/changeling = user.mind.changeling
+ var/channel_pick = alert("Upload or Absorb DNA?", "Channel Select", "Upload", "Absorb")
+
+ if(channel_pick == "Upload")
+ dna_upload(user)
+ if(channel_pick == "Absorb")
+ if(changeling.using_stale_dna(user))//If our current DNA is the stalest, we gotta ditch it.
+ to_chat(user, "We have reached our capacity to store genetic information! We must transform before absorbing more.")
+ return
+ else
+ dna_absorb(user)
+
+/datum/action/changeling/proc/dna_upload(mob/user)
var/datum/changeling/changeling = user.mind.changeling
var/list/names = list()
for(var/datum/dna/DNA in (changeling.absorbed_dna+changeling.protected_dna))
@@ -56,23 +65,7 @@ GLOBAL_LIST_EMPTY(hivemind_bank)
feedback_add_details("changeling_powers","HU")
return 1
-/datum/action/changeling/hivemind_download
- name = "Hive Absorb DNA"
- desc = "Allows us to absorb DNA that has been channeled to the airwaves. Does not count towards absorb objectives. Costs 10 chemicals."
- button_icon_state = "hive_absorb"
- chemical_cost = 10
- dna_cost = -1
-
-/datum/action/changeling/hivemind_download/can_sting(var/mob/living/carbon/user)
- if(!..())
- return
- var/datum/changeling/changeling = user.mind.changeling
- if(changeling.using_stale_dna(user))//If our current DNA is the stalest, we gotta ditch it.
- to_chat(user, "We have reached our capacity to store genetic information! We must transform before absorbing more.")
- return
- return 1
-
-/datum/action/changeling/hivemind_download/sting_action(var/mob/user)
+/datum/action/changeling/proc/dna_absorb(mob/user)
var/datum/changeling/changeling = user.mind.changeling
var/list/names = list()
for(var/datum/dna/DNA in GLOB.hivemind_bank)
diff --git a/code/game/gamemodes/changeling/powers/mutations.dm b/code/game/gamemodes/changeling/powers/mutations.dm
index d12b970d884..be88e0bb6e0 100644
--- a/code/game/gamemodes/changeling/powers/mutations.dm
+++ b/code/game/gamemodes/changeling/powers/mutations.dm
@@ -221,6 +221,7 @@
projectile_type = /obj/item/projectile/tentacle
caliber = "tentacle"
icon_state = "tentacle_end"
+ muzzle_flash_effect = null
var/obj/item/gun/magic/tentacle/gun //the item that shot it
/obj/item/ammo_casing/magic/tentacle/New(obj/item/gun/magic/tentacle/tentacle_gun)
diff --git a/code/game/gamemodes/changeling/powers/shriek.dm b/code/game/gamemodes/changeling/powers/shriek.dm
index 867328c40b3..06332f61670 100644
--- a/code/game/gamemodes/changeling/powers/shriek.dm
+++ b/code/game/gamemodes/changeling/powers/shriek.dm
@@ -11,6 +11,10 @@
/datum/action/changeling/resonant_shriek/sting_action(var/mob/user)
for(var/mob/living/M in get_mobs_in_view(4, user))
if(iscarbon(M))
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ if(H.check_ear_prot() >= HEARING_PROTECTION_TOTAL)
+ continue
if(!M.mind || !M.mind.changeling)
M.MinimumDeafTicks(30)
M.AdjustConfused(20)
diff --git a/code/game/gamemodes/changeling/powers/swap_form.dm b/code/game/gamemodes/changeling/powers/swap_form.dm
index 0e02df12d88..97192c86a0f 100644
--- a/code/game/gamemodes/changeling/powers/swap_form.dm
+++ b/code/game/gamemodes/changeling/powers/swap_form.dm
@@ -18,12 +18,15 @@
if((NOCLONE || SKELETON || HUSK) in target.mutations)
to_chat(user, "DNA of [target] is ruined beyond usability!")
return
- if(!istype(target) || issmall(target) || (NO_DNA in target.dna.species.species_traits))
+ if(!istype(target) || !target.mind || issmall(target) || (NO_DNA in target.dna.species.species_traits))
to_chat(user, "[target] is not compatible with this ability.")
return
if(target.mind.changeling)
to_chat(user, "We are unable to swap forms with another changeling!")
return
+ if(target.has_brain_worms() || user.has_brain_worms())
+ to_chat(user, "A foreign presence repels us from this body!")
+ return
return 1
/datum/action/changeling/swap_form/sting_action(var/mob/living/carbon/user)
@@ -63,6 +66,8 @@
target.add_language("Changeling")
user.remove_language("Changeling")
user.regenerate_icons()
+ if(target.stat == DEAD && target.suiciding) //If Target committed suicide, unset flag for User
+ target.suiciding = 0
for(var/power in lingpowers)
var/datum/action/changeling/S = power
diff --git a/code/game/gamemodes/changeling/powers/tiny_prick.dm b/code/game/gamemodes/changeling/powers/tiny_prick.dm
index fbfb75b8b6a..9bc9fc7b511 100644
--- a/code/game/gamemodes/changeling/powers/tiny_prick.dm
+++ b/code/game/gamemodes/changeling/powers/tiny_prick.dm
@@ -112,7 +112,7 @@
feedback_add_details("changeling_powers","TS")
return TRUE
-datum/action/changeling/sting/extract_dna
+/datum/action/changeling/sting/extract_dna
name = "Extract DNA Sting"
desc = "We stealthily sting a target and extract their DNA. Costs 25 chemicals."
helptext = "Will give you the DNA of your target, allowing you to transform into them."
@@ -132,7 +132,7 @@ datum/action/changeling/sting/extract_dna
feedback_add_details("changeling_powers","ED")
return 1
-datum/action/changeling/sting/mute
+/datum/action/changeling/sting/mute
name = "Mute Sting"
desc = "We silently sting a human, completely silencing them for a short time. Costs 20 chemicals."
helptext = "Does not provide a warning to the victim that they have been stung, until they try to speak and cannot."
@@ -147,7 +147,7 @@ datum/action/changeling/sting/mute
feedback_add_details("changeling_powers","MS")
return 1
-datum/action/changeling/sting/blind
+/datum/action/changeling/sting/blind
name = "Blind Sting"
desc = "We temporarily blind our victim. Costs 25 chemicals."
helptext = "This sting completely blinds a target for a short time, and leaves them with blurred vision for a long time."
@@ -165,7 +165,7 @@ datum/action/changeling/sting/blind
feedback_add_details("changeling_powers","BS")
return 1
-datum/action/changeling/sting/LSD
+/datum/action/changeling/sting/LSD
name = "Hallucination Sting"
desc = "We cause mass terror to our victim. Costs 10 chemicals."
helptext = "We evolve the ability to sting a target with a powerful hallucinogenic chemical. The target does not notice they have been stung, and the effect occurs after 30 to 60 seconds."
@@ -182,7 +182,7 @@ datum/action/changeling/sting/LSD
feedback_add_details("changeling_powers","HS")
return 1
-datum/action/changeling/sting/cryo //Enable when mob cooling is fixed so that frostoil actually makes you cold, instead of mostly just hungry.
+/datum/action/changeling/sting/cryo //Enable when mob cooling is fixed so that frostoil actually makes you cold, instead of mostly just hungry.
name = "Cryogenic Sting"
desc = "We silently sting our victim with a cocktail of chemicals that freezes them from the inside. Costs 15 chemicals."
helptext = "Does not provide a warning to the victim, though they will likely realize they are suddenly freezing."
diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm
index bf5fbb70731..433fc5b526a 100644
--- a/code/game/gamemodes/cult/cult.dm
+++ b/code/game/gamemodes/cult/cult.dm
@@ -20,8 +20,8 @@ GLOBAL_LIST_EMPTY(all_cults)
var/mob/living/carbon/human/H = mind.current
if(ismindshielded(H)) //mindshield protects against conversions unless removed
return FALSE
-// if(mind.offstation_role) cant convert offstation roles such as ghost spawns
-// return FALSE Commented out until we can figure out why offstation_role is getting set to TRUE on normal crew
+ if(mind.offstation_role)
+ return FALSE
if(issilicon(mind.current))
return FALSE //can't convert machines, that's ratvar's thing
if(isguardian(mind.current))
@@ -231,6 +231,8 @@ GLOBAL_LIST_EMPTY(all_cults)
/datum/game_mode/cult/proc/get_unconvertables()
var/list/ucs = list()
for(var/mob/living/carbon/human/player in GLOB.player_list)
+ if(player.mind && player.mind.offstation_role)
+ continue
if(!is_convertable_to_cult(player.mind))
ucs += player.mind
return ucs
diff --git a/code/game/gamemodes/cult/cult_items.dm b/code/game/gamemodes/cult/cult_items.dm
index 388b16d71af..a68e4c1e9ca 100644
--- a/code/game/gamemodes/cult/cult_items.dm
+++ b/code/game/gamemodes/cult/cult_items.dm
@@ -140,6 +140,7 @@
body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS
allowed = list(/obj/item/tome,/obj/item/melee/cultblade)
var/current_charges = 3
+ var/shield_state = "shield-cult"
hoodtype = /obj/item/clothing/head/hooded/cult_hoodie
/obj/item/clothing/head/hooded/cult_hoodie
@@ -164,33 +165,49 @@
if(current_charges)
owner.visible_message("\The [attack_text] is deflected in a burst of blood-red sparks!")
current_charges--
+ playsound(loc, "sparks", 100, 1)
new /obj/effect/temp_visual/cult/sparks(get_turf(owner))
if(!current_charges)
owner.visible_message("The runed shield around [owner] suddenly disappears!")
+ shield_state = "broken"
owner.update_inv_wear_suit()
return 1
return 0
-/obj/item/clothing/suit/hooded/cultrobes/berserker
+/obj/item/clothing/suit/hooded/cultrobes/cult_shield/special_overlays()
+ return mutable_appearance('icons/effects/cult_effects.dmi', shield_state, MOB_LAYER + 0.01)
+
+/obj/item/clothing/suit/hooded/cultrobes/flagellant_robe
name = "flagellant's robes"
desc = "Blood-soaked robes infused with dark magic; allows the user to move at inhuman speeds, but at the cost of increased damage."
- icon_state = "hardsuit-berserker"
- item_state = "hardsuit-berserker"
+ icon_state = "flagellantrobe"
+ item_state = "flagellantrobe"
flags_inv = HIDEJUMPSUIT
allowed = list(/obj/item/tome,/obj/item/melee/cultblade)
body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS
armor = list("melee" = -45, "bullet" = -45, "laser" = -45,"energy" = -45, "bomb" = -45, "bio" = -45, "rad" = -45, "fire" = 0, "acid" = 0)
slowdown = -1
- hoodtype = /obj/item/clothing/head/hooded/berserkerhood
+ sprite_sheets = list(
+ "Vox" = 'icons/mob/species/vox/suit.dmi',
+ "Drask" = 'icons/mob/species/drask/suit.dmi',
+ "Grey" = 'icons/mob/species/grey/suit.dmi'
+ )
+ hoodtype = /obj/item/clothing/head/hooded/flagellant_hood
-/obj/item/clothing/head/hooded/berserkerhood
+/obj/item/clothing/head/hooded/flagellant_hood
name = "flagellant's robes"
desc = "Blood-soaked garb infused with dark magic; allows the user to move at inhuman speeds, but at the cost of increased damage."
- icon_state = "culthood"
+ icon_state = "flagellanthood"
+ item_state = "flagellanthood"
flags_inv = HIDEFACE
flags_cover = HEADCOVERSEYES
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ sprite_sheets = list(
+ "Vox" = 'icons/mob/species/vox/head.dmi',
+ "Drask" = 'icons/mob/species/drask/head.dmi',
+ "Grey" = 'icons/mob/species/grey/head.dmi'
+ )
/obj/item/whetstone/cult
name = "eldritch whetstone"
diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm
index 0e96dd92062..17ae54591aa 100644
--- a/code/game/gamemodes/cult/cult_structures.dm
+++ b/code/game/gamemodes/cult/cult_structures.dm
@@ -120,7 +120,7 @@
selection_prompt = "You study the schematics etched on the forge..."
selection_title = "Forge"
creation_message = "You work the forge as dark knowledge guides your hands, creating %ITEM%!"
- choosable_items = list("Shielded Robe" = /obj/item/clothing/suit/hooded/cultrobes/cult_shield, "Flagellant's Robe" = /obj/item/clothing/suit/hooded/cultrobes/berserker, \
+ choosable_items = list("Shielded Robe" = /obj/item/clothing/suit/hooded/cultrobes/cult_shield, "Flagellant's Robe" = /obj/item/clothing/suit/hooded/cultrobes/flagellant_robe, \
"Cultist Hardsuit" = /obj/item/storage/box/cult)
/obj/structure/cult/functional/forge/New()
@@ -270,14 +270,10 @@ GLOBAL_LIST_INIT(blacklisted_pylon_turfs, typecacheof(list(
/obj/effect/gateway/singularity_pull()
return
-/obj/effect/gateway/Bumped(mob/M as mob|obj)
- spawn(0)
- return
+/obj/effect/gateway/Bumped(atom/movable/AM)
return
-/obj/effect/gateway/Crossed(AM as mob|obj, oldloc)
- spawn(0)
- return
+/obj/effect/gateway/Crossed(atom/movable/AM, oldloc)
return
diff --git a/code/game/gamemodes/cult/talisman.dm b/code/game/gamemodes/cult/talisman.dm
index 60d2a7ff73b..af0dfb5e6e3 100644
--- a/code/game/gamemodes/cult/talisman.dm
+++ b/code/game/gamemodes/cult/talisman.dm
@@ -1,10 +1,10 @@
/obj/item/paper/talisman
- icon = 'icons/obj/paper.dmi'
+ icon = 'icons/obj/bureaucracy.dmi'
icon_state = "paper_talisman"
var/cultist_name = "talisman"
var/cultist_desc = "A basic talisman. It serves no purpose."
var/invocation = "Naise meam!"
- info = "
"
+ info = "​
"
var/uses = 1
var/health_cost = 0 //The amount of health taken from the user when invoking the talisman
diff --git a/code/game/gamemodes/devil/contracts/friend.dm b/code/game/gamemodes/devil/contracts/friend.dm
index 7760ee10ee7..8838dc8a371 100644
--- a/code/game/gamemodes/devil/contracts/friend.dm
+++ b/code/game/gamemodes/devil/contracts/friend.dm
@@ -17,7 +17,8 @@
/obj/effect/mob_spawn/human/demonic_friend/Initialize(mapload, datum/mind/owner_mind, obj/effect/proc_holder/spell/targeted/summon_friend/summoning_spell)
. = ..()
owner = owner_mind
- flavour_text = "You have been given a reprieve from your eternity of torment, to be [owner.name]'s friend for [owner.p_their()] short mortal coil. Be aware that if you do not live up to [owner.name]'s expectations, [owner.p_they()] can send you back to hell with a single thought. [owner.name]'s death will also return you to hell."
+ description = "Be someone's loyal friend/slave. If they die, you die as well." //best I could think of in the moment, not sure how this role plays in practise, have never seen it.
+ flavour_text = "You have been given a reprieve from your eternity of torment, to be [owner.name]'s friend for [owner.p_their()] short mortal coil. Be aware that if you do not live up to [owner.name]'s expectations, [owner.p_they()] can send you back to hell with a single thought. [owner.name]'s death will also return you to hell."
var/area/A = get_area(src)
if(!mapload && A)
notify_ghosts("\A friendship shell has been completed in \the [A.name].", source = src, action=NOTIFY_ATTACK, flashwindow = TRUE)
diff --git a/code/game/gamemodes/devil/devilinfo.dm b/code/game/gamemodes/devil/devilinfo.dm
index ecbeb54f7f7..22a5f4a6cde 100644
--- a/code/game/gamemodes/devil/devilinfo.dm
+++ b/code/game/gamemodes/devil/devilinfo.dm
@@ -92,7 +92,7 @@ GLOBAL_LIST_INIT(lawlorify, list (
var/form = BASIC_DEVIL
var/exists = 0
var/static/list/dont_remove_spells = list(
- /obj/effect/proc_holder/spell/targeted/summon_contract,
+ /obj/effect/proc_holder/spell/targeted/click/summon_contract,
/obj/effect/proc_holder/spell/targeted/conjure_item/violin,
/obj/effect/proc_holder/spell/targeted/summon_dancefloor)
var/ascendable = FALSE
@@ -326,12 +326,12 @@ GLOBAL_LIST_INIT(lawlorify, list (
owner.RemoveSpell(S)
/datum/devilinfo/proc/give_summon_contract()
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/summon_contract(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/summon_contract(null))
/datum/devilinfo/proc/give_base_spells(give_summon_contract = 0)
remove_spells()
- owner.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/fireball/hellish(null))
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork(null))
if(give_summon_contract)
give_summon_contract()
@@ -343,13 +343,13 @@ GLOBAL_LIST_INIT(lawlorify, list (
/datum/devilinfo/proc/give_lizard_spells()
remove_spells()
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork(null))
- owner.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/fireball/hellish(null))
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/infernal_jaunt(null))
/datum/devilinfo/proc/give_true_spells()
remove_spells()
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/greater(null))
- owner.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/fireball/hellish(null))
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/infernal_jaunt(null))
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/sintouch(null))
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 2d71df8c979..8757fa00d4f 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -291,7 +291,8 @@
///////////////////////////////////
/datum/game_mode/proc/get_living_heads()
. = list()
- for(var/mob/living/carbon/human/player in GLOB.mob_list)
+ for(var/thing in GLOB.human_list)
+ var/mob/living/carbon/human/player = thing
var/list/real_command_positions = GLOB.command_positions.Copy() - "Nanotrasen Representative"
if(player.stat != DEAD && player.mind && (player.mind.assigned_role in real_command_positions))
. |= player.mind
@@ -312,7 +313,8 @@
//////////////////////////////////////////////
/datum/game_mode/proc/get_living_sec()
. = list()
- for(var/mob/living/carbon/human/player in GLOB.mob_list)
+ for(var/thing in GLOB.human_list)
+ var/mob/living/carbon/human/player = thing
if(player.stat != DEAD && player.mind && (player.mind.assigned_role in GLOB.security_positions))
. |= player.mind
@@ -321,7 +323,8 @@
////////////////////////////////////////
/datum/game_mode/proc/get_all_sec()
. = list()
- for(var/mob/living/carbon/human/player in GLOB.mob_list)
+ for(var/thing in GLOB.human_list)
+ var/mob/living/carbon/human/player = thing
if(player.mind && (player.mind.assigned_role in GLOB.security_positions))
. |= player.mind
@@ -334,7 +337,7 @@
//////////////////////////
//Reports player logouts//
//////////////////////////
-proc/display_roundstart_logout_report()
+/proc/display_roundstart_logout_report()
var/msg = "Roundstart logout report\n\n"
for(var/mob/living/L in GLOB.mob_list)
@@ -421,9 +424,9 @@ proc/display_roundstart_logout_report()
return nukecode
/datum/game_mode/proc/replace_jobbanned_player(mob/living/M, role_type)
- var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as a [role_type]?", role_type, 0, 100)
+ var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as a [role_type]?", role_type, FALSE, 10 SECONDS)
var/mob/dead/observer/theghost = null
- if(candidates.len)
+ if(length(candidates))
theghost = pick(candidates)
to_chat(M, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!")
message_admins("[key_name_admin(theghost)] has taken control of ([key_name_admin(M)]) to replace a jobbanned player.")
@@ -504,7 +507,7 @@ proc/display_roundstart_logout_report()
message_text += G.get_report()
message_text += " "
- print_command_report(message_text, "[command_name()] Orders")
+ print_command_report(message_text, "[command_name()] Orders", FALSE)
/datum/game_mode/proc/declare_station_goal_completion()
for(var/V in station_goals)
diff --git a/code/game/gamemodes/heist/heist.dm b/code/game/gamemodes/heist/heist.dm
index 142ae33c078..f95702e7f28 100644
--- a/code/game/gamemodes/heist/heist.dm
+++ b/code/game/gamemodes/heist/heist.dm
@@ -250,7 +250,7 @@ GLOBAL_LIST_EMPTY(cortical_stacks) //Stacks for 'leave nobody behind' objective.
..()
-datum/game_mode/proc/auto_declare_completion_heist()
+/datum/game_mode/proc/auto_declare_completion_heist()
if(raiders.len)
var/check_return = 0
if(GAMEMODE_IS_HEIST)
diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm
index a4b8c594b89..9ed2adf3332 100644
--- a/code/game/gamemodes/malfunction/Malf_Modules.dm
+++ b/code/game/gamemodes/malfunction/Malf_Modules.dm
@@ -83,6 +83,36 @@
else
add_ranged_ability(user, enable_text)
+/datum/action/innate/ai/choose_modules
+ name = "Choose Modules"
+ desc = "Spend your processing time to gain a variety of different abilities."
+ button_icon_state = "choose_module"
+ auto_use_uses = FALSE // This is an infinite ability.
+
+/datum/action/innate/ai/choose_modules/Grant(mob/living/L)
+ . = ..()
+ owner_AI.malf_picker = new /datum/module_picker
+
+/datum/action/innate/ai/choose_modules/Trigger()
+ . = ..()
+ owner_AI.malf_picker.use(owner_AI)
+
+/datum/action/innate/ai/return_to_core
+ name = "Return to Main Core"
+ desc = "Leave the APC you are shunted to, and return to your core."
+ icon_icon = 'icons/obj/power.dmi'
+ button_icon_state = "apcemag"
+ auto_use_uses = FALSE // Here just to prevent the "You have X uses remaining" from popping up.
+
+/datum/action/innate/ai/return_to_core/Trigger()
+ . = ..()
+ var/obj/machinery/power/apc/apc = owner_AI.loc
+ if(!istype(apc)) // This shouldn't happen but here for safety.
+ to_chat(src, "You are already in your Main Core.")
+ return
+ apc.malfvacate()
+ qdel(src)
+
//The datum and interface for the malf unlock menu, which lets them choose actions to unlock.
/datum/module_picker
var/temp
@@ -96,13 +126,7 @@
if((AM.power_type && AM.power_type != /datum/action/innate/ai) || AM.upgrade)
possible_modules += AM
-/datum/module_picker/proc/remove_malf_verbs(mob/living/silicon/ai/AI) //Removes all malfunction-related abilities from the target AI.
- for(var/datum/AI_Module/AM in possible_modules)
- for(var/datum/action/A in AI.actions)
- if(istype(A, initial(AM.power_type)))
- qdel(A)
-
-/datum/module_picker/proc/use(user as mob)
+/datum/module_picker/proc/use(mob/user)
var/dat
dat += {"Select use of processing time: (currently #[processing_time] left.)
@@ -592,7 +616,7 @@
active = FALSE
return
var/turf/T = get_turf(owner_AI.eyeobj)
- new /obj/machinery/transformer/conveyor(T)
+ new /obj/machinery/transformer(T, owner_AI)
playsound(T, 'sound/effects/phasein.ogg', 100, 1)
owner_AI.can_shunt = FALSE
to_chat(owner, "You are no longer able to shunt your core to APCs.")
@@ -655,7 +679,7 @@
for(var/thing in GLOB.apcs)
var/obj/machinery/power/apc/apc
if(prob(30 * apc.overload))
- apc.overload_lighting()
+ INVOKE_ASYNC(apc, /obj/machinery/power/apc.proc/overload_lighting)
else
apc.overload++
to_chat(owner, "Overcurrent applied to the powernet.")
@@ -757,3 +781,17 @@
if(AI.eyeobj)
AI.eyeobj.relay_speech = TRUE
+/datum/AI_Module/large/cameracrack
+ module_name = "Core Camera Cracker"
+ mod_pick_name = "cameracrack"
+ description = "By shortcirucuting the camera network chip, it overheats, preventing the camera console from using your internal camera."
+ cost = 10
+ one_purchase = TRUE
+ upgrade = TRUE
+ unlock_text = "Network chip short circuited. Internal camera disconected from network. Minimal damage to other internal components."
+ unlock_sound = 'sound/items/wirecutter.ogg'
+
+/datum/AI_Module/large/cameracrack/upgrade(mob/living/silicon/ai/AI)
+ if(AI.builtInCamera)
+ QDEL_NULL(AI.builtInCamera)
+
diff --git a/code/game/gamemodes/miniantags/borer/borer.dm b/code/game/gamemodes/miniantags/borer/borer.dm
index f99a00567ae..750473b1dfa 100644
--- a/code/game/gamemodes/miniantags/borer/borer.dm
+++ b/code/game/gamemodes/miniantags/borer/borer.dm
@@ -394,7 +394,7 @@
to_chat(src, " You are feeling far too docile to do that.")
return
- var content = ""
+ var/content = ""
content += ""
diff --git a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm
index 8da5b4468d7..7fe146d3e47 100644
--- a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm
+++ b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm
@@ -19,15 +19,13 @@
mob_name = "a swarmer"
death = FALSE
roundstart = FALSE
- flavour_text = {"
- You are a swarmer, a weapon of a long dead civilization. Until further orders from your original masters are received, you must continue to consume and replicate.
- Clicking on any object will try to consume it, either deconstructing it into its components, destroying it, or integrating any materials it has into you if successful.
- Ctrl-Clicking on a mob will attempt to remove it from the area and place it in a safe environment for storage.
- Objectives:
+ important_info = "Follow your objectives, do not make the station inhospitable or try and kill crew."
+ flavour_text = "You are a swarmer, a weapon of a long dead civilization. Until further orders from your original masters are received, you must continue to consume and replicate."
+ description = {" Your goal is to create more of yourself by consuming the station. Clicking on any object will try to consume it, either deconstructing it into its components, destroying it, or integrating any materials it has into you if successful. Ctrl-Clicking on a mob will attempt to remove it from the area and place it in a safe environment for storage.
+ Objectives:
1. Consume resources and replicate until there are no more resources left.
2. Ensure that this location is fit for invasion at a later date; do not perform actions that would render it dangerous or inhospitable.
- 3. Biological resources will be harvested at a later date; do not harm them.
- "}
+ 3. Biological resources will be harvested at a later date; do not harm them."}
/obj/effect/mob_spawn/swarmer/Initialize(mapload)
. = ..()
diff --git a/code/game/gamemodes/miniantags/bot_swarm/swarmer_event.dm b/code/game/gamemodes/miniantags/bot_swarm/swarmer_event.dm
index fbceda6bd94..5c4b83e965c 100644
--- a/code/game/gamemodes/miniantags/bot_swarm/swarmer_event.dm
+++ b/code/game/gamemodes/miniantags/bot_swarm/swarmer_event.dm
@@ -7,7 +7,7 @@
var/swarmer_report = "[command_name()] High-Priority Update"
swarmer_report += "
Our long-range sensors have detected an odd signal emanating from your station's gateway. We recommend immediate investigation of your gateway, as something may have come \
through."
- print_command_report(swarmer_report, "Classified [command_name()] Update")
+ print_command_report(swarmer_report, "Classified [command_name()] Update", FALSE)
GLOB.event_announcement.Announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", 'sound/AI/commandreport.ogg')
/datum/event/spawn_swarmer/start()
diff --git a/code/game/gamemodes/miniantags/guardian/guardian.dm b/code/game/gamemodes/miniantags/guardian/guardian.dm
index f647ae5a6d1..ecfa7ea3f96 100644
--- a/code/game/gamemodes/miniantags/guardian/guardian.dm
+++ b/code/game/gamemodes/miniantags/guardian/guardian.dm
@@ -15,7 +15,7 @@
a_intent = INTENT_HARM
can_change_intents = 0
stop_automated_movement = 1
- floating = 1
+ flying = TRUE
attack_sound = 'sound/weapons/punch1.ogg'
minbodytemp = 0
maxbodytemp = INFINITY
@@ -41,9 +41,15 @@
var/tech_fluff_string = "BOOT SEQUENCE COMPLETE. ERROR MODULE LOADED. THIS SHOULDN'T HAPPEN. Submit a bug report!"
var/bio_fluff_string = "Your scarabs fail to mutate. This shouldn't happen! Submit a bug report!"
var/admin_fluff_string = "URK URF!"//the wheels on the bus...
- var/adminseal = FALSE
var/name_color = "white"//only used with protector shields for the time being
+/mob/living/simple_animal/hostile/guardian/Initialize(mapload, mob/living/host)
+ . = ..()
+ if(!host)
+ return
+ summoner = host
+ host.grant_guardian_actions(src)
+
/mob/living/simple_animal/hostile/guardian/med_hud_set_health()
if(summoner)
var/image/holder = hud_list[HEALTH_HUD]
@@ -59,19 +65,20 @@
else
holder.icon_state = "hudhealthy"
-/mob/living/simple_animal/hostile/guardian/Life(seconds, times_fired) //Dies if the summoner dies
+/mob/living/simple_animal/hostile/guardian/Life(seconds, times_fired)
..()
if(summoner)
if(summoner.stat == DEAD || (!summoner.check_death_method() && summoner.health <= HEALTH_THRESHOLD_DEAD))
+ summoner.remove_guardian_actions()
to_chat(src, "Your summoner has died!")
visible_message("[src] dies along with its user!")
ghostize()
qdel(src)
snapback()
- if(summoned && !summoner && !adminseal)
+ if(summoned && !summoner && !admin_spawned)
to_chat(src, "You somehow lack a summoner! As a result, you dispel!")
ghostize()
- qdel()
+ qdel(src)
/mob/living/simple_animal/hostile/guardian/proc/snapback()
// If the summoner dies instantly, the summoner's ghost may be drawn into null space as the protector is deleted. This check should prevent that.
@@ -197,7 +204,7 @@
//override set to true if message should be passed through instead of going to host communication
/mob/living/simple_animal/hostile/guardian/say(message, override = FALSE)
- if(adminseal || override)//if it's an admin-spawned guardian without a host it can still talk normally
+ if(admin_spawned || override)//if it's an admin-spawned guardian without a host it can still talk normally
return ..(message)
Communicate(message)
@@ -206,61 +213,6 @@
to_chat(src, "You dont have another mode!")
-/mob/living/proc/guardian_comm()
- set name = "Communicate"
- set category = "Guardian"
- set desc = "Communicate telepathically with your guardian."
- var/input = stripped_input(src, "Please enter a message to tell your guardian.", "Message", "")
- if(!input)
- return
-
- // Find the guardian in our host's contents.
- var/mob/living/simple_animal/hostile/guardian/G = locate() in contents
- if(!G)
- return
-
- // Show the message to our guardian and to host.
- to_chat(G, "[src]: [input]")
- to_chat(src, "[src]: [input]")
- log_say("(GUARDIAN to [key_name(G)]) [input]", src)
- create_log(SAY_LOG, "HOST to GUARDIAN: [input]", G)
-
- // Show the message to any ghosts/dead players.
- for(var/mob/M in GLOB.dead_mob_list)
- if(M && M.client && M.stat == DEAD && !isnewplayer(M))
- to_chat(M, "Guardian Communication from [src] ([ghost_follow_link(src, ghost=M)]): [input]")
-
-/mob/living/proc/guardian_recall()
- set name = "Recall Guardian"
- set category = "Guardian"
- set desc = "Forcibly recall your guardian."
- for(var/mob/living/simple_animal/hostile/guardian/G in GLOB.mob_list)
- if(G.summoner == src)
- G.Recall()
-
-/mob/living/proc/guardian_reset()
- set name = "Reset Guardian Player (One Use)"
- set category = "Guardian"
- set desc = "Re-rolls which ghost will control your Guardian. One use."
-
- src.verbs -= /mob/living/proc/guardian_reset
- for(var/mob/living/simple_animal/hostile/guardian/G in GLOB.mob_list)
- if(G.summoner == src)
- var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as [G.real_name]?", ROLE_GUARDIAN, 0, 100)
- var/mob/dead/observer/new_stand = null
- if(candidates.len)
- new_stand = pick(candidates)
- to_chat(G, "Your user reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.")
- to_chat(src, "Your guardian has been successfully reset.")
- message_admins("[key_name_admin(new_stand)] has taken control of ([key_name_admin(G)])")
- G.ghostize()
- G.key = new_stand.key
- else
- to_chat(src, "There were no ghosts willing to take control. Looks like you're stuck with your Guardian for now.")
- spawn(3000)
- verbs += /mob/living/proc/guardian_reset
-
-
/mob/living/simple_animal/hostile/guardian/proc/ToggleLight()
if(!light_on)
set_light(luminosity_on)
@@ -312,7 +264,7 @@
used = FALSE
return
to_chat(user, "[use_message]")
- var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as the [mob_name] of [user.real_name]?", ROLE_GUARDIAN, 0, 100)
+ var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as the [mob_name] of [user.real_name]?", ROLE_GUARDIAN, FALSE, 10 SECONDS, source = src)
var/mob/dead/observer/theghost = null
if(candidates.len)
@@ -363,8 +315,7 @@
if("Protector")
pickedtype = /mob/living/simple_animal/hostile/guardian/protector
- var/mob/living/simple_animal/hostile/guardian/G = new pickedtype(user)
- G.summoner = user
+ var/mob/living/simple_animal/hostile/guardian/G = new pickedtype(user, user)
G.summoned = TRUE
G.key = key
to_chat(G, "You are a [mob_name] bound to serve [user.real_name].")
@@ -372,9 +323,6 @@
to_chat(G, "While personally invincible, you will die if [user.real_name] does, and any damage dealt to you will have a portion passed on to them as you feed upon them to sustain yourself.")
to_chat(G, "[G.playstyle_string]")
G.faction = user.faction
- user.verbs += /mob/living/proc/guardian_comm
- user.verbs += /mob/living/proc/guardian_recall
- user.verbs += /mob/living/proc/guardian_reset
var/color = pick(color_list)
G.name_color = color_list[color]
diff --git a/code/game/gamemodes/miniantags/guardian/host_actions.dm b/code/game/gamemodes/miniantags/guardian/host_actions.dm
new file mode 100644
index 00000000000..c6e48221705
--- /dev/null
+++ b/code/game/gamemodes/miniantags/guardian/host_actions.dm
@@ -0,0 +1,130 @@
+/**
+ * # Base guardian host action
+ *
+ * These are used by guardian hosts to interact with their guardians. These are not buttons that guardians themselves use.
+ */
+/datum/action/guardian
+ name = "Generic guardian host action"
+ icon_icon = 'icons/mob/guardian.dmi'
+ button_icon_state = "base"
+ var/mob/living/simple_animal/hostile/guardian/guardian
+
+/datum/action/guardian/Grant(mob/M, mob/living/simple_animal/hostile/guardian/G)
+ if(!G || !istype(G))
+ stack_trace("/datum/action/guardian created with no guardian to link to.")
+ qdel(src)
+ guardian = G
+ return ..()
+
+/**
+ * # Communicate action
+ *
+ * Allows the guardian host to communicate with their guardian.
+ */
+/datum/action/guardian/communicate
+ name = "Communicate"
+ desc = "Communicate telepathically with your guardian."
+ button_icon_state = "communicate"
+
+/datum/action/guardian/communicate/Trigger()
+ var/input = stripped_input(owner, "Enter a message to tell your guardian:", "Message", "")
+ if(!input)
+ return
+
+ // Show the message to our guardian and to host.
+ to_chat(guardian, "[owner]: [input]")
+ to_chat(owner, "[owner]: [input]")
+ log_say("(GUARDIAN to [key_name(guardian)]) [input]", owner)
+ owner.create_log(SAY_LOG, "HOST to GUARDIAN: [input]", guardian)
+
+ // Show the message to any ghosts/dead players.
+ for(var/mob/M in GLOB.dead_mob_list)
+ if(M && M.client && M.stat == DEAD && !isnewplayer(M))
+ to_chat(M, "Guardian Communication from [owner] ([ghost_follow_link(owner, ghost=M)]): [input]")
+
+/**
+ * # Recall guardian action
+ *
+ * Allows the guardian host to recall their guardian.
+ */
+/datum/action/guardian/recall
+ name = "Recall Guardian"
+ desc = "Forcibly recall your guardian."
+ button_icon_state = "recall"
+
+/datum/action/guardian/recall/Trigger()
+ guardian.Recall()
+
+/**
+ * # Reset guardian action
+ *
+ * Allows the guardian host to exchange their guardian's player for another.
+ */
+/datum/action/guardian/reset_guardian
+ name = "Replace Guardian Player"
+ desc = "Replace your guardian's player with a ghost. This can only be done once."
+ button_icon_state = "reset"
+ var/cooldown_timer
+
+/datum/action/guardian/reset_guardian/IsAvailable()
+ if(cooldown_timer)
+ return FALSE
+ return TRUE
+
+/datum/action/guardian/reset_guardian/Trigger()
+ if(cooldown_timer)
+ to_chat(owner, "This ability is still recharging.")
+ return
+
+ var/confirm = alert("Are you sure you want replace your guardian's player?", "Confirm", "Yes", "No")
+ if(confirm == "No")
+ return
+
+ // Do this immediately, so the user can't spam a bunch of polls.
+ cooldown_timer = addtimer(CALLBACK(src, .proc/reset_cooldown), 5 MINUTES)
+ UpdateButtonIcon()
+
+ to_chat(owner, "Searching for a replacement ghost...")
+ var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as [guardian.real_name]?", ROLE_GUARDIAN, FALSE, 15 SECONDS, source = guardian)
+
+ if(!length(candidates))
+ to_chat(owner, "There were no ghosts willing to take control of your guardian. You can try again in 5 minutes.")
+ return
+
+ var/mob/dead/observer/new_stand = pick(candidates)
+ to_chat(guardian, "Your user reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.")
+ to_chat(owner, "Your guardian has been successfully reset.")
+ message_admins("[key_name_admin(new_stand)] has taken control of ([key_name_admin(guardian)])")
+ guardian.ghostize()
+ guardian.key = new_stand.key
+ qdel(src)
+
+/**
+ * Takes the action button off cooldown and makes it available again.
+ */
+/datum/action/guardian/reset_guardian/proc/reset_cooldown()
+ cooldown_timer = null
+ UpdateButtonIcon()
+
+/**
+ * Grants all existing `/datum/action/guardian` type actions to the src mob.
+ *
+ * Called whenever the host gains their gauardian.
+ */
+/mob/living/proc/grant_guardian_actions(mob/living/simple_animal/hostile/guardian/G)
+ if(!G || !istype(G))
+ return
+ for(var/action in subtypesof(/datum/action/guardian))
+ var/datum/action/guardian/A = new action
+ A.Grant(src, G)
+
+/**
+ * Removes all `/datum/action/guardian` type actions from the src mob.
+ *
+ * Called whenever the host loses their guardian.
+ */
+/mob/living/proc/remove_guardian_actions()
+ for(var/action in actions)
+ var/datum/action/A = action
+ if(istype(A, /datum/action/guardian))
+ A.Remove(src)
diff --git a/code/game/gamemodes/miniantags/guardian/types/healer.dm b/code/game/gamemodes/miniantags/guardian/types/healer.dm
index c3f8046f0a9..72d1a03301e 100644
--- a/code/game/gamemodes/miniantags/guardian/types/healer.dm
+++ b/code/game/gamemodes/miniantags/guardian/types/healer.dm
@@ -26,7 +26,7 @@
melee_damage_lower = 0
melee_damage_upper = 0
melee_damage_type = STAMINA
- adminseal = TRUE
+ admin_spawned = TRUE
/mob/living/simple_animal/hostile/guardian/healer/New()
..()
@@ -68,7 +68,7 @@
hud_used.action_intent.icon_state = a_intent
speed = 0
damage_transfer = 0.7
- if(adminseal)
+ if(admin_spawned)
damage_transfer = 0
melee_damage_lower = 15
melee_damage_upper = 15
@@ -79,7 +79,7 @@
hud_used.action_intent.icon_state = a_intent
speed = 1
damage_transfer = 1
- if(adminseal)
+ if(admin_spawned)
damage_transfer = 0
melee_damage_lower = 0
melee_damage_upper = 0
diff --git a/code/game/gamemodes/miniantags/guardian/types/lightning.dm b/code/game/gamemodes/miniantags/guardian/types/lightning.dm
index bf84e199bab..e2ab6360ae5 100644
--- a/code/game/gamemodes/miniantags/guardian/types/lightning.dm
+++ b/code/game/gamemodes/miniantags/guardian/types/lightning.dm
@@ -3,12 +3,12 @@
layer = LYING_MOB_LAYER
/mob/living/simple_animal/hostile/guardian/beam
- melee_damage_lower = 7
- melee_damage_upper = 7
+ melee_damage_lower = 12
+ melee_damage_upper = 12
attacktext = "shocks"
melee_damage_type = BURN
attack_sound = 'sound/machines/defib_zap.ogg'
- damage_transfer = 0.7
+ damage_transfer = 0.6
range = 7
playstyle_string = "As a Lightning type, you will apply lightning chains to targets on attack and have a lightning chain to your summoner. Lightning chains will shock anyone near them."
magic_fluff_string = "..And draw the Tesla, a shocking, lethal source of power."
@@ -18,6 +18,16 @@
var/list/enemychains = list()
var/successfulshocks = 0
+/mob/living/simple_animal/hostile/guardian/beam/Initialize(mapload, mob/living/host)
+ . = ..()
+ if(!summoner)
+ return
+ if(!(NO_SHOCK in summoner.mutations))
+ summoner.mutations.Add(NO_SHOCK)
+
+/mob/living/simple_animal/hostile/guardian/beam/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = FALSE, override = FALSE, tesla_shock = FALSE, illusion = FALSE, stun = TRUE)
+ return FALSE //You are lightning, you should not be hurt by such things.
+
/mob/living/simple_animal/hostile/guardian/beam/AttackingTarget()
. = ..()
if(. && isliving(target) && target != src && target != summoner)
@@ -106,3 +116,8 @@
)
L.adjustFireLoss(1.2) //adds up very rapidly
. = 1
+
+/mob/living/simple_animal/hostile/guardian/beam/death(gibbed)
+ if(summoner && (NO_SHOCK in summoner.mutations))
+ summoner.mutations.Remove(NO_SHOCK)
+ return ..()
diff --git a/code/game/gamemodes/miniantags/guardian/types/protector.dm b/code/game/gamemodes/miniantags/guardian/types/protector.dm
index 534b7e16934..fda005a58ed 100644
--- a/code/game/gamemodes/miniantags/guardian/types/protector.dm
+++ b/code/game/gamemodes/miniantags/guardian/types/protector.dm
@@ -25,6 +25,7 @@
overlays.Cut()
melee_damage_lower = initial(melee_damage_lower)
melee_damage_upper = initial(melee_damage_upper)
+ obj_damage = initial(obj_damage)
speed = initial(speed)
damage_transfer = 0.4
to_chat(src, "You switch to combat mode.")
@@ -35,6 +36,7 @@
overlays.Add(shield_overlay)
melee_damage_lower = 2
melee_damage_upper = 2
+ obj_damage = 6 //40/7.5 rounded up, we don't want a protector guardian 2 shotting blob tiles while taking 5% damage, thats just silly.
speed = 1
damage_transfer = 0.05 //damage? what's damage?
to_chat(src, "You switch to protection mode.")
diff --git a/code/game/gamemodes/miniantags/guardian/types/ranged.dm b/code/game/gamemodes/miniantags/guardian/types/ranged.dm
index 496e31d1bd3..dab7dfb1eca 100644
--- a/code/game/gamemodes/miniantags/guardian/types/ranged.dm
+++ b/code/game/gamemodes/miniantags/guardian/types/ranged.dm
@@ -1,7 +1,7 @@
/obj/item/projectile/guardian
name = "crystal spray"
icon_state = "guardian"
- damage = 5
+ damage = 25
damage_type = BRUTE
armour_penetration = 100
@@ -11,7 +11,7 @@
melee_damage_upper = 10
damage_transfer = 0.9
projectiletype = /obj/item/projectile/guardian
- ranged_cooldown_time = 1 //fast!
+ ranged_cooldown_time = 5 //fast!
projectilesound = 'sound/effects/hit_on_shattered_glass.ogg'
ranged = 1
range = 13
diff --git a/code/game/gamemodes/miniantags/guardian/types/standard.dm b/code/game/gamemodes/miniantags/guardian/types/standard.dm
index e6d95ffb92c..d458ce2f49d 100644
--- a/code/game/gamemodes/miniantags/guardian/types/standard.dm
+++ b/code/game/gamemodes/miniantags/guardian/types/standard.dm
@@ -45,4 +45,4 @@
playstyle_string = "As a standard type you have no special abilities, but have a high damage resistance and a powerful attack capable of smashing through walls."
environment_smash = 2
battlecry = "URK"
- adminseal = TRUE
+ admin_spawned = TRUE
diff --git a/code/game/gamemodes/miniantags/morph/morph_event.dm b/code/game/gamemodes/miniantags/morph/morph_event.dm
index e9465492a27..7fc9e134195 100644
--- a/code/game/gamemodes/miniantags/morph/morph_event.dm
+++ b/code/game/gamemodes/miniantags/morph/morph_event.dm
@@ -3,7 +3,7 @@
/datum/event/spawn_morph/proc/get_morph()
spawn()
- var/list/candidates = pollCandidates("Do you want to play as a morph?", ROLE_MORPH, 1)
+ var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a morph?", ROLE_MORPH, TRUE, source = /mob/living/simple_animal/hostile/morph)
if(!candidates.len)
key_of_morph = null
return kill()
diff --git a/code/game/gamemodes/miniantags/revenant/revenant.dm b/code/game/gamemodes/miniantags/revenant/revenant.dm
index f86f018757e..cba5ea62a2a 100644
--- a/code/game/gamemodes/miniantags/revenant/revenant.dm
+++ b/code/game/gamemodes/miniantags/revenant/revenant.dm
@@ -16,6 +16,7 @@
var/icon_stun = "revenant_stun"
var/icon_drain = "revenant_draining"
incorporeal_move = 3
+ see_invisible = INVISIBILITY_REVENANT
invisibility = INVISIBILITY_REVENANT
health = INFINITY //Revenants don't use health, they use essence instead
maxHealth = INFINITY
@@ -33,7 +34,7 @@
status_flags = 0
wander = 0
density = 0
- flying = 1
+ flying = TRUE
move_resist = INFINITY
mob_size = MOB_SIZE_TINY
pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
@@ -140,7 +141,7 @@
giveObjectivesandGoals()
giveSpells()
else
- var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as a revenant?", poll_time = 15 SECONDS)
+ var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as a revenant?", poll_time = 15 SECONDS, source = /mob/living/simple_animal/revenant)
var/mob/dead/observer/theghost = null
if(candidates.len)
theghost = pick(candidates)
@@ -396,7 +397,7 @@
spawn()
if(!key_of_revenant)
message_admins("The new revenant's old client either could not be found or is in a new, living mob - grabbing a random candidate instead...")
- var/list/candidates = pollCandidates("Do you want to play as a revenant?", ROLE_REVENANT, 1)
+ var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a revenant?", ROLE_REVENANT, TRUE, source = /mob/living/simple_animal/revenant)
if(!candidates.len)
qdel(R)
message_admins("No candidates were found for the new revenant. Oh well!")
diff --git a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
index 91ed583efc7..47e5e759c77 100644
--- a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
+++ b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
@@ -153,7 +153,7 @@
else
name = "[initial(name)] ([cast_amount]E)"
-/obj/effect/proc_holder/spell/aoe_turf/revenant/can_cast(mob/living/simple_animal/revenant/user = usr)
+/obj/effect/proc_holder/spell/aoe_turf/revenant/can_cast(mob/living/simple_animal/revenant/user = usr, charge_check = TRUE, show_message = FALSE)
if(user.inhibited)
return 0
if(charge_counter < charge_max)
diff --git a/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm b/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm
index 5e5f0f87a79..aa76f3620ac 100644
--- a/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm
+++ b/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm
@@ -13,7 +13,7 @@
return
spawn()
- var/list/candidates = pollCandidates("Do you want to play as a revenant?", ROLE_REVENANT, 1)
+ var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a revenant?", ROLE_REVENANT, TRUE, source = /mob/living/simple_animal/revenant)
if(!candidates.len)
key_of_revenant = null
return kill()
diff --git a/code/game/gamemodes/miniantags/slaughter/slaughter.dm b/code/game/gamemodes/miniantags/slaughter/slaughter.dm
index d7e53bdae28..e58be201521 100644
--- a/code/game/gamemodes/miniantags/slaughter/slaughter.dm
+++ b/code/game/gamemodes/miniantags/slaughter/slaughter.dm
@@ -157,7 +157,7 @@
/mob/living/simple_animal/slaughter/cult/New()
..()
spawn(5)
- var/list/demon_candidates = pollCandidates("Do you want to play as a slaughter demon?", ROLE_DEMON, 1, 100)
+ var/list/demon_candidates = SSghost_spawns.poll_candidates("Do you want to play as a slaughter demon?", ROLE_DEMON, TRUE, 10 SECONDS, source = /mob/living/simple_animal/slaughter/cult)
if(!demon_candidates.len)
visible_message("[src] disappears in a flash of red light!")
qdel(src)
diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm
index 5fed8ae00f4..7c87083dfe5 100644
--- a/code/game/gamemodes/nuclear/nuclear.dm
+++ b/code/game/gamemodes/nuclear/nuclear.dm
@@ -3,7 +3,7 @@
/datum/game_mode
var/list/datum/mind/syndicates = list()
-proc/issyndicate(mob/living/M as mob)
+/proc/issyndicate(mob/living/M as mob)
return istype(M) && M.mind && SSticker && SSticker.mode && (M.mind in SSticker.mode.syndicates)
/datum/game_mode/nuclear
@@ -53,7 +53,6 @@ proc/issyndicate(mob/living/M as mob)
for(var/datum/mind/synd_mind in syndicates)
synd_mind.assigned_role = SPECIAL_ROLE_NUKEOPS //So they aren't chosen for other jobs.
synd_mind.special_role = SPECIAL_ROLE_NUKEOPS
- synd_mind.offstation_role = TRUE
return 1
@@ -104,7 +103,7 @@ proc/issyndicate(mob/living/M as mob)
var/obj/effect/landmark/nuke_spawn = locate("landmark*Nuclear-Bomb")
- var/nuke_code = "[rand(10000, 99999)]"
+ var/nuke_code = rand(10000, 99999)
var/leader_selected = 0
var/agent_number = 1
var/spawnpos = 1
@@ -113,7 +112,7 @@ proc/issyndicate(mob/living/M as mob)
if(spawnpos > synd_spawn.len)
spawnpos = 2
synd_mind.current.loc = synd_spawn[spawnpos]
-
+ synd_mind.offstation_role = TRUE
forge_syndicate_objectives(synd_mind)
create_syndicate(synd_mind)
greet_syndicate(synd_mind)
diff --git a/code/game/gamemodes/nuclear/nuclearbomb.dm b/code/game/gamemodes/nuclear/nuclearbomb.dm
index 8e38f106cc7..209f9689de1 100644
--- a/code/game/gamemodes/nuclear/nuclearbomb.dm
+++ b/code/game/gamemodes/nuclear/nuclearbomb.dm
@@ -14,59 +14,59 @@ GLOBAL_VAR(bomb_set)
icon_state = "nuclearbomb0"
density = 1
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
- var/deployable = 0
- var/extended = 0
- var/lighthack = 0
+ var/extended = FALSE
+ var/lighthack = FALSE
var/timeleft = 120
- var/timing = 0
+ var/timing = FALSE
+ var/exploded = FALSE
var/r_code = "ADMIN"
- var/code = ""
- var/yes_code = 0
- var/safety = 1
+ var/code
+ var/yes_code = FALSE
+ var/safety = TRUE
var/obj/item/disk/nuclear/auth = null
var/removal_stage = NUKE_INTACT
var/lastentered
- var/is_syndicate = 0
+ var/is_syndicate = FALSE
use_power = NO_POWER_USE
var/previous_level = ""
var/datum/wires/nuclearbomb/wires = null
/obj/machinery/nuclearbomb/syndicate
- is_syndicate = 1
+ is_syndicate = TRUE
/obj/machinery/nuclearbomb/New()
..()
- r_code = "[rand(10000, 99999.0)]"//Creates a random code upon object spawn.
+ r_code = rand(10000, 99999.0) // Creates a random code upon object spawn.
wires = new/datum/wires/nuclearbomb(src)
previous_level = get_security_level()
GLOB.poi_list |= src
/obj/machinery/nuclearbomb/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(wires)
GLOB.poi_list.Remove(src)
return ..()
/obj/machinery/nuclearbomb/process()
if(timing)
- GLOB.bomb_set = 1 //So long as there is one nuke timing, it means one nuke is armed.
+ GLOB.bomb_set = TRUE // So long as there is one nuke timing, it means one nuke is armed.
timeleft = max(timeleft - 2, 0) // 2 seconds per process()
if(timeleft <= 0)
INVOKE_ASYNC(src, .proc/explode)
- SSnanoui.update_uis(src)
return
/obj/machinery/nuclearbomb/attackby(obj/item/O as obj, mob/user as mob, params)
if(istype(O, /obj/item/disk/nuclear))
if(extended)
if(!user.drop_item())
- to_chat(user, "\The [O] is stuck to your hand!")
+ to_chat(user, "[O] is stuck to your hand!")
return
O.forceMove(src)
auth = O
add_fingerprint(user)
return attack_hand(user)
else
- to_chat(user, "You need to deploy \the [src] first. Right click on the sprite, select 'Make Deployable' then click on \the [src] with an empty hand.")
+ to_chat(user, "You need to deploy [src] first.")
return
return ..()
@@ -170,181 +170,157 @@ GLOBAL_VAR(bomb_set)
removal_stage = NUKE_SEALANT_OPEN
/obj/machinery/nuclearbomb/attack_ghost(mob/user as mob)
- if(extended)
- attack_hand(user)
+ attack_hand(user)
/obj/machinery/nuclearbomb/attack_hand(mob/user as mob)
- if(extended)
- if(panel_open)
- wires.Interact(user)
- else
- ui_interact(user)
- else if(deployable)
- if(removal_stage != NUKE_MOBILE)
- anchored = 1
- visible_message("With a steely snap, bolts slide out of [src] and anchor it to the flooring!")
- else
- visible_message("\The [src] makes a highly unpleasant crunching noise. It looks like the anchoring bolts have been cut.")
- if(!lighthack)
- flick("nuclearbombc", src)
- icon_state = "nuclearbomb1"
- extended = 1
- return
-
-/obj/machinery/nuclearbomb/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "nuclear_bomb.tmpl", "Nuke Control Panel", 450, 550, state = GLOB.physical_state)
- ui.open()
- ui.set_auto_update(1)
-
-/obj/machinery/nuclearbomb/ui_data(mob/user, datum/topic_state/state)
- var/data[0]
- data["is_syndicate"] = is_syndicate
- data["hacking"] = 0
- data["auth"] = is_auth(user)
- if(is_auth(user))
- if(yes_code)
- data["authstatus"] = timing ? "Functional/Set" : "Functional"
- else
- data["authstatus"] = "Auth. S2"
+ if(panel_open)
+ wires.Interact(user)
else
- if(timing)
- data["authstatus"] = "Set"
- else
- data["authstatus"] = "Auth. S1"
+ tgui_interact(user)
+
+/obj/machinery/nuclearbomb/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_physical_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "NuclearBomb", name, 450, 300, master_ui, state)
+ ui.open()
+
+/obj/machinery/nuclearbomb/tgui_data(mob/user)
+ var/list/data = list()
+ data["extended"] = extended
+ data["authdisk"] = is_auth(user)
+ data["diskname"] = auth ? auth.name : FALSE
+ data["authcode"] = yes_code
+ data["authfull"] = data["authdisk"] && data["authcode"]
data["safe"] = safety ? "Safe" : "Engaged"
data["time"] = timeleft
data["timer"] = timing
data["safety"] = safety
data["anchored"] = anchored
- data["yescode"] = yes_code
- data["message"] = "AUTH"
if(is_auth(user))
- data["message"] = code
if(yes_code)
- data["message"] = "*****"
-
- return data
-
-/obj/machinery/nuclearbomb/verb/make_deployable()
- set category = "Object"
- set name = "Make Deployable"
- set src in oview(1)
-
- if(usr.stat || !usr.canmove || usr.restrained())
- return
-
- if(deployable)
- to_chat(usr, "You close several panels to make [src] undeployable.")
- deployable = 0
+ data["codemsg"] = "CLEAR CODE"
+ else if(code)
+ data["codemsg"] = "RE-ENTER CODE"
+ else
+ data["codemsg"] = "ENTER CODE"
else
- to_chat(usr, "You adjust some panels to make [src] deployable.")
- deployable = 1
- return
+ data["codemsg"] = "-----"
+ return data
/obj/machinery/nuclearbomb/proc/is_auth(var/mob/user)
if(auth)
- return 1
+ return TRUE
else if(user.can_admin_interact())
- return 1
+ return TRUE
else
- return 0
+ return FALSE
-/obj/machinery/nuclearbomb/Topic(href, href_list)
+/obj/machinery/nuclearbomb/tgui_act(action, params)
if(..())
- return 1
-
- if(href_list["auth"])
- if(auth)
- auth.loc = loc
- yes_code = 0
- auth = null
- else
- var/obj/item/I = usr.get_active_hand()
- if(istype(I, /obj/item/disk/nuclear))
- usr.drop_item()
- I.loc = src
- auth = I
- if(is_auth(usr))
- if(href_list["type"])
- if(href_list["type"] == "E")
+ return
+ . = TRUE
+ if(exploded)
+ return
+ switch(action)
+ if("deploy")
+ if(removal_stage != NUKE_MOBILE)
+ anchored = TRUE
+ visible_message("With a steely snap, bolts slide out of [src] and anchor it to the flooring!")
+ else
+ visible_message("[src] makes a highly unpleasant crunching noise. It looks like the anchoring bolts have been cut.")
+ if(!lighthack)
+ flick("nuclearbombc", src)
+ icon_state = "nuclearbomb1"
+ extended = TRUE
+ return
+ if("auth")
+ if(auth)
+ if(!usr.get_active_hand() && Adjacent(usr))
+ usr.put_in_hands(auth)
+ else
+ auth.forceMove(get_turf(src))
+ yes_code = FALSE
+ auth = null
+ else
+ var/obj/item/I = usr.get_active_hand()
+ if(istype(I, /obj/item/disk/nuclear))
+ usr.drop_item()
+ I.forceMove(src)
+ auth = I
+ return
+ if(!is_auth(usr)) // All requests below here require NAD inserted.
+ return FALSE
+ switch(action)
+ if("code")
+ if(yes_code) // Clear code
+ code = null
+ yes_code = FALSE
+ return
+ // If no code set, enter new one
+ var/tempcode = input(usr, "Code", "Input Code", null) as num|null
+ if(tempcode)
+ code = min(max(round(tempcode), 0), 999999)
if(code == r_code)
- yes_code = 1
+ yes_code = TRUE
code = null
else
code = "ERROR"
+ return
+
+ if(!yes_code) // All requests below here require both NAD inserted AND code correct
+ return
+
+ switch(action)
+ if("toggle_anchor")
+ if(removal_stage == NUKE_MOBILE)
+ anchored = FALSE
+ visible_message("[src] makes a highly unpleasant crunching noise. It looks like the anchoring bolts have been cut.")
+ else if(isinspace())
+ to_chat(usr, "There is nothing to anchor to!")
+ return FALSE
else
- if(href_list["type"] == "R")
- yes_code = 0
- code = null
- else
- lastentered = text("[]", href_list["type"])
- if(text2num(lastentered) == null)
- var/turf/LOC = get_turf(usr)
- message_admins("[key_name_admin(usr)] tried to exploit a nuclear bomb by entering non-numerical codes: [lastentered]! ([LOC ? "JMP" : "null"])", 0)
- log_admin("EXPLOIT: [key_name(usr)] tried to exploit a nuclear bomb by entering non-numerical codes: [lastentered]!")
- else
- code += lastentered
- if(length(code) > 5)
- code = "ERROR"
- if(yes_code)
- if(href_list["time"])
- var/time = text2num(href_list["time"])
- timeleft += time
- timeleft = min(max(round(src.timeleft), 120), 600)
- if(href_list["timer"])
- if(timing == -1.0)
- SSnanoui.update_uis(src)
- return
- if(safety)
- to_chat(usr, "The safety is still on.")
- SSnanoui.update_uis(src)
- return
- timing = !(timing)
- if(timing)
- if(!lighthack)
- icon_state = "nuclearbomb2"
- if(!safety)
- message_admins("[key_name_admin(usr)] engaged a nuclear bomb (JMP)")
- if(!is_syndicate)
- set_security_level("delta")
- GLOB.bomb_set = 1 //There can still be issues with this resetting when there are multiple bombs. Not a big deal though for Nuke/N
- else
- GLOB.bomb_set = 0
+ anchored = !(anchored)
+ if(anchored)
+ visible_message("With a steely snap, bolts slide out of [src] and anchor it to the flooring.")
else
+ visible_message("The anchoring bolts slide back into the depths of [src].")
+ return
+ if("set_time")
+ var/time = input(usr, "Detonation time (seconds, min 120, max 600)", "Input Time", 120) as num|null
+ if(time)
+ timeleft = min(max(round(time), 120), 600)
+ if("toggle_safety")
+ safety = !(safety)
+ if(safety)
+ if(!is_syndicate)
+ set_security_level(previous_level)
+ timing = FALSE
+ GLOB.bomb_set = FALSE
+ if("toggle_armed")
+ if(safety)
+ to_chat(usr, "The safety is still on.")
+ return
+ timing = !(timing)
+ if(timing)
+ if(!lighthack)
+ icon_state = "nuclearbomb2"
+ if(!safety)
+ message_admins("[key_name_admin(usr)] engaged a nuclear bomb [ADMIN_JMP(src)]")
if(!is_syndicate)
- set_security_level(previous_level)
- GLOB.bomb_set = 0
- if(!lighthack)
- icon_state = "nuclearbomb1"
- if(href_list["safety"])
- safety = !(safety)
- if(safety)
- if(!is_syndicate)
- set_security_level(previous_level)
- timing = 0
- GLOB.bomb_set = 0
- if(href_list["anchor"])
- if(removal_stage == NUKE_MOBILE)
- anchored = 0
- visible_message("\The [src] makes a highly unpleasant crunching noise. It looks like the anchoring bolts have been cut.")
- SSnanoui.update_uis(src)
- return
-
- if(!isinspace())
- anchored = !(anchored)
- if(anchored)
- visible_message("With a steely snap, bolts slide out of [src] and anchor it to the flooring.")
- else
- visible_message("The anchoring bolts slide back into the depths of [src].")
+ set_security_level("delta")
+ GLOB.bomb_set = TRUE // There can still be issues with this resetting when there are multiple bombs. Not a big deal though for Nuke
else
- to_chat(usr, "There is nothing to anchor to!")
+ GLOB.bomb_set = TRUE
+ else
+ if(!is_syndicate)
+ set_security_level(previous_level)
+ GLOB.bomb_set = FALSE
+ if(!lighthack)
+ icon_state = "nuclearbomb1"
- SSnanoui.update_uis(src)
/obj/machinery/nuclearbomb/blob_act(obj/structure/blob/B)
- if(timing == -1.0)
+ if(exploded)
return
if(timing) //boom
INVOKE_ASYNC(src, .proc/explode)
@@ -359,11 +335,11 @@ GLOBAL_VAR(bomb_set)
#define NUKERANGE 80
/obj/machinery/nuclearbomb/proc/explode()
if(safety)
- timing = 0
+ timing = FALSE
return
- timing = -1.0
- yes_code = 0
- safety = 1
+ exploded = TRUE
+ yes_code = FALSE
+ safety = TRUE
if(!lighthack)
icon_state = "nuclearbomb3"
playsound(src,'sound/machines/alarm.ogg',100,0,5)
@@ -407,6 +383,20 @@ GLOBAL_VAR(bomb_set)
return
return
+/obj/machinery/nuclearbomb/proc/reset_lighthack_callback()
+ lighthack = !lighthack
+
+/obj/machinery/nuclearbomb/proc/reset_safety_callback()
+ safety = !safety
+ if(safety == 1)
+ if(!is_syndicate)
+ set_security_level(previous_level)
+ visible_message("The [src] quiets down.")
+ if(!lighthack)
+ if(icon_state == "nuclearbomb2")
+ icon_state = "nuclearbomb1"
+ else
+ visible_message("The [src] emits a quiet whirling noise!")
//==========DAT FUKKEN DISK===============
/obj/item/disk/nuclear
diff --git a/code/game/gamemodes/nuclear/pinpointer.dm b/code/game/gamemodes/nuclear/pinpointer.dm
index e8f4c22e377..f6e454bd737 100644
--- a/code/game/gamemodes/nuclear/pinpointer.dm
+++ b/code/game/gamemodes/nuclear/pinpointer.dm
@@ -357,7 +357,8 @@
var/list/name_counts = list()
var/list/names = list()
- for(var/mob/living/carbon/human/H in GLOB.mob_list)
+ for(var/thing in GLOB.human_list)
+ var/mob/living/carbon/human/H = thing
if(!trackable(H))
continue
diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm
index b09bb7a851f..213814d52e8 100644
--- a/code/game/gamemodes/revolution/revolution.dm
+++ b/code/game/gamemodes/revolution/revolution.dm
@@ -385,7 +385,8 @@
if(foecount == GLOB.score_arrested)
GLOB.score_allarrested = 1
- for(var/mob/living/carbon/human/player in GLOB.mob_living_list)
+ for(var/thing in GLOB.human_list)
+ var/mob/living/carbon/human/player = thing
if(player.mind)
var/role = player.mind.assigned_role
if(role in list("Captain", "Head of Security", "Head of Personnel", "Chief Engineer", "Research Director"))
@@ -415,7 +416,8 @@
for(var/datum/mind/M in SSticker.mode:revolutionaries)
if(M.current && M.current.stat != DEAD)
revcount++
- for(var/mob/living/carbon/human/player in GLOB.mob_living_list)
+ for(var/thing in GLOB.human_list)
+ var/mob/living/carbon/human/player = thing
if(player.mind)
var/role = player.mind.assigned_role
if(role in list("Captain", "Head of Security", "Head of Personnel", "Chief Engineer", "Research Director"))
diff --git a/code/game/gamemodes/scoreboard.dm b/code/game/gamemodes/scoreboard.dm
index d9e59e9ded1..2e370db5f7a 100644
--- a/code/game/gamemodes/scoreboard.dm
+++ b/code/game/gamemodes/scoreboard.dm
@@ -41,7 +41,8 @@
GLOB.score_deadaipenalty++
GLOB.score_deadcrew++
- for(var/mob/living/carbon/human/I in GLOB.mob_list)
+ for(var/thing in GLOB.human_list)
+ var/mob/living/carbon/human/I = thing
if(I.stat == DEAD && is_station_level(I.z))
GLOB.score_deadcrew++
@@ -60,7 +61,8 @@
var/dmg_score = 0
if(SSshuttle.emergency.mode >= SHUTTLE_ENDGAME)
- for(var/mob/living/carbon/human/E in GLOB.mob_list)
+ for(var/thing in GLOB.human_list)
+ var/mob/living/carbon/human/E = thing
cash_score = 0
dmg_score = 0
var/turf/location = get_turf(E.loc)
diff --git a/code/game/gamemodes/shadowling/shadowling.dm b/code/game/gamemodes/shadowling/shadowling.dm
index b46bc3c6c51..b51810e4967 100644
--- a/code/game/gamemodes/shadowling/shadowling.dm
+++ b/code/game/gamemodes/shadowling/shadowling.dm
@@ -74,7 +74,7 @@ Made by Xhuis
required_enemies = 2
recommended_enemies = 2
restricted_jobs = list("AI", "Cyborg")
- protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Security Pod Pilot", "Magistrate", "Brig Physician", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer")
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Head of Personnel", "Captain", "Blueshield", "Nanotrasen Representative", "Security Pod Pilot", "Magistrate", "Brig Physician", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer")
/datum/game_mode/shadowling/announce()
to_chat(world, "The current game mode is - Shadowling!")
diff --git a/code/game/gamemodes/shadowling/shadowling_abilities.dm b/code/game/gamemodes/shadowling/shadowling_abilities.dm
index 27267d2ec02..4be50a6cd59 100644
--- a/code/game/gamemodes/shadowling/shadowling_abilities.dm
+++ b/code/game/gamemodes/shadowling/shadowling_abilities.dm
@@ -18,80 +18,56 @@
return 0
-/obj/effect/proc_holder/spell/targeted/glare //Stuns and mutes a human target, depending on the distance relative to the shadowling
+/obj/effect/proc_holder/spell/targeted/click/glare //Stuns and mutes a human target, depending on the distance relative to the shadowling
name = "Glare"
desc = "Stuns and mutes a target for a decent duration. Duration depends on the proximity to the target."
panel = "Shadowling Abilities"
charge_max = 300
- clothes_req = 0
+ clothes_req = FALSE
range = 10 //has no effect beyond this range, so setting this makes invalid/useless targets not show up in popup
action_icon_state = "glare"
- humans_only = 1 //useless since we override chose_targets, but might be used for other code later??? Might remove, idk
-/obj/effect/proc_holder/spell/targeted/glare/choose_targets(mob/user)
- var/list/possible_targets = list()
- for(var/mob/living/carbon/human/target in view_or_range(range, user, "view"))
- if(target.stat)
- continue
- if(is_shadow_or_thrall(target))
- continue
- possible_targets += target
- var/mob/living/carbon/human/M
- var/list/targets = list()
- if(possible_targets.len == 1)//no choice involved
- targets = possible_targets
- else
- M = input("Choose the target for the spell.", "Targeting") as mob in possible_targets
- if(M in view_or_range(range, user, "view"))
- targets += M
+ selection_activated_message = "Your prepare to your eyes for a stunning glare! Left-click to cast at a target!"
+ selection_deactivated_message = "Your eyes relax... for now."
+ allowed_type = /mob/living/carbon/human
+/obj/effect/proc_holder/spell/targeted/click/glare/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/user = usr)
+ if(!shadowling_check(user))
+ return FALSE
+ return ..()
- if(!targets.len) //doesn't waste the spell
- revert_cast(user)
+/obj/effect/proc_holder/spell/targeted/click/glare/valid_target(mob/living/carbon/human/target, user)
+ if(!..())
+ return FALSE
+ return !target.stat && !is_shadow_or_thrall(target)
+
+/obj/effect/proc_holder/spell/targeted/click/glare/cast(list/targets, mob/user = usr)
+ var/mob/living/carbon/human/H = targets[1]
+
+ user.visible_message("[user]'s eyes flash a blinding red!")
+ var/distance = get_dist(H, user)
+ if (distance <= 1) //Melee glare
+ H.visible_message("[H] freezes in place, [H.p_their()] eyes glazing over...", \
+ "Your gaze is forcibly drawn into [user]'s eyes, and you are mesmerized by [user.p_their()] heavenly beauty...")
+ H.Stun(10)
+ H.AdjustSilence(10)
+ else //Distant glare
+ var/loss = 10 - distance
+ var/duration = 10 - loss
+ if(loss <= 0)
+ to_chat(user, "Your glare had no effect over a such long distance!")
+ return
+ H.slowed = duration
+ H.AdjustSilence(10)
+ to_chat(H, "A red light flashes across your vision, and your mind tries to resist them.. you are exhausted.. you are not able to speak..")
+ addtimer(CALLBACK(src, .proc/do_stun, H, user, loss), duration SECONDS)
+
+/obj/effect/proc_holder/spell/targeted/click/glare/proc/do_stun(mob/living/carbon/human/target, user, stun_time)
+ if(!istype(target) || target.stat)
return
-
- perform(targets, user = user)
- return
-
-
-/obj/effect/proc_holder/spell/targeted/glare/cast(list/targets, mob/user = usr)
- for(var/mob/living/carbon/human/target in targets)
- if(!ishuman(target))
- to_chat(user, "You may only glare at humans!")
- charge_counter = charge_max
- return
- if(!shadowling_check(user))
- charge_counter = charge_max
- return
- if(target.stat)
- to_chat(user, "[target] must be conscious!")
- charge_counter = charge_max
- return
- if(is_shadow_or_thrall(target))
- to_chat(user, "You don't see why you would want to paralyze an ally.")
- charge_counter = charge_max
- return
- var/mob/living/carbon/human/M = target
- user.visible_message("[user]'s eyes flash a blinding red!")
- var/distance = get_dist(target, user)
- if (distance <= 1) //Melee glare
- target.visible_message("[target] freezes in place, [target.p_their()] eyes glazing over...")
- to_chat(target, "Your gaze is forcibly drawn into [user]'s eyes, and you are mesmerized by [user.p_their()] heavenly beauty...")
- target.Stun(10)
- M.AdjustSilence(10)
- else //Distant glare
- var/loss = 10 - distance
- var/duration = 10 - loss
- if(loss <= 0)
- to_chat(user, "Your glare had no effect over a such long distance!")
- return
- target.slowed = duration
- M.AdjustSilence(10)
- to_chat(target, "A red light flashes across your vision, and your mind tries to resist them.. you are exhausted.. you are not able to speak..")
- sleep(duration*10)
- target.Stun(loss)
- target.visible_message("[target] freezes in place, [target.p_their()] eyes glazing over...")
- to_chat(target, "Red lights suddenly dance in your vision, and you are mesmerized by the heavenly lights...")
+ target.Stun(stun_time)
+ target.visible_message("[target] freezes in place, [target.p_their()] eyes glazing over...",\
+ "Red lights suddenly dance in your vision, and you are mesmerized by the heavenly lights...")
/obj/effect/proc_holder/spell/aoe_turf/veil
name = "Veil"
@@ -109,9 +85,10 @@
return
to_chat(user, "You silently disable all nearby lights.")
for(var/obj/structure/glowshroom/G in orange(2, user)) //Why the fuck was this in the loop below?
- G.visible_message("\The [G] withers away!")
+ G.visible_message("[G] withers away!")
qdel(G)
for(var/turf/T in targets)
+ T.extinguish_light()
for(var/atom/A in T.contents)
A.extinguish_light()
@@ -227,96 +204,80 @@
M.reagents.add_reagent("frostoil", 15) //Half of a cryosting
-/obj/effect/proc_holder/spell/targeted/enthrall //Turns a target into the shadowling's slave. This overrides all previous loyalties
+/obj/effect/proc_holder/spell/targeted/click/enthrall //Turns a target into the shadowling's slave. This overrides all previous loyalties
name = "Enthrall"
desc = "Allows you to enslave a conscious, non-braindead, non-catatonic human to your will. This takes some time to cast."
panel = "Shadowling Abilities"
charge_max = 0
- clothes_req = 0
+ clothes_req = FALSE
range = 1 //Adjacent to user
- var/enthralling = 0
+ var/enthralling = FALSE
action_icon_state = "enthrall"
- humans_only = 1
-/obj/effect/proc_holder/spell/targeted/enthrall/cast(list/targets, mob/user = usr)
+ click_radius = -1 // Precision baby
+ selection_activated_message = "Your prepare your mind to entrall a mortal. Left-click to cast at a target!"
+ selection_deactivated_message = "Your mind relaxes."
+ allowed_type = /mob/living/carbon/human
+
+/obj/effect/proc_holder/spell/targeted/click/enthrall/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE)
+ if(enthralling || !shadowling_check(user))
+ return FALSE
+ return ..()
+
+/obj/effect/proc_holder/spell/targeted/click/enthrall/valid_target(mob/living/carbon/human/target, user)
+ if(!..())
+ return FALSE
+ return target.key && target.mind && !target.stat && !is_shadow_or_thrall(target) && target.client
+
+/obj/effect/proc_holder/spell/targeted/click/enthrall/cast(list/targets, mob/user = usr)
var/mob/living/carbon/human/ling = user
listclearnulls(SSticker.mode.shadowling_thralls)
if(!(ling.mind in SSticker.mode.shadows))
return
- if(!isshadowling(ling))
- if(SSticker.mode.shadowling_thralls.len >= 5)
- charge_counter = charge_max
- return
- for(var/mob/living/carbon/human/target in targets)
- if(!in_range(user, target))
- to_chat(user, "You need to be closer to enthrall [target].")
- charge_counter = charge_max
- return
- if(!target.key || !target.mind)
- to_chat(user, "The target has no mind.")
- charge_counter = charge_max
- return
- if(target.stat)
- to_chat(user, "The target must be conscious.")
- charge_counter = charge_max
- return
- if(is_shadow_or_thrall(target))
- to_chat(user, "You can not enthrall allies.")
- charge_counter = charge_max
- return
- if(!ishuman(target))
- to_chat(user, "You can only enthrall humans.")
- charge_counter = charge_max
- return
- if(enthralling)
- to_chat(user, "You are already enthralling!")
- charge_counter = charge_max
- return
- if(!target.client)
- to_chat(user, "[target]'s mind is vacant of activity.")
- enthralling = 1
- to_chat(user, "This target is valid. You begin the enthralling.")
- to_chat(target, "[user] stares at you. You feel your head begin to pulse.")
+ var/mob/living/carbon/human/target = targets[1]
+ enthralling = TRUE
+ to_chat(user, "This target is valid. You begin the enthralling.")
+ to_chat(target, "[user] stares at you. You feel your head begin to pulse.")
- for(var/progress = 0, progress <= 3, progress++)
- switch(progress)
- if(1)
- to_chat(user, "You place your hands to [target]'s head...")
- user.visible_message("[user] places [user.p_their()] hands onto the sides of [target]'s head!")
- if(2)
- to_chat(user, "You begin preparing [target]'s mind as a blank slate...")
- user.visible_message("[user]'s palms flare a bright red against [target]'s temples!")
- to_chat(target, "A terrible red light floods your mind. You collapse as conscious thought is wiped away.")
- target.Weaken(12)
- sleep(20)
- if(ismindshielded(target))
- to_chat(user, "They have a mindshield implant. You begin to deactivate it - this will take some time.")
- user.visible_message("[user] pauses, then dips [user.p_their()] head in concentration!")
- to_chat(target, "Your mindshield implant becomes hot as it comes under attack!")
- sleep(100) //10 seconds - not spawn() so the enthralling takes longer
- to_chat(user, "The nanobots composing the mindshield implant have been rendered inert. Now to continue.")
- user.visible_message("[user] relaxes again.")
- for(var/obj/item/implant/mindshield/L in target)
- if(L && L.implanted)
- qdel(L)
- to_chat(target, "Your mental protection implant unexpectedly falters, dims, dies.")
- if(3)
- to_chat(user, "You begin planting the tumor that will control the new thrall...")
- user.visible_message("A strange energy passes from [user]'s hands into [target]'s head!")
- to_chat(target, "You feel your memories twisting, morphing. A sense of horror dominates your mind.")
- if(!do_mob(user, target, 70)) //around 21 seconds total for enthralling, 31 for someone with a mindshield implant
- to_chat(user, "The enthralling has been interrupted - your target's mind returns to its previous state.")
- to_chat(target, "You wrest yourself away from [user]'s hands and compose yourself")
- enthralling = 0
- return
+ for(var/progress = 0, progress <= 3, progress++)
+ switch(progress)
+ if(1)
+ to_chat(user, "You place your hands to [target]'s head...")
+ user.visible_message("[user] places [user.p_their()] hands onto the sides of [target]'s head!")
+ if(2)
+ to_chat(user, "You begin preparing [target]'s mind as a blank slate...")
+ user.visible_message("[user]'s palms flare a bright red against [target]'s temples!")
+ to_chat(target, "A terrible red light floods your mind. You collapse as conscious thought is wiped away.")
+ target.Weaken(12)
+ sleep(20)
+ if(ismindshielded(target))
+ to_chat(user, "They have a mindshield implant. You begin to deactivate it - this will take some time.")
+ user.visible_message("[user] pauses, then dips [user.p_their()] head in concentration!")
+ to_chat(target, "Your mindshield implant becomes hot as it comes under attack!")
+ sleep(100) //10 seconds - not spawn() so the enthralling takes longer
+ to_chat(user, "The nanobots composing the mindshield implant have been rendered inert. Now to continue.")
+ user.visible_message("[user] relaxes again.")
+ for(var/obj/item/implant/mindshield/L in target)
+ if(L && L.implanted)
+ qdel(L)
+ to_chat(target, "Your mental protection implant unexpectedly falters, dims, dies.")
+ if(3)
+ to_chat(user, "You begin planting the tumor that will control the new thrall...")
+ user.visible_message("A strange energy passes from [user]'s hands into [target]'s head!")
+ to_chat(target, "You feel your memories twisting, morphing. A sense of horror dominates your mind.")
+ if(!do_mob(user, target, 70)) //around 21 seconds total for enthralling, 31 for someone with a mindshield implant
+ to_chat(user, "The enthralling has been interrupted - your target's mind returns to its previous state.")
+ to_chat(target, "You wrest yourself away from [user]'s hands and compose yourself")
+ enthralling = FALSE
+ return
- enthralling = 0
- to_chat(user, "You have enthralled [target]!")
- target.visible_message("[target] looks to have experienced a revelation!", \
- "False faces all dark not real not real not--")
- target.setOxyLoss(0) //In case the shadowling was choking them out
- SSticker.mode.add_thrall(target.mind)
- target.mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL
+ enthralling = FALSE
+ to_chat(user, "You have enthralled [target]!")
+ target.visible_message("[target] looks to have experienced a revelation!", \
+ "False faces all dark not real not real not--")
+ target.setOxyLoss(0) //In case the shadowling was choking them out
+ SSticker.mode.add_thrall(target.mind)
+ target.mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL
/obj/effect/proc_holder/spell/targeted/shadowling_regenarmor //Resets a shadowling's species to normal, removes genetic defects, and re-equips their armor
name = "Rapid Re-Hatch"
@@ -404,7 +365,7 @@
reviveThrallAcquired = 1
to_chat(target, "The power of your thralls has granted you the Black Recuperation ability. \
This will, after a short time, bring a dead thrall completely back to life with no bodily defects.")
- target.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/reviveThrall(null))
+ target.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/reviveThrall(null))
if(thralls < victory_threshold)
to_chat(target, "You do not have the power to ascend. You require [victory_threshold] thralls, but only [thralls] living thralls are present.")
@@ -570,169 +531,170 @@
-/obj/effect/proc_holder/spell/targeted/reviveThrall
+/obj/effect/proc_holder/spell/targeted/click/reviveThrall
name = "Black Recuperation"
desc = "Revives or empowers a thrall."
panel = "Shadowling Abilities"
range = 1
charge_max = 600
- clothes_req = 0
- include_user = 0
+ clothes_req = FALSE
+ include_user = FALSE
action_icon_state = "revive_thrall"
- humans_only = 1
+ click_radius = -1 // Precision baby
+ selection_activated_message = "You start focusing your powers on mending wounds of allies. Left-click to cast at a target!"
+ selection_deactivated_message = "Your mind relaxes."
+ allowed_type = /mob/living/carbon/human
-/obj/effect/proc_holder/spell/targeted/reviveThrall/cast(list/targets, mob/user = usr)
+/obj/effect/proc_holder/spell/targeted/click/reviveThrall/can_cast(mob/user = usr)
if(!shadowling_check(user))
- charge_counter = charge_max
- return
- for(var/mob/living/carbon/human/thrallToRevive in targets)
- var/choice = alert(user,"Empower a living thrall or revive a dead one?",,"Empower","Revive","Cancel")
- switch(choice)
- if("Empower")
- if(!is_thrall(thrallToRevive))
- to_chat(user, "[thrallToRevive] is not a thrall.")
- charge_counter = charge_max
- return
- if(thrallToRevive.stat != CONSCIOUS)
- to_chat(user, "[thrallToRevive] must be conscious to become empowered.")
- charge_counter = charge_max
- return
- if(isshadowlinglesser(thrallToRevive))
- to_chat(user, "[thrallToRevive] is already empowered.")
- charge_counter = charge_max
- return
- var/empowered_thralls = 0
- for(var/datum/mind/M in SSticker.mode.shadowling_thralls)
- if(!ishuman(M.current))
- return
- var/mob/living/carbon/human/H = M.current
- if(isshadowlinglesser(H))
- empowered_thralls++
- if(empowered_thralls >= EMPOWERED_THRALL_LIMIT)
- to_chat(user, "You cannot spare this much energy. There are too many empowered thralls.")
- charge_counter = charge_max
- return
- user.visible_message("[user] places [user.p_their()] hands over [thrallToRevive]'s face, red light shining from beneath.", \
- "You place your hands on [thrallToRevive]'s face and begin gathering energy...")
- to_chat(thrallToRevive, "[user] places [user.p_their()] hands over your face. You feel energy gathering. Stand still...")
- if(!do_mob(user, thrallToRevive, 80))
- to_chat(user, "Your concentration snaps. The flow of energy ebbs.")
- charge_counter = charge_max
- return
- to_chat(user, "You release a massive surge of power into [thrallToRevive]!")
- user.visible_message("Red lightning surges into [thrallToRevive]'s face!")
- playsound(thrallToRevive, 'sound/weapons/egloves.ogg', 50, 1)
- playsound(thrallToRevive, 'sound/machines/defib_zap.ogg', 50, 1)
- user.Beam(thrallToRevive,icon_state="red_lightning",icon='icons/effects/effects.dmi',time=1)
- thrallToRevive.Weaken(5)
- thrallToRevive.visible_message("[thrallToRevive] collapses, [thrallToRevive.p_their()] skin and face distorting!", \
- "AAAAAAAAAAAAAAAAAAAGH-")
- sleep(20)
- thrallToRevive.visible_message("[thrallToRevive] slowly rises, no longer recognizable as human.", \
- "You feel new power flow into you. You have been gifted by your masters. You now closely resemble them. You are empowered in \
- darkness but wither slowly in light. In addition, you now have glare and true shadow walk.")
- thrallToRevive.set_species(/datum/species/shadow/ling/lesser)
- thrallToRevive.mind.RemoveSpell(/obj/effect/proc_holder/spell/targeted/lesser_shadow_walk)
- thrallToRevive.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/glare(null))
- thrallToRevive.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadow_walk(null))
- if("Revive")
- if(!is_thrall(thrallToRevive))
- to_chat(user, "[thrallToRevive] is not a thrall.")
- charge_counter = charge_max
- return
- if(thrallToRevive.stat != DEAD)
- to_chat(user, "[thrallToRevive] is not dead.")
- charge_counter = charge_max
- return
- user.visible_message("[user] kneels over [thrallToRevive], placing [user.p_their()] hands on [thrallToRevive.p_their()] chest.", \
- "You crouch over the body of your thrall and begin gathering energy...")
- thrallToRevive.notify_ghost_cloning("Your masters are resuscitating you! Re-enter your corpse if you wish to be brought to life.", source = thrallToRevive)
- if(!do_mob(user, thrallToRevive, 30))
- to_chat(user, "Your concentration snaps. The flow of energy ebbs.")
- charge_counter = charge_max
- return
- to_chat(user, "You release a massive surge of power into [thrallToRevive]!")
- user.visible_message("Red lightning surges from [user]'s hands into [thrallToRevive]'s chest!")
- playsound(thrallToRevive, 'sound/weapons/egloves.ogg', 50, 1)
- playsound(thrallToRevive, 'sound/machines/defib_zap.ogg', 50, 1)
- user.Beam(thrallToRevive,icon_state="red_lightning",icon='icons/effects/effects.dmi',time=1)
- sleep(10)
- if(thrallToRevive.revive())
- thrallToRevive.visible_message("[thrallToRevive] heaves in breath, dim red light shining in [thrallToRevive.p_their()] eyes.", \
- "You have returned. One of your masters has brought you from the darkness beyond.")
- thrallToRevive.Weaken(4)
- thrallToRevive.emote("gasp")
- playsound(thrallToRevive, "bodyfall", 50, 1)
- else
- charge_counter = charge_max
- return
+ return FALSE
+ return ..()
-/obj/effect/proc_holder/spell/targeted/shadowling_extend_shuttle
+/obj/effect/proc_holder/spell/targeted/click/reviveThrall/valid_target(mob/living/carbon/human/target, user)
+ if(!..())
+ return FALSE
+
+ return is_thrall(target)
+
+/obj/effect/proc_holder/spell/targeted/click/reviveThrall/cast(list/targets, mob/user = usr)
+ var/mob/living/carbon/human/thrallToRevive = targets[1]
+ if(thrallToRevive.stat == CONSCIOUS)
+ if(isshadowlinglesser(thrallToRevive))
+ to_chat(user, "[thrallToRevive] is already empowered.")
+ revert_cast(user)
+ return
+ var/empowered_thralls = 0
+ for(var/datum/mind/M in SSticker.mode.shadowling_thralls)
+ if(!ishuman(M.current))
+ return
+ var/mob/living/carbon/human/H = M.current
+ if(isshadowlinglesser(H))
+ empowered_thralls++
+ if(empowered_thralls >= EMPOWERED_THRALL_LIMIT)
+ to_chat(user, "You cannot spare this much energy. There are too many empowered thralls.")
+ revert_cast(user)
+ return
+ user.visible_message("[user] places [user.p_their()] hands over [thrallToRevive]'s face, red light shining from beneath.", \
+ "You place your hands on [thrallToRevive]'s face and begin gathering energy...")
+ to_chat(thrallToRevive, "[user] places [user.p_their()] hands over your face. You feel energy gathering. Stand still...")
+ if(!do_mob(user, thrallToRevive, 80))
+ to_chat(user, "Your concentration snaps. The flow of energy ebbs.")
+ revert_cast(user)
+ return
+ to_chat(user, "You release a massive surge of power into [thrallToRevive]!")
+ user.visible_message("Red lightning surges into [thrallToRevive]'s face!")
+ playsound(thrallToRevive, 'sound/weapons/egloves.ogg', 50, 1)
+ playsound(thrallToRevive, 'sound/machines/defib_zap.ogg', 50, 1)
+ user.Beam(thrallToRevive,icon_state="red_lightning",icon='icons/effects/effects.dmi',time=1)
+ thrallToRevive.Weaken(5)
+ thrallToRevive.visible_message("[thrallToRevive] collapses, [thrallToRevive.p_their()] skin and face distorting!", \
+ "AAAAAAAAAAAAAAAAAAAGH-")
+ sleep(20)
+ thrallToRevive.visible_message("[thrallToRevive] slowly rises, no longer recognizable as human.", \
+ "You feel new power flow into you. You have been gifted by your masters. You now closely resemble them. You are empowered in \
+ darkness but wither slowly in light. In addition, you now have glare and true shadow walk.")
+ thrallToRevive.set_species(/datum/species/shadow/ling/lesser)
+ thrallToRevive.mind.RemoveSpell(/obj/effect/proc_holder/spell/targeted/lesser_shadow_walk)
+ thrallToRevive.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/glare(null))
+ thrallToRevive.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadow_walk(null))
+ else if(thrallToRevive.stat == DEAD)
+ user.visible_message("[user] kneels over [thrallToRevive], placing [user.p_their()] hands on [thrallToRevive.p_their()] chest.", \
+ "You crouch over the body of your thrall and begin gathering energy...")
+ thrallToRevive.notify_ghost_cloning("Your masters are resuscitating you! Re-enter your corpse if you wish to be brought to life.", source = thrallToRevive)
+ if(!do_mob(user, thrallToRevive, 30))
+ to_chat(user, "Your concentration snaps. The flow of energy ebbs.")
+ revert_cast(user)
+ return
+ to_chat(user, "You release a massive surge of power into [thrallToRevive]!")
+ user.visible_message("Red lightning surges from [user]'s hands into [thrallToRevive]'s chest!")
+ playsound(thrallToRevive, 'sound/weapons/egloves.ogg', 50, 1)
+ playsound(thrallToRevive, 'sound/machines/defib_zap.ogg', 50, 1)
+ user.Beam(thrallToRevive,icon_state="red_lightning",icon='icons/effects/effects.dmi',time=1)
+ sleep(10)
+ if(thrallToRevive.revive())
+ thrallToRevive.visible_message("[thrallToRevive] heaves in breath, dim red light shining in [thrallToRevive.p_their()] eyes.", \
+ "You have returned. One of your masters has brought you from the darkness beyond.")
+ thrallToRevive.Weaken(4)
+ thrallToRevive.emote("gasp")
+ playsound(thrallToRevive, "bodyfall", 50, 1)
+ else
+ to_chat(user, "The target must be awake to empower or dead to revive.")
+ revert_cast(user)
+
+/obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle
name = "Destroy Engines"
desc = "Extends the time of the emergency shuttle's arrival by ten minutes using a life force of our enemy. Shuttle will be unable to be recalled. This can only be used once."
panel = "Shadowling Abilities"
range = 1
- clothes_req = 0
+ clothes_req = FALSE
charge_max = 600
+ click_radius = -1 // Precision baby
+ selection_activated_message = "You start gathering destructive powers to delay the shuttle. Left-click to cast at a target!"
+ selection_deactivated_message = "Your mind relaxes."
+ allowed_type = /mob/living/carbon/human
action_icon_state = "extend_shuttle"
var/global/extendlimit = 0
-/obj/effect/proc_holder/spell/targeted/shadowling_extend_shuttle/cast(list/targets, mob/user = usr)
+/obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE)
if(!shadowling_check(user))
- charge_counter = charge_max
- return
+ return FALSE
if(extendlimit == 1)
- to_chat(user, "Shuttle was already delayed.")
- charge_counter = charge_max
- return
- for(var/mob/living/carbon/human/target in targets)
- if(target.stat)
- charge_counter = charge_max
- return
- if(is_shadow_or_thrall(target))
- to_chat(user, "[target] must not be an ally.")
- charge_counter = charge_max
- return
- if(SSshuttle.emergency.mode != SHUTTLE_CALL)
+ if(show_message)
+ to_chat(user, "Shuttle was already delayed.")
+ return FALSE
+ if(SSshuttle.emergency.mode != SHUTTLE_CALL)
+ if(show_message)
to_chat(user, "The shuttle must be inbound only to the station.")
- charge_counter = charge_max
- return
- var/mob/living/carbon/human/M = target
- user.visible_message("[user]'s eyes flash a bright red!", \
- "You begin to draw [M]'s life force.")
- M.visible_message("[M]'s face falls slack, [M.p_their()] jaw slightly distending.", \
- "You are suddenly transported... far, far away...")
- extendlimit = 1
- if(!do_after(user, 150, target = M))
- extendlimit = 0
- to_chat(M, "You are snapped back to reality, your haze dissipating!")
- to_chat(user, "You have been interrupted. The draw has failed.")
- return
- to_chat(user, "You project [M]'s life force toward the approaching shuttle, extending its arrival duration!")
- M.visible_message("[M]'s eyes suddenly flare red. They proceed to collapse on the floor, not breathing.", \
- "...speeding by... ...pretty blue glow... ...touch it... ...no glow now... ...no light... ...nothing at all...")
- M.death()
- if(SSshuttle.emergency.mode == SHUTTLE_CALL)
- var/more_minutes = 6000
- var/timer = SSshuttle.emergency.timeLeft(1) + more_minutes
- GLOB.event_announcement.Announce("Major system failure aboard the emergency shuttle. This will extend its arrival time by approximately 10 minutes and the shuttle is unable to be recalled.", "System Failure", 'sound/misc/notice1.ogg')
- SSshuttle.emergency.setTimer(timer)
- SSshuttle.emergency.canRecall = FALSE
- user.mind.spell_list.Remove(src) //Can only be used once!
- qdel(src)
+ return FALSE
+ return ..()
+
+/obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle/valid_target(mob/living/carbon/human/target, user)
+ if(!..())
+ return FALSE
+ return !target.stat && !is_shadow_or_thrall(target)
+
+
+/obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle/cast(list/targets, mob/user = usr)
+ var/mob/living/carbon/human/target = targets[1]
+
+ user.visible_message("[user]'s eyes flash a bright red!", \
+ "You begin to draw [target]'s life force.")
+ target.visible_message("[target]'s face falls slack, [target.p_their()] jaw slightly distending.", \
+ "You are suddenly transported... far, far away...")
+ extendlimit = 1
+ if(!do_after(user, 150, target = target))
+ extendlimit = 0
+ to_chat(target, "You are snapped back to reality, your haze dissipating!")
+ to_chat(user, "You have been interrupted. The draw has failed.")
+ return
+ to_chat(user, "You project [target]'s life force toward the approaching shuttle, extending its arrival duration!")
+ target.visible_message("[target]'s eyes suddenly flare red. They proceed to collapse on the floor, not breathing.", \
+ "...speeding by... ...pretty blue glow... ...touch it... ...no glow now... ...no light... ...nothing at all...")
+ target.death()
+ if(SSshuttle.emergency.mode == SHUTTLE_CALL)
+ var/more_minutes = 6000
+ var/timer = SSshuttle.emergency.timeLeft(1) + more_minutes
+ GLOB.event_announcement.Announce("Major system failure aboard the emergency shuttle. This will extend its arrival time by approximately 10 minutes and the shuttle is unable to be recalled.", "System Failure", 'sound/misc/notice1.ogg')
+ SSshuttle.emergency.setTimer(timer)
+ SSshuttle.emergency.canRecall = FALSE
+ user.mind.spell_list.Remove(src) //Can only be used once!
+ qdel(src)
// ASCENDANT ABILITIES BEYOND THIS POINT //
-/obj/effect/proc_holder/spell/targeted/annihilate
+/obj/effect/proc_holder/spell/targeted/click/annihilate
name = "Annihilate"
desc = "Gibs someone instantly."
panel = "Ascendant"
range = 7
- charge_max = 0
- clothes_req = 0
+ charge_max = FALSE
+ clothes_req = FALSE
action_icon_state = "annihilate"
+ selection_activated_message = "You start thinking about gibs. Left-click to cast at a target!"
+ selection_deactivated_message = "Your mind relaxes."
+ allowed_type = /mob/living/carbon/human
-/obj/effect/proc_holder/spell/targeted/annihilate/cast(list/targets, mob/user = usr)
+/obj/effect/proc_holder/spell/targeted/click/annihilate/cast(list/targets, mob/user = usr)
var/mob/living/simple_animal/ascendant_shadowling/SHA = user
if(SHA.phasing)
to_chat(user, "You are not in the same plane of existence. Unphase first.")
@@ -755,45 +717,42 @@
-/obj/effect/proc_holder/spell/targeted/hypnosis
+/obj/effect/proc_holder/spell/targeted/click/hypnosis
name = "Hypnosis"
desc = "Instantly enthralls a human."
panel = "Ascendant"
range = 7
- charge_max = 0
- clothes_req = 0
+ charge_max = FALSE
+ clothes_req = FALSE
action_icon_state = "enthrall"
-/obj/effect/proc_holder/spell/targeted/hypnosis/cast(list/targets, mob/user = usr)
- var/mob/living/simple_animal/ascendant_shadowling/SHA = user
- if(SHA.phasing)
- charge_counter = charge_max
- to_chat(user, "You are not in the same plane of existence. Unphase first.")
- return
+ click_radius = -1
+ selection_activated_message = "You start preparing to mindwash over a mortal mind. Left-click to cast at a target!"
+ selection_deactivated_message = "Your mind relaxes."
+ allowed_type = /mob/living/carbon/human
- for(var/mob/living/carbon/human/target in targets)
- if(is_shadow_or_thrall(target))
- to_chat(user, "You cannot enthrall an ally.")
- charge_counter = charge_max
- return
- if(!target.ckey || !target.mind)
- to_chat(user, "The target has no mind.")
- charge_counter = charge_max
- return
- if(target.stat)
- to_chat(user, "The target must be conscious.")
- charge_counter = charge_max
- return
- if(!ishuman(target))
- to_chat(user, "You can only enthrall humans.")
- charge_counter = charge_max
- return
+/obj/effect/proc_holder/spell/targeted/click/hypnosis/can_cast(mob/living/simple_animal/ascendant_shadowling/user = usr, charge_check = TRUE, show_message = FALSE)
+ if(!istype(user))
+ return FALSE
+ if(user.phasing)
+ if(show_message)
+ to_chat(user, "You are not in the same plane of existence. Unphase first.")
+ return FALSE
+ return ..()
- to_chat(user, "You instantly rearrange [target]'s memories, hyptonitizing [target.p_them()] into a thrall.")
- to_chat(target, "An agonizing spike of pain drives into your mind, and--")
- SSticker.mode.add_thrall(target.mind)
- target.mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL
- target.add_language("Shadowling Hivemind")
+/obj/effect/proc_holder/spell/targeted/click/hypnosis/valid_target(mob/living/carbon/human/target, user)
+ if(!..())
+ return FALSE
+ return !is_shadow_or_thrall(target) && target.ckey && target.mind && !target.stat
+
+/obj/effect/proc_holder/spell/targeted/click/hypnosis/cast(list/targets, mob/user = usr)
+ var/mob/living/carbon/human/target = targets[1]
+
+ to_chat(user, "You instantly rearrange [target]'s memories, hyptonitizing [target.p_them()] into a thrall.")
+ to_chat(target, "An agonizing spike of pain drives into your mind, and--")
+ SSticker.mode.add_thrall(target.mind)
+ target.mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL
+ target.add_language("Shadowling Hivemind")
diff --git a/code/game/gamemodes/shadowling/special_shadowling_abilities.dm b/code/game/gamemodes/shadowling/special_shadowling_abilities.dm
index 5fc5e1e78cc..28c934f7d38 100644
--- a/code/game/gamemodes/shadowling/special_shadowling_abilities.dm
+++ b/code/game/gamemodes/shadowling/special_shadowling_abilities.dm
@@ -99,14 +99,14 @@ GLOBAL_LIST_INIT(possibleShadowlingNames, list("U'ruan", "Y`shej", "Nex", "Hel-u
to_chat(H, "Your powers are awoken. You may now live to your fullest extent. Remember your goal. Cooperate with your thralls and allies.")
H.ExtinguishMob()
H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadow_vision(null))
- H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/enthrall(null))
- H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/glare(null))
+ H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/enthrall(null))
+ H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/glare(null))
H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/veil(null))
H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadow_walk(null))
H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/flashfreeze(null))
H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/collective_mind(null))
H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadowling_regenarmor(null))
- H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadowling_extend_shuttle(null))
+ H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle(null))
QDEL_NULL(H.hud_used)
H.hud_used = new /datum/hud/human(H, ui_style2icon(H.client.prefs.UI_style), H.client.prefs.UI_style_color, H.client.prefs.UI_style_alpha)
@@ -163,7 +163,7 @@ GLOBAL_LIST_INIT(possibleShadowlingNames, list("U'ruan", "Y`shej", "Nex", "Hel-u
to_chat(M, "An immense pressure slams you onto the ground!")
for(var/thing in GLOB.apcs)
var/obj/machinery/power/apc/A = thing
- A.overload_lighting()
+ INVOKE_ASYNC(A, /obj/machinery/power/apc.proc/overload_lighting)
var/mob/living/simple_animal/ascendant_shadowling/A = new /mob/living/simple_animal/ascendant_shadowling(H.loc)
A.announce("VYSHA NERADA YEKHEZET U'RUU!!", 5, 'sound/hallucinations/veryfar_noise.ogg')
for(var/obj/effect/proc_holder/spell/S in H.mind.spell_list)
@@ -172,8 +172,8 @@ GLOBAL_LIST_INIT(possibleShadowlingNames, list("U'ruan", "Y`shej", "Nex", "Hel-u
H.mind.transfer_to(A)
A.name = H.real_name
A.languages = H.languages
- A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/annihilate(null))
- A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/hypnosis(null))
+ A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/annihilate(null))
+ A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/hypnosis(null))
A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadowling_phase_shift(null))
A.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/ascendant_storm(null))
A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadowlingAscendantTransmit(null))
diff --git a/code/game/gamemodes/steal_items.dm b/code/game/gamemodes/steal_items.dm
index 4175883fc1a..c7ce0261bd0 100644
--- a/code/game/gamemodes/steal_items.dm
+++ b/code/game/gamemodes/steal_items.dm
@@ -43,7 +43,7 @@
protected_jobs = list("Captain")
/datum/theft_objective/hoslaser
- name = "the head of security's recreated antique laser gun"
+ name = "the head of security's X-01 multiphase energy gun"
typepath = /obj/item/gun/energy/gun/hos
protected_jobs = list("Head Of Security")
@@ -57,7 +57,7 @@
typepath = /obj/item/aicard
location_override = "AI Satellite. An intellicard for transportation can be found in Tech Storage, Science Department or manufactured"
-datum/theft_objective/ai/check_special_completion(var/obj/item/aicard/C)
+/datum/theft_objective/ai/check_special_completion(var/obj/item/aicard/C)
if(..())
for(var/mob/living/silicon/ai/A in C)
if(istype(A, /mob/living/silicon/ai) && A.stat != 2) //See if any AI's are alive inside that card.
diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm
index c7ab4490f48..e7c5b4541a8 100644
--- a/code/game/gamemodes/traitor/traitor.dm
+++ b/code/game/gamemodes/traitor/traitor.dm
@@ -82,22 +82,10 @@
/datum/game_mode/proc/auto_declare_completion_traitor()
if(traitors.len)
- var/text = "The traitors were:"
+ var/text = "The traitors were: "
for(var/datum/mind/traitor in traitors)
var/traitorwin = 1
-
- text += " [traitor.key] was [traitor.name] ("
- if(traitor.current)
- if(traitor.current.stat == DEAD)
- text += "died"
- else
- text += "survived"
- if(traitor.current.real_name != traitor.name)
- text += " as [traitor.current.real_name]"
- else
- text += "body destroyed"
- text += ")"
-
+ text += printplayer(traitor)
var/TC_uses = 0
var/uplink_true = 0
@@ -131,12 +119,20 @@
if(traitorwin)
- text += " The [special_role_text] was successful!"
+ text += " The [special_role_text] was successful! "
feedback_add_details("traitor_success","SUCCESS")
else
- text += " The [special_role_text] has failed!"
+ text += " The [special_role_text] has failed! "
feedback_add_details("traitor_success","FAIL")
+ if(length(SSticker.mode.implanted))
+ text += "
The mindslaves were: "
+ for(var/datum/mind/mindslave in SSticker.mode.implanted)
+ text += printplayer(mindslave)
+ var/datum/mind/master_mind = SSticker.mode.implanted[mindslave]
+ var/mob/living/carbon/human/master = master_mind.current
+ text += " (slaved by: [master]) "
+
var/phrases = jointext(GLOB.syndicate_code_phrase, ", ")
var/responses = jointext(GLOB.syndicate_code_response, ", ")
diff --git a/code/game/gamemodes/vampire/vampire.dm b/code/game/gamemodes/vampire/vampire.dm
index 3caf860383d..75748448527 100644
--- a/code/game/gamemodes/vampire/vampire.dm
+++ b/code/game/gamemodes/vampire/vampire.dm
@@ -279,6 +279,7 @@ You are weak to holy things and starlight. Don't go into space and avoid the Cha
var/blood = 0
var/old_bloodtotal = 0 //used to see if we increased our blood total
var/old_bloodusable = 0 //used to see if we increased our blood usable
+ var/blood_volume_warning = 9999 //Blood volume threshold for warnings
if(owner.is_muzzled())
to_chat(owner, "[owner.wear_mask] prevents you from biting [H]!")
draining = null
@@ -291,13 +292,10 @@ You are weak to holy things and starlight. Don't go into space and avoid the Cha
H.LAssailant = owner
while(do_mob(owner, H, 50))
if(!(owner.mind in SSticker.mode.vampires))
- to_chat(owner, "Your fangs have disappeared!")
+ to_chat(owner, "Your fangs have disappeared!")
return
old_bloodtotal = bloodtotal
old_bloodusable = bloodusable
- if(!H.blood_volume)
- to_chat(owner, "They've got no blood left to give.")
- break
if(H.stat < DEAD)
if(H.ckey || H.player_ghosted) //Requires ckey regardless if monkey or humanoid, or the body has been ghosted before it died
blood = min(20, H.blood_volume) // if they have less than 20 blood, give them the remnant else they get 20 blood
@@ -312,6 +310,17 @@ You are weak to holy things and starlight. Don't go into space and avoid the Cha
to_chat(owner, "You have accumulated [bloodtotal] [bloodtotal > 1 ? "units" : "unit"] of blood[bloodusable != old_bloodusable ? ", and have [bloodusable] left to use" : ""].")
check_vampire_upgrade()
H.blood_volume = max(H.blood_volume - 25, 0)
+ //Blood level warnings (Code 'borrowed' from Fulp)
+ if(H.blood_volume)
+ if(H.blood_volume <= BLOOD_VOLUME_BAD && blood_volume_warning > BLOOD_VOLUME_BAD)
+ to_chat(owner, "Your victim's blood volume is dangerously low.")
+ else if(H.blood_volume <= BLOOD_VOLUME_OKAY && blood_volume_warning > BLOOD_VOLUME_OKAY)
+ to_chat(owner, "Your victim's blood is at an unsafe level.")
+ blood_volume_warning = H.blood_volume //Set to blood volume, so that you only get the message once
+ else
+ to_chat(owner, "You have bled your victim dry!")
+ break
+
if(ishuman(owner))
var/mob/living/carbon/human/V = owner
if(!H.ckey && !H.player_ghosted)//Only runs if there is no ckey and the body has not being ghosted while alive
diff --git a/code/game/gamemodes/vampire/vampire_powers.dm b/code/game/gamemodes/vampire/vampire_powers.dm
index 7e0f00f8721..0ff7192a7e6 100644
--- a/code/game/gamemodes/vampire/vampire_powers.dm
+++ b/code/game/gamemodes/vampire/vampire_powers.dm
@@ -15,7 +15,7 @@
if(!gain_desc)
gain_desc = "You have gained \the [src] ability."
-/obj/effect/proc_holder/spell/vampire/cast_check(skipcharge = 0, mob/living/user = usr)
+/obj/effect/proc_holder/spell/vampire/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/user = usr)
if(!user.mind)
return 0
if(!ishuman(user))
@@ -45,7 +45,7 @@
return 0
return ..()
-/obj/effect/proc_holder/spell/vampire/can_cast(mob/user = usr)
+/obj/effect/proc_holder/spell/vampire/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE)
if(!user.mind)
return 0
if(!ishuman(user))
@@ -145,7 +145,7 @@
/obj/effect/proc_holder/spell/vampire/self/rejuvenate
name = "Rejuvenate"
- desc= "Flush your system with spare blood to remove any incapacitating effects."
+ desc= "Use reserve blood to enliven your body, removing any incapacitating effects."
action_icon_state = "vampire_rejuvinate"
charge_max = 200
stat_allowed = 1
@@ -158,7 +158,7 @@
user.SetParalysis(0)
user.SetSleeping(0)
U.adjustStaminaLoss(-75)
- to_chat(user, "You flush your system with clean blood and remove any incapacitating effects.")
+ to_chat(user, "You instill your body with clean blood and remove any incapacitating effects.")
spawn(1)
if(usr.mind.vampire.get_ability(/datum/vampire_passive/regen))
for(var/i = 1 to 5)
@@ -264,7 +264,7 @@
continue
if(ishuman(C))
var/mob/living/carbon/human/H = C
- if(istype(H.l_ear, /obj/item/clothing/ears/earmuffs) || istype(H.r_ear, /obj/item/clothing/ears/earmuffs))
+ if(H.check_ear_prot() >= HEARING_PROTECTION_TOTAL)
continue
if(!affects(C))
continue
@@ -354,10 +354,10 @@
var/datum/objective/protect/serve_objective = new
serve_objective.owner = user.mind
serve_objective.target = H.mind
- serve_objective.explanation_text = "You have been Enthralled by [user]. Follow [user.p_their()] every command."
+ serve_objective.explanation_text = "You have been Enthralled by [user.real_name]. Follow [user.p_their()] every command."
H.mind.objectives += serve_objective
- to_chat(H, "You have been Enthralled by [user]. Follow [user.p_their()] every command.")
+ to_chat(H, "You have been Enthralled by [user.real_name]. Follow [user.p_their()] every command.")
to_chat(user, "You have successfully Enthralled [H]. If [H.p_they()] refuse[H.p_s()] to do as you say just adminhelp.")
H.Stun(2)
add_attack_logs(user, H, "Vampire-thralled")
diff --git a/code/game/gamemodes/wizard/artefact.dm b/code/game/gamemodes/wizard/artefact.dm
index 09b228bdd0c..cb0c277b1e6 100644
--- a/code/game/gamemodes/wizard/artefact.dm
+++ b/code/game/gamemodes/wizard/artefact.dm
@@ -50,7 +50,8 @@
to_chat(H, "You already used this contract!")
return
used = 1
- var/list/candidates = pollCandidates("Do you want to play as the wizard apprentice of [H.real_name]?", ROLE_WIZARD, 1)
+ var/image/source = image('icons/obj/cardboard_cutout.dmi', "cutout_wizard")
+ var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as the wizard apprentice of [H.real_name]?", ROLE_WIZARD, TRUE, source = source)
if(candidates.len)
var/mob/C = pick(candidates)
new /obj/effect/particle_effect/smoke(H.loc)
@@ -60,7 +61,7 @@
switch(href_list["school"])
if("destruction")
M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/projectile/magic_missile(null))
- M.mind.AddSpell(new /obj/effect/proc_holder/spell/fireball(null))
+ M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/fireball(null))
to_chat(M, "Your service has not gone unrewarded, however. Studying under [H.real_name], you have learned powerful, destructive spells. You are able to cast magic missile and fireball.")
if("bluespace")
M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/area_teleport/teleport(null))
@@ -73,7 +74,7 @@
to_chat(M, "Your service has not gone unrewarded, however. Studying under [H.real_name], you have learned livesaving survival spells. You are able to cast charge and forcewall.")
if("robeless")
M.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null))
- M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mind_transfer(null))
+ M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/mind_transfer(null))
to_chat(M, "Your service has not gone unrewarded, however. Studying under [H.real_name], you have learned stealthy, robeless spells. You are able to cast knock and mindswap.")
M.equip_to_slot_or_del(new /obj/item/radio/headset(M), slot_l_ear)
@@ -307,7 +308,8 @@ GLOBAL_LIST_EMPTY(multiverse)
if(M.assigned == assigned)
M.cooldown = cooldown
- var/list/candidates = pollCandidates("Do you want to play as the wizard apprentice of [user.real_name]?", ROLE_WIZARD, 1, 100)
+ var/image/source = image('icons/obj/cardboard_cutout.dmi', "cutout_wizard")
+ var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as the wizard apprentice of [user.real_name]?", ROLE_WIZARD, TRUE, 10 SECONDS, source = source)
if(candidates.len)
var/mob/C = pick(candidates)
spawn_copy(C.client, get_turf(user.loc), user)
@@ -834,6 +836,10 @@ GLOBAL_LIST_EMPTY(multiverse)
var/wgw = sanitize(input(user, "What would you like the victim to say", "Voodoo", null) as text)
target.say(wgw)
log_game("[user][user.key] made [target][target.key] say [wgw] with a voodoo doll.")
+ log_say("Wicker doll say to [target][target.key]: [wgw]", user)
+ log_admin("[user][user.key] made [target][target.key] say [wgw] with a voodoo doll.")
+ user.create_log(SAY_LOG, "forced [target] to say [wgw] through [src].", target)
+ target.create_log(SAY_LOG, "was forced to say [wgw] through [src] by [user].", user)
if("eyes")
user.set_machine(src)
user.reset_perspective(target)
@@ -866,8 +872,9 @@ GLOBAL_LIST_EMPTY(multiverse)
possible = list()
if(!link)
return
- for(var/mob/living/carbon/human/H in GLOB.alive_mob_list)
- if(md5(H.dna.uni_identity) in link.fingerprints)
+ for(var/thing in GLOB.human_list)
+ var/mob/living/carbon/human/H = thing
+ if(H.stat != DEAD && (md5(H.dna.uni_identity) in link.fingerprints))
possible |= H
/obj/item/voodoo/proc/GiveHint(mob/victim,force=0)
diff --git a/code/game/gamemodes/wizard/raginmages.dm b/code/game/gamemodes/wizard/raginmages.dm
index cc52b431249..d51b16d2cd3 100644
--- a/code/game/gamemodes/wizard/raginmages.dm
+++ b/code/game/gamemodes/wizard/raginmages.dm
@@ -118,7 +118,8 @@
return FALSE
making_mage = TRUE
- var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as a raging Space Wizard?", ROLE_WIZARD, TRUE, poll_time = 20 SECONDS)
+ var/image/source = image('icons/obj/cardboard_cutout.dmi', "cutout_wizard")
+ var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as a raging Space Wizard?", ROLE_WIZARD, TRUE, poll_time = 20 SECONDS, source = source)
var/mob/dead/observer/harry = null
message_admins("SWF is still pissed, sending another wizard - [max_mages - mages_made] left.")
diff --git a/code/game/gamemodes/wizard/soulstone.dm b/code/game/gamemodes/wizard/soulstone.dm
index 4bda254d930..aab4318c774 100644
--- a/code/game/gamemodes/wizard/soulstone.dm
+++ b/code/game/gamemodes/wizard/soulstone.dm
@@ -363,7 +363,7 @@
break
if(!chosen_ghost) //Failing that, we grab a ghost
- var/list/consenting_candidates = pollCandidates("Would you like to play as a Shade?", ROLE_CULTIST, FALSE, poll_time = 100)
+ var/list/consenting_candidates = SSghost_spawns.poll_candidates("Would you like to play as a Shade?", ROLE_CULTIST, FALSE, poll_time = 10 SECONDS, source = /mob/living/simple_animal/shade)
if(consenting_candidates.len)
chosen_ghost = pick(consenting_candidates)
if(!T)
diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm
index d1695a86603..8efb04483cb 100644
--- a/code/game/gamemodes/wizard/spellbook.dm
+++ b/code/game/gamemodes/wizard/spellbook.dm
@@ -80,7 +80,7 @@
user.mind.spell_list.Remove(aspell)
qdel(aspell)
if(S) //If we created a temporary spell above, delete it now.
- qdel(S)
+ QDEL_NULL(S)
return cost * (spell_levels + 1)
return -1
@@ -132,7 +132,7 @@
/datum/spellbook_entry/horseman
name = "Curse of the Horseman"
- spell_type = /obj/effect/proc_holder/spell/targeted/horsemask
+ spell_type = /obj/effect/proc_holder/spell/targeted/click/horsemask
log_name = "HH"
category = "Offensive"
@@ -144,7 +144,7 @@
/datum/spellbook_entry/fireball
name = "Fireball"
- spell_type = /obj/effect/proc_holder/spell/fireball
+ spell_type = /obj/effect/proc_holder/spell/targeted/click/fireball
log_name = "FB"
category = "Offensive"
@@ -257,7 +257,7 @@
/datum/spellbook_entry/mindswap
name = "Mindswap"
- spell_type = /obj/effect/proc_holder/spell/targeted/mind_transfer
+ spell_type = /obj/effect/proc_holder/spell/targeted/click/mind_transfer
log_name = "MT"
category = "Mobility"
@@ -610,7 +610,7 @@
var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon - /datum/spellbook_entry/loadout
for(var/T in entry_types)
var/datum/spellbook_entry/E = new T
- if(GAMEMODE_IS_WIZARD && E.is_ragin_restricted)
+ if(GAMEMODE_IS_RAGIN_MAGES && E.is_ragin_restricted)
qdel(E)
continue
entries |= E
@@ -877,7 +877,7 @@
return
/obj/item/spellbook/oneuse/fireball
- spell = /obj/effect/proc_holder/spell/fireball
+ spell = /obj/effect/proc_holder/spell/targeted/click/fireball
spellname = "fireball"
icon_state = "bookfireball"
desc = "This book feels warm to the touch."
@@ -910,7 +910,7 @@
user.EyeBlind(10)
/obj/item/spellbook/oneuse/mindswap
- spell = /obj/effect/proc_holder/spell/targeted/mind_transfer
+ spell = /obj/effect/proc_holder/spell/targeted/click/mind_transfer
spellname = "mindswap"
icon_state = "bookmindswap"
desc = "This book's cover is pristine, though its pages look ragged and torn."
@@ -934,8 +934,8 @@
to_chat(user, "You stare at the book some more, but there doesn't seem to be anything else to learn...")
return
- var/obj/effect/proc_holder/spell/targeted/mind_transfer/swapper = new
- swapper.cast(user, stored_swap, 1)
+ var/obj/effect/proc_holder/spell/targeted/click/mind_transfer/swapper = new
+ swapper.cast(user, stored_swap)
to_chat(stored_swap, "You're suddenly somewhere else... and someone else?!")
to_chat(user, "Suddenly you're staring at [src] again... where are you, who are you?!")
@@ -966,7 +966,7 @@
user.Weaken(20)
/obj/item/spellbook/oneuse/horsemask
- spell = /obj/effect/proc_holder/spell/targeted/horsemask
+ spell = /obj/effect/proc_holder/spell/targeted/click/horsemask
spellname = "horses"
icon_state = "bookhorses"
desc = "This book is more horse than your mind has room for."
diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm
index a0bb4c349af..77972b00880 100644
--- a/code/game/gamemodes/wizard/wizard.dm
+++ b/code/game/gamemodes/wizard/wizard.dm
@@ -45,9 +45,8 @@
for(var/datum/mind/wizard in wizards)
log_game("[key_name(wizard)] has been selected as a Wizard")
forge_wizard_objectives(wizard)
- //learn_basic_spells(wizard.current)
equip_wizard(wizard.current)
- name_wizard(wizard.current)
+ INVOKE_ASYNC(src, .proc/name_wizard, wizard.current)
greet_wizard(wizard)
if(use_huds)
update_wiz_icons_added(wizard)
@@ -89,17 +88,15 @@
var/wizard_name_first = pick(GLOB.wizard_first)
var/wizard_name_second = pick(GLOB.wizard_second)
var/randomname = "[wizard_name_first] [wizard_name_second]"
- spawn(0)
- var/newname = sanitize(copytext(input(wizard_mob, "You are the Space Wizard. Would you like to change your name to something else?", "Name change", randomname) as null|text,1,MAX_NAME_LEN))
+ var/newname = sanitize(copytext(input(wizard_mob, "You are the Space Wizard. Would you like to change your name to something else?", "Name change", randomname) as null|text,1,MAX_NAME_LEN))
- if(!newname)
- newname = randomname
+ if(!newname)
+ newname = randomname
- wizard_mob.real_name = newname
- wizard_mob.name = newname
- if(wizard_mob.mind)
- wizard_mob.mind.name = newname
- return
+ wizard_mob.real_name = newname
+ wizard_mob.name = newname
+ if(wizard_mob.mind)
+ wizard_mob.mind.name = newname
/datum/game_mode/proc/greet_wizard(var/datum/mind/wizard, var/you_are=1)
addtimer(CALLBACK(wizard.current, /mob/.proc/playsound_local, null, 'sound/ambience/antag/ragesmages.ogg', 100, 0), 30)
diff --git a/code/game/gamemodes/wizard/wizloadouts.dm b/code/game/gamemodes/wizard/wizloadouts.dm
index c050b27f273..518b9a68ca8 100644
--- a/code/game/gamemodes/wizard/wizloadouts.dm
+++ b/code/game/gamemodes/wizard/wizloadouts.dm
@@ -18,7 +18,7 @@
Care should be taken in hiding the item you choose as your phylactery after using Bind Soul, as you cannot revive if it destroyed or too far from your body!
\
Provides Bind Soul, Ethereal Jaunt, Fireball, Rod Form, Disable Tech, and Greater Forcewall."
log_name = "DL"
- spells_path = list(/obj/effect/proc_holder/spell/targeted/lichdom, /obj/effect/proc_holder/spell/targeted/ethereal_jaunt, /obj/effect/proc_holder/spell/fireball, \
+ spells_path = list(/obj/effect/proc_holder/spell/targeted/lichdom, /obj/effect/proc_holder/spell/targeted/ethereal_jaunt, /obj/effect/proc_holder/spell/targeted/click/fireball, \
/obj/effect/proc_holder/spell/targeted/rod_form, /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech, /obj/effect/proc_holder/spell/targeted/forcewall/greater)
is_ragin_restricted = TRUE
diff --git a/code/game/jobs/access.dm b/code/game/jobs/access.dm
index d5a59a36872..ac533f0b7ef 100644
--- a/code/game/jobs/access.dm
+++ b/code/game/jobs/access.dm
@@ -426,6 +426,7 @@
//gets the alt title, failing that the actual job rank
//this is unused
+// THEN WHY IS IT STILL HERE?? -AA07, 2020-07-31
/obj/proc/sdsdsd() //GetJobDisplayName
if(!istype(src, /obj/item/pda) && !istype(src,/obj/item/card/id))
return
@@ -442,7 +443,7 @@
return "Unknown"
-proc/GetIdCard(var/mob/living/carbon/human/H)
+/proc/GetIdCard(var/mob/living/carbon/human/H)
if(H.wear_id)
var/id = H.wear_id.GetID()
if(id)
@@ -451,7 +452,7 @@ proc/GetIdCard(var/mob/living/carbon/human/H)
var/obj/item/I = H.get_active_hand()
return I.GetID()
-proc/FindNameFromID(var/mob/living/carbon/human/H)
+/proc/FindNameFromID(var/mob/living/carbon/human/H)
ASSERT(istype(H))
var/obj/item/card/id/C = H.get_active_hand()
if( istype(C) || istype(C, /obj/item/pda) )
@@ -480,7 +481,7 @@ proc/FindNameFromID(var/mob/living/carbon/human/H)
if(ID)
return ID.registered_name
-proc/get_all_job_icons() //For all existing HUD icons
+/proc/get_all_job_icons() //For all existing HUD icons
return GLOB.joblist + list("Prisoner")
/obj/proc/GetJobName() //Used in secHUD icon generation
@@ -510,3 +511,26 @@ proc/get_all_job_icons() //For all existing HUD icons
return rankName
return "Unknown" //Return unknown if none of the above apply
+
+/proc/get_accesslist_static_data(num_min_region = REGION_GENERAL, num_max_region = REGION_COMMAND)
+ var/list/retval
+ for(var/i in num_min_region to num_max_region)
+ var/list/accesses = list()
+ var/list/available_accesses
+ if(i == REGION_CENTCOMM) // Override necessary, because get_region_accesses(REGION_CENTCOM) returns BOTH CC and crew accesses.
+ available_accesses = get_all_centcom_access()
+ else
+ available_accesses = get_region_accesses(i)
+ for(var/access in available_accesses)
+ var/access_desc = (i == REGION_CENTCOMM) ? get_centcom_access_desc(access) : get_access_desc(access)
+ if (access_desc)
+ accesses += list(list(
+ "desc" = replacetext(access_desc, " ", " "),
+ "ref" = access,
+ ))
+ retval += list(list(
+ "name" = get_region_accesses_name(i),
+ "regid" = i,
+ "accesses" = accesses
+ ))
+ return retval
diff --git a/code/game/jobs/job/central.dm b/code/game/jobs/job/central.dm
index f91460ba234..8684ca7d1b0 100644
--- a/code/game/jobs/job/central.dm
+++ b/code/game/jobs/job/central.dm
@@ -94,7 +94,7 @@
)
cybernetic_implants = list(
/obj/item/organ/internal/cyberimp/eyes/xray,
- /obj/item/organ/internal/cyberimp/brain/anti_stun,
+ /obj/item/organ/internal/cyberimp/brain/anti_stun/hardened,
/obj/item/organ/internal/cyberimp/chest/nutriment/plus,
/obj/item/organ/internal/cyberimp/arm/combat/centcom
)
diff --git a/code/game/jobs/job/engineering.dm b/code/game/jobs/job/engineering.dm
index 84f903dd2b1..a3333e1dc82 100644
--- a/code/game/jobs/job/engineering.dm
+++ b/code/game/jobs/job/engineering.dm
@@ -18,7 +18,7 @@
ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS,
ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_MECHANIC, ACCESS_MINERAL_STOREROOM)
minimal_player_age = 21
- exp_requirements = 300
+ exp_requirements = 1200
exp_type = EXP_TYPE_ENGINEERING
outfit = /datum/outfit/job/chief_engineer
diff --git a/code/game/jobs/job/medical.dm b/code/game/jobs/job/medical.dm
index 2d1b091a10d..b1b81e0b849 100644
--- a/code/game/jobs/job/medical.dm
+++ b/code/game/jobs/job/medical.dm
@@ -16,7 +16,7 @@
ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE,
ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_PSYCHIATRIST, ACCESS_MAINT_TUNNELS, ACCESS_PARAMEDIC, ACCESS_MINERAL_STOREROOM)
minimal_player_age = 21
- exp_requirements = 300
+ exp_requirements = 1200
exp_type = EXP_TYPE_MEDICAL
outfit = /datum/outfit/job/cmo
diff --git a/code/game/jobs/job/science.dm b/code/game/jobs/job/science.dm
index 684de933832..f6fdb02477b 100644
--- a/code/game/jobs/job/science.dm
+++ b/code/game/jobs/job/science.dm
@@ -18,7 +18,7 @@
ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD,
ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_XENOARCH, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM, ACCESS_NETWORK)
minimal_player_age = 21
- exp_requirements = 300
+ exp_requirements = 1200
exp_type = EXP_TYPE_SCIENCE
// All science-y guys get bonuses for maxing out their tech.
required_objectives = list(
diff --git a/code/game/jobs/job/security.dm b/code/game/jobs/job/security.dm
index a32656598df..6f4eb3759de 100644
--- a/code/game/jobs/job/security.dm
+++ b/code/game/jobs/job/security.dm
@@ -18,7 +18,7 @@
ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING,
ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_PILOT, ACCESS_WEAPONS)
minimal_player_age = 21
- exp_requirements = 300
+ exp_requirements = 1200
exp_type = EXP_TYPE_SECURITY
disabilities_allowed = 0
outfit = /datum/outfit/job/hos
@@ -64,7 +64,7 @@
minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_WEAPONS)
minimal_player_age = 21
exp_requirements = 600
- exp_type = EXP_TYPE_CREW
+ exp_type = EXP_TYPE_SECURITY
outfit = /datum/outfit/job/warden
/datum/outfit/job/warden
diff --git a/code/game/jobs/job/silicon.dm b/code/game/jobs/job/silicon.dm
index c510518716a..fef9c2302b3 100644
--- a/code/game/jobs/job/silicon.dm
+++ b/code/game/jobs/job/silicon.dm
@@ -32,7 +32,7 @@
minimal_player_age = 21
exp_requirements = 300
exp_type = EXP_TYPE_CREW
- alt_titles = list("Android", "Robot")
+ alt_titles = list("Robot")
/datum/job/cyborg/equip(mob/living/carbon/human/H)
if(!H)
diff --git a/code/game/jobs/job/supervisor.dm b/code/game/jobs/job/supervisor.dm
index 1d627f131b2..87ab2774384 100644
--- a/code/game/jobs/job/supervisor.dm
+++ b/code/game/jobs/job/supervisor.dm
@@ -13,7 +13,7 @@ GLOBAL_DATUM_INIT(captain_announcement, /datum/announcement/minor, new(do_newsca
access = list() //See get_access()
minimal_access = list() //See get_access()
minimal_player_age = 30
- exp_requirements = 300
+ exp_requirements = 1200
exp_type = EXP_TYPE_COMMAND
disabilities_allowed = 0
outfit = /datum/outfit/job/captain
@@ -33,7 +33,7 @@ GLOBAL_DATUM_INIT(captain_announcement, /datum/announcement/minor, new(do_newsca
shoes = /obj/item/clothing/shoes/brown
head = /obj/item/clothing/head/caphat
l_ear = /obj/item/radio/headset/heads/captain/alt
- glasses = /obj/item/clothing/glasses/sunglasses
+ glasses = /obj/item/clothing/glasses/hud/skills/sunglasses
id = /obj/item/card/id/gold
pda = /obj/item/pda/captain
backpack_contents = list(
@@ -67,7 +67,7 @@ GLOBAL_DATUM_INIT(captain_announcement, /datum/announcement/minor, new(do_newsca
req_admin_notify = 1
is_command = 1
minimal_player_age = 21
- exp_requirements = 300
+ exp_requirements = 1200
exp_type = EXP_TYPE_COMMAND
access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_FORENSICS_LOCKERS,
ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS,
@@ -89,6 +89,7 @@ GLOBAL_DATUM_INIT(captain_announcement, /datum/announcement/minor, new(do_newsca
uniform = /obj/item/clothing/under/rank/head_of_personnel
shoes = /obj/item/clothing/shoes/brown
head = /obj/item/clothing/head/hopcap
+ glasses = /obj/item/clothing/glasses/hud/skills/sunglasses
l_ear = /obj/item/radio/headset/heads/hop
id = /obj/item/card/id/silver
pda = /obj/item/pda/heads/hop
@@ -134,6 +135,7 @@ GLOBAL_DATUM_INIT(captain_announcement, /datum/announcement/minor, new(do_newsca
uniform = /obj/item/clothing/under/rank/ntrep
suit = /obj/item/clothing/suit/storage/ntrep
shoes = /obj/item/clothing/shoes/centcom
+ glasses = /obj/item/clothing/glasses/hud/skills/sunglasses
l_ear = /obj/item/radio/headset/heads/ntrep
id = /obj/item/card/id/nanotrasen
l_pocket = /obj/item/lighter/zippo/nt_rep
diff --git a/code/game/jobs/job/support.dm b/code/game/jobs/job/support.dm
index 09328865b59..fda831242f9 100644
--- a/code/game/jobs/job/support.dm
+++ b/code/game/jobs/job/support.dm
@@ -458,6 +458,7 @@
total_positions = 0
spawn_positions = 0
supervisors = "the head of personnel"
+ department_head = list("Head of Personnel")
selection_color = "#dddddd"
access = list(ACCESS_MAINT_TUNNELS, ACCESS_GATEWAY, ACCESS_EVA, ACCESS_EXTERNAL_AIRLOCKS)
minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_GATEWAY, ACCESS_EVA, ACCESS_EXTERNAL_AIRLOCKS)
diff --git a/code/game/jobs/job/support_chaplain.dm b/code/game/jobs/job/support_chaplain.dm
index b39eed344ae..bfd9c4520f5 100644
--- a/code/game/jobs/job/support_chaplain.dm
+++ b/code/game/jobs/job/support_chaplain.dm
@@ -26,7 +26,7 @@
/obj/item/camera/spooky = 1,
/obj/item/nullrod = 1
)
-
+
/datum/outfit/job/chaplain/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
. = ..()
@@ -77,7 +77,7 @@
new_deity = deity_name
B.deity_name = new_deity
- H.AddSpell(new /obj/effect/proc_holder/spell/targeted/chaplain_bless(null))
+ H.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/chaplain_bless(null))
var/accepted = 0
var/outoftime = 0
diff --git a/code/game/jobs/job_exp.dm b/code/game/jobs/job_exp.dm
index f0e6f9bff42..aa9114c2963 100644
--- a/code/game/jobs/job_exp.dm
+++ b/code/game/jobs/job_exp.dm
@@ -224,19 +224,17 @@ GLOBAL_LIST_INIT(role_playtime_requirements, list(
else
return "none"
-/proc/update_exp(var/mins, var/ann = 0)
- if(!establish_db_connection())
- return -1
- spawn(0)
- for(var/client/L in GLOB.clients)
- if(L.inactivity >= (10 MINUTES))
- continue
- spawn(0)
- L.update_exp_client(mins, ann)
- sleep(10)
+/proc/update_exp(mins = 0, ann = 0)
+ if(!GLOB.dbcon.IsConnected())
+ return
+ for(var/client/L in GLOB.clients)
+ if(L.inactivity >= (10 MINUTES))
+ continue
+ L.update_exp_client(mins, ann)
+ CHECK_TICK
-/client/proc/update_exp_client(var/minutes, var/announce_changes = 0)
- if(!src ||!ckey)
+/client/proc/update_exp_client(minutes = 0, announce_changes = 0)
+ if(!src || !ckey || !GLOB.dbcon.IsConnected())
return
var/DBQuery/exp_read = GLOB.dbcon.NewQuery("SELECT exp FROM [format_table_name("player")] WHERE ckey='[ckey]'")
if(!exp_read.Execute())
diff --git a/code/game/jobs/jobs.dm b/code/game/jobs/jobs.dm
index 81cc54aa3c3..2f8e426e030 100644
--- a/code/game/jobs/jobs.dm
+++ b/code/game/jobs/jobs.dm
@@ -69,7 +69,7 @@ GLOBAL_LIST_INIT(supply_positions, list(
"Shaft Miner"
))
-GLOBAL_LIST_INIT(service_positions, (support_positions - supply_positions + list("Head of Personnel")))
+GLOBAL_LIST_INIT(service_positions, (list("Head of Personnel") + (support_positions - supply_positions)))
GLOBAL_LIST_INIT(security_positions, list(
diff --git a/code/game/machinery/Freezer.dm b/code/game/machinery/Freezer.dm
index d9243005ead..a2a69fad357 100644
--- a/code/game/machinery/Freezer.dm
+++ b/code/game/machinery/Freezer.dm
@@ -104,59 +104,46 @@
to_chat(user, "Close the maintenance panel first.")
return
- src.ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/atmospherics/unary/cold_sink/freezer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- // update the ui if it exists, returns null if no ui is passed/found
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/atmospherics/unary/cold_sink/freezer/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- // the ui does not exist, so we'll create a new() one
- // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "freezer.tmpl", "Gas Cooling System", 540, 300)
- // open the new ui window
+ ui = new(user, src, ui_key, "GasFreezer", "Gas Cooling System", 540, 200)
ui.open()
- // auto update every Master Controller tick
- ui.set_auto_update(1)
-/obj/machinery/atmospherics/unary/cold_sink/freezer/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- data["on"] = on ? 1 : 0
- data["gasPressure"] = round(air_contents.return_pressure())
- data["gasTemperature"] = round(air_contents.temperature)
- data["gasTemperatureCelsius"] = round(air_contents.temperature - T0C,1)
+/obj/machinery/atmospherics/unary/cold_sink/freezer/tgui_data(mob/user)
+ var/list/data = list()
+ data["on"] = on
+ data["pressure"] = round(air_contents.return_pressure())
+ data["temperature"] = round(air_contents.temperature)
+ data["temperatureCelsius"] = round(air_contents.temperature - T0C, 1)
if(air_contents.total_moles() == 0 && air_contents.temperature == 0)
- data["gasTemperatureCelsius"] = 0
- data["minGasTemperature"] = round(min_temperature)
- data["maxGasTemperature"] = round(T20C)
- data["targetGasTemperature"] = round(current_temperature)
- data["targetGasTemperatureCelsius"] = round(current_temperature - T0C,1)
-
- var/temp_class = "good"
- if(air_contents.temperature > (T0C - 20))
- temp_class = "bad"
- else if(air_contents.temperature < (T0C - 20) && air_contents.temperature > (T0C - 100))
- temp_class = "average"
- data["gasTemperatureClass"] = temp_class
+ data["temperatureCelsius"] = 0
+ data["min"] = round(min_temperature)
+ data["max"] = round(T20C)
+ data["target"] = round(current_temperature)
+ data["targetCelsius"] = round(current_temperature - T0C, 1)
return data
-/obj/machinery/atmospherics/unary/cold_sink/freezer/Topic(href, href_list)
+/obj/machinery/atmospherics/unary/cold_sink/freezer/tgui_act(action, params)
if(..())
- return 1
- if(href_list["toggleStatus"])
- src.on = !src.on
- update_icon()
- else if(href_list["minimum"])
- current_temperature = min_temperature
- else if(href_list["maximum"])
- current_temperature = T20C
- else if(href_list["temp"])
- var/amount = text2num(href_list["temp"])
- if(amount > 0)
- src.current_temperature = min(T20C, src.current_temperature+amount)
- else
- src.current_temperature = max(min_temperature, src.current_temperature+amount)
- src.add_fingerprint(usr)
- return 1
+ return
+ add_fingerprint(usr)
+ . = TRUE
+
+ switch(action)
+ if("power")
+ on = !on
+ update_icon()
+ if("minimum")
+ current_temperature = min_temperature
+ if("maximum")
+ current_temperature = T20C
+ if("temp")
+ var/amount = params["temp"]
+ amount = text2num(amount)
+ current_temperature = clamp(amount, T20C, min_temperature)
/obj/machinery/atmospherics/unary/cold_sink/freezer/power_change()
..()
@@ -273,57 +260,46 @@
if(panel_open)
to_chat(user, "Close the maintenance panel first.")
return
- src.ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/atmospherics/unary/heat_reservoir/heater/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- // update the ui if it exists, returns null if no ui is passed/found
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/atmospherics/unary/heat_reservoir/heater/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- // the ui does not exist, so we'll create a new() one
- // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "freezer.tmpl", "Gas Heating System", 540, 300)
- // open the new ui window
+ ui = new(user, src, ui_key, "GasFreezer", "Gas Heating System", 540, 200)
ui.open()
- // auto update every Master Controller tick
- ui.set_auto_update(1)
-/obj/machinery/atmospherics/unary/heat_reservoir/heater/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- data["on"] = on ? 1 : 0
- data["gasPressure"] = round(air_contents.return_pressure())
- data["gasTemperature"] = round(air_contents.temperature)
- data["gasTemperatureCelsius"] = round(air_contents.temperature - T0C,1)
+/obj/machinery/atmospherics/unary/heat_reservoir/heater/tgui_data(mob/user)
+ var/list/data = list()
+ data["on"] = on
+ data["pressure"] = round(air_contents.return_pressure())
+ data["temperature"] = round(air_contents.temperature)
+ data["temperatureCelsius"] = round(air_contents.temperature - T0C, 1)
if(air_contents.total_moles() == 0 && air_contents.temperature == 0)
- data["gasTemperatureCelsius"] = 0
- data["minGasTemperature"] = round(T20C)
- data["maxGasTemperature"] = round(T20C+max_temperature)
- data["targetGasTemperature"] = round(current_temperature)
- data["targetGasTemperatureCelsius"] = round(current_temperature - T0C,1)
-
- var/temp_class = "normal"
- if(air_contents.temperature > (T20C+40))
- temp_class = "bad"
- data["gasTemperatureClass"] = temp_class
+ data["temperatureCelsius"] = 0
+ data["min"] = round(T20C)
+ data["max"] = round(T20C + max_temperature)
+ data["target"] = round(current_temperature)
+ data["targetCelsius"] = round(current_temperature - T0C, 1)
return data
-/obj/machinery/atmospherics/unary/heat_reservoir/heater/Topic(href, href_list)
+/obj/machinery/atmospherics/unary/heat_reservoir/heater/tgui_act(action, params)
if(..())
- return 1
- if(href_list["toggleStatus"])
- src.on = !src.on
- update_icon()
- else if(href_list["minimum"])
- current_temperature = T20C
- else if(href_list["maximum"])
- current_temperature = max_temperature + T20C
- else if(href_list["temp"])
- var/amount = text2num(href_list["temp"])
- if(amount > 0)
- src.current_temperature = min((T20C+max_temperature), src.current_temperature+amount)
- else
- src.current_temperature = max(T20C, src.current_temperature+amount)
- src.add_fingerprint(usr)
- return 1
+ return
+ add_fingerprint(usr)
+ . = TRUE
+
+ switch(action)
+ if("power")
+ on = !on
+ update_icon()
+ if("minimum")
+ current_temperature = T20C
+ if("maximum")
+ current_temperature = max_temperature + T20C
+ if("temp")
+ var/amount = params["temp"]
+ amount = text2num(amount)
+ current_temperature = clamp(amount, T20C, T20C + max_temperature)
/obj/machinery/atmospherics/unary/heat_reservoir/heater/power_change()
..()
diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm
index e4d0befa61e..d33518be857 100644
--- a/code/game/machinery/Sleeper.dm
+++ b/code/game/machinery/Sleeper.dm
@@ -125,7 +125,7 @@
return attack_hand(user)
/obj/machinery/sleeper/attack_ghost(mob/user)
- return attack_hand(user)
+ tgui_interact(user)
/obj/machinery/sleeper/attack_hand(mob/user)
if(stat & (NOPOWER|BROKEN))
@@ -135,17 +135,17 @@
to_chat(user, "Close the maintenance panel first.")
return
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/sleeper/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/sleeper/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "sleeper.tmpl", "Sleeper", 550, 770)
+ ui = new(user, src, ui_key, "Sleeper", "Sleeper", 550, 775)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/sleeper/ui_data(mob/user, datum/topic_state/state)
+/obj/machinery/sleeper/tgui_data(mob/user)
var/data[0]
+ data["amounts"] = amounts
data["hasOccupant"] = occupant ? 1 : 0
var/occupantData[0]
var/crisis = 0
@@ -210,9 +210,13 @@
if(beaker)
data["isBeakerLoaded"] = 1
if(beaker.reagents)
+ data["beakerMaxSpace"] = beaker.reagents.maximum_volume
data["beakerFreeSpace"] = round(beaker.reagents.maximum_volume - beaker.reagents.total_volume)
else
+ data["beakerMaxSpace"] = 0
data["beakerFreeSpace"] = 0
+ else
+ data["isBeakerLoaded"] = FALSE
var/chemicals[0]
for(var/re in possible_chems)
@@ -234,51 +238,52 @@
if(temp.id in occupant.reagents.overdose_list())
overdosing = 1
- // Because I don't know how to do this on the nano side
pretty_amount = round(reagent_amount, 0.05)
chemicals.Add(list(list("title" = temp.name, "id" = temp.id, "commands" = list("chemical" = temp.id), "occ_amount" = reagent_amount, "pretty_amount" = pretty_amount, "injectable" = injectable, "overdosing" = overdosing, "od_warning" = caution)))
data["chemicals"] = chemicals
return data
-/obj/machinery/sleeper/Topic(href, href_list)
- if(!controls_inside && usr == occupant)
- return 0
-
+/obj/machinery/sleeper/tgui_act(action, params)
if(..())
- return 1
-
+ return
+ if(!controls_inside && usr == occupant)
+ return
if(panel_open)
to_chat(usr, "Close the maintenance panel first.")
- return 0
+ return
+ if(stat & (NOPOWER|BROKEN))
+ return
- if((usr.contents.Find(src) || ((get_dist(src, usr) <= 1) && istype(loc, /turf))) || (istype(usr, /mob/living/silicon/ai)))
- if(href_list["chemical"])
- if(occupant)
- if(occupant.stat == DEAD)
- to_chat(usr, "This person has no life for to preserve anymore. Take [occupant.p_them()] to a department capable of reanimating them.")
- else if(occupant.health > min_health || (href_list["chemical"] in emergency_chems))
- inject_chemical(usr,href_list["chemical"],text2num(href_list["amount"]))
- else
- to_chat(usr, "This person is not in good enough condition for sleepers to be effective! Use another means of treatment, such as cryogenics!")
-
- if(href_list["removebeaker"])
+ . = TRUE
+ switch(action)
+ if("chemical")
+ if(!occupant)
+ return
+ if(occupant.stat == DEAD)
+ to_chat(usr, "This person has no life to preserve anymore. Take [occupant.p_them()] to a department capable of reanimating them.")
+ return
+ var/chemical = params["chemid"]
+ var/amount = text2num(params["amount"])
+ if(!length(chemical) || amount <= 0)
+ return
+ if(occupant.health > min_health || (chemical in emergency_chems))
+ inject_chemical(usr, chemical, amount)
+ else
+ to_chat(usr, "This person is not in good enough condition for sleepers to be effective! Use another means of treatment, such as cryogenics!")
+ if("removebeaker")
remove_beaker()
-
- if(href_list["togglefilter"])
+ if("togglefilter")
toggle_filter()
-
- if(href_list["ejectify"])
+ if("ejectify")
eject()
-
- if(href_list["auto_eject_dead_on"])
+ if("auto_eject_dead_on")
auto_eject_dead = TRUE
-
- if(href_list["auto_eject_dead_off"])
+ if("auto_eject_dead_off")
auto_eject_dead = FALSE
-
- add_fingerprint(usr)
- return 1
+ else
+ return FALSE
+ add_fingerprint(usr)
/obj/machinery/sleeper/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/reagent_containers/glass))
@@ -290,6 +295,7 @@
beaker = I
I.forceMove(src)
user.visible_message("[user] adds \a [I] to [src]!", "You add \a [I] to [src]!")
+ SStgui.update_uis(src)
return
else
@@ -328,6 +334,7 @@
to_chat(M, "You feel cool air surround you. You go numb as your senses turn inward.")
add_fingerprint(user)
qdel(G)
+ SStgui.update_uis(src)
return
return ..()
@@ -374,9 +381,11 @@
occupant = null
updateUsrDialog()
update_icon()
+ SStgui.update_uis(src)
if(A == beaker)
beaker = null
updateUsrDialog()
+ SStgui.update_uis(src)
/obj/machinery/sleeper/emp_act(severity)
if(filtering)
@@ -394,7 +403,7 @@
qdel(src)
/obj/machinery/sleeper/proc/toggle_filter()
- if(filtering)
+ if(filtering || !beaker)
filtering = 0
else
filtering = 1
@@ -410,26 +419,28 @@
// eject trash the occupant dropped
for(var/atom/movable/A in contents - component_parts - list(beaker))
A.forceMove(loc)
+ SStgui.update_uis(src)
-/obj/machinery/sleeper/proc/inject_chemical(mob/living/user as mob, chemical, amount)
+/obj/machinery/sleeper/force_eject_occupant()
+ go_out()
+
+/obj/machinery/sleeper/proc/inject_chemical(mob/living/user, chemical, amount)
if(!(chemical in possible_chems))
to_chat(user, "The sleeper does not offer that chemical!")
return
+ if(!(amount in amounts))
+ return
if(occupant)
if(occupant.reagents)
if(occupant.reagents.get_reagent_amount(chemical) + amount <= max_chem)
occupant.reagents.add_reagent(chemical, amount)
- return
else
to_chat(user, "You can not inject any more of this chemical.")
- return
else
to_chat(user, "The patient rejects the chemicals!")
- return
else
to_chat(user, "There's no occupant in the sleeper!")
- return
/obj/machinery/sleeper/verb/eject()
set name = "Eject Sleeper"
@@ -456,6 +467,7 @@
filtering = 0
beaker.forceMove(usr.loc)
beaker = null
+ SStgui.update_uis(src)
add_fingerprint(usr)
return
@@ -508,6 +520,7 @@
add_fingerprint(user)
if(user.pulling == L)
user.stop_pulling()
+ SStgui.update_uis(src)
return
return
@@ -544,6 +557,7 @@
for(var/obj/O in src)
qdel(O)
add_fingerprint(usr)
+ SStgui.update_uis(src)
return
return
diff --git a/code/game/machinery/adv_med.dm b/code/game/machinery/adv_med.dm
index 1ee30b6e9d0..97ab80fc1d7 100644
--- a/code/game/machinery/adv_med.dm
+++ b/code/game/machinery/adv_med.dm
@@ -65,6 +65,7 @@
icon_state = "body_scanner_1"
add_fingerprint(user)
qdel(TYPECAST_YOUR_SHIT)
+ SStgui.update_uis(src)
return
return ..()
@@ -127,12 +128,13 @@
occupant = H
icon_state = "bodyscanner"
add_fingerprint(user)
+ SStgui.update_uis(src)
/obj/machinery/bodyscanner/attack_ai(user)
return attack_hand(user)
/obj/machinery/bodyscanner/attack_ghost(user)
- return attack_hand(user)
+ tgui_interact(user)
/obj/machinery/bodyscanner/attack_hand(user)
if(stat & (NOPOWER|BROKEN))
@@ -145,7 +147,7 @@
to_chat(user, "Close the maintenance panel first.")
return
- ui_interact(user)
+ tgui_interact(user)
/obj/machinery/bodyscanner/relaymove(mob/user)
if(user.incapacitated())
@@ -171,6 +173,10 @@
// eject trash the occupant dropped
for(var/atom/movable/A in contents - component_parts)
A.forceMove(loc)
+ SStgui.update_uis(src)
+
+/obj/machinery/bodyscanner/force_eject_occupant()
+ go_out()
/obj/machinery/bodyscanner/ex_act(severity)
if(occupant)
@@ -189,17 +195,16 @@
new /obj/effect/gibspawner/generic(get_turf(loc)) //I REPLACE YOUR TECHNOLOGY WITH FLESH!
qdel(src)
-/obj/machinery/bodyscanner/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/bodyscanner/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "adv_med.tmpl", "Body Scanner", 690, 600)
+ ui = new(user, src, ui_key, "BodyScanner", "Body Scanner", 690, 600)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/bodyscanner/ui_data(mob/user, datum/topic_state/state)
+/obj/machinery/bodyscanner/tgui_data(mob/user)
var/data[0]
- data["occupied"] = occupant ? 1 : 0
+ data["occupied"] = occupant ? TRUE : FALSE
var/occupantData[0]
if(occupant)
@@ -233,9 +238,9 @@
occupantData["hasBorer"] = occupant.has_brain_worms()
var/bloodData[0]
- bloodData["hasBlood"] = 0
+ bloodData["hasBlood"] = FALSE
if(!(NO_BLOOD in occupant.dna.species.species_traits))
- bloodData["hasBlood"] = 1
+ bloodData["hasBlood"] = TRUE
bloodData["volume"] = occupant.blood_volume
bloodData["percent"] = round(((occupant.blood_volume / BLOOD_VOLUME_NORMAL)*100))
bloodData["pulse"] = occupant.get_pulse(GETPULSE_TOOL)
@@ -279,19 +284,19 @@
if(E.status & ORGAN_BROKEN)
organStatus["broken"] = E.broken_description
if(E.is_robotic())
- organStatus["robotic"] = 1
+ organStatus["robotic"] = TRUE
if(E.status & ORGAN_SPLINTED)
- organStatus["splinted"] = 1
+ organStatus["splinted"] = TRUE
if(E.status & ORGAN_DEAD)
- organStatus["dead"] = 1
+ organStatus["dead"] = TRUE
organData["status"] = organStatus
if(istype(E, /obj/item/organ/external/chest) && occupant.is_lung_ruptured())
- organData["lungRuptured"] = 1
+ organData["lungRuptured"] = TRUE
if(E.internal_bleeding)
- organData["internalBleeding"] = 1
+ organData["internalBleeding"] = TRUE
extOrganData.Add(list(organData))
@@ -316,27 +321,33 @@
occupantData["blind"] = (BLINDNESS in occupant.mutations)
occupantData["colourblind"] = (COLOURBLIND in occupant.mutations)
- occupantData["nearsighted"] = (NEARSIGHTED in occupant.mutations)
+ occupantData["nearsighted"] = (NEARSIGHTED in occupant.mutations)
data["occupant"] = occupantData
return data
-/obj/machinery/bodyscanner/Topic(href, href_list)
+/obj/machinery/bodyscanner/tgui_act(action, params)
if(..())
- return 1
+ return
+ if(stat & (NOPOWER|BROKEN))
+ return
- if(href_list["ejectify"])
- eject()
-
- if(href_list["print_p"])
- visible_message("[src] rattles and prints out a sheet of paper.")
- var/obj/item/paper/P = new /obj/item/paper(loc)
- playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1)
- P.info = "Body Scan - [href_list["name"]] "
- P.info += "Time of scan: [station_time_timestamp()]
"
- P.info += "[generate_printing_text()]"
- P.info += "
Notes: "
- P.name = "Body Scan - [href_list["name"]]"
+ . = TRUE
+ switch(action)
+ if("ejectify")
+ eject()
+ if("print_p")
+ visible_message("[src] rattles and prints out a sheet of paper.")
+ var/obj/item/paper/P = new /obj/item/paper(loc)
+ playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, TRUE)
+ var/name = occupant ? occupant.name : "Unknown"
+ P.info = "Body Scan - [name] "
+ P.info += "Time of scan: [station_time_timestamp()]
"
+ P.info += "[generate_printing_text()]"
+ P.info += "
Notes: "
+ P.name = "Body Scan - [name]"
+ else
+ return FALSE
/obj/machinery/bodyscanner/proc/generate_printing_text()
var/dat = ""
diff --git a/code/game/machinery/alarm.dm b/code/game/machinery/alarm.dm
index 9fda2cfe45c..951f2a1e9cf 100644
--- a/code/game/machinery/alarm.dm
+++ b/code/game/machinery/alarm.dm
@@ -222,6 +222,7 @@
first_run()
/obj/machinery/alarm/Destroy()
+ SStgui.close_uis(wires)
GLOB.air_alarms -= src
if(SSradio)
SSradio.remove_object(src, frequency)
@@ -314,7 +315,7 @@
temperature_dangerlevel
)
- if(old_danger_level!=danger_level)
+ if(old_danger_level != danger_level)
apply_danger_level()
if(mode == AALARM_MODE_REPLACEMENT && environment_pressure < ONE_ATMOSPHERE * 0.05)
@@ -534,28 +535,35 @@
"checks"= 0,
))
-/obj/machinery/alarm/proc/apply_danger_level(var/new_danger_level)
- if(report_danger_level && alarm_area.atmosalert(new_danger_level, src))
- post_alert(new_danger_level)
+/obj/machinery/alarm/proc/apply_danger_level()
+ var/new_area_danger_level = ATMOS_ALARM_NONE
+ for(var/obj/machinery/alarm/AA in alarm_area)
+ if(!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted)
+ new_area_danger_level = max(new_area_danger_level, AA.danger_level)
+ if(alarm_area.atmosalert(new_area_danger_level, src)) //if area was in normal state or if area was in alert state
+ post_alert(new_area_danger_level)
update_icon()
/obj/machinery/alarm/proc/post_alert(alert_level)
+ if(!report_danger_level)
+ return
var/datum/radio_frequency/frequency = SSradio.return_frequency(alarm_frequency)
+
if(!frequency)
return
var/datum/signal/alert_signal = new
alert_signal.source = src
alert_signal.transmission_method = 1
- alert_signal.data["zone"] = alarm_area.name
+ alert_signal.data["zone"] = get_area_name(src, TRUE)
alert_signal.data["type"] = "Atmospheric"
- if(alert_level==2)
+ if(alert_level == ATMOS_ALARM_DANGER)
alert_signal.data["alert"] = "severe"
- else if(alert_level==1)
+ else if(alert_level == ATMOS_ALARM_WARNING)
alert_signal.data["alert"] = "minor"
- else if(alert_level==0)
+ else if(alert_level == ATMOS_ALARM_NONE)
alert_signal.data["alert"] = "clear"
frequency.post_signal(src, alert_signal)
@@ -889,14 +897,14 @@
if(href_list["atmos_alarm"])
if(alarm_area.atmosalert(ATMOS_ALARM_DANGER, src))
- apply_danger_level(ATMOS_ALARM_DANGER)
+ post_alert(ATMOS_ALARM_DANGER)
alarmActivated = 1
update_icon()
return 1
if(href_list["atmos_reset"])
if(alarm_area.atmosalert(ATMOS_ALARM_NONE, src, TRUE))
- apply_danger_level(ATMOS_ALARM_NONE)
+ post_alert(ATMOS_ALARM_NONE)
alarmActivated = 0
update_icon()
return 1
@@ -951,7 +959,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.")
updateUsrDialog()
@@ -1030,7 +1038,7 @@
. = TRUE
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
- if(wires.wires_status == 31) // all wires cut
+ if(wires.is_all_cut()) // all wires cut
var/obj/item/stack/cable_coil/new_coil = new /obj/item/stack/cable_coil(user.drop_location())
new_coil.amount = 5
buildstage = AIR_ALARM_BUILDING
@@ -1076,6 +1084,15 @@
if(buildstage < 1)
. += "The circuit is missing."
+/obj/machinery/alarm/proc/unshort_callback()
+ if(shorted)
+ shorted = FALSE
+ update_icon()
+
+/obj/machinery/alarm/proc/enable_ai_control_callback()
+ if(aidisabled)
+ aidisabled = FALSE
+
/obj/machinery/alarm/all_access
name = "all-access air alarm"
desc = "This particular atmos control unit appears to have no access restrictions."
diff --git a/code/game/machinery/atmoalter/canister.dm b/code/game/machinery/atmoalter/canister.dm
index e927415c759..b950f3d51a3 100644
--- a/code/game/machinery/atmoalter/canister.dm
+++ b/code/game/machinery/atmoalter/canister.dm
@@ -11,6 +11,7 @@
list("name" = "\[SPECIAL\]", "icon" = "whiters")
)
possibleseccolor = list( // no point in having the N2O and "whiters" ones in these lists
+ list("name" = "\[None\]", "icon" = "none"),
list("name" = "\[N2\]", "icon" = "red-c"),
list("name" = "\[O2\]", "icon" = "blue-c"),
list("name" = "\[Toxin (Bio)\]", "icon" = "orange-c"),
@@ -19,6 +20,7 @@
list("name" = "\[CAUTION\]", "icon" = "yellow-c")
)
possibletertcolor = list(
+ list("name" = "\[None\]", "icon" = "none"),
list("name" = "\[N2\]", "icon" = "red-c-1"),
list("name" = "\[O2\]", "icon" = "blue-c-1"),
list("name" = "\[Toxin (Bio)\]", "icon" = "orange-c-1"),
@@ -27,6 +29,7 @@
list("name" = "\[CAUTION\]", "icon" = "yellow-c-1")
)
possiblequartcolor = list(
+ list("name" = "\[None\]", "icon" = "none"),
list("name" = "\[N2\]", "icon" = "red-c-2"),
list("name" = "\[O2\]", "icon" = "blue-c-2"),
list("name" = "\[Toxin (Bio)\]", "icon" = "orange-c-2"),
@@ -34,12 +37,6 @@
list("name" = "\[Air\]", "icon" = "grey-c-2"),
list("name" = "\[CAUTION\]", "icon" = "yellow-c-2")
)
-
- possibledecals = list( //var that stores all possible decals, used by ui
- list("name" = "Low temperature canister", "icon" = "cold"),
- list("name" = "High temperature canister", "icon" = "hot"),
- list("name" = "Plasma containing canister", "icon" = "plasma")
- )
GLOBAL_DATUM_INIT(canister_icon_container, /datum/canister_icons, new())
/obj/machinery/portable_atmospherics/canister
@@ -52,22 +49,17 @@ GLOBAL_DATUM_INIT(canister_icon_container, /datum/canister_icons, new())
max_integrity = 250
integrity_failure = 100
- var/menu = 0
- //used by nanoui: 0 = main menu, 1 = relabel
-
var/valve_open = 0
var/release_pressure = ONE_ATMOSPHERE
var/list/canister_color //variable that stores colours
- var/list/decals //list that stores the decals
+ var/list/color_index // list which stores tgui color indexes for the recoloring options, to enable previously-set colors to show up right
//lists for check_change()
var/list/oldcolor
- var/list/olddecals
//passed to the ui to render the color lists
var/list/colorcontainer
- var/list/possibledecals
var/can_label = 1
var/filled = 0.5
@@ -82,18 +74,12 @@ GLOBAL_DATUM_INIT(canister_icon_container, /datum/canister_icons, new())
/obj/machinery/portable_atmospherics/canister/New()
..()
canister_color = list(
- "prim" = "yellow",
- "sec" = "none",
- "ter" = "none",
- "quart" = "none")
+ "prim" = "yellow",
+ "sec" = "none",
+ "ter" = "none",
+ "quart" = "none"
+ )
oldcolor = new /list()
- decals = list("cold" = 0, "hot" = 0, "plasma" = 0)
- colorcontainer = list()
- possibledecals = list()
- update_icon()
-
-/obj/machinery/portable_atmospherics/canister/proc/init_data_vars()
- //passed to the ui to render the color lists
colorcontainer = list(
"prim" = list(
"options" = GLOB.canister_icon_container.possiblemaincolor,
@@ -112,26 +98,8 @@ GLOBAL_DATUM_INIT(canister_icon_container, /datum/canister_icons, new())
"name" = "Quaternary color",
)
)
-
- //var/anycolor used by the nanoUI, 0: no color applied. 1: color applied
- for(var/C in colorcontainer)
- if(C == "prim") continue
- var/list/L = colorcontainer[C]
- if(!(canister_color[C]) || (canister_color[C] == "none"))
- L.Add(list("anycolor" = 0))
- else
- L.Add(list("anycolor" = 1))
- colorcontainer[C] = L
-
- possibledecals = list()
-
- var/i
- var/list/L = GLOB.canister_icon_container.possibledecals
- for(i=1;i<=L.len;i++)
- var/list/LL = L[i]
- LL = LL.Copy() //make sure we don't edit the datum list
- LL.Add(list("active" = decals[LL["icon"]])) //"active" used by nanoUI
- possibledecals.Add(LL)
+ color_index = list()
+ update_icon()
/obj/machinery/portable_atmospherics/canister/proc/check_change()
var/old_flag = update_flag
@@ -155,10 +123,6 @@ GLOBAL_DATUM_INIT(canister_icon_container, /datum/canister_icons, new())
update_flag |= 64
oldcolor = canister_color.Copy()
- if(list2params(olddecals) != list2params(decals))
- update_flag |= 128
- olddecals = decals.Copy()
-
if(update_flag == old_flag)
return 1
else
@@ -174,17 +138,16 @@ update_flag
16 = tank_pressure < 15*ONE_ATMOS
32 = tank_pressure go boom.
64 = colors
-128 = decals
-(note: colors and decals has to be applied every icon update)
+(note: colors has to be applied every icon update)
*/
- if(src.destroyed)
- src.overlays = 0
- src.icon_state = text("[]-1", src.canister_color["prim"])//yes, I KNOW the colours don't reflect when the can's borked, whatever.
+ if(destroyed)
+ overlays = 0
+ icon_state = text("[]-1", canister_color["prim"])//yes, I KNOW the colours don't reflect when the can's borked, whatever.
return
- if(icon_state != src.canister_color["prim"])
- icon_state = src.canister_color["prim"]
+ if(icon_state != canister_color["prim"])
+ icon_state = canister_color["prim"]
if(check_change()) //Returns 1 if no change needed to icons.
return
@@ -192,14 +155,12 @@ update_flag
overlays.Cut()
for(var/C in canister_color)
- if(C == "prim") continue
- if(canister_color[C] == "none") continue
+ if(C == "prim")
+ continue
+ if(canister_color[C] == "none")
+ continue
overlays.Add(canister_color[C])
- for(var/D in decals)
- if(decals[D])
- overlays.Add("decal-" + D)
-
if(update_flag & 1)
overlays += "can-open"
if(update_flag & 2)
@@ -213,35 +174,9 @@ update_flag
else if(update_flag & 32)
overlays += "can-o3"
- update_flag &= ~196 //the flags 128 and 64 represent change, not states. As such, we have to reset them to be able to detect a change on the next go.
+ update_flag &= ~68 //the flag 64 represents change, not states. As such, we have to reset them to be able to detect a change on the next go.
return
-//template modification exploit prevention, used in Topic()
-/obj/machinery/portable_atmospherics/canister/proc/is_a_color(var/inputVar, var/checkColor = "all")
- if(checkColor == "prim" || checkColor == "all")
- for(var/list/L in GLOB.canister_icon_container.possiblemaincolor)
- if(L["icon"] == inputVar)
- return 1
- if(checkColor == "sec" || checkColor == "all")
- for(var/list/L in GLOB.canister_icon_container.possibleseccolor)
- if(L["icon"] == inputVar)
- return 1
- if(checkColor == "ter" || checkColor == "all")
- for(var/list/L in GLOB.canister_icon_container.possibletertcolor)
- if(L["icon"] == inputVar)
- return 1
- if(checkColor == "quart" || checkColor == "all")
- for(var/list/L in GLOB.canister_icon_container.possiblequartcolor)
- if(L["icon"] == inputVar)
- return 1
- return 0
-
-/obj/machinery/portable_atmospherics/canister/proc/is_a_decal(var/inputVar)
- for(var/list/L in GLOB.canister_icon_container.possibledecals)
- if(L["icon"] == inputVar)
- return 1
- return 0
-
/obj/machinery/portable_atmospherics/canister/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
..()
if(exposed_temperature > temperature_resistance)
@@ -271,7 +206,7 @@ update_flag
stat |= BROKEN
density = FALSE
- playsound(src.loc, 'sound/effects/spray.ogg', 10, TRUE, -3)
+ playsound(loc, 'sound/effects/spray.ogg', 10, TRUE, -3)
update_icon()
if(holding)
@@ -297,7 +232,7 @@ update_flag
var/transfer_moles = 0
if((air_contents.temperature > 0) && (pressure_delta > 0))
- transfer_moles = pressure_delta*environment.volume/(air_contents.temperature * R_IDEAL_GAS_EQUATION)
+ transfer_moles = pressure_delta * environment.volume / (air_contents.temperature * R_IDEAL_GAS_EQUATION)
//Actually transfer the gas
var/datum/gas_mixture/removed = air_contents.remove(transfer_moles)
@@ -307,7 +242,7 @@ update_flag
else
loc.assume_air(removed)
air_update_turf()
- src.update_icon()
+ update_icon()
if(air_contents.return_pressure() < 1)
@@ -315,20 +250,20 @@ update_flag
else
can_label = 0
- src.updateDialog()
+ updateDialog()
return
/obj/machinery/portable_atmospherics/canister/return_air()
return air_contents
/obj/machinery/portable_atmospherics/canister/proc/return_temperature()
- var/datum/gas_mixture/GM = src.return_air()
+ var/datum/gas_mixture/GM = return_air()
if(GM && GM.volume>0)
return GM.temperature
return 0
/obj/machinery/portable_atmospherics/canister/proc/return_pressure()
- var/datum/gas_mixture/GM = src.return_air()
+ var/datum/gas_mixture/GM = return_air()
if(GM && GM.volume>0)
return GM.return_pressure()
return 0
@@ -343,141 +278,112 @@ update_flag
else if(valve_open && holding)
investigate_log("[key_name(user)] started a transfer into [holding]. ", "atmos")
-/obj/machinery/portable_atmospherics/canister/attack_ai(var/mob/user as mob)
- src.add_hiddenprint(user)
- return src.attack_hand(user)
+/obj/machinery/portable_atmospherics/canister/attack_ai(var/mob/user)
+ add_hiddenprint(user)
+ return attack_hand(user)
-/obj/machinery/portable_atmospherics/canister/attack_ghost(var/mob/user as mob)
- return src.ui_interact(user)
+/obj/machinery/portable_atmospherics/canister/attack_ghost(var/mob/user)
+ return tgui_interact(user)
-/obj/machinery/portable_atmospherics/canister/attack_hand(var/mob/user as mob)
- return src.ui_interact(user)
+/obj/machinery/portable_atmospherics/canister/attack_hand(var/mob/user)
+ return tgui_interact(user)
-/obj/machinery/portable_atmospherics/canister/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = GLOB.physical_state)
- if(src.destroyed)
- return
-
- // update the ui if it exists, returns null if no ui is passed/found
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/portable_atmospherics/canister/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_physical_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- // the ui does not exist, so we'll create a new() one
- // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "canister.tmpl", "Canister", 480, 400, state = state)
- // open the new ui window
+ ui = new(user, src, ui_key, "Canister", name, 600, 350, master_ui, state)
ui.open()
- // auto update every Master Controller tick
- ui.set_auto_update(1)
-
-/obj/machinery/portable_atmospherics/canister/ui_data(mob/user, datum/topic_state/state)
- init_data_vars() //set up var/colorcontainer and var/possibledecals
-
- // this is the data which will be sent to the ui
- var/data[0]
- data["name"] = name
- data["menu"] = menu ? 1 : 0
- data["canLabel"] = can_label ? 1 : 0
- data["canister_color"] = canister_color
- data["colorContainer"] = colorcontainer.Copy()
- colorcontainer.Cut()
- data["possibleDecals"] = possibledecals.Copy()
- possibledecals.Cut()
+/obj/machinery/portable_atmospherics/canister/tgui_data()
+ var/data = list()
data["portConnected"] = connected_port ? 1 : 0
data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
data["releasePressure"] = round(release_pressure ? release_pressure : 0)
- data["minReleasePressure"] = round(ONE_ATMOSPHERE/10)
- data["maxReleasePressure"] = round(10*ONE_ATMOSPHERE)
+ data["defaultReleasePressure"] = ONE_ATMOSPHERE
+ data["minReleasePressure"] = round(ONE_ATMOSPHERE / 10)
+ data["maxReleasePressure"] = round(ONE_ATMOSPHERE * 10)
data["valveOpen"] = valve_open ? 1 : 0
-
+ data["name"] = name
+ data["canLabel"] = can_label ? 1 : 0
+ data["colorContainer"] = colorcontainer.Copy()
+ data["color_index"] = color_index
data["hasHoldingTank"] = holding ? 1 : 0
if(holding)
data["holdingTank"] = list("name" = holding.name, "tankPressure" = round(holding.air_contents.return_pressure()))
-
return data
-/obj/machinery/portable_atmospherics/canister/Topic(href, href_list)
+/obj/machinery/portable_atmospherics/canister/tgui_act(action, params)
if(..())
- return 1
-
- if(href_list["choice"] == "menu")
- menu = text2num(href_list["mode_target"])
-
- if(href_list["toggle"])
- var/logmsg
- if(valve_open)
- if(holding)
- logmsg = "Valve was closed by [key_name(usr)], stopping the transfer into the [holding] "
- else
- logmsg = "Valve was closed by [key_name(usr)], stopping the transfer into the air "
- else
- if(holding)
- logmsg = "Valve was opened by [key_name(usr)], starting the transfer into the [holding] "
- else
- logmsg = "Valve was opened by [key_name(usr)], starting the transfer into the air "
- if(air_contents.toxins > 0)
- message_admins("[key_name_admin(usr)] opened a canister that contains plasma in [get_area(src)]! (JMP)")
- log_admin("[key_name(usr)] opened a canister that contains plasma at [get_area(src)]: [x], [y], [z]")
- if(air_contents.sleeping_agent > 0)
- message_admins("[key_name_admin(usr)] opened a canister that contains N2O in [get_area(src)]! (JMP)")
- log_admin("[key_name(usr)] opened a canister that contains N2O at [get_area(src)]: [x], [y], [z]")
- investigate_log(logmsg, "atmos")
- release_log += logmsg
- valve_open = !valve_open
-
- if(href_list["remove_tank"])
- if(holding)
- if(valve_open)
- valve_open = 0
- release_log += "Valve was closed by [key_name(usr)], stopping the transfer into the [holding] "
- holding.loc = loc
- holding = null
-
- if(href_list["pressure_adj"])
- var/diff = text2num(href_list["pressure_adj"])
- if(diff > 0)
- release_pressure = min(10*ONE_ATMOSPHERE, release_pressure+diff)
- else
- release_pressure = max(ONE_ATMOSPHERE/10, release_pressure+diff)
-
- if(href_list["rename"])
- if(can_label)
- var/T = sanitize(copytext(input("Choose canister label", "Name", name) as text|null,1,MAX_NAME_LEN))
- if(can_label) //Exploit prevention
- if(T)
- name = T
+ return
+ var/can_min_release_pressure = round(ONE_ATMOSPHERE / 10)
+ var/can_max_release_pressure = round(ONE_ATMOSPHERE * 10)
+ . = TRUE
+ switch(action)
+ if("relabel")
+ if(can_label)
+ var/T = sanitize(copytext(input("Choose canister label", "Name", name) as text|null, 1, MAX_NAME_LEN))
+ if(can_label) //Exploit prevention
+ if(T)
+ name = T
+ else
+ name = "canister"
else
- name = "canister"
+ to_chat(usr, "As you attempted to rename it the pressure rose!")
+ . = FALSE
+ if("pressure")
+ var/pressure = params["pressure"]
+ if(pressure == "reset")
+ pressure = ONE_ATMOSPHERE
+ else if(pressure == "min")
+ pressure = can_min_release_pressure
+ else if(pressure == "max")
+ pressure = can_max_release_pressure
+ else if(pressure == "input")
+ pressure = input("New release pressure ([can_min_release_pressure]-[can_max_release_pressure] kPa):", name, release_pressure) as num|null
+ if(isnull(pressure))
+ . = FALSE
+ else if(text2num(pressure) != null)
+ pressure = text2num(pressure)
+ if(.)
+ release_pressure = clamp(round(pressure), can_min_release_pressure, can_max_release_pressure)
+ investigate_log("was set to [release_pressure] kPa by [key_name(usr)].", "atmos")
+ if("valve")
+ var/logmsg
+ valve_open = !valve_open
+ if(valve_open)
+ logmsg = "Valve was opened by [key_name(usr)], starting a transfer into the [holding || "air"]. "
+ if(!holding)
+ logmsg = "Valve was opened by [key_name(usr)], starting a transfer into the air. "
+ if(air_contents.toxins > 0)
+ message_admins("[key_name_admin(usr)] opened a canister that contains plasma in [get_area(src)]! (JMP)")
+ log_admin("[key_name(usr)] opened a canister that contains plasma at [get_area(src)]: [x], [y], [z]")
+ if(air_contents.sleeping_agent > 0)
+ message_admins("[key_name_admin(usr)] opened a canister that contains N2O in [get_area(src)]! (JMP)")
+ log_admin("[key_name(usr)] opened a canister that contains N2O at [get_area(src)]: [x], [y], [z]")
else
- to_chat(usr, "As you attempted to rename it the pressure rose!")
-
- if(href_list["choice"] == "Primary color")
- if(is_a_color(href_list["icon"],"prim"))
- canister_color["prim"] = href_list["icon"]
- if(href_list["choice"] == "Secondary color")
- if(href_list["icon"] == "none")
- canister_color["sec"] = "none"
- else if(is_a_color(href_list["icon"],"sec"))
- canister_color["sec"] = href_list["icon"]
- if(href_list["choice"] == "Tertiary color")
- if(href_list["icon"] == "none")
- canister_color["ter"] = "none"
- else if(is_a_color(href_list["icon"],"ter"))
- canister_color["ter"] = href_list["icon"]
- if(href_list["choice"] == "Quaternary color")
- if(href_list["icon"] == "none")
- canister_color["quart"] = "none"
- else if(is_a_color(href_list["icon"],"quart"))
- canister_color["quart"] = href_list["icon"]
-
- if(href_list["choice"] == "decals")
- if(is_a_decal(href_list["icon"]))
- decals[href_list["icon"]] = (decals[href_list["icon"]] == 0)
-
- src.add_fingerprint(usr)
+ logmsg = "Valve was closed by [key_name(usr)], stopping the transfer into the [holding || "air"]. "
+ investigate_log(logmsg, "atmos")
+ release_log += logmsg
+ if("eject")
+ if(holding)
+ if(valve_open)
+ valve_open = FALSE
+ release_log += "Valve was closed by [key_name(usr)], stopping the transfer into the [holding] "
+ replace_tank(usr, FALSE)
+ if("recolor")
+ if(can_label)
+ var/ctype = params["ctype"]
+ var/cnum = text2num(params["nc"])
+ if(isnull(colorcontainer[ctype]))
+ message_admins("[key_name_admin(usr)] passed an invalid ctype var to a canister.")
+ return
+ var/newcolor = sanitize_integer(cnum, 0, length(colorcontainer[ctype]["options"]))
+ color_index[ctype] = newcolor
+ newcolor++ // javascript starts arrays at 0, byond (for some reason) starts them at 1, this converts JS values to byond values
+ canister_color[ctype] = colorcontainer[ctype]["options"][newcolor]["icon"]
+ add_fingerprint(usr)
update_icon()
- return 1
-
/obj/machinery/portable_atmospherics/canister/toxins
name = "Canister \[Toxin (Plasma)\]"
@@ -513,19 +419,18 @@ update_flag
..()
canister_color["prim"] = "orange"
- decals["plasma"] = 1
- src.air_contents.toxins = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature)
+ air_contents.toxins = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
- src.update_icon()
+ update_icon()
return 1
/obj/machinery/portable_atmospherics/canister/oxygen/New()
..()
canister_color["prim"] = "blue"
- src.air_contents.oxygen = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature)
+ air_contents.oxygen = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
- src.update_icon()
+ update_icon()
return 1
/obj/machinery/portable_atmospherics/canister/sleeping_agent/New()
@@ -534,25 +439,25 @@ update_flag
canister_color["prim"] = "redws"
air_contents.sleeping_agent = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
- src.update_icon()
+ update_icon()
return 1
/obj/machinery/portable_atmospherics/canister/nitrogen/New()
..()
canister_color["prim"] = "red"
- src.air_contents.nitrogen = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature)
+ air_contents.nitrogen = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
- src.update_icon()
+ update_icon()
return 1
/obj/machinery/portable_atmospherics/canister/carbon_dioxide/New()
..()
canister_color["prim"] = "black"
- src.air_contents.carbon_dioxide = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature)
+ air_contents.carbon_dioxide = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
- src.update_icon()
+ update_icon()
return 1
@@ -560,17 +465,17 @@ update_flag
..()
canister_color["prim"] = "grey"
- src.air_contents.oxygen = (O2STANDARD*src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature)
- src.air_contents.nitrogen = (N2STANDARD*src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature)
+ air_contents.oxygen = (O2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
+ air_contents.nitrogen = (N2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
- src.update_icon()
+ update_icon()
return 1
/obj/machinery/portable_atmospherics/canister/custom_mix/New()
..()
canister_color["prim"] = "whiters"
- src.update_icon() // Otherwise new canisters do not have their icon updated with the pressure light, likely want to add this to the canister class constructor, avoiding at current time to refrain from screwing up code for other canisters. --DZD
+ update_icon() // Otherwise new canisters do not have their icon updated with the pressure light, likely want to add this to the canister class constructor, avoiding at current time to refrain from screwing up code for other canisters. --DZD
return 1
/obj/machinery/portable_atmospherics/canister/welder_act(mob/user, obj/item/I)
diff --git a/code/game/machinery/atmoalter/pump.dm b/code/game/machinery/atmoalter/pump.dm
index 4d54b162466..8198976bc14 100644
--- a/code/game/machinery/atmoalter/pump.dm
+++ b/code/game/machinery/atmoalter/pump.dm
@@ -1,21 +1,26 @@
+
+/// The maximum `target_pressure` you can set on the pump. Equates to about 1013.25 kPa.
+#define MAX_TARGET_PRESSURE 10 * ONE_ATMOSPHERE
+/// The pump will be siphoning gas.
+#define DIRECTION_IN 0
+/// The pump will be pumping gas out.
+#define DIRECTION_OUT 1
+
/obj/machinery/portable_atmospherics/pump
name = "Portable Air Pump"
-
icon = 'icons/obj/atmos.dmi'
icon_state = "psiphon:0"
- density = 1
-
- var/on = 0
- var/direction_out = 0 //0 = siphoning, 1 = releasing
- var/target_pressure = 100
-
- var/pressuremin = 0
- var/pressuremax = 10 * ONE_ATMOSPHERE
-
+ density = TRUE
volume = 1000
+ /// If the pump is turned on or off.
+ var/on = FALSE
+ /// The direction the pump is operating in. This should be either `DIRECTION_IN` or `DIRECTION_OUT`.
+ var/direction = DIRECTION_IN
+ /// The desired pressure the pump should be outputting, either into the atmosphere, or into a holding tank.
+ var/target_pressure = 101.325
/obj/machinery/portable_atmospherics/pump/update_icon()
- src.overlays = 0
+ overlays = 0
if(on)
icon_state = "psiphon:1"
@@ -39,7 +44,7 @@
on = !on
if(prob(100/severity))
- direction_out = !direction_out
+ direction = !direction
target_pressure = rand(0,1300)
update_icon()
@@ -54,7 +59,7 @@
environment = holding.air_contents
else
environment = loc.return_air()
- if(direction_out)
+ if(direction == DIRECTION_OUT)
var/pressure_delta = target_pressure - environment.return_pressure()
//Can not have a pressure delta that would cause environment pressure > tank pressure
@@ -87,9 +92,7 @@
air_update_turf()
air_contents.merge(removed)
- //src.update_icon()
- src.updateDialog()
return
/obj/machinery/portable_atmospherics/pump/return_air()
@@ -102,72 +105,78 @@
if(on)
on = FALSE
update_icon()
- else if(on && holding && direction_out)
+ else if(on && holding && direction == DIRECTION_OUT)
investigate_log("[key_name(user)] started a transfer into [holding]. ", "atmos")
-/obj/machinery/portable_atmospherics/pump/attack_ai(var/mob/user as mob)
- src.add_hiddenprint(user)
- return src.attack_hand(user)
+/obj/machinery/portable_atmospherics/pump/attack_ai(mob/user)
+ add_hiddenprint(user)
+ return attack_hand(user)
-/obj/machinery/portable_atmospherics/pump/attack_ghost(var/mob/user as mob)
- return src.attack_hand(user)
+/obj/machinery/portable_atmospherics/pump/attack_ghost(mob/user)
+ return attack_hand(user)
-/obj/machinery/portable_atmospherics/pump/attack_hand(var/mob/user as mob)
- ui_interact(user)
+/obj/machinery/portable_atmospherics/pump/attack_hand(mob/user)
+ tgui_interact(user)
-/obj/machinery/portable_atmospherics/pump/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = GLOB.physical_state)
- // update the ui if it exists, returns null if no ui is passed/found
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/portable_atmospherics/pump/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- // the ui does not exist, so we'll create a new() one
- // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "portpump.tmpl", "Portable Pump", 480, 400, state = state)
- // open the new ui window
+ ui = new(user, src, ui_key, "PortablePump", "Portable Pump", 434, 377, master_ui, state)
ui.open()
- // auto update every Master Controller tick
- ui.set_auto_update(1)
+ ui.set_autoupdate(TRUE)
-/obj/machinery/portable_atmospherics/pump/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.physical_state)
- var/data[0]
- data["portConnected"] = connected_port ? 1 : 0
- data["tankPressure"] = round(air_contents.return_pressure() > 0 ? air_contents.return_pressure() : 0)
- data["targetpressure"] = round(target_pressure)
- data["pump_dir"] = direction_out
- data["minpressure"] = round(pressuremin)
- data["maxpressure"] = round(pressuremax)
- data["on"] = on ? 1 : 0
-
- data["hasHoldingTank"] = holding ? 1 : 0
+/obj/machinery/portable_atmospherics/pump/tgui_data(mob/user)
+ var/list/data = list(
+ "on" = on,
+ "direction" = direction,
+ "port_connected" = connected_port ? TRUE : FALSE,
+ "max_target_pressure" = MAX_TARGET_PRESSURE,
+ "target_pressure" = round(target_pressure, 0.001),
+ "tank_pressure" = air_contents.return_pressure() > 0 ? round(air_contents.return_pressure(), 0.001) : 0
+ )
if(holding)
- data["holdingTank"] = list("name" = holding.name, "tankPressure" = round(holding.air_contents.return_pressure() > 0 ? holding.air_contents.return_pressure() : 0))
+ data["has_holding_tank"] = TRUE
+ data["holding_tank"] = list("name" = holding.name, "tank_pressure" = holding.air_contents.return_pressure() > 0 ? round(holding.air_contents.return_pressure(), 0.001) : 0)
+ else
+ data["has_holding_tank"] = FALSE
return data
-/obj/machinery/portable_atmospherics/pump/Topic(href, href_list)
+/obj/machinery/portable_atmospherics/pump/tgui_act(action, list/params)
if(..())
- return 1
+ return
- if(href_list["power"])
- on = !on
- if(on && direction_out)
- investigate_log("[key_name(usr)] started a transfer into [holding]. ", "atmos")
- update_icon()
+ switch(action)
+ if("power")
+ on = !on
+ if(on && direction == DIRECTION_OUT)
+ investigate_log("[key_name(usr)] started a transfer into [holding]. ", "atmos")
+ update_icon()
+ return TRUE
- if(href_list["direction"])
- direction_out = !direction_out
- if(on && holding)
- investigate_log("[key_name(usr)] started a transfer into [holding]. ", "atmos")
+ if("set_direction")
+ if(text2num(params["direction"]) == DIRECTION_IN)
+ direction = DIRECTION_IN
+ else
+ direction = DIRECTION_OUT
+ if(on && holding)
+ investigate_log("[key_name(usr)] started a transfer into [holding]. ", "atmos")
+ return TRUE
- if(href_list["remove_tank"])
- if(holding)
- on = FALSE
- holding.loc = loc
- holding = null
- update_icon()
+ if("remove_tank")
+ if(holding)
+ on = FALSE
+ holding.forceMove(get_turf(src))
+ holding = null
+ update_icon()
+ return TRUE
- if(href_list["pressure_adj"])
- var/diff = text2num(href_list["pressure_adj"])
- target_pressure = clamp(target_pressure+diff, pressuremin, pressuremax)
- update_icon()
+ if("set_pressure")
+ target_pressure = clamp(text2num(params["pressure"]), 0, MAX_TARGET_PRESSURE)
+ return TRUE
- src.add_fingerprint(usr)
+ add_fingerprint(usr)
+
+#undef MAX_TARGET_PRESSURE
+#undef DIRECTION_IN
+#undef DIRECTION_OUT
diff --git a/code/game/machinery/atmoalter/scrubber.dm b/code/game/machinery/atmoalter/scrubber.dm
index 7af7751ff2c..6bfb5e07274 100644
--- a/code/game/machinery/atmoalter/scrubber.dm
+++ b/code/game/machinery/atmoalter/scrubber.dm
@@ -1,18 +1,18 @@
+
+#define MAX_RATE 10 * ONE_ATMOSPHERE
+
/obj/machinery/portable_atmospherics/scrubber
name = "Portable Air Scrubber"
-
icon = 'icons/obj/atmos.dmi'
icon_state = "pscrubber:0"
- density = 1
-
- var/on = 0
- var/volume_rate = 800
- var/widenet = 0 //is this scrubber acting on the 3x3 area around it.
-
+ density = TRUE
volume = 750
-
- var/minrate = 0//probably useless, but whatever
- var/maxrate = 10 * ONE_ATMOSPHERE
+ /// Whether the scrubber is switched on or off.
+ var/on = FALSE
+ /// The volume of gas that can be scrubbed every time `process_atmos()` is called (0.5 seconds).
+ var/volume_rate = 101.325
+ /// Is this scrubber acting on the 3x3 area around it.
+ var/widenet = FALSE
/obj/machinery/portable_atmospherics/scrubber/emp_act(severity)
if(stat & (BROKEN|NOPOWER))
@@ -26,7 +26,7 @@
..(severity)
/obj/machinery/portable_atmospherics/scrubber/update_icon()
- src.overlays = 0
+ overlays = 0
if(on)
icon_state = "pscrubber:1"
@@ -53,7 +53,7 @@
for(var/turf/simulated/tile in T.GetAtmosAdjacentTurfs(alldir=1))
scrub(tile)
-/obj/machinery/portable_atmospherics/scrubber/proc/scrub(var/turf/simulated/tile)
+/obj/machinery/portable_atmospherics/scrubber/proc/scrub(turf/simulated/tile)
var/datum/gas_mixture/environment
if(holding)
environment = holding.air_contents
@@ -99,62 +99,62 @@
/obj/machinery/portable_atmospherics/scrubber/return_air()
return air_contents
-/obj/machinery/portable_atmospherics/scrubber/attack_ai(var/mob/user as mob)
- src.add_hiddenprint(user)
- return src.attack_hand(user)
+/obj/machinery/portable_atmospherics/scrubber/attack_ai(mob/user)
+ add_hiddenprint(user)
+ return attack_hand(user)
-/obj/machinery/portable_atmospherics/scrubber/attack_ghost(var/mob/user as mob)
- return src.attack_hand(user)
+/obj/machinery/portable_atmospherics/scrubber/attack_ghost(mob/user)
+ return attack_hand(user)
-/obj/machinery/portable_atmospherics/scrubber/attack_hand(var/mob/user as mob)
- ui_interact(user)
+/obj/machinery/portable_atmospherics/scrubber/attack_hand(mob/user)
+ tgui_interact(user)
return
-/obj/machinery/portable_atmospherics/scrubber/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = GLOB.physical_state)
- // update the ui if it exists, returns null if no ui is passed/found
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/portable_atmospherics/scrubber/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- // the ui does not exist, so we'll create a new() one
- // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "portscrubber.tmpl", "Portable Scrubber", 480, 400, state = state)
- // open the new ui window
+ ui = new(user, src, ui_key, "PortableScrubber", "Portable Scrubber", 433, 346, master_ui, state)
ui.open()
- // auto update every Master Controller tick
- ui.set_auto_update(1)
+ ui.set_autoupdate(TRUE)
-/obj/machinery/portable_atmospherics/scrubber/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.physical_state)
- var/data[0]
- data["portConnected"] = connected_port ? 1 : 0
- data["tankPressure"] = round(air_contents.return_pressure() > 0 ? air_contents.return_pressure() : 0)
- data["rate"] = round(volume_rate)
- data["minrate"] = round(minrate)
- data["maxrate"] = round(maxrate)
- data["on"] = on ? 1 : 0
-
- data["hasHoldingTank"] = holding ? 1 : 0
+/obj/machinery/portable_atmospherics/scrubber/tgui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.physical_state)
+ var/list/data = list(
+ "on" = on,
+ "port_connected" = connected_port ? TRUE : FALSE,
+ "max_rate" = MAX_RATE,
+ "rate" = round(volume_rate, 0.001),
+ "tank_pressure" = air_contents.return_pressure() > 0 ? round(air_contents.return_pressure(), 0.001) : 0
+ )
if(holding)
- data["holdingTank"] = list("name" = holding.name, "tankPressure" = round(holding.air_contents.return_pressure() > 0 ? holding.air_contents.return_pressure() : 0))
+ data["has_holding_tank"] = TRUE
+ data["holding_tank"] = list("name" = holding.name, "tank_pressure" = holding.air_contents.return_pressure() > 0 ? round(holding.air_contents.return_pressure(), 0.001) : 0)
+ else
+ data["has_holding_tank"] = FALSE
return data
-/obj/machinery/portable_atmospherics/scrubber/Topic(href, href_list)
+/obj/machinery/portable_atmospherics/scrubber/tgui_act(action, list/params)
if(..())
- return 1
+ return
- if(href_list["power"])
- on = !on
- update_icon()
+ switch(action)
+ if("power")
+ on = !on
+ update_icon()
+ return TRUE
- if(href_list["remove_tank"])
- if(holding)
- holding.loc = loc
- holding = null
+ if("remove_tank")
+ if(holding)
+ holding.forceMove(get_turf(src))
+ holding = null
+ update_icon()
+ return TRUE
- if(href_list["volume_adj"])
- var/diff = text2num(href_list["volume_adj"])
- volume_rate = clamp(volume_rate+diff, minrate, maxrate)
+ if("set_rate")
+ volume_rate = clamp(text2num(params["rate"]), 0, MAX_RATE)
+ return TRUE
- src.add_fingerprint(usr)
+ add_fingerprint(usr)
/obj/machinery/portable_atmospherics/scrubber/huge
name = "Huge Air Scrubber"
@@ -175,36 +175,38 @@
name = "[name] (ID [id])"
-/obj/machinery/portable_atmospherics/scrubber/huge/attack_hand(var/mob/user as mob)
+/obj/machinery/portable_atmospherics/scrubber/huge/attack_hand(mob/user)
to_chat(usr, "You can't directly interact with this machine. Use the area atmos computer.")
/obj/machinery/portable_atmospherics/scrubber/huge/update_icon()
- src.overlays = 0
+ overlays = 0
if(on)
icon_state = "scrubber:1"
else
icon_state = "scrubber:0"
-/obj/machinery/portable_atmospherics/scrubber/huge/attackby(var/obj/item/W as obj, var/mob/user as mob, params)
- if(istype(W, /obj/item/wrench))
- if(stationary)
- to_chat(user, "The bolts are too tight for you to unscrew!")
- return
- if(on)
- to_chat(user, "Turn it off first!")
- return
-
- anchored = !anchored
- playsound(loc, W.usesound, 50, 1)
- to_chat(user, "You [anchored ? "wrench" : "unwrench"] \the [src].")
- return
-
+/obj/machinery/portable_atmospherics/scrubber/huge/attackby(obj/item/W, mob/user, params)
if((istype(W, /obj/item/analyzer)) && get_dist(user, src) <= 1)
atmosanalyzer_scan(air_contents, user)
return
return ..()
+/obj/machinery/portable_atmospherics/scrubber/huge/wrench_act(mob/user, obj/item/I)
+ . = TRUE
+ if(stationary)
+ to_chat(user, "The bolts are too tight for you to unscrew!")
+ return
+ if(on)
+ to_chat(user, "Turn it off first!")
+ return
+ if(!I.use_tool(src, user, 0, volume = I.tool_volume))
+ return
+ anchored = !anchored
+ to_chat(user, "You [anchored ? "wrench" : "unwrench"] [src].")
+
/obj/machinery/portable_atmospherics/scrubber/huge/stationary
name = "Stationary Air Scrubber"
stationary = 1
+
+#undef MAX_RATE
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index c22922e4d14..3fffc598334 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -24,7 +24,7 @@
use_power = IDLE_POWER_USE
idle_power_usage = 10
active_power_usage = 100
- var/busy = 0
+ var/busy = FALSE
var/prod_coeff
var/datum/wires/autolathe/wires = null
@@ -33,7 +33,7 @@
var/list/datum/design/matching_designs
var/temp_search
var/selected_category
- var/screen = 1
+ var/list/recipiecache = list()
var/list/categories = list("Tools", "Electronics", "Construction", "Communication", "Security", "Machinery", "Medical", "Miscellaneous", "Dinnerware", "Imported")
@@ -65,6 +65,7 @@
RefreshParts()
/obj/machinery/autolathe/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(wires)
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
materials.retrieve_all()
@@ -78,63 +79,121 @@
if(panel_open)
wires.Interact(user)
else
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/autolathe/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/autolathe/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "autolathe.tmpl", name, 800, 550)
+ ui = new(user, src, ui_key, "Autolathe", name, 750, 700, master_ui, state)
ui.open()
-/obj/machinery/autolathe/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
+
+/obj/machinery/autolathe/tgui_static_data(mob/user)
+ var/list/data = list()
+ data["categories"] = categories
+ if(!recipiecache.len)
+ var/list/recipes = list()
+ for(var/v in files.known_designs)
+ var/datum/design/D = files.known_designs[v]
+ var/list/cost_list = design_cost_data(D)
+ var/list/matreq = list()
+ for(var/list/x in cost_list)
+ if(!x["amount"])
+ continue
+ if(x["name"] == "metal") // Do not use MAT_METAL or MAT_GLASS here.
+ matreq["metal"] = x["amount"]
+ if(x["name"] == "glass")
+ matreq["glass"] = x["amount"]
+ var/obj/item/I = D.build_path
+ var/maxmult = 1
+ if(ispath(D.build_path, /obj/item/stack))
+ maxmult = D.maxstack
+ recipes.Add(list(list(
+ "name" = D.name,
+ "category" = D.category,
+ "uid" = D.UID(),
+ "requirements" = matreq,
+ "hacked" = ("hacked" in D.category) ? TRUE : FALSE,
+ "max_multiplier" = maxmult,
+ "image" = "[icon2base64(icon(initial(I.icon), initial(I.icon_state), SOUTH, 1))]"
+ )))
+ recipiecache = recipes
+ data["recipes"] = recipiecache
+ return data
+
+/obj/machinery/autolathe/tgui_data(mob/user)
+ var/list/data = list() //..()
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- var/data[0]
- data["screen"] = screen
data["total_amount"] = materials.total_amount
data["max_amount"] = materials.max_amount
+ data["fill_percent"] = round((materials.total_amount / materials.max_amount) * 100)
data["metal_amount"] = materials.amount(MAT_METAL)
data["glass_amount"] = materials.amount(MAT_GLASS)
- switch(screen)
- if(AUTOLATHE_MAIN_MENU)
- data["uid"] = UID()
- data["categories"] = categories
- if(AUTOLATHE_CATEGORY_MENU)
- data["selected_category"] = selected_category
- var/list/designs = list()
- data["designs"] = designs
- for(var/v in files.known_designs)
- var/datum/design/D = files.known_designs[v]
- if(!(selected_category in D.category))
- continue
- var/list/design = list()
- designs[++designs.len] = design
- design["name"] = D.name
- design["id"] = D.id
- design["disabled"] = disabled || !can_build(D) ? "disabled" : null
- if(ispath(D.build_path, /obj/item/stack))
- design["max_multiplier"] = min(D.maxstack, D.materials[MAT_METAL] ? round(materials.amount(MAT_METAL) / D.materials[MAT_METAL]) : INFINITY, D.materials[MAT_GLASS] ? round(materials.amount(MAT_GLASS) / D.materials[MAT_GLASS]) : INFINITY)
- else
- design["max_multiplier"] = null
- design["materials"] = design_cost_data(D)
- if(AUTOLATHE_SEARCH_MENU)
- data["search"] = temp_search
- var/list/designs = list()
- data["designs"] = designs
- for(var/datum/design/D in matching_designs)
- var/list/design = list()
- designs[++designs.len] = design
- design["name"] = D.name
- design["id"] = D.id
- design["disabled"] = disabled || !can_build(D) ? "disabled" : null
- if(ispath(D.build_path, /obj/item/stack))
- design["max_multiplier"] = min(D.maxstack, D.materials[MAT_METAL] ? round(materials.amount(MAT_METAL) / D.materials[MAT_METAL]) : INFINITY, D.materials[MAT_GLASS] ? round(materials.amount(MAT_GLASS) / D.materials[MAT_GLASS]) : INFINITY)
- else
- design["max_multiplier"] = null
- design["materials"] = design_cost_data(D)
-
- data = queue_data(data)
+ data["busyname"] = FALSE
+ data["busyamt"] = 1
+ if(length(being_built) > 0)
+ var/datum/design/D = being_built[1]
+ data["busyname"] = istype(D) && D.name ? D.name : FALSE
+ data["busyamt"] = length(being_built) > 1 ? being_built[2] : 1
+ data["showhacked"] = hacked ? TRUE : FALSE
+ data["buildQueue"] = queue
+ data["buildQueueLen"] = queue.len
return data
+/obj/machinery/autolathe/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
+ if(..())
+ return FALSE
+
+ add_fingerprint(usr)
+
+ . = TRUE
+ switch(action)
+ if("clear_queue")
+ queue = list()
+ if("remove_from_queue")
+ var/index = text2num(params["remove_from_queue"])
+ if(isnum(index) && ISINRANGE(index, 1, queue.len))
+ remove_from_queue(index)
+ to_chat(usr, "Removed item from queue.")
+ if("make")
+ BuildTurf = loc
+ var/datum/design/design_last_ordered
+ design_last_ordered = locateUID(params["make"])
+ if(!istype(design_last_ordered))
+ to_chat(usr, "Invalid design")
+ return
+ if(!(design_last_ordered.build_type & AUTOLATHE))
+ to_chat(usr, "Invalid design (not buildable in autolathe, report this error.)")
+ return
+ var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
+ if(design_last_ordered.materials["$metal"] > materials.amount(MAT_METAL))
+ to_chat(usr, "Invalid design (not enough metal)")
+ return
+ if(design_last_ordered.materials["$glass"] > materials.amount(MAT_GLASS))
+ to_chat(usr, "Invalid design (not enough glass)")
+ return
+ if(!hacked && ("hacked" in design_last_ordered.category))
+ to_chat(usr, "Invalid design (lathe requires hacking)")
+ return
+ //multiplier checks : only stacks can have one and its value is 1, 10 ,25 or max_multiplier
+ var/multiplier = text2num(params["multiplier"])
+ var/max_multiplier = min(design_last_ordered.maxstack, design_last_ordered.materials[MAT_METAL] ?round(materials.amount(MAT_METAL)/design_last_ordered.materials[MAT_METAL]):INFINITY,design_last_ordered.materials[MAT_GLASS]?round(materials.amount(MAT_GLASS)/design_last_ordered.materials[MAT_GLASS]):INFINITY)
+ var/is_stack = ispath(design_last_ordered.build_path, /obj/item/stack)
+
+ if(!is_stack && (multiplier > 1))
+ return
+ if(!(multiplier in list(1, 10, 25, max_multiplier))) //"enough materials ?" is checked in the build proc
+ message_admins("Player [key_name_admin(usr)] attempted to pass invalid multiplier [multiplier] to an autolathe in tgui_act. Possible href exploit.")
+ return
+ if((queue.len + 1) < queue_max_len)
+ add_to_queue(design_last_ordered, multiplier)
+ else
+ to_chat(usr, "The autolathe queue is full!")
+ if(!busy)
+ busy = TRUE
+ process_queue()
+ busy = FALSE
+
/obj/machinery/autolathe/proc/design_cost_data(datum/design/D)
var/list/data = list()
var/coeff = get_coeff(D)
@@ -184,14 +243,21 @@
if(istype(O, /obj/item/disk/design_disk))
var/obj/item/disk/design_disk/D = O
if(D.blueprint)
+ if(!(D.blueprint.build_type & AUTOLATHE)) // otherwise, would silently fail in AddDesign2Known
+ to_chat(user, "This design is not compatible with the autolathe.")
+ return 1
user.visible_message("[user] begins to load \the [O] in \the [src]...",
"You begin to load a design from \the [O]...",
"You hear the chatter of a floppy drive.")
playsound(get_turf(src), 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1)
- busy = 1
+ busy = TRUE
if(do_after(user, 14.4, target = src))
+ if(!("Imported" in D.blueprint.category)) // R&D should always ensure this is set on design disks, but it doesn't.
+ D.blueprint.category += "Imported" // now it will actually show up in the list.
files.AddDesign2Known(D.blueprint)
- busy = 0
+ recipiecache = list()
+ SStgui.close_uis(src) // forces all connected users to re-open the TGUI. Imported entries won't show otherwise due to static_data
+ busy = FALSE
else
to_chat(user, "That disk does not have a design on it!")
return 1
@@ -221,7 +287,6 @@
to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.")
return
if(default_deconstruction_screwdriver(user, "autolathe_t", "autolathe", I))
- SSnanoui.update_uis(src)
I.play_tool_sound(user, I.tool_volume)
/obj/machinery/autolathe/wirecutter_act(mob/user, obj/item/I)
@@ -253,7 +318,7 @@
if(MAT_GLASS)
flick("autolathe_r", src)//plays glass insertion animation
use_power(min(1000, amount_inserted / 100))
- SSnanoui.update_uis(src)
+ SStgui.update_uis(src)
/obj/machinery/autolathe/attack_ghost(mob/user)
interact(user)
@@ -263,79 +328,6 @@
return
interact(user)
-/obj/machinery/autolathe/Topic(href, href_list)
- if(..())
- return 1
-
- if(href_list["menu"])
- screen = text2num(href_list["menu"])
-
- if(href_list["category"])
- selected_category = href_list["category"]
- screen = AUTOLATHE_CATEGORY_MENU
-
- if(href_list["make"])
- BuildTurf = loc
-
- /////////////////
- //href protection
- var/datum/design/design_last_ordered
- design_last_ordered = files.FindDesignByID(href_list["make"]) //check if it's a valid design
- if(!design_last_ordered)
- return
- if(!(design_last_ordered.build_type & AUTOLATHE))
- return
-
- //multiplier checks : only stacks can have one and its value is 1, 10 ,25 or max_multiplier
- var/multiplier = text2num(href_list["multiplier"])
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- var/max_multiplier = min(design_last_ordered.maxstack, design_last_ordered.materials[MAT_METAL] ?round(materials.amount(MAT_METAL)/design_last_ordered.materials[MAT_METAL]):INFINITY,design_last_ordered.materials[MAT_GLASS]?round(materials.amount(MAT_GLASS)/design_last_ordered.materials[MAT_GLASS]):INFINITY)
- var/is_stack = ispath(design_last_ordered.build_path, /obj/item/stack)
-
- if(!is_stack && (multiplier > 1))
- return
- if(!(multiplier in list(1, 10, 25, max_multiplier))) //"enough materials ?" is checked in the build proc
- return
- /////////////////
-
- if((queue.len + 1) < queue_max_len)
- add_to_queue(design_last_ordered,multiplier)
- else
- to_chat(usr, "The autolathe queue is full!")
- if(!busy)
- busy = 1
- process_queue()
- busy = 0
-
- if(href_list["remove_from_queue"])
- var/index = text2num(href_list["remove_from_queue"])
- if(isnum(index) && ISINRANGE(index, 1, queue.len))
- remove_from_queue(index)
- if(href_list["queue_move"] && href_list["index"])
- var/index = text2num(href_list["index"])
- var/new_index = index + text2num(href_list["queue_move"])
- if(isnum(index) && isnum(new_index))
- if(ISINRANGE(new_index, 1, queue.len))
- queue.Swap(index,new_index)
- if(href_list["clear_queue"])
- queue = list()
- if(href_list["search"])
- if(href_list["to_search"])
- temp_search = href_list["to_search"]
- if(!temp_search)
- return
- matching_designs.Cut()
-
- for(var/v in files.known_designs)
- var/datum/design/D = files.known_designs[v]
- if(findtext(D.name, temp_search))
- matching_designs.Add(D)
-
- screen = AUTOLATHE_SEARCH_MENU
-
- SSnanoui.update_uis(src)
- return 1
-
/obj/machinery/autolathe/RefreshParts()
var/tot_rating = 0
prod_coeff = 0
@@ -370,7 +362,7 @@
else
var/list/materials_used = list(MAT_METAL=metal_cost/coeff, MAT_GLASS=glass_cost/coeff)
materials.use_amount(materials_used)
- SSnanoui.update_uis(src)
+ SStgui.update_uis(src)
sleep(32/coeff)
if(is_stack)
var/obj/item/stack/S = new D.build_path(BuildTurf)
@@ -379,7 +371,7 @@
var/obj/item/new_item = new D.build_path(BuildTurf)
new_item.materials[MAT_METAL] /= coeff
new_item.materials[MAT_GLASS] /= coeff
- SSnanoui.update_uis(src)
+ SStgui.update_uis(src)
desc = initial(desc)
/obj/machinery/autolathe/proc/can_build(datum/design/D, multiplier = 1, custom_metal, custom_glass)
@@ -454,7 +446,6 @@
D = listgetindex(listgetindex(queue, 1),1)
multiplier = listgetindex(listgetindex(queue,1),2)
being_built = new /list()
- //visible_message("[bicon(src)] \The [src] beeps, \"Queue processing finished successfully.\"")
/obj/machinery/autolathe/proc/adjust_hacked(hack)
hacked = hack
@@ -467,3 +458,17 @@
for(var/datum/design/D in files.known_designs)
if("hacked" in D.category)
files.known_designs -= D.id
+ SStgui.close_uis(src) // forces all connected users to re-open the TGUI, thus adding/removing hacked entries from lists
+ recipiecache = list()
+
+/obj/machinery/autolathe/proc/check_hacked_callback()
+ if(!wires.is_cut(WIRE_AUTOLATHE_HACK))
+ adjust_hacked(FALSE)
+
+/obj/machinery/autolathe/proc/check_electrified_callback()
+ if(!wires.is_cut(WIRE_ELECTRIFY))
+ shocked = FALSE
+
+/obj/machinery/autolathe/proc/check_disabled_callback()
+ if(!wires.is_cut(WIRE_AUTOLATHE_DISABLE))
+ disabled = FALSE
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index b66cad8d328..60beb105dce 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -20,7 +20,6 @@
anchored = TRUE
var/start_active = FALSE //If it ignores the random chance to start broken on round start
var/invuln = null
- var/obj/item/camera_bug/bug = null
var/obj/item/camera_assembly/assembly = null
//OTHER
@@ -28,13 +27,14 @@
var/view_range = 7
var/short_range = 2
+ var/alarm_on = FALSE
var/busy = FALSE
var/emped = FALSE //Number of consecutive EMP's on this camera
var/in_use_lights = 0 // TO BE IMPLEMENTED
var/toggle_sound = 'sound/items/wirecutter.ogg'
-/obj/machinery/camera/Initialize()
+/obj/machinery/camera/Initialize(mapload)
. = ..()
wires = new(src)
assembly = new(src)
@@ -44,25 +44,30 @@
GLOB.cameranet.cameras += src
GLOB.cameranet.addCamera(src)
+ if(isturf(loc))
+ LAZYADD(myArea.cameras, UID())
if(is_station_level(z) && prob(3) && !start_active)
toggle_cam(null, FALSE)
- wires.CutAll()
+ wires.cut_all()
+
+/obj/machinery/camera/proc/set_area_motion(area/A)
+ area_motion = A
/obj/machinery/camera/Destroy()
+ SStgui.close_uis(wires)
toggle_cam(null, FALSE) //kick anyone viewing out
QDEL_NULL(assembly)
- if(istype(bug))
- bug.bugged_cameras -= c_tag
- if(bug.current == src)
- bug.current = null
- bug = null
QDEL_NULL(wires)
GLOB.cameranet.removeCamera(src) //Will handle removal from the camera network and the chunks, so we don't need to worry about that
GLOB.cameranet.cameras -= src
+ if(isarea(myArea))
+ LAZYREMOVE(myArea.cameras, UID())
var/area/ai_monitored/A = get_area(src)
if(istype(A))
- A.motioncamera = null
+ A.motioncameras -= src
area_motion = null
+ cancelCameraAlarm()
+ cancelAlarm()
return ..()
/obj/machinery/camera/emp_act(severity)
@@ -158,6 +163,10 @@
// OTHER
else if((istype(I, /obj/item/paper) || istype(I, /obj/item/pda)) && isliving(user))
+ if (!can_use())
+ to_chat(user, "You can't show something to a disabled camera!")
+ return
+
var/mob/living/U = user
var/obj/item/paper/X = null
var/obj/item/pda/PDA = null
@@ -173,7 +182,7 @@
var/datum/data/pda/app/notekeeper/N = PDA.find_program(/datum/data/pda/app/notekeeper)
if(N)
itemname = PDA.name
- info = N.notehtml
+ info = N.note
to_chat(U, "You hold \the [itemname] up to the camera ...")
U.changeNext_move(CLICK_CD_MELEE)
for(var/mob/O in GLOB.player_list)
@@ -190,19 +199,6 @@
to_chat(O, "[U] holds \a [itemname] up to one of the cameras ...")
O << browse(text("[][]", itemname, info), text("window=[]", itemname))
- else if(istype(I, /obj/item/camera_bug))
- if(!can_use())
- to_chat(user, "Camera non-functional.")
- return
- if(istype(bug))
- to_chat(user, "Camera bug removed.")
- bug.bugged_cameras -= c_tag
- bug = null
- else
- to_chat(user, "Camera bugged.")
- bug = I
- bug.bugged_cameras[c_tag] = src
-
else if(istype(I, /obj/item/laser_pointer))
var/obj/item/laser_pointer/L = I
L.laser_act(src, user)
@@ -252,7 +248,7 @@
if(status && !(flags & NODECONSTRUCT))
triggerCameraAlarm()
toggle_cam(null, FALSE)
- wires.CutAll()
+ wires.cut_all()
/obj/machinery/camera/deconstruct(disassembled = TRUE)
if(!(flags & NODECONSTRUCT))
@@ -282,9 +278,16 @@
status = !status
if(can_use())
GLOB.cameranet.addCamera(src)
+ if(isturf(loc))
+ myArea = get_area(src)
+ LAZYADD(myArea.cameras, UID())
+ else
+ myArea = null
else
set_light(0)
GLOB.cameranet.removeCamera(src)
+ if(isarea(myArea))
+ LAZYREMOVE(myArea.cameras, UID())
GLOB.cameranet.updateChunk(x, y, z)
var/change_msg = "deactivates"
if(status)
@@ -313,12 +316,12 @@
to_chat(O, "The screen bursts into static.")
/obj/machinery/camera/proc/triggerCameraAlarm()
- if(is_station_contact(z))
- SSalarms.camera_alarm.triggerAlarm(loc, src)
+ alarm_on = TRUE
+ SSalarm.triggerAlarm("Camera", get_area(src), list(UID()), src)
/obj/machinery/camera/proc/cancelCameraAlarm()
- if(is_station_contact(z))
- SSalarms.camera_alarm.clearAlarm(loc, src)
+ alarm_on = FALSE
+ SSalarm.cancelAlarm("Camera", get_area(src), src)
/obj/machinery/camera/proc/can_use()
if(!status)
@@ -414,8 +417,8 @@
/obj/machinery/camera/portable //Cameras which are placed inside of things, such as helmets.
var/turf/prev_turf
-/obj/machinery/camera/portable/New()
- ..()
+/obj/machinery/camera/portable/Initialize(mapload)
+ . = ..()
assembly.state = 0 //These cameras are portable, and so shall be in the portable state if removed.
assembly.anchored = 0
assembly.update_icon()
diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm
index 16fb6eca204..8a32c6d6374 100644
--- a/code/game/machinery/camera/motion.dm
+++ b/code/game/machinery/camera/motion.dm
@@ -1,64 +1,64 @@
/obj/machinery/camera
-
- var/list/motionTargets = list()
+ var/list/localMotionTargets = list()
var/detectTime = 0
var/area/ai_monitored/area_motion = null
- var/alarm_delay = 100
-
+ var/alarm_delay = 30 // Don't forget, there's another 3 seconds in queueAlarm()
/obj/machinery/camera/process()
// motion camera event loop
- if(stat & (EMPED|NOPOWER))
- return
if(!isMotion())
. = PROCESS_KILL
return
+ if(stat & (EMPED|NOPOWER))
+ return
if(detectTime > 0)
var/elapsed = world.time - detectTime
if(elapsed > alarm_delay)
triggerAlarm()
else if(detectTime == -1)
- for(var/mob/target in motionTargets)
- if(target.stat == 2) lostTarget(target)
- // If not detecting with motion camera...
- if(!area_motion)
- // See if the camera is still in range
- if(!in_range(src, target))
- // If they aren't in range, lose the target.
- lostTarget(target)
+ for(var/thing in getTargetList())
+ var/mob/target = locateUID(thing)
+ if(QDELETED(target) || target.stat == DEAD || (!area_motion && !in_range(src, target)))
+ //If not part of a monitored area and the camera is not in range or the target is dead
+ lostTargetRef(thing)
-/obj/machinery/camera/proc/newTarget(var/mob/target)
- if(istype(target, /mob/living/silicon/ai)) return 0
+/obj/machinery/camera/proc/getTargetList()
+ if(area_motion)
+ return area_motion.motionTargets
+ return localMotionTargets
+
+/obj/machinery/camera/proc/newTarget(mob/target)
+ if(isAI(target))
+ return FALSE
if(detectTime == 0)
detectTime = world.time // start the clock
- if(!(target in motionTargets))
- motionTargets += target
- return 1
+ var/list/targets = getTargetList()
+ targets |= target.UID()
+ return TRUE
-/obj/machinery/camera/proc/lostTarget(var/mob/target)
- if(target in motionTargets)
- motionTargets -= target
- if(motionTargets.len == 0)
+/obj/machinery/camera/proc/lostTargetRef(uid)
+ var/list/targets = getTargetList()
+ targets -= uid
+ if(length(targets))
cancelAlarm()
/obj/machinery/camera/proc/cancelAlarm()
- if(!status || (stat & NOPOWER))
- return FALSE
- if(detectTime == -1 && is_station_contact(z))
- SSalarms.motion_alarm.clearAlarm(loc, src)
+ if(detectTime == -1)
+ if(status)
+ SSalarm.cancelAlarm("Motion", get_area(src), src)
detectTime = 0
return TRUE
/obj/machinery/camera/proc/triggerAlarm()
- if(!status || (stat & NOPOWER))
+ if(!detectTime)
return FALSE
- if(!detectTime || !is_station_contact(z))
- return FALSE
- SSalarms.motion_alarm.triggerAlarm(loc, src)
+ if(status)
+ SSalarm.triggerAlarm("Motion", get_area(src), list(UID()), src)
+ visible_message("A red light flashes on the [src]!")
detectTime = -1
return TRUE
-/obj/machinery/camera/HasProximity(atom/movable/AM as mob|obj)
+/obj/machinery/camera/HasProximity(atom/movable/AM)
// Motion cameras outside of an "ai monitored" area will use this to detect stuff.
if(!area_motion)
if(isliving(AM))
diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm
index b7ae75cd13f..33c970639e5 100644
--- a/code/game/machinery/camera/presets.dm
+++ b/code/game/machinery/camera/presets.dm
@@ -2,7 +2,7 @@
// EMP
-/obj/machinery/camera/emp_proof/Initialize()
+/obj/machinery/camera/emp_proof/Initialize(mapload)
. = ..()
upgradeEmpProof()
@@ -11,19 +11,23 @@
/obj/machinery/camera/xray
icon_state = "xraycam" // Thanks to Krutchen for the icons.
-/obj/machinery/camera/xray/Initialize()
+/obj/machinery/camera/xray/Initialize(mapload)
. = ..()
upgradeXRay()
// MOTION
+/obj/machinery/camera/motion
+ name = "motion-sensitive security camera"
-/obj/machinery/camera/motion/Initialize()
+/obj/machinery/camera/motion/Initialize(mapload)
. = ..()
upgradeMotion()
// ALL UPGRADES
+/obj/machinery/camera/all
+ icon_state = "xraycamera" //mapping icon.
-/obj/machinery/camera/all/Initialize()
+/obj/machinery/camera/all/Initialize(mapload)
. = ..()
upgradeEmpProof()
upgradeXRay()
@@ -78,6 +82,10 @@
// If you are upgrading Motion, and it isn't in the camera's New(), add it to the machines list.
/obj/machinery/camera/proc/upgradeMotion()
+ if(isMotion())
+ return
+ if(name == initial(name))
+ name = "motion-sensitive security camera"
assembly.upgrades.Add(new /obj/item/assembly/prox_sensor(assembly))
setPowerUsage()
// Add it to machines that process
diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm
index 21fddce64b7..1b083bd9c87 100644
--- a/code/game/machinery/computer/Operating.dm
+++ b/code/game/machinery/computer/Operating.dm
@@ -40,8 +40,7 @@
add_fingerprint(user)
if(stat & (BROKEN|NOPOWER))
return
- ui_interact(user)
-
+ tgui_interact(user)
/obj/machinery/computer/operating/attack_hand(mob/user)
if(..(user))
@@ -52,16 +51,15 @@
add_fingerprint(user)
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/operating/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)//ui is mostly copy pasta from the sleeper ui
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/operating/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "op_computer.tmpl", "Patient Monitor", 650, 455)
+ ui = new(user, src, ui_key, "OperatingComputer", "Patient Monitor", 650, 455, master_ui, state)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/computer/operating/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
+/obj/machinery/computer/operating/tgui_data(mob/user)
var/data[0]
var/mob/living/carbon/human/occupant
if(table)
@@ -136,38 +134,43 @@
return data
-/obj/machinery/computer/operating/Topic(href, href_list)
+/obj/machinery/computer/operating/tgui_act(action, params)
if(..())
- return 1
+ return
+ if(stat & (NOPOWER|BROKEN))
+ return
+
if((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon)))
usr.set_machine(src)
- if(href_list["verboseOn"])
- verbose=1
- if(href_list["verboseOff"])
- verbose=0
- if(href_list["healthOn"])
- healthAnnounce=1
- if(href_list["healthOff"])
- healthAnnounce=0
- if(href_list["critOn"])
- crit=1
- if(href_list["critOff"])
- crit=0
- if(href_list["oxyOn"])
- oxy=1
- if(href_list["oxyOff"])
- oxy=0
- if(href_list["oxy_adj"]!=0)
- oxyAlarm=oxyAlarm+text2num(href_list["oxy_adj"])
- if(href_list["choiceOn"])
- choice=1
- if(href_list["choiceOff"])
- choice=0
- if(href_list["health_adj"]!=0)
- healthAlarm=healthAlarm+text2num(href_list["health_adj"])
- return
-
+ . = TRUE
+ switch(action)
+ if("verboseOn")
+ verbose = TRUE
+ if("verboseOff")
+ verbose = FALSE
+ if("healthOn")
+ healthAnnounce = TRUE
+ if("healthOff")
+ healthAnnounce = FALSE
+ if("critOn")
+ crit = TRUE
+ if("critOff")
+ crit = FALSE
+ if("oxyOn")
+ oxy = TRUE
+ if("oxyOff")
+ oxy = FALSE
+ if("oxy_adj")
+ oxyAlarm = clamp(text2num(params["new"]), -100, 100)
+ if("choiceOn")
+ choice = TRUE
+ if("choiceOff")
+ choice = FALSE
+ if("health_adj")
+ healthAlarm = clamp(text2num(params["new"]), -100, 100)
+ else
+ return FALSE
/obj/machinery/computer/operating/process()
@@ -178,6 +181,7 @@
atom_say("New patient detected, loading stats")
victim = table.victim
atom_say("[victim.real_name], [victim.dna.blood_type] blood, [victim.stat ? "Non-Responsive" : "Awake"]")
+ SStgui.update_uis(src)
if(nextTick < world.time)
nextTick=world.time + OP_COMPUTER_COOLDOWN
if(crit && victim.health <= -50 )
diff --git a/code/game/machinery/computer/ai_core.dm b/code/game/machinery/computer/ai_core.dm
index 44b55ed1d2c..1fe5c3eaea8 100644
--- a/code/game/machinery/computer/ai_core.dm
+++ b/code/game/machinery/computer/ai_core.dm
@@ -108,14 +108,10 @@
update_icon()
return
- if(AI_READY_CORE)
- if(istype(P, /obj/item/aicard))
- P.transfer_ai("INACTIVE", "AICARD", src, user)
- return
return ..()
/obj/structure/AIcore/crowbar_act(mob/living/user, obj/item/I)
- if(state !=CIRCUIT_CORE || state != GLASS_CORE || !(state == CABLED_CORE && brain))
+ if(state !=CIRCUIT_CORE && state != GLASS_CORE && !(state == CABLED_CORE && brain))
return
. = TRUE
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
@@ -229,7 +225,7 @@
qdel(src)
/obj/structure/AIcore/welder_act(mob/user, obj/item/I)
- if(!state)
+ if(state)
return
. = TRUE
if(!I.tool_use_check(user, 0))
@@ -288,7 +284,7 @@ That prevents a few funky behaviors.
//The type of interaction, the player performing the operation, the AI itself, and the card object, if any.
-atom/proc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card)
+/atom/proc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card)
if(istype(card))
if(card.flush)
to_chat(user, "ERROR: AI flush is in progress, cannot execute transfer protocol.")
diff --git a/code/game/machinery/computer/aifixer.dm b/code/game/machinery/computer/aifixer.dm
index 0d715fe5759..6b198deba49 100644
--- a/code/game/machinery/computer/aifixer.dm
+++ b/code/game/machinery/computer/aifixer.dm
@@ -22,23 +22,22 @@
return ..()
/obj/machinery/computer/aifixer/attack_ai(var/mob/user as mob)
- ui_interact(user)
+ tgui_interact(user)
/obj/machinery/computer/aifixer/attack_hand(var/mob/user as mob)
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/aifixer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/aifixer/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "ai_fixer.tmpl", "AI System Integrity Restorer", 550, 500)
+ ui = new(user, src, ui_key, "AIFixer", name, 550, 500, master_ui, state)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/computer/aifixer/ui_data(mob/user, datum/topic_state/state)
+/obj/machinery/computer/aifixer/tgui_data(mob/user, datum/topic_state/state)
var/data[0]
+ data["occupant"] = (occupant ? occupant.name : null) // a null occupant isn't passed on if this is below the if.
if(occupant)
- data["occupant"] = occupant.name
data["reference"] = "\ref[occupant]"
data["integrity"] = (occupant.health+100)/2
data["stat"] = occupant.stat
@@ -48,45 +47,49 @@
var/laws[0]
for(var/datum/ai_law/law in occupant.laws.all_laws())
- laws.Add(list(list("law" = law.law, "number" = law.get_index())))
-
+ if(law in occupant.laws.ion_laws) // If we're an ion law, give it an ion index code
+ laws.Add(ionnum() + ". " + law.law)
+ else
+ laws.Add(num2text(law.get_index()) + ". " + law.law)
data["laws"] = laws
+ data["has_laws"] = length(laws)
return data
-/obj/machinery/computer/aifixer/Topic(href, href_list)
+/obj/machinery/computer/aifixer/tgui_act(action, params)
if(..())
- return 1
+ return
+ switch(action)
+ if("fix")
+ if(active) // Prevent from starting a fix while fixing.
+ to_chat(usr, "You are already fixing this AI!")
+ return
+ active = TRUE
+ INVOKE_ASYNC(src, .proc/fix_ai)
+ add_fingerprint(usr)
- if(href_list["fix"])
- active = 1
- while(occupant.health < 100)
- occupant.adjustOxyLoss(-1, FALSE)
- occupant.adjustFireLoss(-1, FALSE)
- occupant.adjustToxLoss(-1, FALSE)
- occupant.adjustBruteLoss(-1, FALSE)
- occupant.updatehealth()
- if(occupant.health >= 0 && occupant.stat == DEAD)
- occupant.update_revive()
- occupant.lying = 0
- update_icon()
- sleep(10)
- active = 0
- add_fingerprint(usr)
+ if("wireless")
+ occupant.control_disabled = !occupant.control_disabled
- if(href_list["wireless"])
- var/wireless = text2num(href_list["wireless"])
- if(wireless == 0 || wireless == 1)
- occupant.control_disabled = wireless
+ if("radio")
+ occupant.aiRadio.disabledAi = !occupant.aiRadio.disabledAi
- if(href_list["radio"])
- var/radio = text2num(href_list["radio"])
- if(radio == 0 || radio == 1)
- occupant.aiRadio.disabledAi = radio
-
- SSnanoui.update_uis(src)
update_icon()
- return
+ return TRUE
+
+/obj/machinery/computer/aifixer/proc/fix_ai() // Can we fix it? Probrably.
+ while(occupant.health < 100)
+ occupant.adjustOxyLoss(-1, FALSE)
+ occupant.adjustFireLoss(-1, FALSE)
+ occupant.adjustToxLoss(-1, FALSE)
+ occupant.adjustBruteLoss(-1, FALSE)
+ occupant.updatehealth()
+ if(occupant.health >= 0 && occupant.stat == DEAD)
+ occupant.update_revive()
+ occupant.lying = FALSE
+ update_icon()
+ sleep(10)
+ active = FALSE
/obj/machinery/computer/aifixer/update_icon()
..()
@@ -113,7 +116,7 @@
if(stat & (NOPOWER|BROKEN))
to_chat(user, "[src] is offline and cannot take an AI at this time!")
return
- AI.loc = src
+ AI.forceMove(src)
occupant = AI
AI.control_disabled = 1
AI.aiRadio.disabledAi = 1
@@ -125,7 +128,7 @@
if(occupant && !active)
to_chat(occupant, "You have been downloaded to a mobile storage device. Still no remote access.")
to_chat(user, "Transfer successful: [occupant.name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory.")
- occupant.loc = card
+ occupant.forceMove(card)
occupant = null
update_icon()
else if(active)
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 20ff79041a9..adbed5f15eb 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -571,20 +571,22 @@
event = null
else if(href_list["killcrew"]) //shoot a crewmember
+ if(length(settlers) <= 0 || alive <= 0)
+ return
var/sheriff = remove_crewmember() //I shot the sheriff
- playsound(loc,'sound/weapons/gunshots/gunshot.ogg', 100, 1)
+ playsound(loc, 'sound/weapons/gunshots/gunshot.ogg', 100, 1)
- if(settlers.len == 0 || alive == 0)
+ if(length(settlers) == 0 || alive == 0)
atom_say("The last crewmember [sheriff], shot themselves, GAME OVER!")
if(emagged)
- usr.death(0)
- emagged = 0
- gameover = 1
+ usr.death(FALSE)
+ emagged = FALSE
+ gameover = TRUE
event = null
else if(emagged)
if(usr.name == sheriff)
atom_say("The crew of the ship chose to kill [usr.name]!")
- usr.death(0)
+ usr.death(FALSE)
if(event == ORION_TRAIL_LING) //only ends the ORION_TRAIL_LING event, since you can do this action in multiple places
event = null
diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm
index 123cee3ae4e..0be4aa0449b 100644
--- a/code/game/machinery/computer/atmos_alert.dm
+++ b/code/game/machinery/computer/atmos_alert.dm
@@ -1,84 +1,90 @@
-GLOBAL_LIST_EMPTY(priority_air_alarms)
-GLOBAL_LIST_EMPTY(minor_air_alarms)
-
-
/obj/machinery/computer/atmos_alert
name = "atmospheric alert computer"
desc = "Used to access the station's atmospheric sensors."
circuit = /obj/item/circuitboard/atmos_alert
+ var/ui_x = 350
+ var/ui_y = 300
icon_keyboard = "atmos_key"
icon_screen = "alert:0"
light_color = LIGHT_COLOR_CYAN
+ var/list/priority_alarms = list()
+ var/list/minor_alarms = list()
+ var/receive_frequency = ATMOS_FIRE_FREQ
+ var/datum/radio_frequency/radio_connection
-/obj/machinery/computer/atmos_alert/New()
- ..()
- SSalarms.atmosphere_alarm.register(src, /obj/machinery/computer/station_alert/.proc/update_icon)
+/obj/machinery/computer/atmos_alert/Initialize(mapload)
+ . = ..()
+ set_frequency(receive_frequency)
/obj/machinery/computer/atmos_alert/Destroy()
- SSalarms.atmosphere_alarm.unregister(src)
- return ..()
+ SSradio.remove_object(src, receive_frequency)
+ return ..()
/obj/machinery/computer/atmos_alert/attack_hand(mob/user)
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/atmos_alert/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/atmos_alert/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "atmos_alert.tmpl", src.name, 500, 500)
+ ui = new(user, src, ui_key, "AtmosAlertConsole", name, ui_x, ui_y, master_ui, state)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/computer/atmos_alert/ui_data(mob/user, datum/topic_state/state)
- var/data[0]
- var/major_alarms[0]
- var/minor_alarms[0]
+/obj/machinery/computer/atmos_alert/tgui_data(mob/user)
+ var/list/data = list()
- for(var/datum/alarm/alarm in SSalarms.atmosphere_alarm.major_alarms())
- major_alarms[++major_alarms.len] = list("name" = sanitize(alarm.alarm_name()), "ref" = "\ref[alarm]")
-
- for(var/datum/alarm/alarm in SSalarms.atmosphere_alarm.minor_alarms())
- minor_alarms[++minor_alarms.len] = list("name" = sanitize(alarm.alarm_name()), "ref" = "\ref[alarm]")
-
- data["priority_alarms"] = major_alarms
- data["minor_alarms"] = minor_alarms
+ data["priority"] = list()
+ for(var/zone in priority_alarms)
+ data["priority"] |= zone
+ data["minor"] = list()
+ for(var/zone in minor_alarms)
+ data["minor"] |= zone
return data
-/obj/machinery/computer/atmos_alert/update_icon()
- var/list/alarms = SSalarms.atmosphere_alarm.major_alarms()
- if(alarms.len)
- icon_screen = "alert:2"
- else
- alarms = SSalarms.atmosphere_alarm.minor_alarms()
- if(alarms.len)
- icon_screen = "alert:1"
- else
- icon_screen = "alert:0"
- ..()
-
-/obj/machinery/computer/atmos_alert/Topic(href, href_list)
+/obj/machinery/computer/atmos_alert/tgui_act(action, params)
if(..())
- return 1
+ return
+ switch(action)
+ if("clear")
+ var/zone = params["zone"]
+ if(zone in priority_alarms)
+ to_chat(usr, "Priority alarm for [zone] cleared.")
+ priority_alarms -= zone
+ . = TRUE
+ if(zone in minor_alarms)
+ to_chat(usr, "Minor alarm for [zone] cleared.")
+ minor_alarms -= zone
+ . = TRUE
+ update_icon()
- if(href_list["clear_alarm"])
- var/datum/alarm/alarm = locate(href_list["clear_alarm"]) in SSalarms.atmosphere_alarm.alarms
- if(alarm)
- for(var/datum/alarm_source/alarm_source in alarm.sources)
- var/obj/machinery/alarm/air_alarm = alarm_source.source
- if(istype(air_alarm))
- var/list/new_ref = list("atmos_reset" = 1)
- air_alarm.Topic(href, new_ref, state = GLOB.air_alarm_topic)
- update_icon()
- return 1
+/obj/machinery/computer/atmos_alert/proc/set_frequency(new_frequency)
+ SSradio.remove_object(src, receive_frequency)
+ receive_frequency = new_frequency
+ radio_connection = SSradio.add_object(src, receive_frequency, RADIO_ATMOSIA)
-GLOBAL_DATUM_INIT(air_alarm_topic, /datum/topic_state/air_alarm_topic, new)
+/obj/machinery/computer/atmos_alert/receive_signal(datum/signal/signal)
+ if(!signal)
+ return
-/datum/topic_state/air_alarm_topic/href_list(var/mob/user)
- var/list/extra_href = list()
- extra_href["remote_connection"] = 1
- extra_href["remote_access"] = 1
+ var/zone = signal.data["zone"]
+ var/severity = signal.data["alert"]
- return extra_href
+ if(!zone || !severity)
+ return
-/datum/topic_state/air_alarm_topic/can_use_topic(var/src_object, var/mob/user)
- return STATUS_INTERACTIVE
+ minor_alarms -= zone
+ priority_alarms -= zone
+ if(severity == "severe")
+ priority_alarms += zone
+ else if(severity == "minor")
+ minor_alarms += zone
+ update_icon()
+
+/obj/machinery/computer/atmos_alert/update_icon()
+ if(length(priority_alarms))
+ icon_screen = "alert:2"
+ else if(length(minor_alarms))
+ icon_screen = "alert:1"
+ else
+ icon_screen = "alert:0"
+ ..()
diff --git a/code/game/machinery/computer/brigcells.dm b/code/game/machinery/computer/brigcells.dm
index a391bfa4021..b220ded29c5 100644
--- a/code/game/machinery/computer/brigcells.dm
+++ b/code/game/machinery/computer/brigcells.dm
@@ -12,7 +12,7 @@
/obj/machinery/computer/brigcells/attack_ai(mob/user)
attack_hand(user)
- ui_interact(user)
+ tgui_interact(user)
/obj/machinery/computer/brigcells/attack_hand(mob/user)
add_fingerprint(user)
@@ -21,38 +21,46 @@
if(!allowed(user))
to_chat(user, "Access denied.")
return
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/brigcells/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "brig_cells.tmpl", "Brig Cell Management", 1000, 400)
- ui.open()
- ui.set_auto_update(1)
+/obj/machinery/computer/brigcells/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "BrigCells", "Brig Cell Management", 1000, 400, master_ui, state)
+ ui.open()
+ ui.set_autoupdate(TRUE)
-/obj/machinery/computer/brigcells/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- var/list/timers = list()
- for(var/obj/machinery/door_timer/T in GLOB.celltimers_list)
- var/timer = list()
- timer["cell_id"] = T.name
- timer["occupant"] = T.occupant
- timer["crimes"] = T.crimes
- timer["brigged_by"] = T.officer
- if(T.time == 0)
- timer["background"] = "'background-color:#007f47'"
- else
- timer["background"] = "'background-color:#890E26'"
- timer["time_set"] = seconds_to_clock(T.time / 10)
- timer["time_left"] = seconds_to_clock(T.timeleft())
- timer["ref"] = "\ref[T]"
- timers[++timers.len] += timer
- timers = sortByKey(timers, "cell_id")
- data["cells"] = timers
- return data
+/obj/machinery/computer/brigcells/tgui_data(mob/user)
+ var/list/data = list()
+ var/list/timers = list()
+ for(var/obj/machinery/door_timer/T in GLOB.celltimers_list)
+ var/timer = list()
+ timer["cell_id"] = T.name
+ timer["occupant"] = T.occupant
+ timer["crimes"] = T.crimes
+ timer["brigged_by"] = T.officer
+ timer["time_set_seconds"] = round(T.timetoset / 10, 1)
+ timer["time_left_seconds"] = round(T.timeleft(), 1)
+ timer["ref"] = "\ref[T]"
+ timers[++timers.len] += timer
+ timers = sortByKey(timers, "cell_id")
+ data["cells"] = timers
+ return data
-/obj/machinery/computer/brigcells/Topic(href, href_list)
- if(href_list["release"])
- var/obj/machinery/door_timer/T = locate(href_list["release"])
- T.timer_end()
- T.Radio.autosay("Timer stopped manually from a cell management console.", T.name, "Security", list(z))
+/obj/machinery/computer/brigcells/tgui_act(action, params)
+ if (..())
+ return FALSE
+
+ if(!allowed(usr))
+ to_chat(usr, "Access denied.")
+ return FALSE
+
+ if (action == "release")
+ var/ref = params["ref"]
+ var/obj/machinery/door_timer/T = locate(ref)
+ if (T)
+ T.timer_end()
+ T.Radio.autosay("Timer stopped manually from a cell management console.", T.name, "Security", list(z))
+ return TRUE
+
+ return FALSE
diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm
index b502d6e5441..a02c46524c5 100644
--- a/code/game/machinery/computer/buildandrepair.dm
+++ b/code/game/machinery/computer/buildandrepair.dm
@@ -159,18 +159,12 @@
name = "Circuit board (Security Records)"
build_path = /obj/machinery/computer/secure_data
origin_tech = "programming=2;combat=2"
-/obj/item/circuitboard/skills
- name = "Circuit board (Employment Records)"
- build_path = /obj/machinery/computer/skills
/obj/item/circuitboard/stationalert_engineering
name = "Circuit Board (Station Alert Console (Engineering))"
build_path = /obj/machinery/computer/station_alert
-/obj/item/circuitboard/stationalert_security
- name = "Circuit Board (Station Alert Console (Security))"
+/obj/item/circuitboard/stationalert
+ name = "Circuit Board (Station Alert Console)"
build_path = /obj/machinery/computer/station_alert
-/obj/item/circuitboard/stationalert_all
- name = "Circuit Board (Station Alert Console (All))"
- build_path = /obj/machinery/computer/station_alert/all
/obj/item/circuitboard/atmos_alert
name = "Circuit Board (Atmospheric Alert Computer)"
build_path = /obj/machinery/computer/atmos_alert
@@ -236,7 +230,10 @@
/obj/item/circuitboard/brigcells
name = "Circuit board (Brig Cell Control)"
build_path = /obj/machinery/computer/brigcells
-
+/obj/item/circuitboard/sm_monitor
+ name = "Circuit board (Supermatter Monitoring Console)"
+ build_path = /obj/machinery/computer/sm_monitor
+ origin_tech = "programming=2;powerstorage=2"
// RD console circuits, so that {de,re}constructing one of the special consoles doesn't ruin everything forever
/obj/item/circuitboard/rdconsole
@@ -283,7 +280,7 @@
origin_tech = "programming=3;powerstorage=3"
/obj/item/circuitboard/ordercomp
name = "Circuit board (Supply Ordering Console)"
- build_path = /obj/machinery/computer/ordercomp
+ build_path = /obj/machinery/computer/supplycomp/public
origin_tech = "programming=3"
/obj/item/circuitboard/supplycomp
name = "Circuit board (Supply Shuttle Console)"
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 3f8fc080bf8..cb7e1b2d20b 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -10,75 +10,167 @@
var/mapping = 0 // For the overview file (overview.dm), not used on this page
var/list/network = list()
- var/list/available_networks = list()
- var/list/watchers = list() //who's using the console, associated with the camera they're on.
+ var/obj/machinery/camera/active_camera
+ var/list/watchers = list()
-/obj/machinery/computer/security/New() // Lists existing networks and their required access. Format: available_networks[] = list()
- generate_network_access()
- ..()
+ // Stuff needed to render the map
+ var/map_name
+ var/const/default_map_size = 15
+ var/obj/screen/map_view/cam_screen
+ /// All the plane masters that need to be applied.
+ var/list/cam_plane_masters
+ var/obj/screen/background/cam_background
-/obj/machinery/computer/security/proc/generate_network_access()
- available_networks["SS13"] = list(ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Telecomms"] = list(ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Research Outpost"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Mining Outpost"] = list(ACCESS_QM,ACCESS_HOP,ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Research"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Prison"] = list(ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Labor Camp"] = list(ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Interrogation"] = list(ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Atmosphere Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Fire Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Power Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Supermatter"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["MiniSat"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Singularity"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Anomaly Isolation"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Toxins"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["Telepad"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["TestChamber"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN)
- available_networks["ERT"] = list(ACCESS_CENT_SPECOPS_COMMANDER,ACCESS_CENT_COMMANDER)
- available_networks["CentComm"] = list(ACCESS_CENT_SECURITY,ACCESS_CENT_COMMANDER)
- available_networks["Thunderdome"] = list(ACCESS_CENT_THUNDER,ACCESS_CENT_COMMANDER)
+ // Parent object this camera is assigned to. Used for camera bugs
+ var/atom/movable/parent
+
+/obj/machinery/computer/security/tgui_host()
+ return parent ? parent : src
+
+/obj/machinery/computer/security/Initialize()
+ . = ..()
+ // Initialize map objects
+ map_name = "camera_console_[UID()]_map"
+ cam_screen = new
+ cam_screen.name = "screen"
+ cam_screen.assigned_map = map_name
+ cam_screen.del_on_map_removal = FALSE
+ cam_screen.screen_loc = "[map_name]:1,1"
+ cam_plane_masters = list()
+ for(var/plane in subtypesof(/obj/screen/plane_master))
+ var/obj/screen/instance = new plane()
+ instance.assigned_map = map_name
+ instance.del_on_map_removal = FALSE
+ instance.screen_loc = "[map_name]:CENTER"
+ cam_plane_masters += instance
+ cam_background = new
+ cam_background.assigned_map = map_name
+ cam_background.del_on_map_removal = FALSE
/obj/machinery/computer/security/Destroy()
- if(watchers.len)
- for(var/mob/M in watchers)
- M.unset_machine() //to properly reset the view of the users if the console is deleted.
+ qdel(cam_screen)
+ QDEL_LIST(cam_plane_masters)
+ qdel(cam_background)
return ..()
-/obj/machinery/computer/security/proc/isCameraFarAway(obj/machinery/camera/C)
- var/turf/consoleturf = get_turf(src)
- var/turf/cameraturf = get_turf(C)
- if((is_away_level(cameraturf.z) || is_away_level(consoleturf.z)) && !atoms_share_level(cameraturf, consoleturf)) //can only recieve away mission cameras on away missions
+/obj/machinery/computer/security/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ // Update UI
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ // Show static if can't use the camera
+ if(!active_camera?.can_use())
+ show_camera_static()
+ if(!ui)
+ var/user_uid = user.UID()
+ var/is_living = isliving(user)
+ // Ghosts shouldn't count towards concurrent users, which produces
+ // an audible terminal_on click.
+ if(is_living)
+ watchers += user_uid
+ // Turn on the console
+ if(length(watchers) == 1 && is_living)
+ playsound(src, 'sound/machines/terminal_on.ogg', 25, FALSE)
+ use_power(active_power_usage)
+ // Register map objects
+ user.client.register_map_obj(cam_screen)
+ for(var/plane in cam_plane_masters)
+ user.client.register_map_obj(plane)
+ user.client.register_map_obj(cam_background)
+ // Open UI
+ ui = new(user, src, ui_key, "CameraConsole", name, 870, 708, master_ui, state)
+ ui.open()
+
+/obj/machinery/computer/security/tgui_data()
+ var/list/data = list()
+ data["network"] = network
+ data["activeCamera"] = null
+ if(active_camera)
+ data["activeCamera"] = list(
+ name = active_camera.c_tag,
+ status = active_camera.status,
+ )
+ return data
+
+/obj/machinery/computer/security/tgui_static_data()
+ var/list/data = list()
+ data["mapRef"] = map_name
+ var/list/cameras = get_available_cameras()
+ data["cameras"] = list()
+ for(var/i in cameras)
+ var/obj/machinery/camera/C = cameras[i]
+ data["cameras"] += list(list(
+ name = C.c_tag,
+ ))
+ return data
+
+
+/obj/machinery/computer/security/tgui_act(action, params)
+ if(..())
+ return
+
+ if(action == "switch_camera")
+ var/c_tag = params["name"]
+ var/list/cameras = get_available_cameras()
+ var/obj/machinery/camera/C = cameras[c_tag]
+ active_camera = C
+ playsound(src, get_sfx("terminal_type"), 25, FALSE)
+
+ // Show static if can't use the camera
+ if(!active_camera?.can_use())
+ show_camera_static()
+ return TRUE
+
+ var/list/visible_turfs = list()
+ for(var/turf/T in (C.isXRay() \
+ ? range(C.view_range, C) \
+ : view(C.view_range, C)))
+ visible_turfs += T
+
+ var/list/bbox = get_bbox_of_atoms(visible_turfs)
+ var/size_x = bbox[3] - bbox[1] + 1
+ var/size_y = bbox[4] - bbox[2] + 1
+
+ cam_screen.vis_contents = visible_turfs
+ cam_background.icon_state = "clear"
+ cam_background.fill_rect(1, 1, size_x, size_y)
+
return TRUE
-/obj/machinery/computer/security/check_eye(mob/user)
- if((stat & (NOPOWER|BROKEN)) || user.incapacitated() || !user.has_vision())
- user.unset_machine()
- return
- if(!(user in watchers))
- user.unset_machine()
- return
- if(!watchers[user])
- user.unset_machine()
- return
- var/obj/machinery/camera/C = watchers[user]
- if(isCameraFarAway(C))
- user.unset_machine()
- return
- if(!can_access_camera(C, user))
- user.unset_machine()
-
-/obj/machinery/computer/security/on_unset_machine(mob/user)
- watchers.Remove(user)
- user.reset_perspective(null)
+// Returns the list of cameras accessible from this computer
+/obj/machinery/computer/security/proc/get_available_cameras()
+ var/list/L = list()
+ for (var/obj/machinery/camera/C in GLOB.cameranet.cameras)
+ if((is_away_level(z) || is_away_level(C.z)) && (C.z != z))//if on away mission, can only receive feed from same z_level cameras
+ continue
+ L.Add(C)
+ var/list/D = list()
+ for(var/obj/machinery/camera/C in L)
+ if(!C.network)
+ stack_trace("Camera in a cameranet has no camera network")
+ continue
+ if(!(islist(C.network)))
+ stack_trace("Camera in a cameranet has a non-list camera network")
+ continue
+ var/list/tempnetwork = C.network & network
+ if(tempnetwork.len)
+ D["[C.c_tag]"] = C
+ return D
/obj/machinery/computer/security/attack_hand(mob/user)
if(stat || ..())
user.unset_machine()
return
- ui_interact(user)
+ tgui_interact(user)
+
+/obj/machinery/computer/security/attack_ai(mob/user)
+ to_chat(user, "You realise its kind of stupid to access a camera console when you have the entire camera network at your metaphorical fingertips")
+ return
+
+
+/obj/machinery/computer/security/proc/show_camera_static()
+ cam_screen.vis_contents.Cut()
+ cam_background.icon_state = "scanline2"
+ cam_background.fill_rect(1, 1, default_map_size, default_map_size)
/obj/machinery/computer/security/telescreen/multitool_act(mob/user, obj/item/I)
. = TRUE
@@ -99,212 +191,6 @@
if("West")
pixel_x = -32
-/obj/machinery/computer/security/emag_act(user as mob)
- if(!emagged)
- emagged = 1
- to_chat(user, "You have authorized full network access!")
- attack_hand(user)
- else
- attack_hand(user)
-
-/obj/machinery/computer/security/proc/get_user_access(mob/user)
- var/list/access = list()
-
- if(emagged)
- access = get_all_accesses() // Assume captain level access when emagged
- else if(ishuman(user))
- access = user.get_access()
- else if((isAI(user) || isrobot(user)) && CanUseTopic(user, GLOB.default_state) == STATUS_INTERACTIVE)
- access = get_all_accesses() // Assume captain level access when AI
- else if(user.can_admin_interact())
- access = get_all_accesses()
- return access
-
-/obj/machinery/computer/security/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "sec_camera.tmpl", "Camera Console", 900, 800)
-
- // adding a template with the key "mapContent" enables the map ui functionality
- ui.add_template("mapContent", "sec_camera_map_content.tmpl")
- // adding a template with the key "mapHeader" replaces the map header content
- ui.add_template("mapHeader", "sec_camera_map_header.tmpl")
-
- ui.open()
-
-/obj/machinery/computer/security/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
-
- var/list/cameras = list()
- for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
- if(isCameraFarAway(C))
- continue
- if(!can_access_camera(C, user))
- continue
-
- cameras[++cameras.len] = C.nano_structure()
-
- for(var/i = cameras.len, i > 0, i--) //based off /proc/camera_sort, sorts cameras alphabetically for the UI
- for(var/j = 1 to i - 1)
- var/a = cameras[j]
- var/b = cameras[j + 1]
- if(sorttext(a["name"], b["name"]) < 0)
- cameras.Swap(j, j + 1)
-
- data["cameras"] = cameras
-
- var/list/access = get_user_access(user)
- if(emagged)
- data["emagged"] = 1
-
- var/list/networks_list = list()
- // Loop through the ID's permission, and check which networks the ID has access to.
- for(var/net in available_networks) // Loop through networks.
- for(var/req in available_networks[net]) // Loop through access levels of the networks.
- if(req in access)
- if(net in network) // Checks if the network is currently active.
- networks_list.Add(list(list("name" = net, "active" = 1)))
- else
- networks_list.Add(list(list("name" = net, "active" = 0)))
- break
-
- if(networks_list.len)
- data["networks"] = networks_list
-
- data["current"] = null
- if(watchers[user])
- var/obj/machinery/camera/watched = watchers[user]
- data["current"] = watched.nano_structure()
-
- return data
-
-/obj/machinery/computer/security/Topic(href, href_list)
- if(..())
- usr.unset_machine()
- return 1
-
- if(href_list["switchTo"])
- var/obj/machinery/camera/C = locate(href_list["switchTo"]) in GLOB.cameranet.cameras
- if(!C)
- return 1
-
- switch_to_camera(usr, C)
-
- else if(href_list["reset"])
- usr.unset_machine()
-
- else if(href_list["activate"]) // Activate: enable or disable networks
- var/net = href_list["activate"] // Network to be enabled or disabled.
- var/active = href_list["active"] // Is the network currently active.
- var/list/access = get_user_access(usr)
- for(var/a in available_networks[net])
- if(a in access) // Re-check for authorization.
- if(text2num(active) == 1)
- network -= net
- break
- else
- network += net
- break
-
- SSnanoui.update_uis(src)
-
-// Check if camera is accessible when jumping
-/obj/machinery/computer/security/proc/can_access_camera(var/obj/machinery/camera/C, var/mob/M)
- if(CanUseTopic(M, GLOB.default_state) != STATUS_INTERACTIVE || M.incapacitated() || !M.has_vision())
- return 0
-
- if(isrobot(M))
- var/list/viewing = viewers(src)
- if(!viewing.Find(M))
- return 0
-
- if(isAI(M))
- var/mob/living/silicon/ai/A = M
- if(!A.is_in_chassis())
- return 0
-
- if(!issilicon(M) && !Adjacent(M))
- return 0
-
- var/list/shared_networks = network & C.network
- if(!shared_networks.len || !C.can_use())
- return 0
-
- return 1
-
-// Switching to cameras
-/obj/machinery/computer/security/proc/switch_to_camera(var/mob/user, var/obj/machinery/camera/C)
- if(!can_access_camera(C, user))
- user.unset_machine()
- return 1
-
- if(isAI(user))
- var/mob/living/silicon/ai/A = user
- A.eyeobj.setLoc(get_turf(C))
- A.client.eye = A.eyeobj
- else
- user.reset_perspective(C)
- watchers[user] = C
- use_power(50)
-
-//Camera control: moving.
-/obj/machinery/computer/security/proc/jump_on_click(var/mob/user, var/A)
- if(user.machine != src)
- return
-
- var/obj/machinery/camera/jump_to
-
- if(istype(A, /obj/machinery/camera))
- jump_to = A
-
- else if(ismob(A))
- if(ishuman(A))
- var/mob/living/carbon/human/H = A
- jump_to = locate() in H.head
- else if(isrobot(A))
- var/mob/living/silicon/robot/R = A
- jump_to = R.camera
-
- else if(isobj(A))
- var/obj/O = A
- jump_to = locate() in O
-
- else if(isturf(A))
- var/best_dist = INFINITY
- for(var/obj/machinery/camera/camera in get_area(A))
- if(!camera.can_use())
- continue
- if(!can_access_camera(camera, user))
- continue
- var/dist = get_dist(camera,A)
- if(dist < best_dist)
- best_dist = dist
- jump_to = camera
-
- if(isnull(jump_to))
- return
-
- if(can_access_camera(jump_to, user))
- switch_to_camera(user, jump_to)
-
-// Camera control: mouse.
-/atom/DblClick()
- ..()
- if(istype(usr.machine, /obj/machinery/computer/security))
- var/obj/machinery/computer/security/console = usr.machine
- console.jump_on_click(usr, src)
-
-// Camera control: arrow keys.
-/mob/Move(n, direct)
- if(istype(machine, /obj/machinery/computer/security))
- var/obj/machinery/computer/security/console = machine
- var/turf/T = get_turf(console.watchers[src])
- for(var/i; i < 10; i++)
- T = get_step(T, direct)
- console.jump_on_click(src, T)
- return
- return ..()
-
// Other computer monitors.
/obj/machinery/computer/security/telescreen
name = "telescreen"
diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm
index 8d6c545e0a8..7211970fd14 100644
--- a/code/game/machinery/computer/card.dm
+++ b/code/game/machinery/computer/card.dm
@@ -2,6 +2,12 @@
//increase the slots of many jobs.
GLOBAL_VAR_INIT(time_last_changed_position, 0)
+#define IDCOMPUTER_SCREEN_TRANSFER 0
+#define IDCOMPUTER_SCREEN_SLOTS 1
+#define IDCOMPUTER_SCREEN_ACCESS 2
+#define IDCOMPUTER_SCREEN_RECORDS 3
+#define IDCOMPUTER_SCREEN_DEPT 4
+
/obj/machinery/computer/card
name = "identification computer"
desc = "Terminal for programming Nanotrasen employee ID cards to access parts of the station."
@@ -12,9 +18,9 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
light_color = LIGHT_COLOR_LIGHTBLUE
var/obj/item/card/id/scan = null
var/obj/item/card/id/modify = null
- var/mode = 0.0
- var/printing = null
+ var/mode = 0
var/target_dept = 0 //Which department this computer has access to. 0=all departments
+ var/obj/item/radio/Radio
//Cooldown for closing positions in seconds
//if set to -1: No cooldown... probably a bad idea
@@ -54,15 +60,27 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
//Assoc array: "JobName" = (int)
var/list/opened_positions = list()
+
+/obj/machinery/computer/card/Initialize()
+ ..()
+ Radio = new /obj/item/radio(src)
+ Radio.listening = 0
+ Radio.config(list("Command" = 0))
+ Radio.follow_target = src
+
+/obj/machinery/computer/card/Destroy()
+ QDEL_NULL(Radio)
+ return ..()
+
/obj/machinery/computer/card/proc/is_centcom()
- return istype(src, /obj/machinery/computer/card/centcom)
+ return FALSE
/obj/machinery/computer/card/proc/is_authenticated(var/mob/user)
if(user.can_admin_interact())
- return 1
+ return TRUE
if(scan)
return check_access(scan)
- return 0
+ return FALSE
/obj/machinery/computer/card/proc/get_target_rank()
return modify && modify.assignment ? modify.assignment : "Unassigned"
@@ -134,18 +152,18 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
if(!istype(id_card))
return ..()
- if(!scan && (ACCESS_CHANGE_IDS in id_card.access))
+ if(!scan && check_access(id_card))
user.drop_item()
- id_card.loc = src
+ id_card.forceMove(src)
scan = id_card
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
else if(!modify)
user.drop_item()
- id_card.loc = src
+ id_card.forceMove(src)
modify = id_card
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
- SSnanoui.update_uis(src)
+ SStgui.update_uis(src)
attack_hand(user)
//Check if you can't touch a job in any way whatsoever
@@ -156,68 +174,123 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
/obj/machinery/computer/card/proc/job_blacklisted_partial(datum/job/job)
return (job.type in blacklisted_partial)
-//Logic check for Topic() if you can open the job
+// Logic check for if you can open the job
/obj/machinery/computer/card/proc/can_open_job(datum/job/job)
if(job)
- if(!job_blacklisted_full(job) && !job_blacklisted_partial(job) && job_in_department(job, FALSE))
- if((job.total_positions <= GLOB.player_list.len * (max_relative_positions / 100)))
- var/delta = (world.time / 10) - GLOB.time_last_changed_position
- if((change_position_cooldown < delta) || (opened_positions[job.title] < 0))
- return 1
- return -2
- return -1
- return 0
+ if(job_blacklisted_full(job))
+ return FALSE
+ if(job_blacklisted_partial(job))
+ return FALSE
+ if(!job_in_department(job, FALSE))
+ return FALSE
+ if((job.total_positions > GLOB.player_list.len * (max_relative_positions / 100)))
+ return FALSE
+ if(opened_positions[job.title] < 0)
+ return TRUE
+ var/delta = (world.time / 10) - GLOB.time_last_changed_position
+ if(change_position_cooldown < delta)
+ return TRUE
+ return FALSE
-//Logic check for Topic() if you can close the job
+// Logic check for if you can close the job
/obj/machinery/computer/card/proc/can_close_job(datum/job/job)
if(job)
- if(!job_blacklisted_full(job) && !job_blacklisted_partial(job) && job_in_department(job, FALSE))
- if(job.total_positions > job.current_positions && !(job in SSjobs.prioritized_jobs))
- var/delta = (world.time / 10) - GLOB.time_last_changed_position
- if((change_position_cooldown < delta) || (opened_positions[job.title] > 0))
- return 1
- return -2
- return -1
- return 0
+ if(job_blacklisted_full(job))
+ return FALSE
+ if(job_blacklisted_partial(job))
+ return FALSE
+ if(!job_in_department(job, FALSE))
+ return FALSE
+ if(job in SSjobs.prioritized_jobs) // different to above
+ return FALSE
+ if(job.total_positions <= job.current_positions) // different to above
+ return FALSE
+ if(opened_positions[job.title] > 0) // different to above
+ return TRUE
+ var/delta = (world.time / 10) - GLOB.time_last_changed_position
+ if(change_position_cooldown < delta)
+ return TRUE
+ return FALSE
/obj/machinery/computer/card/proc/can_prioritize_job(datum/job/job)
if(job)
- if(!job_blacklisted_full(job) && job_in_department(job, FALSE))
- if(job in SSjobs.prioritized_jobs)
- return 2
- else
- if(SSjobs.prioritized_jobs.len >= 3)
- return 0
- if(job.total_positions <= job.current_positions)
- return 0
- return 1
- return -1
+ if(job_blacklisted_full(job))
+ return FALSE
+ if(!job_in_department(job, FALSE))
+ return FALSE
+ if(job in SSjobs.prioritized_jobs)
+ return TRUE // because this also lets us un-prioritize the job
+ if(SSjobs.prioritized_jobs.len >= 3)
+ return FALSE
+ if(job.total_positions <= job.current_positions)
+ return FALSE
+ return TRUE
+ return FALSE
+/obj/machinery/computer/card/proc/has_idchange_access()
+ return scan && scan.access && (ACCESS_CHANGE_IDS in scan.access) ? TRUE : FALSE
-/obj/machinery/computer/card/proc/job_in_department(datum/job/targetjob, includecivs = 1)
+/obj/machinery/computer/card/proc/job_in_department(datum/job/targetjob, includecivs = TRUE)
if(!scan || !scan.access)
- return 0
+ return FALSE
if(!target_dept)
- return 1
+ return TRUE
if(!scan.assignment)
- return 0
- if(ACCESS_CAPTAIN in scan.access)
- return 1
+ return FALSE
+ if(has_idchange_access())
+ return TRUE
if(!targetjob || !targetjob.title)
- return 0
+ return FALSE
if(targetjob.title in get_subordinates(scan.assignment, includecivs))
- return 1
- return 0
+ return TRUE
+ return FALSE
/obj/machinery/computer/card/proc/get_subordinates(rank, addcivs)
var/list/jobs_returned = list()
for(var/datum/job/thisjob in SSjobs.occupations)
+ if(thisjob.title in GLOB.nonhuman_positions) // hides AI from list when Captain ID is inserted into dept console
+ continue
if(rank in thisjob.department_head)
jobs_returned += thisjob.title
if(addcivs)
jobs_returned += "Civilian"
return jobs_returned
+/obj/machinery/computer/card/proc/get_employees(list/selectedranks)
+ var/list/names_returned = list()
+ if(isnull(GLOB.data_core.general) || isnull(GLOB.data_core.security))
+ return names_returned
+ for(var/datum/data/record/R in GLOB.data_core.general)
+ if(!R.fields || !R.fields["name"] || !R.fields["real_rank"])
+ continue
+ if(!(R.fields["real_rank"] in selectedranks))
+ continue
+ for(var/datum/data/record/E in GLOB.data_core.security)
+ if(E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"])
+ var/buttontext
+ var/isdemotable = FALSE
+ if(status_valid_for_demotion(E.fields["criminal"]))
+ buttontext = "Demote"
+ isdemotable = TRUE
+ else if(E.fields["criminal"] == SEC_RECORD_STATUS_DEMOTE)
+ buttontext = "Pending Demotion"
+ else
+ buttontext = "Ineligible"
+ names_returned.Add(list(list(
+ "name" = E.fields["name"],
+ "crimstat" = E.fields["criminal"],
+ "title" = R.fields["real_rank"],
+ "buttontext" = buttontext,
+ "demotable" = isdemotable
+ )))
+ break
+ return names_returned
+
+/obj/machinery/computer/card/proc/status_valid_for_demotion(crimstat)
+ if(crimstat in list(SEC_RECORD_STATUS_NONE, SEC_RECORD_STATUS_MONITOR, SEC_RECORD_STATUS_RELEASED, SEC_RECORD_STATUS_SEARCH))
+ return TRUE
+ return FALSE
+
/obj/machinery/computer/card/attack_ai(var/mob/user as mob)
return attack_hand(user)
@@ -227,103 +300,119 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
if(stat & (NOPOWER|BROKEN))
return
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/card/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- user.set_machine(src)
-
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/card/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "identification_computer.tmpl", src.name, 775, 700)
+ ui = new(user, src, ui_key, "CardComputer", name, 800, 800, master_ui, state)
ui.open()
-/obj/machinery/computer/card/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- data["src"] = UID()
- data["station_name"] = station_name()
+/obj/machinery/computer/card/tgui_data(mob/user)
+ var/list/data = list()
data["mode"] = mode
- data["printing"] = printing
- data["manifest"] = GLOB.data_core ? GLOB.data_core.get_manifest(0) : null
- data["target_name"] = modify ? modify.name : "-----"
- data["target_owner"] = modify && modify.registered_name ? modify.registered_name : "-----"
- data["target_rank"] = get_target_rank()
- data["scan_name"] = scan ? scan.name : "-----"
- data["scan_owner"] = scan && scan.registered_name ? scan.registered_name : null
- data["authenticated"] = is_authenticated(user)
- data["has_modify"] = !!modify
- data["account_number"] = modify ? modify.associated_account_number : null
- data["centcom_access"] = is_centcom()
- data["all_centcom_access"] = null
- data["regions"] = null
+ data["modify_name"] = modify ? modify.name : FALSE
+ data["modify_owner"] = modify && modify.registered_name ? modify.registered_name : "-----"
+ data["modify_rank"] = get_target_rank()
+ data["modify_lastlog"] = modify && modify.lastlog ? modify.lastlog : FALSE
+ data["scan_name"] = scan ? scan.name : FALSE
+ data["scan_rank"] = scan ? scan.rank : FALSE
+
+ data["authenticated"] = is_authenticated(user) ? TRUE : FALSE
data["target_dept"] = target_dept
- data["card_is_owned"] = modify && modify.owner_ckey
+ data["iscentcom"] = is_centcom() ? TRUE : FALSE
- var/list/job_formats = SSjobs.format_jobs_for_id_computer(modify)
-
- data["top_jobs"] = format_jobs(list("Captain", "Custom"), data["target_rank"], job_formats)
- data["engineering_jobs"] = format_jobs(GLOB.engineering_positions, data["target_rank"], job_formats)
- data["medical_jobs"] = format_jobs(GLOB.medical_positions, data["target_rank"], job_formats)
- data["science_jobs"] = format_jobs(GLOB.science_positions, data["target_rank"], job_formats)
- data["security_jobs"] = format_jobs(GLOB.security_positions, data["target_rank"], job_formats)
- data["support_jobs"] = format_jobs(GLOB.support_positions, data["target_rank"], job_formats)
- data["civilian_jobs"] = format_jobs(GLOB.civilian_positions, data["target_rank"], job_formats)
- data["special_jobs"] = format_jobs(GLOB.whitelisted_positions, data["target_rank"], job_formats)
- data["centcom_jobs"] = format_jobs(get_all_centcom_jobs(), data["target_rank"], job_formats)
- data["card_skins"] = format_card_skins(get_station_card_skins())
-
- data["job_slots"] = format_job_slots()
-
- var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1)
- var/mins = round(time_to_wait / 60)
- var/seconds = time_to_wait - (60*mins)
- data["cooldown_mins"] = mins
- data["cooldown_secs"] = (seconds < 10) ? "0[seconds]" : seconds
-
- if(mode == 3 && is_authenticated(user))
- data["id_change_html"] = SSjobs.fetch_transfer_record_html(is_centcom())
-
- if(modify)
- data["current_skin"] = modify.icon_state
-
- if(modify && is_centcom())
- var/list/all_centcom_access = list()
- for(var/access in get_all_centcom_access())
- all_centcom_access.Add(list(list(
- "desc" = replacetext(get_centcom_access_desc(access), " ", " "),
- "ref" = access,
- "allowed" = (access in modify.access) ? 1 : 0)))
-
- data["all_centcom_access"] = all_centcom_access
- data["all_centcom_skins"] = format_card_skins(get_centcom_card_skins())
-
- else if(modify)
- var/list/regions = list()
- for(var/i = 1; i <= 7; i++)
- var/list/accesses = list()
- for(var/access in get_region_accesses(i))
- if(get_access_desc(access))
- accesses.Add(list(list(
- "desc" = replacetext(get_access_desc(access), " ", " "),
- "ref" = access,
- "allowed" = (access in modify.access) ? 1 : 0)))
-
- regions.Add(list(list(
- "name" = get_region_accesses_name(i),
- "accesses" = accesses)))
-
- data["regions"] = regions
+ switch(mode)
+ if(IDCOMPUTER_SCREEN_TRANSFER) // JOB TRANSFER
+ if(modify)
+ if(!scan)
+ return data
+ else if(target_dept)
+ data["jobs_dept"] = get_subordinates(scan.assignment, FALSE)
+ data["canterminate"] = has_idchange_access()
+ else
+ data["account_number"] = modify ? modify.associated_account_number : null
+ data["jobs_top"] = list("Captain", "Custom")
+ data["jobs_engineering"] = GLOB.engineering_positions
+ data["jobs_medical"] = GLOB.medical_positions
+ data["jobs_science"] = GLOB.science_positions
+ data["jobs_security"] = GLOB.security_positions
+ data["jobs_service"] = GLOB.service_positions
+ data["jobs_supply"] = GLOB.supply_positions - "Head of Personnel"
+ data["jobs_civilian"] = GLOB.civilian_positions
+ data["jobs_karma"] = GLOB.whitelisted_positions
+ data["jobs_centcom"] = get_all_centcom_jobs()
+ data["jobFormats"] = SSjobs.format_jobs_for_id_computer(modify)
+ data["current_skin"] = modify.icon_state
+ data["card_skins"] = format_card_skins(get_station_card_skins())
+ data["all_centcom_skins"] = is_centcom() ? format_card_skins(get_centcom_card_skins()) : FALSE
+ if(IDCOMPUTER_SCREEN_SLOTS) // JOB SLOTS
+ data["job_slots"] = format_job_slots()
+ data["priority_jobs"] = list()
+ for(var/datum/job/a in SSjobs.prioritized_jobs)
+ data["priority_jobs"] += a.title
+ var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1)
+ if(time_to_wait > 0)
+ var/mins = round(time_to_wait / 60)
+ var/seconds = time_to_wait - (60*mins)
+ seconds = (seconds < 10) ? "0[seconds]" : seconds
+ data["cooldown_time"] = "[mins]:[seconds]"
+ else
+ data["cooldown_time"] = FALSE
+ if(IDCOMPUTER_SCREEN_ACCESS) // ACCESS CHANGES
+ if(modify)
+ data["selectedAccess"] = modify.access
+ data["regions"] = get_accesslist_static_data(REGION_GENERAL, is_centcom() ? REGION_CENTCOMM : REGION_COMMAND)
+ if(IDCOMPUTER_SCREEN_RECORDS) // RECORDS
+ if(is_authenticated(user))
+ data["records"] = SSjobs.format_job_change_records(data["iscentcom"])
+ if(IDCOMPUTER_SCREEN_DEPT) // DEPARTMENT EMPLOYEE LIST
+ if(is_authenticated(user) && scan) // .requires both (aghosts don't count)
+ data["jobs_dept"] = get_subordinates(scan.assignment, FALSE)
+ data["people_dept"] = get_employees(data["jobs_dept"])
return data
-/obj/machinery/computer/card/Topic(href, href_list)
- if(..())
- return 1
+/obj/machinery/computer/card/proc/regenerate_id_name()
+ if(modify)
+ modify.name = text("[modify.registered_name]'s ID Card ([modify.assignment])")
- switch(href_list["choice"])
- if("modify")
+/obj/machinery/computer/card/tgui_act(action, params)
+ if(..())
+ return
+ . = TRUE
+
+ // 1st, handle the functions that require no authorization at all
+
+ switch(action)
+ if("scan") // inserting or removing your authorizing ID
+ if(scan)
+ if(ishuman(usr))
+ scan.forceMove(get_turf(src))
+ if(!usr.get_active_hand() && Adjacent(usr))
+ usr.put_in_hands(scan)
+ scan = null
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
+ else
+ scan.forceMove(get_turf(src))
+ scan = null
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
+ else if(Adjacent(usr))
+ var/obj/item/I = usr.get_active_hand()
+ if(istype(I, /obj/item/card/id))
+ if(!check_access(I))
+ playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 0)
+ to_chat(usr, "This card does not have access.")
+ return FALSE
+ usr.drop_item()
+ I.forceMove(src)
+ scan = I
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
+ return
+ if("modify") // inserting or removing the ID you plan to modify
if(modify)
GLOB.data_core.manifest_modify(modify.registered_name, modify.assignment)
- modify.name = text("[modify.registered_name]'s ID Card ([modify.assignment])")
+ regenerate_id_name()
if(ishuman(usr))
modify.forceMove(get_turf(src))
if(!usr.get_active_hand() && Adjacent(usr))
@@ -341,260 +430,299 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
I.forceMove(src)
modify = I
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
+ return
+ if("mode") // changing mode in the menu
+ mode = text2num(params["mode"])
+ return
- if("scan")
- if(scan)
- if(ishuman(usr))
- scan.forceMove(get_turf(src))
- if(!usr.get_active_hand() && Adjacent(usr))
- usr.put_in_hands(scan)
- scan = null
- playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
- else
- scan.forceMove(get_turf(src))
- scan = null
- playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
- else if(Adjacent(usr))
- var/obj/item/I = usr.get_active_hand()
- if(istype(I, /obj/item/card/id))
- usr.drop_item()
- I.forceMove(src)
- scan = I
- playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
+ // Everything below HERE requires auth
+ if(!is_authenticated(usr))
+ playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 0)
+ to_chat(usr, "This function is not available unless you are logged in.")
+ return FALSE
- if("access")
- if(href_list["allowed"] && !target_dept)
- if(is_authenticated(usr))
- var/access_type = text2num(href_list["access_target"])
- var/access_allowed = text2num(href_list["allowed"])
- if(access_type in (is_centcom() ? get_all_centcom_access() : get_all_accesses()))
- modify.access -= access_type
- if(!access_allowed)
- modify.access += access_type
-
- if("skin")
- if(!target_dept)
- var/skin = href_list["skin_target"]
- if(is_authenticated(usr) && modify && ((skin in get_station_card_skins()) || ((skin in get_centcom_card_skins()) && is_centcom())))
- modify.icon_state = href_list["skin_target"]
-
- if("assign")
- if(is_authenticated(usr) && modify)
- var/t1 = href_list["assign_target"]
- if(target_dept && modify.assignment == "Demoted")
- visible_message("[src]: Demoted individuals must see the HoP for a new job.")
- return 0
+ // 2nd, handle the functions that are available to head-level consoles (department consoles)
+ switch(action)
+ if("assign") // transfer to a new job
+ if(!modify)
+ return
+ var/t1 = params["assign_target"]
+ if(target_dept)
+ if(modify.assignment == "Demoted")
+ playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 0)
+ visible_message("[src]: Reassigning a demoted individual requires a full ID computer.")
+ return FALSE
if(!job_in_department(SSjobs.GetJob(modify.rank), FALSE))
- visible_message("[src]: Cross-department job transfers must be done by the HoP.")
- return 0
+ playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 0)
+ visible_message("[src]: Reassigning someone outside your department requires a full ID computer.")
+ return FALSE
if(!job_in_department(SSjobs.GetJob(t1)))
- return 0
- if(t1 == "Custom")
- var/temp_t = sanitize(copytext(input("Enter a custom job assignment.","Assignment"),1,MAX_MESSAGE_LEN))
- //let custom jobs function as an impromptu alt title, mainly for sechuds
- if(temp_t && modify)
- SSjobs.log_job_transfer(modify.registered_name, modify.getRankAndAssignment(), temp_t, scan.registered_name)
- modify.assignment = temp_t
- log_game("[key_name(usr)] has given \"[modify.registered_name]\" the custom job title \"[temp_t]\".")
+ return FALSE
+ if(t1 == "Custom")
+ var/temp_t = sanitize(reject_bad_name(copytext(input("Enter a custom job assignment.", "Assignment"), 1, MAX_MESSAGE_LEN), TRUE))
+ //let custom jobs function as an impromptu alt title, mainly for sechuds
+ if(temp_t && scan && modify)
+ var/oldrank = modify.getRankAndAssignment()
+ SSjobs.log_job_transfer(modify.registered_name, oldrank, temp_t, scan.registered_name, null)
+ modify.lastlog = "[station_time_timestamp()]: Reassigned by \"[scan.registered_name]\" from \"[oldrank]\" to \"[temp_t]\"."
+ modify.assignment = temp_t
+ log_game("[key_name(usr)] ([scan.assignment]) has reassigned \"[modify.registered_name]\" from \"[oldrank]\" to \"[temp_t]\".")
+ SSjobs.notify_dept_head(modify.rank, "[scan.registered_name] has transferred \"[modify.registered_name]\" the \"[oldrank]\" to \"[temp_t]\".")
+ else
+ var/list/access = list()
+ if(is_centcom() && islist(get_centcom_access(t1)))
+ access = get_centcom_access(t1)
else
- var/list/access = list()
- if(is_centcom() && islist(get_centcom_access(t1)))
- access = get_centcom_access(t1)
- else
- var/datum/job/jobdatum
- for(var/jobtype in typesof(/datum/job))
- var/datum/job/J = new jobtype
- if(ckey(J.title) == ckey(t1))
- jobdatum = J
- break
- if(!jobdatum)
- to_chat(usr, "No log exists for this job: [t1]")
- return
+ var/datum/job/jobdatum
+ for(var/jobtype in typesof(/datum/job))
+ var/datum/job/J = new jobtype
+ if(ckey(J.title) == ckey(t1))
+ jobdatum = J
+ break
+ if(!jobdatum)
+ to_chat(usr, "No log exists for this job: [t1]")
+ return
- access = jobdatum.get_access()
+ access = jobdatum.get_access()
- var/jobnamedata = modify.getRankAndAssignment()
- log_game("[key_name(usr)] has reassigned \"[modify.registered_name]\" from \"[jobnamedata]\" to \"[t1]\".")
- if(t1 == "Civilian")
- message_admins("[key_name_admin(usr)] has reassigned \"[modify.registered_name]\" from \"[jobnamedata]\" to \"[t1]\".")
+ var/jobnamedata = modify.getRankAndAssignment()
+ log_game("[key_name(usr)] ([scan.assignment]) has reassigned \"[modify.registered_name]\" from \"[jobnamedata]\" to \"[t1]\".")
+ if(t1 == "Civilian")
+ message_admins("[key_name_admin(usr)] has reassigned \"[modify.registered_name]\" from \"[jobnamedata]\" to \"[t1]\".")
- SSjobs.log_job_transfer(modify.registered_name, jobnamedata, t1, scan.registered_name)
+ SSjobs.log_job_transfer(modify.registered_name, jobnamedata, t1, scan.registered_name, null)
+ modify.lastlog = "[station_time_timestamp()]: Reassigned by \"[scan.registered_name]\" from \"[jobnamedata]\" to \"[t1]\"."
+ SSjobs.notify_dept_head(t1, "[scan.registered_name] has transferred \"[modify.registered_name]\" the \"[jobnamedata]\" to \"[t1]\".")
+ if(modify.owner_uid)
SSjobs.slot_job_transfer(modify.rank, t1)
- var/mob/living/carbon/human/H = modify.getPlayer()
- if(istype(H))
- if(jobban_isbanned(H, t1))
- message_admins("[ADMIN_FULLMONTY(H)] has been assigned the job [t1], in possible violation of their job ban.")
- if(H.mind)
- H.mind.playtime_role = t1
+ var/mob/living/carbon/human/H = modify.getPlayer()
+ if(istype(H))
+ if(jobban_isbanned(H, t1))
+ message_admins("[ADMIN_FULLMONTY(H)] has been assigned the job [t1], in possible violation of their job ban.")
+ if(H.mind)
+ H.mind.playtime_role = t1
- modify.access = access
- modify.rank = t1
- modify.assignment = t1
+ modify.access = access
+ modify.rank = t1
+ modify.assignment = t1
+ regenerate_id_name()
+ return
+ if("demote")
+ if(modify.assignment == "Demoted")
+ playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 0)
+ visible_message("[src]: Demoted crew cannot be demoted any further. If further action is warranted, ask the Captain about Termination.")
+ return FALSE
+ if(!job_in_department(SSjobs.GetJob(modify.rank), FALSE))
+ playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 0)
+ visible_message("[src]: Heads may only demote members of their own department.")
+ return FALSE
+ var/reason = sanitize(copytext(input("Enter legal reason for demotion. Enter nothing to cancel.","Legal Demotion"), 1, MAX_MESSAGE_LEN))
+ if(!reason || !is_authenticated(usr) || !modify)
+ return FALSE
+ var/list/access = list()
+ var/datum/job/jobdatum = new /datum/job/civilian
+ access = jobdatum.get_access()
+ var/jobnamedata = modify.getRankAndAssignment()
+ var/m_ckey = modify.getPlayerCkey()
+ var/m_ckey_text = m_ckey ? "([m_ckey])" : "(no ckey)"
+ log_game("[key_name(usr)] ([scan.assignment]) has demoted \"[modify.registered_name]\" [m_ckey_text] the \"[jobnamedata]\" for: \"[reason]\".")
+ message_admins("[key_name_admin(usr)] has demoted \"[modify.registered_name]\" [m_ckey_text] the \"[jobnamedata]\" for: \"[reason]\".")
+ usr.create_log(MISC_LOG, "demoted \"[modify.registered_name]\" [m_ckey_text] the \"[jobnamedata]\"")
+ SSjobs.log_job_transfer(modify.registered_name, jobnamedata, "Demoted", scan.registered_name, reason)
+ modify.lastlog = "[station_time_timestamp()]: DEMOTED by \"[scan.registered_name]\" ([scan.assignment]) from \"[jobnamedata]\" for: \"[reason]\"."
+ SSjobs.notify_dept_head(modify.rank, "[scan.registered_name] ([scan.assignment]) has demoted \"[modify.registered_name]\" ([jobnamedata]) for \"[reason]\".")
+ modify.access = access
+ modify.rank = "Civilian"
+ modify.assignment = "Demoted"
+ modify.icon_state = "id"
+ regenerate_id_name()
+ return
+ if("terminate")
+ if(!has_idchange_access()) // because captain/HOP can use this even on dept consoles
+ playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 0)
+ visible_message("[src]: Only the Captain or HOP may completely terminate the employment of a crew member.")
+ return FALSE
+ var/jobnamedata = modify.getRankAndAssignment()
+ var/reason = sanitize(copytext(input("Enter legal reason for termination. Enter nothing to cancel.", "Employment Termination"), 1, MAX_MESSAGE_LEN))
+ if(!reason || !has_idchange_access() || !modify)
+ return FALSE
+ var/m_ckey = modify.getPlayerCkey()
+ var/m_ckey_text = m_ckey ? "([m_ckey])" : "(no ckey)"
+ log_game("[key_name(usr)] ([scan.assignment]) has terminated \"[modify.registered_name]\" [m_ckey_text] the \"[jobnamedata]\" for: \"[reason]\".")
+ message_admins("[key_name_admin(usr)] has terminated \"[modify.registered_name]\" [m_ckey_text] the \"[jobnamedata]\" for: \"[reason]\".")
+ usr.create_log(MISC_LOG, "terminated the employment of \"[modify.registered_name]\" [m_ckey_text] the \"[jobnamedata]\"")
+ SSjobs.log_job_transfer(modify.registered_name, jobnamedata, "Terminated", scan.registered_name, reason)
+ modify.lastlog = "[station_time_timestamp()]: TERMINATED by \"[scan.registered_name]\" ([scan.assignment]) from \"[jobnamedata]\" for: \"[reason]\"."
+ SSjobs.notify_dept_head(modify.rank, "[scan.registered_name] ([scan.assignment]) has terminated the employment of \"[modify.registered_name]\" the \"[jobnamedata]\" for \"[reason]\".")
+ modify.assignment = "Terminated"
+ modify.access = list()
+ regenerate_id_name()
+ return
+ if("make_job_available") // MAKE ANOTHER JOB POSITION AVAILABLE FOR LATE JOINERS
+ var/edit_job_target = params["job"]
+ var/datum/job/j = SSjobs.GetJob(edit_job_target)
+ if(!job_in_department(j, FALSE))
+ return FALSE
+ if(!j)
+ return FALSE
+ if(!can_open_job(j))
+ return FALSE
+ if(opened_positions[edit_job_target] >= 0)
+ GLOB.time_last_changed_position = world.time / 10
+ j.total_positions++
+ opened_positions[edit_job_target]++
+ log_game("[key_name(usr)] ([scan.assignment]) has opened a job slot for job \"[j.title]\".")
+ message_admins("[key_name_admin(usr)] has opened a job slot for job \"[j.title]\".")
+ return
+ if("make_job_unavailable") // MAKE JOB POSITION UNAVAILABLE FOR LATE JOINERS
+ var/edit_job_target = params["job"]
+ var/datum/job/j = SSjobs.GetJob(edit_job_target)
+ if(!job_in_department(j, FALSE))
+ return FALSE
+ if(!j)
+ return FALSE
+ if(!can_close_job(j))
+ return FALSE
+ //Allow instant closing without cooldown if a position has been opened before
+ if(opened_positions[edit_job_target] <= 0)
+ GLOB.time_last_changed_position = world.time / 10
+ j.total_positions--
+ opened_positions[edit_job_target]--
+ log_game("[key_name(usr)] ([scan.assignment]) has closed a job slot for job \"[j.title]\".")
+ message_admins("[key_name_admin(usr)] has closed a job slot for job \"[j.title]\".")
+ return
+ if("remote_demote")
+ var/reason = sanitize(copytext(input("Enter legal reason for demotion. Enter nothing to cancel.","Legal Demotion"), 1, MAX_MESSAGE_LEN))
+ if(!reason || !is_authenticated(usr) || !scan)
+ return FALSE
+ for(var/datum/data/record/E in GLOB.data_core.general)
+ if(E.fields["name"] == params["remote_demote"])
+ var/datum/job/j = SSjobs.GetJob(E.fields["real_rank"])
+ var/tempname = params["remote_demote"]
+ var/temprank = E.fields["real_rank"]
+ if(!j)
+ visible_message("[src]: This employee has either no job, or a customized job ([temprank]).")
+ return FALSE
+ if(!job_in_department(j, FALSE))
+ visible_message("[src]: Only the head of this employee may demote them.")
+ return FALSE
+ for(var/datum/data/record/R in GLOB.data_core.security)
+ if(R.fields["id"] == E.fields["id"])
+ if(status_valid_for_demotion(R.fields["criminal"]))
+ set_criminal_status(usr, R, SEC_RECORD_STATUS_DEMOTE, reason, scan.assignment)
+ Radio.autosay("[scan.registered_name] ([scan.assignment]) has set [tempname] ([temprank]) to demote for: [reason]", name, "Command", list(z))
+ message_admins("[key_name_admin(usr)] ([scan.assignment]) has set [tempname] ([temprank]) to demote for: \"[reason]\"")
+ log_game("[key_name(usr)] ([scan.assignment]) has set \"[tempname]\" ([temprank]) to demote for: \"[reason]\".")
+ SSjobs.notify_by_name(tempname, "[scan.registered_name] ([scan.assignment]) has ordered your demotion. Report to their office, or the HOP. Reason given: \"[reason]\"")
+ else
+ playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 0)
+ to_chat(usr, "[src]: Cannot demote, due to their current security status.")
+ return FALSE
+ return
+ return
- if("reg")
- if(is_authenticated(usr) && !target_dept)
- var/t2 = modify
- if((modify == t2 && (in_range(src, usr) || (istype(usr, /mob/living/silicon))) && istype(loc, /turf)))
- var/temp_name = reject_bad_name(href_list["reg"])
- if(temp_name)
- modify.registered_name = temp_name
- else
- visible_message("[src] buzzes rudely.")
- SSnanoui.update_uis(src)
+ // Everything below here requires a full ID computer (dept consoles do not qualify)
+ if(target_dept)
+ playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 0)
+ to_chat(usr, "This function is not available on department-level consoles.")
+ return
- if("account")
- if(is_authenticated(usr) && !target_dept)
- var/t2 = modify
- if((modify == t2 && (in_range(src, usr) || (istype(usr, /mob/living/silicon))) && istype(loc, /turf)))
- var/account_num = text2num(href_list["account"])
- modify.associated_account_number = account_num
- SSnanoui.update_uis(src)
+ // 3rd, handle the functions that require a full ID computer
+ switch(action)
+ // Changing basic card info
+ if("reg") // registered name on card
+ var/temp_name = reject_bad_name(input(usr, "Who is this ID for?", "ID Card Renaming", modify.registered_name), TRUE)
+ if(!modify || !temp_name)
+ playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 0)
+ visible_message("[src] buzzes rudely.")
+ return FALSE
+ modify.registered_name = temp_name
+ regenerate_id_name()
+ return
+ if("account") // card account number
+ var/account_num = input(usr, "Account Number", "Input Number", null) as num|null
+ if(!scan || !modify)
+ return FALSE
+ modify.associated_account_number = clamp(round(account_num), 0, 999999)
+ return
+ if("skin")
+ if(!modify)
+ return FALSE
+ var/skin = params["skin_target"]
+ var/skin_list = is_centcom() ? get_centcom_card_skins() : get_station_card_skins()
+ if(skin in skin_list)
+ modify.icon_state = skin
+ return
+ // Changing card access
+ if("set") // add/remove a single access number
+ var/access = text2num(params["access"])
+ var/list/changable = is_centcom() ? get_all_centcom_access() + get_all_accesses() : get_all_accesses()
+ if(access in changable)
+ if(access in modify.access)
+ modify.access -= access
+ else
+ modify.access += access
+ return
+ if("grant_region")
+ var/region = text2num(params["region"])
+ if(isnull(region) || region < REGION_GENERAL || region > (is_centcom() ? REGION_CENTCOMM : REGION_COMMAND))
+ return
+ modify.access |= get_region_accesses(region)
+ return
+ if("deny_region")
+ var/region = text2num(params["region"])
+ if(isnull(region) || region < REGION_GENERAL || region > (is_centcom() ? REGION_CENTCOMM : REGION_COMMAND))
+ return
+ modify.access -= get_region_accesses(region)
+ return
+ if("clear_all")
+ modify.access = list()
+ return
+ if("grant_all")
+ modify.access = get_all_accesses()
+ return
- if("mode")
- mode = text2num(href_list["mode_target"])
+ // JOB SLOT MANAGEMENT functions
- if("wipe_my_logs")
- if(is_authenticated(usr) && is_centcom())
- var/delcount = SSjobs.delete_log_records(scan.registered_name, FALSE)
- if(delcount)
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
- SSnanoui.update_uis(src)
+ if("prioritize_job") // TOGGLE WHETHER JOB APPEARS AS PRIORITIZED IN THE LOBBY
+ var/priority_target = params["job"]
+ var/datum/job/j = SSjobs.GetJob(priority_target)
+ if(!j)
+ return FALSE
+ if(!job_in_department(j))
+ return FALSE
+ var/priority = TRUE
+ if(j in SSjobs.prioritized_jobs)
+ SSjobs.prioritized_jobs -= j
+ priority = FALSE
+ else if(SSjobs.prioritized_jobs.len < 3)
+ SSjobs.prioritized_jobs += j
+ else
+ return FALSE
+ log_game("[key_name(usr)] ([scan.assignment]) [priority ? "prioritized" : "unprioritized"] the job \"[j.title]\".")
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
+ return
- if("wipe_all_logs")
+ if("wipe_all_logs") // Delete all records from 'records' section
if(is_authenticated(usr) && !target_dept)
var/delcount = SSjobs.delete_log_records(scan.registered_name, TRUE)
if(delcount)
+ message_admins("[key_name_admin(usr)] has wiped all ID computer logs.")
+ usr.create_log(MISC_LOG, "wiped all ID computer logs.")
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
- SSnanoui.update_uis(src)
+ return
- if("print")
- if(!printing && !target_dept)
- printing = 1
- playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1)
- spawn(50)
- printing = null
- SSnanoui.update_uis(src)
+ // Everything below here is exclusive to the CC card computer.
+ if(!is_centcom())
+ return
- var/obj/item/paper/P = new(loc)
- if(mode == 2)
- P.name = "crew manifest ([station_time_timestamp()])"
- P.info = {"Crew Manifest
-
- [GLOB.data_core ? GLOB.data_core.get_manifest(0) : ""]
- "}
- else if(modify && !mode)
- P.name = "access report"
- P.info = {"Access Report
- Prepared By: [scan && scan.registered_name ? scan.registered_name : "Unknown"]
- For: [modify.registered_name ? modify.registered_name : "Unregistered"]
-
- Assignment: [modify.assignment]
- Account Number: #[modify.associated_account_number]
- Blood Type: [modify.blood_type]
- Access:
- "}
-
- var/first = 1
- for(var/A in modify.access)
- P.info += "[first ? "" : ", "][get_access_desc(A)]"
- first = 0
- P.info += " "
-
- if("terminate")
- if(is_authenticated(usr) && !target_dept)
- var/jobnamedata = modify.getRankAndAssignment()
- log_game("[key_name(usr)] has terminated the employment of \"[modify.registered_name]\" the \"[jobnamedata]\".")
- message_admins("[key_name_admin(usr)] has terminated the employment of \"[modify.registered_name]\" the \"[jobnamedata]\".")
- SSjobs.log_job_transfer(modify.registered_name, jobnamedata, "Terminated", scan.registered_name)
- modify.assignment = "Terminated"
- modify.access = list()
-
- if("demote")
- if(is_authenticated(usr))
- if(modify.assignment == "Demoted")
- visible_message("[src]: Demoted crew cannot be demoted any further. If further action is warranted, ask the Captain about Termination.")
- return 0
- if(!job_in_department(SSjobs.GetJob(modify.rank), FALSE))
- visible_message("[src]: Heads may only demote members of their own department.")
- return 0
-
- var/list/access = list()
- var/datum/job/jobdatum = new /datum/job/civilian
- access = jobdatum.get_access()
-
- var/jobnamedata = modify.getRankAndAssignment()
- log_game("[key_name(usr)] has demoted \"[modify.registered_name]\" the \"[jobnamedata]\" to \"Civilian (Demoted)\".")
- message_admins("[key_name_admin(usr)] has demoted \"[modify.registered_name]\" the \"[jobnamedata]\" to \"Civilian (Demoted)\".")
- SSjobs.log_job_transfer(modify.registered_name, jobnamedata, "Demoted", scan.registered_name)
-
- modify.access = access
- modify.rank = "Civilian"
- modify.assignment = "Demoted"
- modify.icon_state = "id"
-
- if("make_job_available")
- // MAKE ANOTHER JOB POSITION AVAILABLE FOR LATE JOINERS
- if(is_authenticated(usr))
- var/edit_job_target = href_list["job"]
- var/datum/job/j = SSjobs.GetJob(edit_job_target)
- if(!job_in_department(j, FALSE))
- return 0
- if(!j)
- return 0
- if(can_open_job(j) != 1)
- return 0
- if(opened_positions[edit_job_target] >= 0)
- GLOB.time_last_changed_position = world.time / 10
- j.total_positions++
- opened_positions[edit_job_target]++
- log_game("[key_name(usr)] has opened a job slot for job \"[j]\".")
- message_admins("[key_name_admin(usr)] has opened a job slot for job \"[j.title]\".")
- SSnanoui.update_uis(src)
-
- if("make_job_unavailable")
- // MAKE JOB POSITION UNAVAILABLE FOR LATE JOINERS
- if(is_authenticated(usr))
- var/edit_job_target = href_list["job"]
- var/datum/job/j = SSjobs.GetJob(edit_job_target)
- if(!job_in_department(j, FALSE))
- return 0
- if(!j)
- return 0
- if(can_close_job(j) != 1)
- return 0
- //Allow instant closing without cooldown if a position has been opened before
- if(opened_positions[edit_job_target] <= 0)
- GLOB.time_last_changed_position = world.time / 10
- j.total_positions--
- opened_positions[edit_job_target]--
- log_game("[key_name(usr)] has closed a job slot for job \"[j]\".")
- message_admins("[key_name_admin(usr)] has closed a job slot for job \"[j.title]\".")
- SSnanoui.update_uis(src)
-
- if("prioritize_job")
- // TOGGLE WHETHER JOB APPEARS AS PRIORITIZED IN THE LOBBY
- if(is_authenticated(usr) && !target_dept)
- var/priority_target = href_list["job"]
- var/datum/job/j = SSjobs.GetJob(priority_target)
- if(!j)
- return 0
- if(!job_in_department(j))
- return 0
- var/priority = TRUE
- if(j in SSjobs.prioritized_jobs)
- SSjobs.prioritized_jobs -= j
- priority = FALSE
- else if(SSjobs.prioritized_jobs.len < 3)
- SSjobs.prioritized_jobs += j
- else
- return 0
- log_game("[key_name(usr)] [priority ? "prioritized" : "unprioritized"] the job \"[j.title]\".")
+ switch(action)
+ if("wipe_my_logs")
+ var/delcount = SSjobs.delete_log_records(scan.registered_name, FALSE)
+ if(delcount)
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
- if(modify)
- modify.name = text("[modify.registered_name]'s ID Card ([modify.assignment])")
- return 1
/obj/machinery/computer/card/centcom
name = "\improper CentComm identification computer"
@@ -604,6 +732,9 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
blacklisted_full = list()
blacklisted_partial = list()
+/obj/machinery/computer/card/centcom/is_centcom()
+ return TRUE
+
/obj/machinery/computer/card/minor
name = "department management console"
target_dept = TARGET_DEPT_GENERIC
diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm
index 3091caa82a2..04a4f7e0db4 100644
--- a/code/game/machinery/computer/cloning.dm
+++ b/code/game/machinery/computer/cloning.dm
@@ -1,3 +1,6 @@
+#define MENU_MAIN 1
+#define MENU_RECORDS 2
+
/obj/machinery/computer/cloning
name = "cloning console"
icon = 'icons/obj/computer.dmi'
@@ -6,11 +9,11 @@
circuit = /obj/item/circuitboard/cloning
req_access = list(ACCESS_HEADS) //Only used for record deletion right now.
var/obj/machinery/dna_scannernew/scanner = null //Linked scanner. For scanning.
- var/list/pods = list() //Linked cloning pods.
- var/temp = ""
- var/scantemp = "Scanner ready."
- var/menu = 1 //Which menu screen to display
- var/list/records = list()
+ var/list/pods = null //Linked cloning pods.
+ var/list/temp = null
+ var/list/scantemp = null
+ var/menu = MENU_MAIN //Which menu screen to display
+ var/list/records = null
var/datum/dna2/record/active_record = null
var/obj/item/disk/data/diskette = null //Mostly so the geneticist can steal everything.
var/loading = 0 // Nice loading text
@@ -24,6 +27,9 @@
/obj/machinery/computer/cloning/Initialize()
..()
+ pods = list()
+ records = list()
+ set_scan_temp("Scanner ready.", "good")
updatemodules()
/obj/machinery/computer/cloning/Destroy()
@@ -91,7 +97,7 @@
W.loc = src
src.diskette = W
to_chat(user, "You insert [W].")
- SSnanoui.update_uis(src)
+ SStgui.update_uis(src)
return
else if(istype(W, /obj/item/multitool))
var/obj/item/multitool/M = W
@@ -117,19 +123,21 @@
return
updatemodules()
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/cloning/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
+/obj/machinery/computer/cloning/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
if(stat & (NOPOWER|BROKEN))
return
- // Set up the Nano UI
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+ var/datum/asset/cloning/assets = get_asset_datum(/datum/asset/cloning)
+ assets.send(user)
+
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "cloning_console.tmpl", "Cloning Console UI", 640, 520)
+ ui = new(user, src, ui_key, "CloningConsole", "Cloning Console", 640, 520)
ui.open()
-/obj/machinery/computer/cloning/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
+/obj/machinery/computer/cloning/tgui_data(mob/user)
var/data[0]
data["menu"] = src.menu
data["scanner"] = sanitize("[src.scanner]")
@@ -143,7 +151,18 @@
if(pod.efficiency > 5)
canpodautoprocess = 1
- tempods.Add(list(list("pod" = "\ref[pod]", "name" = sanitize(capitalize(pod.name)), "biomass" = pod.biomass)))
+ var/status = "idle"
+ if(pod.mess)
+ status = "mess"
+ else if(pod.occupant && !(pod.stat & NOPOWER))
+ status = "cloning"
+ tempods.Add(list(list(
+ "pod" = "\ref[pod]",
+ "name" = sanitize(capitalize(pod.name)),
+ "biomass" = pod.biomass,
+ "status" = status,
+ "progress" = (pod.occupant && pod.occupant.stat != DEAD) ? pod.get_completion() : 0
+ )))
data["pods"] = tempods
data["loading"] = loading
@@ -164,192 +183,194 @@
data["selected_pod"] = "\ref[selected_pod]"
var/list/temprecords[0]
for(var/datum/dna2/record/R in records)
- var tempRealName = R.dna.real_name
+ var/tempRealName = R.dna.real_name
temprecords.Add(list(list("record" = "\ref[R]", "realname" = sanitize(tempRealName))))
data["records"] = temprecords
- if(src.menu == 3)
- if(src.active_record)
- data["activerecord"] = "\ref[src.active_record]"
- var/obj/item/implant/health/H = null
- if(src.active_record.implant)
- H = locate(src.active_record.implant)
+ if(selected_pod && (selected_pod in pods) && selected_pod.biomass >= CLONE_BIOMASS)
+ data["podready"] = 1
+ else
+ data["podready"] = 0
- if((H) && (istype(H)))
- data["health"] = H.sensehealth()
- data["realname"] = sanitize(src.active_record.dna.real_name)
- data["unidentity"] = src.active_record.dna.uni_identity
- data["strucenzymes"] = src.active_record.dna.struc_enzymes
- if(selected_pod && (selected_pod in pods) && selected_pod.biomass >= CLONE_BIOMASS)
- data["podready"] = 1
- else
- data["podready"] = 0
+ data["modal"] = tgui_modal_data(src)
return data
-/obj/machinery/computer/cloning/Topic(href, href_list)
+/obj/machinery/computer/cloning/tgui_act(action, params)
if(..())
- return 1
-
- if(loading)
+ return
+ if(stat & (NOPOWER|BROKEN))
return
- if(href_list["scan"] && scanner && scanner.occupant)
- scantemp = "Scanner ready."
-
- loading = 1
-
- spawn(20)
- if(can_brainscan() && scan_mode)
- scan_mob(scanner.occupant, scan_brain = 1)
- else
- scan_mob(scanner.occupant)
-
- loading = 0
- SSnanoui.update_uis(src)
-
- if(href_list["task"])
- switch(href_list["task"])
- if("autoprocess")
- autoprocess = 1
- SSnanoui.update_uis(src)
- if("stopautoprocess")
- autoprocess = 0
- SSnanoui.update_uis(src)
-
- //No locking an open scanner.
- else if((href_list["lock"]) && (!isnull(src.scanner)))
- if((!src.scanner.locked) && (src.scanner.occupant))
- src.scanner.locked = 1
- else
- src.scanner.locked = 0
-
- else if(href_list["view_rec"])
- src.active_record = locate(href_list["view_rec"])
- if(istype(src.active_record,/datum/dna2/record))
- if((isnull(src.active_record.ckey)))
- qdel(src.active_record)
- src.temp = "Error: Record corrupt."
- else
- src.menu = 3
- else
- src.active_record = null
- src.temp = "Error: Record missing."
-
- else if(href_list["del_rec"])
- if((!src.active_record) || (src.menu < 3))
- return
- if(src.menu == 3) //If we are viewing a record, confirm deletion
- src.temp = "Please confirm that you want to delete the record?"
- src.menu = 4
-
- else if(src.menu == 4)
- var/obj/item/card/id/C = usr.get_active_hand()
- if(istype(C)||istype(C, /obj/item/pda))
- if(src.check_access(C))
- src.records.Remove(src.active_record)
- qdel(src.active_record)
- src.temp = "Record deleted."
- src.menu = 2
+ . = TRUE
+ switch(tgui_modal_act(src, action, params))
+ if(TGUI_MODAL_ANSWER)
+ if(params["id"] == "del_rec" && active_record)
+ var/obj/item/card/id/C = usr.get_active_hand()
+ if(!istype(C) && !istype(C, /obj/item/pda))
+ set_temp("ID not in hand.", "danger")
+ return
+ if(check_access(C))
+ records.Remove(active_record)
+ qdel(active_record)
+ set_temp("Record deleted.", "success")
+ menu = MENU_RECORDS
else
- src.temp = "Error: Access denied."
-
- else if(href_list["disk"]) //Load or eject.
- switch(href_list["disk"])
- if("load")
- if((isnull(src.diskette)) || isnull(src.diskette.buf))
- src.temp = "Error: The disk's data could not be read."
- SSnanoui.update_uis(src)
- return
- if(isnull(src.active_record))
- src.temp = "Error: No active record was found."
- src.menu = 1
- SSnanoui.update_uis(src)
- return
-
- src.active_record = src.diskette.buf.copy()
-
- src.temp = "Load successful."
-
- if("eject")
- if(!isnull(src.diskette))
- src.diskette.loc = src.loc
- src.diskette = null
-
- else if(href_list["save_disk"]) //Save to disk!
- if((isnull(src.diskette)) || (src.diskette.read_only) || (isnull(src.active_record)))
- src.temp = "Error: The data could not be saved."
- SSnanoui.update_uis(src)
+ set_temp("Access denied.", "danger")
return
- // DNA2 makes things a little simpler.
- src.diskette.buf=src.active_record.copy()
- src.diskette.buf.types=0
- switch(href_list["save_disk"]) //Save as Ui/Ui+Ue/Se
- if("ui")
- src.diskette.buf.types=DNA2_BUF_UI
- if("ue")
- src.diskette.buf.types=DNA2_BUF_UI|DNA2_BUF_UE
- if("se")
- src.diskette.buf.types=DNA2_BUF_SE
- src.diskette.name = "data disk - '[src.active_record.dna.real_name]'"
- src.temp = "Save \[[href_list["save_disk"]]\] successful."
+ switch(action)
+ if("scan")
+ if(!scanner || !scanner.occupant || loading)
+ return
+ set_scan_temp("Scanner ready.", "good")
+ loading = TRUE
- else if(href_list["refresh"])
- SSnanoui.update_uis(src)
-
- else if(href_list["selectpod"])
- var/obj/machinery/clonepod/selected = locate(href_list["selectpod"])
- if(istype(selected) && (selected in pods))
- selected_pod = selected
-
- else if(href_list["clone"])
- var/datum/dna2/record/C = locate(href_list["clone"])
- //Look for that player! They better be dead!
- if(istype(C))
- //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs.
- if(!pods.len)
- temp = "Error: No cloning pod detected."
- else
- var/obj/machinery/clonepod/pod = selected_pod
- var/cloneresult
- if(!selected_pod)
- temp = "Error: No cloning pod selected."
- else if(pod.occupant)
- temp = "Error: The cloning pod is currently occupied."
- else if(pod.biomass < CLONE_BIOMASS)
- temp = "Error: Not enough biomass."
- else if(pod.mess)
- temp = "Error: The cloning pod is malfunctioning."
- else if(!config.revival_cloning)
- temp = "Error: Unable to initiate cloning cycle."
+ spawn(20)
+ if(can_brainscan() && scan_mode)
+ scan_mob(scanner.occupant, scan_brain = TRUE)
else
- cloneresult = pod.growclone(C)
- if(cloneresult)
- if(cloneresult > 0)
- temp = "Initiating cloning cycle..."
- records.Remove(C)
- qdel(C)
- menu = 1
+ scan_mob(scanner.occupant)
+ loading = FALSE
+ SStgui.update_uis(src)
+ if("autoprocess")
+ autoprocess = text2num(params["on"]) > 0
+ if("lock")
+ if(isnull(scanner) || !scanner.occupant) //No locking an open scanner.
+ return
+ scanner.locked = !scanner.locked
+ if("view_rec")
+ var/ref = params["ref"]
+ if(!length(ref))
+ return
+ active_record = locate(ref)
+ if(istype(active_record))
+ if(isnull(active_record.ckey))
+ qdel(active_record)
+ set_temp("Error: Record corrupt.", "danger")
+ else
+ var/obj/item/implant/health/H = null
+ if(active_record.implant)
+ H = locate(active_record.implant)
+ var/list/payload = list(
+ activerecord = "\ref[active_record]",
+ health = (H && istype(H)) ? H.sensehealth() : "",
+ realname = sanitize(active_record.dna.real_name),
+ unidentity = active_record.dna.uni_identity,
+ strucenzymes = active_record.dna.struc_enzymes,
+ )
+ tgui_modal_message(src, action, "", null, payload)
+ else
+ active_record = null
+ set_temp("Error: Record missing.", "danger")
+ if("del_rec")
+ if(!active_record)
+ return
+ tgui_modal_boolean(src, action, "Please confirm that you want to delete the record by holding your ID and pressing Delete:", yes_text = "Delete", no_text = "Cancel")
+ if("disk") // Disk management.
+ if(!length(params["option"]))
+ return
+ switch(params["option"])
+ if("load")
+ if(isnull(diskette) || isnull(diskette.buf))
+ set_temp("Error: The disk's data could not be read.", "danger")
+ return
+ else if(isnull(active_record))
+ set_temp("Error: No active record was found.", "danger")
+ menu = MENU_MAIN
+ return
+
+ active_record = diskette.buf.copy()
+ set_temp("Successfully loaded from disk.", "success")
+ if("save")
+ if(isnull(diskette) || diskette.read_only || isnull(active_record))
+ set_temp("Error: The data could not be saved.", "danger")
+ return
+
+ // DNA2 makes things a little simpler.
+ var/types
+ switch(params["savetype"]) // Save as Ui/Ui+Ue/Se
+ if("ui")
+ types = DNA2_BUF_UI
+ if("ue")
+ types = DNA2_BUF_UI|DNA2_BUF_UE
+ if("se")
+ types = DNA2_BUF_SE
+ else
+ set_temp("Error: Invalid save format.", "danger")
+ return
+ diskette.buf = active_record.copy()
+ diskette.buf.types = types
+ diskette.name = "data disk - '[active_record.dna.real_name]'"
+ set_temp("Successfully saved to disk.", "success")
+ if("eject")
+ if(!isnull(diskette))
+ diskette.loc = loc
+ diskette = null
+ if("refresh")
+ SStgui.update_uis(src)
+ if("selectpod")
+ var/ref = params["ref"]
+ if(!length(ref))
+ return
+ var/obj/machinery/clonepod/selected = locate(ref)
+ if(istype(selected) && (selected in pods))
+ selected_pod = selected
+ if("clone")
+ var/ref = params["ref"]
+ if(!length(ref))
+ return
+ var/datum/dna2/record/C = locate(ref)
+ //Look for that player! They better be dead!
+ if(istype(C))
+ tgui_modal_clear(src)
+ //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs.
+ if(!length(pods))
+ set_temp("Error: No cloning pod detected.", "danger")
+ else
+ var/obj/machinery/clonepod/pod = selected_pod
+ var/cloneresult
+ if(!selected_pod)
+ set_temp("Error: No cloning pod selected.", "danger")
+ else if(pod.occupant)
+ set_temp("Error: The cloning pod is currently occupied.", "danger")
+ else if(pod.biomass < CLONE_BIOMASS)
+ set_temp("Error: Not enough biomass.", "danger")
+ else if(pod.mess)
+ set_temp("Error: The cloning pod is malfunctioning.", "danger")
+ else if(!config.revival_cloning)
+ set_temp("Error: Unable to initiate cloning cycle.", "danger")
else
- temp = "[C.name] => Initialisation failure."
-
+ cloneresult = pod.growclone(C)
+ if(cloneresult)
+ set_temp("Initiating cloning cycle...", "success")
+ records.Remove(C)
+ qdel(C)
+ menu = MENU_MAIN
+ else
+ set_temp("Error: Initialisation failure.", "danger")
+ else
+ set_temp("Error: Data corruption.", "danger")
+ if("menu")
+ menu = clamp(text2num(params["num"]), MENU_MAIN, MENU_RECORDS)
+ if("toggle_mode")
+ if(loading)
+ return
+ if(can_brainscan())
+ scan_mode = !scan_mode
+ else
+ scan_mode = FALSE
+ if("eject")
+ if(usr.incapacitated() || !scanner || loading)
+ return
+ scanner.eject_occupant(usr)
+ scanner.add_fingerprint(usr)
+ if("cleartemp")
+ temp = null
else
- temp = "Error: Data corruption."
-
- else if(href_list["menu"])
- src.menu = text2num(href_list["menu"])
- temp = ""
- scantemp = "Scanner ready."
- else if(href_list["toggle_mode"])
- if(can_brainscan())
- scan_mode = !scan_mode
- else
- scan_mode = 0
+ return FALSE
src.add_fingerprint(usr)
- SSnanoui.update_uis(src)
- return
/obj/machinery/computer/cloning/proc/scan_mob(mob/living/carbon/human/subject as mob, var/scan_brain = 0)
if(stat & NOPOWER)
@@ -360,46 +381,46 @@
return
if(isnull(subject) || (!(ishuman(subject))) || (!subject.dna))
if(isalien(subject))
- scantemp = "Error: Xenomorphs are not scannable."
- SSnanoui.update_uis(src)
+ set_scan_temp("Xenomorphs are not scannable.", "bad")
+ SStgui.update_uis(src)
return
// can add more conditions for specific non-human messages here
else
- scantemp = "Error: Subject species is not scannable."
- SSnanoui.update_uis(src)
+ set_scan_temp("Subject species is not scannable.", "bad")
+ SStgui.update_uis(src)
return
if(subject.get_int_organ(/obj/item/organ/internal/brain))
var/obj/item/organ/internal/brain/Brn = subject.get_int_organ(/obj/item/organ/internal/brain)
if(istype(Brn))
if(NO_SCAN in Brn.dna.species.species_traits)
- scantemp = "Error: [Brn.dna.species.name_plural] are not scannable."
- SSnanoui.update_uis(src)
+ set_scan_temp("[Brn.dna.species.name_plural] are not scannable.", "bad")
+ SStgui.update_uis(src)
return
if(!subject.get_int_organ(/obj/item/organ/internal/brain))
- scantemp = "Error: No brain detected in subject."
- SSnanoui.update_uis(src)
+ set_scan_temp("No brain detected in subject.", "bad")
+ SStgui.update_uis(src)
return
if(subject.suiciding)
- scantemp = "Error: Subject has committed suicide and is not scannable."
- SSnanoui.update_uis(src)
+ set_scan_temp("Subject has committed suicide and is not scannable.", "bad")
+ SStgui.update_uis(src)
return
if((!subject.ckey) || (!subject.client))
- scantemp = "Error: Subject's brain is not responding. Further attempts after a short delay may succeed."
- SSnanoui.update_uis(src)
+ set_scan_temp("Subject's brain is not responding. Further attempts after a short delay may succeed.", "bad")
+ SStgui.update_uis(src)
return
if((NOCLONE in subject.mutations) && src.scanner.scan_level < 2)
- scantemp = "Error: Subject has incompatible genetic mutations."
- SSnanoui.update_uis(src)
+ set_scan_temp("Subject has incompatible genetic mutations.", "bad")
+ SStgui.update_uis(src)
return
if(!isnull(find_record(subject.ckey)))
- scantemp = "Subject already in database."
- SSnanoui.update_uis(src)
+ set_scan_temp("Subject already in database.")
+ SStgui.update_uis(src)
return
for(var/obj/machinery/clonepod/pod in pods)
if(pod.occupant && pod.clonemind == subject.mind)
- scantemp = "Subject already getting cloned."
- SSnanoui.update_uis(src)
+ set_scan_temp("Subject already getting cloned.")
+ SStgui.update_uis(src)
return
subject.dna.check_integrity()
@@ -434,8 +455,8 @@
R.mind = "\ref[subject.mind]"
src.records += R
- scantemp = "Subject successfully scanned. " + extra_info
- SSnanoui.update_uis(src)
+ set_scan_temp("Subject successfully scanned. [extra_info]", "good")
+ SStgui.update_uis(src)
//Find a specific record by key.
/obj/machinery/computer/cloning/proc/find_record(var/find_key)
@@ -451,3 +472,30 @@
/obj/machinery/computer/cloning/proc/can_brainscan()
return (scanner && scanner.scan_level > 3)
+
+/**
+ * Sets a temporary message to display to the user
+ *
+ * Arguments:
+ * * text - Text to display, null/empty to clear the message from the UI
+ * * style - The style of the message: (color name), info, success, warning, danger
+ */
+/obj/machinery/computer/cloning/proc/set_temp(text = "", style = "info", update_now = FALSE)
+ temp = list(text = text, style = style)
+ if(update_now)
+ SStgui.update_uis(src)
+
+/**
+ * Sets a temporary scan message to display to the user
+ *
+ * Arguments:
+ * * text - Text to display, null/empty to clear the message from the UI
+ * * color - The color of the message: (color name)
+ */
+/obj/machinery/computer/cloning/proc/set_scan_temp(text = "", color = "", update_now = FALSE)
+ scantemp = list(text = text, color = color)
+ if(update_now)
+ SStgui.update_uis(src)
+
+#undef MENU_MAIN
+#undef MENU_RECORDS
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index e161891cdbc..60563e692b3 100644
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -1,35 +1,39 @@
#define COMM_SCREEN_MAIN 1
#define COMM_SCREEN_STAT 2
#define COMM_SCREEN_MESSAGES 3
-#define COMM_SCREEN_SECLEVEL 4
#define COMM_AUTHENTICATION_NONE 0
#define COMM_AUTHENTICATION_MIN 1
#define COMM_AUTHENTICATION_MAX 2
+#define COMM_MSGLEN_MINIMUM 6
+#define COMM_CCMSGLEN_MINIMUM 20
+
// The communications computer
/obj/machinery/computer/communications
name = "communications console"
- desc = "This can be used for various important functions. Still under developement."
+ desc = "This allows the Captain to contact Central Command, or change the alert level. It also allows the command staff to call the Escape Shuttle."
icon_keyboard = "tech_key"
icon_screen = "comm"
req_access = list(ACCESS_HEADS)
circuit = /obj/item/circuitboard/communications
- var/prints_intercept = 1
- var/authenticated = COMM_AUTHENTICATION_NONE
var/list/messagetitle = list()
var/list/messagetext = list()
- var/currmsg = 0
- var/aicurrmsg = 0
+ var/currmsg
+
+ var/authenticated = COMM_AUTHENTICATION_NONE
var/menu_state = COMM_SCREEN_MAIN
var/ai_menu_state = COMM_SCREEN_MAIN
- var/message_cooldown = 0
- var/centcomm_message_cooldown = 0
+ var/aicurrmsg
+
+ var/message_cooldown
+ var/centcomm_message_cooldown
var/tmp_alertlevel = 0
var/stat_msg1
var/stat_msg2
- var/display_type="blank"
+ var/display_type = "blank"
+ var/display_icon
var/datum/announcement/priority/crew_announcement = new
@@ -70,123 +74,113 @@
feedback_inc("alert_comms_blue",1)
tmp_alertlevel = 0
-/obj/machinery/computer/communications/Topic(href, href_list)
- if(..(href, href_list))
- return 1
-
- if(!is_secure_level(src.z))
+/obj/machinery/computer/communications/tgui_act(action, params)
+ if(..())
+ return
+ if(!is_secure_level(z))
to_chat(usr, "Unable to establish a connection: You're too far away from the station!")
- return 1
+ return
- if(href_list["login"])
+ . = TRUE
+
+ if(action == "auth")
if(!ishuman(usr))
to_chat(usr, "Access denied.")
+ return FALSE
+ // Logout function.
+ if(authenticated != COMM_AUTHENTICATION_NONE)
+ authenticated = COMM_AUTHENTICATION_NONE
+ crew_announcement.announcer = null
+ setMenuState(usr, COMM_SCREEN_MAIN)
return
-
+ // Login function.
var/list/access = usr.get_access()
if(allowed(usr))
authenticated = COMM_AUTHENTICATION_MIN
-
if(ACCESS_CAPTAIN in access)
authenticated = COMM_AUTHENTICATION_MAX
var/mob/living/carbon/human/H = usr
var/obj/item/card/id = H.get_idcard(TRUE)
if(istype(id))
crew_announcement.announcer = GetNameAndAssignmentFromId(id)
-
- SSnanoui.update_uis(src)
- return
-
- if(href_list["logout"])
- authenticated = COMM_AUTHENTICATION_NONE
- crew_announcement.announcer = ""
- setMenuState(usr,COMM_SCREEN_MAIN)
- SSnanoui.update_uis(src)
+ if(authenticated == COMM_AUTHENTICATION_NONE)
+ to_chat(usr, "You need to wear your ID.")
return
+ // All functions below this point require authentication.
if(!is_authenticated(usr))
- return 1
+ return FALSE
- switch(href_list["operation"])
+ switch(action)
if("main")
- setMenuState(usr,COMM_SCREEN_MAIN)
-
- if("changeseclevel")
- setMenuState(usr,COMM_SCREEN_SECLEVEL)
+ setMenuState(usr, COMM_SCREEN_MAIN)
if("newalertlevel")
if(isAI(usr) || isrobot(usr))
to_chat(usr, "Firewalls prevent you from changing the alert level.")
- return 1
+ return
else if(usr.can_admin_interact())
- change_security_level(text2num(href_list["level"]))
- return 1
+ change_security_level(text2num(params["level"]))
+ return
else if(!ishuman(usr))
to_chat(usr, "Security measures prevent you from changing the alert level.")
- return 1
+ return
- var/mob/living/carbon/human/L = usr
- var/obj/item/card = L.get_active_hand()
- var/obj/item/card/id/I = (card && card.GetID()) || L.wear_id || L.wear_pda
- if(istype(I, /obj/item/pda))
- var/obj/item/pda/pda = I
- I = pda.id
- if(I && istype(I))
+ var/mob/living/carbon/human/H = usr
+ var/obj/item/card/id/I = H.get_idcard(TRUE)
+ if(istype(I))
if(ACCESS_CAPTAIN in I.access)
- change_security_level(text2num(href_list["level"]))
+ change_security_level(text2num(params["level"]))
else
to_chat(usr, "You are not authorized to do this.")
- setMenuState(usr,COMM_SCREEN_MAIN)
+ setMenuState(usr, COMM_SCREEN_MAIN)
else
- to_chat(usr, "You need to swipe your ID.")
+ to_chat(usr, "You need to wear your ID.")
if("announce")
if(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)
- if(message_cooldown)
+ if(message_cooldown > world.time)
to_chat(usr, "Please allow at least one minute to pass between announcements.")
- SSnanoui.update_uis(src)
return
var/input = input(usr, "Please write a message to announce to the station crew.", "Priority Announcement")
- if(!input || message_cooldown || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX))
- SSnanoui.update_uis(src)
+ if(!input || message_cooldown > world.time || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX))
+ return
+ if(length(input) < COMM_MSGLEN_MINIMUM)
+ to_chat(usr, "Message '[input]' is too short. [COMM_MSGLEN_MINIMUM] character minimum.")
return
crew_announcement.Announce(input)
- message_cooldown = 1
- spawn(600)//One minute cooldown
- message_cooldown = 0
+ message_cooldown = world.time + 600 //One minute
if("callshuttle")
var/input = clean_input("Please enter the reason for calling the shuttle.", "Shuttle Call Reason.","")
if(!input || ..() || !is_authenticated(usr))
- SSnanoui.update_uis(src)
return
-
call_shuttle_proc(usr, input)
if(SSshuttle.emergency.timer)
post_status("shuttle")
- setMenuState(usr,COMM_SCREEN_MAIN)
+ setMenuState(usr, COMM_SCREEN_MAIN)
if("cancelshuttle")
if(isAI(usr) || isrobot(usr))
to_chat(usr, "Firewalls prevent you from recalling the shuttle.")
- SSnanoui.update_uis(src)
- return 1
+ return
var/response = alert("Are you sure you wish to recall the shuttle?", "Confirm", "Yes", "No")
if(response == "Yes")
cancel_call_proc(usr)
if(SSshuttle.emergency.timer)
post_status("shuttle")
- setMenuState(usr,COMM_SCREEN_MAIN)
+ setMenuState(usr, COMM_SCREEN_MAIN)
if("messagelist")
- currmsg = 0
- if(href_list["msgid"])
- setCurrentMessage(usr, text2num(href_list["msgid"]))
- setMenuState(usr,COMM_SCREEN_MESSAGES)
+ currmsg = null
+ aicurrmsg = null
+ if(params["msgid"])
+ setCurrentMessage(usr, text2num(params["msgid"]))
+ setMenuState(usr, COMM_SCREEN_MESSAGES)
if("delmessage")
- if(href_list["msgid"])
- currmsg = text2num(href_list["msgid"])
+ if(params["msgid"])
+ currmsg = text2num(params["msgid"])
var/response = alert("Are you sure you wish to delete this message?", "Confirm", "Yes", "No")
if(response == "Yes")
if(currmsg)
@@ -196,95 +190,95 @@
messagetitle.Remove(title)
messagetext.Remove(text)
if(currmsg == id)
- currmsg = 0
+ currmsg = null
if(aicurrmsg == id)
- aicurrmsg = 0
- setMenuState(usr,COMM_SCREEN_MESSAGES)
+ aicurrmsg = null
+ setMenuState(usr, COMM_SCREEN_MESSAGES)
if("status")
- setMenuState(usr,COMM_SCREEN_STAT)
+ setMenuState(usr, COMM_SCREEN_STAT)
// Status display stuff
if("setstat")
- display_type=href_list["statdisp"]
+ display_type = params["statdisp"]
switch(display_type)
if("message")
+ display_icon = null
post_status("message", stat_msg1, stat_msg2, usr)
if("alert")
- post_status("alert", href_list["alert"], user = usr)
+ display_icon = params["alert"]
+ post_status("alert", params["alert"], user = usr)
else
- post_status(href_list["statdisp"], user = usr)
- setMenuState(usr,COMM_SCREEN_STAT)
+ display_icon = null
+ post_status(params["statdisp"], user = usr)
+ setMenuState(usr, COMM_SCREEN_STAT)
if("setmsg1")
stat_msg1 = clean_input("Line 1", "Enter Message Text", stat_msg1)
- setMenuState(usr,COMM_SCREEN_STAT)
+ setMenuState(usr, COMM_SCREEN_STAT)
if("setmsg2")
stat_msg2 = clean_input("Line 2", "Enter Message Text", stat_msg2)
- setMenuState(usr,COMM_SCREEN_STAT)
+ setMenuState(usr, COMM_SCREEN_STAT)
if("nukerequest")
if(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)
- if(centcomm_message_cooldown)
+ if(centcomm_message_cooldown > world.time)
to_chat(usr, "Arrays recycling. Please stand by.")
- SSnanoui.update_uis(src)
return
var/input = stripped_input(usr, "Please enter the reason for requesting the nuclear self-destruct codes. Misuse of the nuclear request system will not be tolerated under any circumstances. Transmission does not guarantee a response.", "Self Destruct Code Request.","")
if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX))
- SSnanoui.update_uis(src)
+ return
+ if(length(input) < COMM_CCMSGLEN_MINIMUM)
+ to_chat(usr, "Message '[input]' is too short. [COMM_CCMSGLEN_MINIMUM] character minimum.")
return
Nuke_request(input, usr)
to_chat(usr, "Request sent.")
log_game("[key_name(usr)] has requested the nuclear codes from Centcomm")
GLOB.priority_announcement.Announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self Destruct Codes Requested",'sound/AI/commandreport.ogg')
- centcomm_message_cooldown = 1
- spawn(6000)//10 minute cooldown
- centcomm_message_cooldown = 0
- setMenuState(usr,COMM_SCREEN_MAIN)
+ centcomm_message_cooldown = world.time + 6000 // 10 minutes
+ setMenuState(usr, COMM_SCREEN_MAIN)
if("MessageCentcomm")
if(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)
- if(centcomm_message_cooldown)
+ if(centcomm_message_cooldown > world.time)
to_chat(usr, "Arrays recycling. Please stand by.")
- SSnanoui.update_uis(src)
return
var/input = stripped_input(usr, "Please choose a message to transmit to Centcomm via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "To abort, send an empty message.", "")
if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX))
- SSnanoui.update_uis(src)
+ return
+ if(length(input) < COMM_CCMSGLEN_MINIMUM)
+ to_chat(usr, "Message '[input]' is too short. [COMM_CCMSGLEN_MINIMUM] character minimum.")
return
Centcomm_announce(input, usr)
print_centcom_report(input, station_time_timestamp() + " Captain's Message")
to_chat(usr, "Message transmitted.")
log_game("[key_name(usr)] has made a Centcomm announcement: [input]")
- centcomm_message_cooldown = 1
- spawn(6000)//10 minute cooldown
- centcomm_message_cooldown = 0
- setMenuState(usr,COMM_SCREEN_MAIN)
+ centcomm_message_cooldown = world.time + 6000 // 10 minutes
+ setMenuState(usr, COMM_SCREEN_MAIN)
// OMG SYNDICATE ...LETTERHEAD
if("MessageSyndicate")
if((is_authenticated(usr) == COMM_AUTHENTICATION_MAX) && (src.emagged))
- if(centcomm_message_cooldown)
+ if(centcomm_message_cooldown > world.time)
to_chat(usr, "Arrays recycling. Please stand by.")
- SSnanoui.update_uis(src)
return
var/input = stripped_input(usr, "Please choose a message to transmit to \[ABNORMAL ROUTING CORDINATES\] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "To abort, send an empty message.", "")
if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX))
- SSnanoui.update_uis(src)
+ return
+ if(length(input) < COMM_CCMSGLEN_MINIMUM)
+ to_chat(usr, "Message '[input]' is too short. [COMM_CCMSGLEN_MINIMUM] character minimum.")
return
Syndicate_announce(input, usr)
to_chat(usr, "Message transmitted.")
log_game("[key_name(usr)] has made a Syndicate announcement: [input]")
- centcomm_message_cooldown = 1
- spawn(6000)//10 minute cooldown
- centcomm_message_cooldown = 0
- setMenuState(usr,COMM_SCREEN_MAIN)
+ centcomm_message_cooldown = world.time + 6000 // 10 minutes
+ setMenuState(usr, COMM_SCREEN_MAIN)
if("RestoreBackup")
to_chat(usr, "Backup routing data restored!")
src.emagged = 0
- setMenuState(usr,COMM_SCREEN_MAIN)
+ setMenuState(usr, COMM_SCREEN_MAIN)
if("RestartNanoMob")
if(SSmob_hunt)
@@ -298,18 +292,13 @@
else
to_chat(usr, "Nano-Mob Hunter GO! game server is offline for extended maintenance. Contact your Central Command administrators for more info if desired.")
- if("ToggleATC")
- GLOB.atc.squelched = !GLOB.atc.squelched
- to_chat(usr, "ATC traffic is now: [GLOB.atc.squelched ? "Disabled" : "Enabled"].")
- SSnanoui.update_uis(src)
- return 1
/obj/machinery/computer/communications/emag_act(user as mob)
if(!emagged)
src.emagged = 1
to_chat(user, "You scramble the communication routing circuits!")
- SSnanoui.update_uis(src)
+ SStgui.update_uis(src)
/obj/machinery/computer/communications/attack_ai(var/mob/user as mob)
return src.attack_hand(user)
@@ -325,28 +314,25 @@
to_chat(user, "Unable to establish a connection: You're too far away from the station!")
return
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/communications/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null)
- // update the ui if it exists, returns null if no ui is passed/found
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui)
+/obj/machinery/computer/communications/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- // the ui does not exist, so we'll create a new() one
- // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "comm_console.tmpl", "Communications Console", 400, 500)
- // open the new ui window
+ ui = new(user, src, ui_key, "CommunicationsComputer", name, 500, 600, master_ui, state)
ui.open()
-/obj/machinery/computer/communications/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
+/obj/machinery/computer/communications/tgui_data(mob/user)
+ var/list/data = list()
data["is_ai"] = isAI(user) || isrobot(user)
data["menu_state"] = data["is_ai"] ? ai_menu_state : menu_state
data["emagged"] = emagged
data["authenticated"] = is_authenticated(user, 0)
- data["screen"] = getMenuState(usr)
+ data["authmax"] = data["authenticated"] == COMM_AUTHENTICATION_MAX ? TRUE : FALSE
data["stat_display"] = list(
"type" = display_type,
+ "icon" = display_icon,
"line_1" = (stat_msg1 ? stat_msg1 : "-----"),
"line_2" = (stat_msg2 ? stat_msg2 : "-----"),
@@ -364,12 +350,20 @@
)
)
- data["security_level"] = GLOB.security_level
+ data["security_level"] = GLOB.security_level
+ switch(GLOB.security_level)
+ if(SEC_LEVEL_GREEN)
+ data["security_level_color"] = "green";
+ if(SEC_LEVEL_BLUE)
+ data["security_level_color"] = "blue";
+ if(SEC_LEVEL_RED)
+ data["security_level_color"] = "red";
+ else
+ data["security_level_color"] = "purple";
data["str_security_level"] = capitalize(get_security_level())
data["levels"] = list(
- list("id" = SEC_LEVEL_GREEN, "name" = "Green"),
- list("id" = SEC_LEVEL_BLUE, "name" = "Blue"),
- //SEC_LEVEL_RED = list("name"="Red"),
+ list("id" = SEC_LEVEL_GREEN, "name" = "Green", "icon" = "dove"),
+ list("id" = SEC_LEVEL_BLUE, "name" = "Blue", "icon" = "eye"),
)
var/list/msg_data = list()
@@ -377,29 +371,30 @@
msg_data.Add(list(list("title" = messagetitle[i], "body" = messagetext[i], "id" = i)))
data["messages"] = msg_data
+
+ data["current_message"] = null
+ data["current_message_title"] = null
if((data["is_ai"] && aicurrmsg) || (!data["is_ai"] && currmsg))
data["current_message"] = data["is_ai"] ? messagetext[aicurrmsg] : messagetext[currmsg]
data["current_message_title"] = data["is_ai"] ? messagetitle[aicurrmsg] : messagetitle[currmsg]
data["lastCallLoc"] = SSshuttle.emergencyLastCallLoc ? format_text(SSshuttle.emergencyLastCallLoc.name) : null
+ data["msg_cooldown"] = message_cooldown ? (round((message_cooldown - world.time) / 10)) : 0
+ data["cc_cooldown"] = centcomm_message_cooldown ? (round((centcomm_message_cooldown - world.time) / 10)) : 0
- var/shuttle[0]
- switch(SSshuttle.emergency.mode)
- if(SHUTTLE_IDLE, SHUTTLE_RECALL)
- shuttle["callStatus"] = 2 //#define
- else
- shuttle["callStatus"] = 1
- if(SSshuttle.emergency.mode == SHUTTLE_CALL)
+ var/secondsToRefuel = SSshuttle.secondsToRefuel()
+ data["esc_callable"] = SSshuttle.emergency.mode == SHUTTLE_IDLE && !secondsToRefuel ? TRUE : FALSE
+ data["esc_recallable"] = SSshuttle.emergency.mode == SHUTTLE_CALL ? TRUE : FALSE
+ data["esc_status"] = FALSE
+ if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL)
var/timeleft = SSshuttle.emergency.timeLeft()
- shuttle["eta"] = "[timeleft / 60 % 60]:[add_zero(num2text(timeleft % 60), 2)]"
-
- data["shuttle"] = shuttle
-
- data["atcSquelched"] = GLOB.atc.squelched
-
+ data["esc_status"] = SSshuttle.emergency.mode == SHUTTLE_CALL ? "ETA:" : "RECALLING:"
+ data["esc_status"] += " [timeleft / 60 % 60]:[add_zero(num2text(timeleft % 60), 2)]"
+ else if(secondsToRefuel)
+ data["esc_status"] = "Refueling: [secondsToRefuel / 60 % 60]:[add_zero(num2text(secondsToRefuel % 60), 2)]"
+ data["esc_section"] = data["esc_status"] || data["esc_callable"] || data["esc_recallable"] || data["lastCallLoc"]
return data
-
/obj/machinery/computer/communications/proc/setCurrentMessage(var/mob/user,var/value)
if(isAI(user) || isrobot(user))
aicurrmsg = value
@@ -418,12 +413,6 @@
else
menu_state=value
-/obj/machinery/computer/communications/proc/getMenuState(var/mob/user)
- if(isAI(user) || isrobot(user))
- return ai_menu_state
- else
- return menu_state
-
/proc/call_shuttle_proc(var/mob/user, var/reason)
if(GLOB.sent_strike_team == 1)
to_chat(user, "Central Command will not allow the shuttle to be called. Consider all contracts terminated.")
@@ -527,24 +516,16 @@
SSshuttle.autoEvac()
return ..()
-/proc/print_command_report(text = "", title = "Central Command Update")
+/proc/print_command_report(text = "", title = "Central Command Update", add_to_records = TRUE)
for(var/obj/machinery/computer/communications/C in GLOB.shuttle_caller_list)
if(!(C.stat & (BROKEN|NOPOWER)) && is_station_contact(C.z))
var/obj/item/paper/P = new /obj/item/paper(C.loc)
P.name = "paper- '[title]'"
P.info = text
P.update_icon()
- C.messagetitle.Add("[title]")
- C.messagetext.Add(text)
- for(var/datum/computer_file/program/comm/P in GLOB.shuttle_caller_list)
- var/turf/T = get_turf(P.computer)
- if(T && P.program_state != PROGRAM_STATE_KILLED && is_station_contact(T.z))
- if(P.computer)
- var/obj/item/computer_hardware/printer/printer = P.computer.all_components[MC_PRINT]
- if(printer)
- printer.print_text(text, "paper- '[title]'")
- P.messagetitle.Add("[title]")
- P.messagetext.Add(text)
+ if(add_to_records)
+ C.messagetitle.Add("[title]")
+ C.messagetext.Add(text)
/proc/print_centcom_report(text = "", title = "Incoming Message")
for(var/obj/machinery/computer/communications/C in GLOB.shuttle_caller_list)
@@ -555,12 +536,5 @@
P.update_icon()
C.messagetitle.Add("[title]")
C.messagetext.Add(text)
- for(var/datum/computer_file/program/comm/P in GLOB.shuttle_caller_list)
- var/turf/T = get_turf(P.computer)
- if(T && P.program_state != PROGRAM_STATE_KILLED && is_admin_level(T.z))
- if(P.computer)
- var/obj/item/computer_hardware/printer/printer = P.computer.all_components[MC_PRINT]
- if(printer)
- printer.print_text(text, "paper- '[title]'")
- P.messagetitle.Add("[title]")
- P.messagetext.Add(text)
+
+
diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm
index 131956ca3c1..d6a1ccdd33c 100644
--- a/code/game/machinery/computer/crew.dm
+++ b/code/game/machinery/computer/crew.dm
@@ -27,7 +27,7 @@
return
tgui_interact(user)
-/obj/machinery/computer/crew/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+/obj/machinery/computer/crew/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
crew_monitor.tgui_interact(user, ui_key, ui, force_open)
/obj/machinery/computer/crew/interact(mob/user)
diff --git a/code/game/machinery/computer/depot.dm b/code/game/machinery/computer/depot.dm
index 62c82d9bd95..f2e89923eb4 100644
--- a/code/game/machinery/computer/depot.dm
+++ b/code/game/machinery/computer/depot.dm
@@ -165,7 +165,7 @@
alerts_when_broken = TRUE
/obj/machinery/computer/syndicate_depot/selfdestruct/get_menu(mob/user)
- var menutext = {"Syndicate Depot Fusion Reactor Control
+ var/menutext = {"Syndicate Depot Fusion Reactor Control
Disable Containment Field
"}
return menutext
@@ -193,10 +193,6 @@
/obj/machinery/computer/syndicate_depot/shieldcontrol/New()
. = ..()
perimeterarea = locate(/area/syndicate_depot/perimeter)
- if(istype(perimeterarea) && (GAMEMODE_IS_NUCLEAR || prob(20)))
- spawn(200)
- perimeterarea.perimeter_shields_up()
- depotarea.perimeter_shield_status = TRUE
/obj/machinery/computer/syndicate_depot/shieldcontrol/Destroy()
if(istype(perimeterarea) && perimeterarea.shield_list.len)
@@ -204,7 +200,7 @@
return ..()
/obj/machinery/computer/syndicate_depot/shieldcontrol/get_menu(mob/user)
- var menutext = {"Syndicate Depot Shield Grid Control
+ var/menutext = {"Syndicate Depot Shield Grid Control
"}
menutext += {"(SYNDI-LEADER) Whole-base Shield: [perimeterarea.shield_list.len ? "ON" : "OFF"] ([perimeterarea.shield_list.len ? "Disable" : "Enable"]) "}
menutext += {"(SYNDI-LEADER) Armory Shield: [depotarea.shield_list.len ? "ON" : "OFF"] ([depotarea.shield_list.len ? "Disable" : "Enable"]) "}
diff --git a/code/game/machinery/computer/law.dm b/code/game/machinery/computer/law.dm
index 74cc417e7ce..d7987477a65 100644
--- a/code/game/machinery/computer/law.dm
+++ b/code/game/machinery/computer/law.dm
@@ -11,55 +11,57 @@
light_range_on = 2
- verb/AccessInternals()
- set category = "Object"
- set name = "Access Computer's Internals"
- set src in oview(1)
- if(get_dist(src, usr) > 1 || usr.restrained() || usr.lying || usr.stat || istype(usr, /mob/living/silicon))
- return
-
- opened = !opened
- if(opened)
- to_chat(usr, "The access panel is now open.")
- else
- to_chat(usr, "The access panel is now closed.")
+// What the fuck even is this
+/obj/machinery/computer/aiupload/verb/AccessInternals()
+ set category = "Object"
+ set name = "Access Computer's Internals"
+ set src in oview(1)
+ if(get_dist(src, usr) > 1 || usr.restrained() || usr.lying || usr.stat || istype(usr, /mob/living/silicon))
return
+ opened = !opened
+ if(opened)
+ to_chat(usr, "The access panel is now open.")
+ else
+ to_chat(usr, "The access panel is now closed.")
+ return
- attackby(obj/item/O as obj, mob/user as mob, params)
- if(istype(O, /obj/item/aiModule))
- if(!current)//no AI selected
- to_chat(user, "No AI selected. Please chose a target before proceeding with upload.")
- return
- var/turf/T = get_turf(current)
- if(!atoms_share_level(T, src))
- to_chat(user, "Unable to establish a connection: You're too far away from the target silicon!")
- return
- var/obj/item/aiModule/M = O
- M.install(src)
+
+/obj/machinery/computer/aiupload/attackby(obj/item/O as obj, mob/user as mob, params)
+ if(istype(O, /obj/item/aiModule))
+ if(!current)//no AI selected
+ to_chat(user, "No AI selected. Please chose a target before proceeding with upload.")
return
- return ..()
-
-
- attack_hand(var/mob/user as mob)
- if(src.stat & NOPOWER)
- to_chat(usr, "The upload computer has no power!")
- return
- if(src.stat & BROKEN)
- to_chat(usr, "The upload computer is broken!")
+ var/turf/T = get_turf(current)
+ if(!atoms_share_level(T, src))
+ to_chat(user, "Unable to establish a connection: You're too far away from the target silicon!")
return
+ var/obj/item/aiModule/M = O
+ M.install(src)
+ return
+ return ..()
- src.current = select_active_ai(user)
- if(!src.current)
- to_chat(usr, "No active AIs detected.")
- else
- to_chat(usr, "[src.current.name] selected for law changes.")
+/obj/machinery/computer/aiupload/attack_hand(var/mob/user as mob)
+ if(src.stat & NOPOWER)
+ to_chat(usr, "The upload computer has no power!")
+ return
+ if(src.stat & BROKEN)
+ to_chat(usr, "The upload computer is broken!")
return
- attack_ghost(user as mob)
- return 1
+ src.current = select_active_ai(user)
+ if(!src.current)
+ to_chat(usr, "No active AIs detected.")
+ else
+ to_chat(usr, "[src.current.name] selected for law changes.")
+ return
+
+/obj/machinery/computer/aiupload/attack_ghost(user as mob)
+ return 1
+
+// Why is this not a subtype
/obj/machinery/computer/borgupload
name = "cyborg upload console"
desc = "Used to upload laws to Cyborgs."
@@ -69,35 +71,35 @@
var/mob/living/silicon/robot/current = null
- attackby(obj/item/aiModule/module as obj, mob/user as mob, params)
- if(istype(module, /obj/item/aiModule))
- if(!current)//no borg selected
- to_chat(user, "No borg selected. Please chose a target before proceeding with upload.")
- return
- var/turf/T = get_turf(current)
- if(!atoms_share_level(T, src))
- to_chat(user, "Unable to establish a connection: You're too far away from the target silicon!")
- return
- module.install(src)
+/obj/machinery/computer/borgupload/attackby(obj/item/aiModule/module as obj, mob/user as mob, params)
+ if(istype(module, /obj/item/aiModule))
+ if(!current)//no borg selected
+ to_chat(user, "No borg selected. Please chose a target before proceeding with upload.")
return
- return ..()
-
-
- attack_hand(var/mob/user as mob)
- if(src.stat & NOPOWER)
- to_chat(usr, "The upload computer has no power!")
- return
- if(src.stat & BROKEN)
- to_chat(usr, "The upload computer is broken!")
+ var/turf/T = get_turf(current)
+ if(!atoms_share_level(T, src))
+ to_chat(user, "Unable to establish a connection: You're too far away from the target silicon!")
return
+ module.install(src)
+ return
+ return ..()
- src.current = freeborg()
- if(!src.current)
- to_chat(usr, "No free cyborgs detected.")
- else
- to_chat(usr, "[src.current.name] selected for law changes.")
+/obj/machinery/computer/borgupload/attack_hand(var/mob/user as mob)
+ if(src.stat & NOPOWER)
+ to_chat(usr, "The upload computer has no power!")
+ return
+ if(src.stat & BROKEN)
+ to_chat(usr, "The upload computer is broken!")
return
- attack_ghost(user as mob)
+ src.current = freeborg()
+
+ if(!src.current)
+ to_chat(usr, "No free cyborgs detected.")
+ else
+ to_chat(usr, "[src.current.name] selected for law changes.")
+ return
+
+/obj/machinery/computer/borgupload/attack_ghost(user as mob)
return 1
diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm
index 3674358f07f..48e946a999b 100644
--- a/code/game/machinery/computer/medical.dm
+++ b/code/game/machinery/computer/medical.dm
@@ -1,10 +1,12 @@
-#define MED_DATA_MAIN 1 // Main menu
#define MED_DATA_R_LIST 2 // Record list
#define MED_DATA_MAINT 3 // Records maintenance
#define MED_DATA_RECORD 4 // Record
#define MED_DATA_V_DATA 5 // Virus database
#define MED_DATA_MEDBOT 6 // Medbot monitor
+#define FIELD(N, V, E) list(field = N, value = V, edit = E)
+#define MED_FIELD(N, V, E, LB) list(field = N, value = V, edit = E, line_break = LB)
+
/obj/machinery/computer/med_data //TODO:SANITY
name = "medical records console"
desc = "This can be used to check medical records."
@@ -12,28 +14,55 @@
icon_screen = "medcomp"
req_one_access = list(ACCESS_MEDICAL, ACCESS_FORENSICS_LOCKERS)
circuit = /obj/item/circuitboard/med_data
- var/obj/item/card/id/scan = null
- var/authenticated = null
- var/rank = null
var/screen = null
var/datum/data/record/active1 = null
var/datum/data/record/active2 = null
- var/temp = null
+ var/list/temp = null
var/printing = null
+ // The below are used to make modal generation more convenient
+ var/static/list/field_edit_questions
+ var/static/list/field_edit_choices
light_color = LIGHT_COLOR_DARKBLUE
+/obj/machinery/computer/med_data/Initialize()
+ ..()
+ field_edit_questions = list(
+ // General
+ "sex" = "Please select new sex:",
+ "age" = "Please input new age:",
+ "fingerprint" = "Please input new fingerprint hash:",
+ "p_stat" = "Please select new physical status:",
+ "m_stat" = "Please select new mental status:",
+ // Medical
+ "blood_type" = "Please select new blood type:",
+ "b_dna" = "Please input new DNA:",
+ "mi_dis" = "Please input new minor disabilities:",
+ "mi_dis_d" = "Please summarize minor disabilities:",
+ "ma_dis" = "Please input new major disabilities:",
+ "ma_dis_d" = "Please summarize major disabilities:",
+ "alg" = "Please input new allergies:",
+ "alg_d" = "Please summarize allergies:",
+ "cdi" = "Please input new current diseases:",
+ "cdi_d" = "Please summarize current diseases:",
+ "notes" = "Please input new important notes:",
+ )
+ field_edit_choices = list(
+ // General
+ "sex" = list("Male", "Female"),
+ "p_stat" = list("*Deceased*", "*SSD*", "Active", "Physically Unfit", "Disabled"),
+ "m_stat" = list("*Insane*", "*Unstable*", "*Watch*", "Stable"),
+ // Medical
+ "blood_type" = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"),
+ )
+
/obj/machinery/computer/med_data/Destroy()
active1 = null
active2 = null
return ..()
/obj/machinery/computer/med_data/attackby(obj/item/O, mob/user, params)
- if(istype(O, /obj/item/card/id) && !scan)
- usr.drop_item()
- O.forceMove(src)
- scan = O
- ui_interact(user)
+ if(tgui_login_attackby(O, user))
return
return ..()
@@ -44,21 +73,23 @@
to_chat(user, "Unable to establish a connection: You're too far away from the station!")
return
add_fingerprint(user)
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/med_data/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/med_data/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "med_data.tmpl", name, 800, 380)
+ ui = new(user, src, ui_key, "MedicalRecords", "Medical Records", 800, 380, master_ui, state)
ui.open()
+ ui.set_autoupdate(FALSE)
-/obj/machinery/computer/med_data/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
+/obj/machinery/computer/med_data/tgui_data(mob/user)
var/data[0]
data["temp"] = temp
- data["scan"] = scan ? scan.name : null
- data["authenticated"] = authenticated
data["screen"] = screen
- if(authenticated)
+ data["printing"] = printing
+ // This proc appends login state to data.
+ tgui_login_data(data, user)
+ if(data["loginState"]["logged_in"])
switch(screen)
if(MED_DATA_R_LIST)
if(!isnull(GLOB.data_core.general))
@@ -72,17 +103,17 @@
if(istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1))
var/list/fields = list()
general["fields"] = fields
- fields[++fields.len] = list("field" = "Name:", "value" = active1.fields["name"], "edit" = null)
- fields[++fields.len] = list("field" = "ID:", "value" = active1.fields["id"], "edit" = null)
- fields[++fields.len] = list("field" = "Sex:", "value" = active1.fields["sex"], "edit" = "sex")
- fields[++fields.len] = list("field" = "Age:", "value" = active1.fields["age"], "edit" = "age")
- fields[++fields.len] = list("field" = "Fingerprint:", "value" = active1.fields["fingerprint"], "edit" = "fingerprint")
- fields[++fields.len] = list("field" = "Physical Status:", "value" = active1.fields["p_stat"], "edit" = "p_stat")
- fields[++fields.len] = list("field" = "Mental Status:", "value" = active1.fields["m_stat"], "edit" = "m_stat")
+ fields[++fields.len] = FIELD("Name", active1.fields["name"], null)
+ fields[++fields.len] = FIELD("ID", active1.fields["id"], null)
+ fields[++fields.len] = FIELD("Sex", active1.fields["sex"], "sex")
+ fields[++fields.len] = FIELD("Age", active1.fields["age"], "age")
+ fields[++fields.len] = FIELD("Fingerprint", active1.fields["fingerprint"], "fingerprint")
+ fields[++fields.len] = FIELD("Physical Status", active1.fields["p_stat"], "p_stat")
+ fields[++fields.len] = FIELD("Mental Status", active1.fields["m_stat"], "m_stat")
var/list/photos = list()
general["photos"] = photos
- photos[++photos.len] = list("photo" = active1.fields["photo-south"])
- photos[++photos.len] = list("photo" = active1.fields["photo-west"])
+ photos[++photos.len] = active1.fields["photo-south"]
+ photos[++photos.len] = active1.fields["photo-west"]
general["has_photos"] = (active1.fields["photo-south"] || active1.fields["photo-west"] ? 1 : 0)
general["empty"] = 0
else
@@ -93,17 +124,17 @@
if(istype(active2, /datum/data/record) && GLOB.data_core.medical.Find(active2))
var/list/fields = list()
medical["fields"] = fields
- fields[++fields.len] = list("field" = "Blood Type:", "value" = active2.fields["blood_type"], "edit" = "blood_type", "line_break" = 0)
- fields[++fields.len] = list("field" = "DNA:", "value" = active2.fields["b_dna"], "edit" = "b_dna", "line_break" = 1)
- fields[++fields.len] = list("field" = "Minor Disabilities:", "value" = active2.fields["mi_dis"], "edit" = "mi_dis", "line_break" = 0)
- fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["mi_dis_d"], "edit" = "mi_dis_d", "line_break" = 1)
- fields[++fields.len] = list("field" = "Major Disabilities:", "value" = active2.fields["ma_dis"], "edit" = "ma_dis", "line_break" = 0)
- fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["ma_dis_d"], "edit" = "ma_dis_d", "line_break" = 1)
- fields[++fields.len] = list("field" = "Allergies:", "value" = active2.fields["alg"], "edit" = "alg", "line_break" = 0)
- fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["alg_d"], "edit" = "alg_d", "line_break" = 1)
- fields[++fields.len] = list("field" = "Current Diseases:", "value" = active2.fields["cdi"], "edit" = "cdi", "line_break" = 0)
- fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["cdi_d"], "edit" = "cdi_d", "line_break" = 1)
- fields[++fields.len] = list("field" = "Important Notes:", "value" = active2.fields["notes"], "edit" = "notes", "line_break" = 0)
+ fields[++fields.len] = MED_FIELD("Blood Type", active2.fields["blood_type"], "blood_type", FALSE)
+ fields[++fields.len] = MED_FIELD("DNA", active2.fields["b_dna"], "b_dna", TRUE)
+ fields[++fields.len] = MED_FIELD("Minor Disabilities", active2.fields["mi_dis"], "mi_dis", FALSE)
+ fields[++fields.len] = MED_FIELD("Details", active2.fields["mi_dis_d"], "mi_dis_d", TRUE)
+ fields[++fields.len] = MED_FIELD("Major Disabilities", active2.fields["ma_dis"], "ma_dis", FALSE)
+ fields[++fields.len] = MED_FIELD("Details", active2.fields["ma_dis_d"], "ma_dis_d", TRUE)
+ fields[++fields.len] = MED_FIELD("Allergies", active2.fields["alg"], "alg", FALSE)
+ fields[++fields.len] = MED_FIELD("Details", active2.fields["alg_d"], "alg_d", TRUE)
+ fields[++fields.len] = MED_FIELD("Current Diseases", active2.fields["cdi"], "cdi", FALSE)
+ fields[++fields.len] = MED_FIELD("Details", active2.fields["cdi_d"], "cdi_d", TRUE)
+ fields[++fields.len] = MED_FIELD("Important Notes", active2.fields["notes"], "notes", TRUE)
if(!active2.fields["comments"] || !islist(active2.fields["comments"]))
active2.fields["comments"] = list()
medical["comments"] = active2.fields["comments"]
@@ -127,7 +158,9 @@
var/turf/T = get_turf(M)
if(T)
var/medbot = list()
+ var/area/A = get_area(T)
medbot["name"] = M.name
+ medbot["area"] = A.name
medbot["x"] = T.x
medbot["y"] = T.y
medbot["on"] = M.on
@@ -138,395 +171,254 @@
else
medbot["use_beaker"] = 0
data["medbots"] += list(medbot)
+
+ data["modal"] = tgui_modal_data(src)
return data
-/obj/machinery/computer/med_data/Topic(href, href_list)
+/obj/machinery/computer/med_data/tgui_act(action, params)
if(..())
- return 1
+ return
+ if(stat & (NOPOWER|BROKEN))
+ return
if(!GLOB.data_core.general.Find(active1))
active1 = null
if(!GLOB.data_core.medical.Find(active2))
active2 = null
- if(href_list["temp"])
- temp = null
+ . = TRUE
+ if(tgui_act_modal(action, params))
+ return
+ if(tgui_login_act(action, params))
+ return
- if(href_list["temp_action"])
- if(href_list["temp_action"])
- var/temp_href = splittext(href_list["temp_action"], "=")
- switch(temp_href[1])
- if("del_all2")
- for(var/datum/data/record/R in GLOB.data_core.medical)
- qdel(R)
- setTemp("All records deleted.")
- if("p_stat")
- if(active1)
- switch(temp_href[2])
- if("deceased")
- active1.fields["p_stat"] = "*Deceased*"
- if("ssd")
- active1.fields["p_stat"] = "*SSD*"
- if("active")
- active1.fields["p_stat"] = "Active"
- if("unfit")
- active1.fields["p_stat"] = "Physically Unfit"
- if("disabled")
- active1.fields["p_stat"] = "Disabled"
- if("m_stat")
- if(active1)
- switch(temp_href[2])
- if("insane")
- active1.fields["m_stat"] = "*Insane*"
- if("unstable")
- active1.fields["m_stat"] = "*Unstable*"
- if("watch")
- active1.fields["m_stat"] = "*Watch*"
- if("stable")
- active1.fields["m_stat"] = "Stable"
- if("blood_type")
- if(active2)
- switch(temp_href[2])
- if("an")
- active2.fields["blood_type"] = "A-"
- if("bn")
- active2.fields["blood_type"] = "B-"
- if("abn")
- active2.fields["blood_type"] = "AB-"
- if("on")
- active2.fields["blood_type"] = "O-"
- if("ap")
- active2.fields["blood_type"] = "A+"
- if("bp")
- active2.fields["blood_type"] = "B+"
- if("abp")
- active2.fields["blood_type"] = "AB+"
- if("op")
- active2.fields["blood_type"] = "O+"
- if("del_r2")
- QDEL_NULL(active2)
-
- if(href_list["scan"])
- if(scan)
- scan.forceMove(loc)
- if(ishuman(usr) && !usr.get_active_hand())
- usr.put_in_hands(scan)
- scan = null
+ switch(action)
+ if("cleartemp")
+ temp = null
else
- var/obj/item/I = usr.get_active_hand()
- if(istype(I, /obj/item/card/id))
- usr.drop_item()
- I.forceMove(src)
- scan = I
+ . = FALSE
- if(href_list["login"])
- if(isAI(usr))
- authenticated = usr.name
- rank = "AI"
- else if(isrobot(usr))
- authenticated = usr.name
- var/mob/living/silicon/robot/R = usr
- rank = "[R.modtype] [R.braintype]"
- else if(istype(scan, /obj/item/card/id))
- if(check_access(scan))
- authenticated = scan.registered_name
- rank = scan.assignment
+ if(.)
+ return
- if(authenticated)
- active1 = null
- active2 = null
- screen = MED_DATA_MAIN
+ if(tgui_login_get().logged_in)
+ . = TRUE
+ switch(action)
+ if("screen")
+ screen = clamp(text2num(params["screen"]) || 0, MED_DATA_R_LIST, MED_DATA_MEDBOT)
+ active1 = null
+ active2 = null
+ if("vir")
+ var/type = text2path(params["vir"] || "")
+ if(!ispath(type, /datum/disease))
+ return
- if(authenticated)
- if(href_list["logout"])
- authenticated = null
- screen = null
- active1 = null
- active2 = null
+ var/datum/disease/D = new type(0)
+ var/list/payload = list(
+ name = D.name,
+ max_stages = D.max_stages,
+ spread_text = D.spread_text,
+ cure = D.cure_text || "None",
+ desc = D.desc,
+ severity = D.severity
+ );
+ tgui_modal_message(src, "virus", "", null, payload)
+ qdel(D)
+ if("del_all")
+ for(var/datum/data/record/R in GLOB.data_core.medical)
+ qdel(R)
+ set_temp("All medical records deleted.")
+ if("del_r")
+ if(active2)
+ set_temp("Medical record deleted.")
+ qdel(active2)
+ if("d_rec")
+ var/datum/data/record/general_record = locate(params["d_rec"] || "")
+ if(!GLOB.data_core.general.Find(general_record))
+ set_temp("Record not found.", "danger")
+ return
- if(href_list["screen"])
- screen = text2num(href_list["screen"])
- if(screen < 1)
- screen = MED_DATA_MAIN
+ var/datum/data/record/medical_record
+ for(var/datum/data/record/M in GLOB.data_core.medical)
+ if(M.fields["name"] == general_record.fields["name"] && M.fields["id"] == general_record.fields["id"])
+ medical_record = M
+ break
- active1 = null
- active2 = null
-
- if(href_list["vir"])
- var/type = href_list["vir"]
- var/datum/disease/D = new type(0)
- var/afs = ""
- for(var/mob/M in D.viable_mobtypes)
- afs += "[initial(M.name)];"
- var/severity = D.severity
- switch(severity)
- if("Harmful", "Minor")
- severity = "[severity]"
- if("Medium")
- severity = "[severity]"
- if("Dangerous!")
- severity = "[severity]"
- if("BIOHAZARD THREAT!")
- severity = "[severity]"
- setTemp({"Name: [D.name]
- Number of stages: [D.max_stages]
- Spread: [D.spread_text] Transmission
- Possible Cure: [(D.cure_text||"none")]
- Affected Lifeforms:[afs]
- Notes: [D.desc]
- Severity: [severity]"})
- qdel(D)
-
- if(href_list["del_all"])
- var/list/buttons = list()
- buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_all2=1")
- buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null)
- setTemp("Are you sure you wish to delete all records?", buttons)
-
- if(href_list["field"])
- if(..())
- return 1
- var/a1 = active1
- var/a2 = active2
- switch(href_list["field"])
- if("fingerprint")
- if(istype(active1, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please input fingerprint hash:", "Med. records", active1.fields["fingerprint"], null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active1 != a1)
- return 1
- active1.fields["fingerprint"] = t1
- if("sex")
- if(istype(active1, /datum/data/record))
- if(active1.fields["sex"] == "Male")
- active1.fields["sex"] = "Female"
- else
- active1.fields["sex"] = "Male"
- if("age")
- if(istype(active1, /datum/data/record))
- var/t1 = input("Please input age:", "Med. records", active1.fields["age"], null) as num
- if(!t1 || ..() || active1 != a1)
- return 1
- active1.fields["age"] = t1
- if("mi_dis")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please input minor disabilities list:", "Med. records", active2.fields["mi_dis"], null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["mi_dis"] = t1
- if("mi_dis_d")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please summarize minor dis.:", "Med. records", active2.fields["mi_dis_d"], null) as message)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["mi_dis_d"] = t1
- if("ma_dis")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please input major diabilities list:", "Med. records", active2.fields["ma_dis"], null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["ma_dis"] = t1
- if("ma_dis_d")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please summarize major dis.:", "Med. records", active2.fields["ma_dis_d"], null) as message)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["ma_dis_d"] = t1
- if("alg")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please state allergies:", "Med. records", active2.fields["alg"], null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["alg"] = t1
- if("alg_d")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please summarize allergies:", "Med. records", active2.fields["alg_d"], null) as message)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["alg_d"] = t1
- if("cdi")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please state diseases:", "Med. records", active2.fields["cdi"], null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["cdi"] = t1
- if("cdi_d")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please summarize diseases:", "Med. records", active2.fields["cdi_d"], null) as message)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["cdi_d"] = t1
- if("notes")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(html_encode(trim(input("Please summarize notes:", "Med. records", html_decode(active2.fields["notes"]), null) as message)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["notes"] = t1
- if("p_stat")
- if(istype(active1, /datum/data/record))
- var/list/buttons = list()
- buttons[++buttons.len] = list("name" = "*Deceased*", "icon" = "stethoscope", "href" = "p_stat=deceased", "status" = (active1.fields["p_stat"] == "*Deceased*" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "*SSD*", "icon" = "stethoscope", "href" = "p_stat=ssd", "status" = (active1.fields["p_stat"] == "*SSD*" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "Active", "icon" = "stethoscope", "href" = "p_stat=active", "status" = (active1.fields["p_stat"] == "Active" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "Physically Unfit", "icon" = "stethoscope", "href" = "p_stat=unfit", "status" = (active1.fields["p_stat"] == "Physically Unfit" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "Disabled", "icon" = "stethoscope", "href" = "p_stat=disabled", "status" = (active1.fields["p_stat"] == "Disabled" ? "selected" : null))
- setTemp("Physical Condition", buttons)
- if("m_stat")
- if(istype(active1, /datum/data/record))
- var/list/buttons = list()
- buttons[++buttons.len] = list("name" = "*Insane*", "icon" = "stethoscope", "href" = "m_stat=insane", "status" = (active1.fields["m_stat"] == "*Insane*" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "*Unstable*", "icon" = "stethoscope", "href" = "m_stat=unstable", "status" = (active1.fields["m_stat"] == "*Unstable*" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "*Watch*", "icon" = "stethoscope", "href" = "m_stat=watch", "status" = (active1.fields["m_stat"] == "*Watch*" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "Stable", "icon" = "stethoscope", "href" = "m_stat=stable", "status" = (active1.fields["m_stat"] == "Stable" ? "selected" : null))
- setTemp("Mental Condition", buttons)
- if("blood_type")
- if(istype(active2, /datum/data/record))
- var/list/buttons = list()
- buttons[++buttons.len] = list("name" = "A-", "icon" = "tint", "href" = "blood_type=an", "status" = (active2.fields["blood_type"] == "A-" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "A+", "icon" = "tint", "href" = "blood_type=ap", "status" = (active2.fields["blood_type"] == "A+" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "B-", "icon" = "tint", "href" = "blood_type=bn", "status" = (active2.fields["blood_type"] == "B-" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "B+", "icon" = "tint", "href" = "blood_type=bp", "status" = (active2.fields["blood_type"] == "B+" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "AB-", "icon" = "tint", "href" = "blood_type=abn", "status" = (active2.fields["blood_type"] == "AB-" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "AB+", "icon" = "tint", "href" = "blood_type=abp", "status" = (active2.fields["blood_type"] == "AB+" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "O-", "icon" = "tint", "href" = "blood_type=on", "status" = (active2.fields["blood_type"] == "O-" ? "selected" : null))
- buttons[++buttons.len] = list("name" = "O+", "icon" = "tint", "href" = "blood_type=op", "status" = (active2.fields["blood_type"] == "O+" ? "selected" : null))
- setTemp("Blood Type", buttons)
- if("b_dna")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please input DNA hash:", "Med. records", active2.fields["b_dna"], null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["b_dna"] = t1
- if("vir_name")
- var/datum/data/record/v = locate(href_list["edit_vir"])
- if(v)
- var/t1 = copytext(trim(sanitize(input("Please input pathogen name:", "VirusDB", v.fields["name"], null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active1 != a1)
- return 1
- v.fields["name"] = t1
- if("vir_desc")
- var/datum/data/record/v = locate(href_list["edit_vir"])
- if(v)
- var/t1 = copytext(trim(sanitize(input("Please input information about pathogen:", "VirusDB", v.fields["description"], null) as message)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active1 != a1)
- return 1
- v.fields["description"] = t1
-
- if(href_list["del_r"])
- if(active2)
- var/list/buttons = list()
- buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_r2=1", "status" = null)
- buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null)
- setTemp("Are you sure you wish to delete the record (Medical Portion Only)?", buttons)
-
- if(href_list["d_rec"])
- var/datum/data/record/R = locate(href_list["d_rec"])
- var/datum/data/record/M = locate(href_list["d_rec"])
- if(!GLOB.data_core.general.Find(R))
- setTemp("Record not found!")
- return 1
- for(var/datum/data/record/E in GLOB.data_core.medical)
- if(E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"])
- M = E
- active1 = R
- active2 = M
- screen = MED_DATA_RECORD
-
- if(href_list["new"])
- if(istype(active1, /datum/data/record) && !istype(active2, /datum/data/record))
- var/datum/data/record/R = new /datum/data/record()
- R.fields["name"] = active1.fields["name"]
- R.fields["id"] = active1.fields["id"]
- R.name = "Medical Record #[R.fields["id"]]"
- R.fields["blood_type"] = "Unknown"
- R.fields["b_dna"] = "Unknown"
- R.fields["mi_dis"] = "None"
- R.fields["mi_dis_d"] = "No minor disabilities have been declared."
- R.fields["ma_dis"] = "None"
- R.fields["ma_dis_d"] = "No major disabilities have been diagnosed."
- R.fields["alg"] = "None"
- R.fields["alg_d"] = "No allergies have been detected in this patient."
- R.fields["cdi"] = "None"
- R.fields["cdi_d"] = "No diseases have been diagnosed at the moment."
- R.fields["notes"] = "No notes."
- GLOB.data_core.medical += R
- active2 = R
+ active1 = general_record
+ active2 = medical_record
screen = MED_DATA_RECORD
-
- if(href_list["add_c"])
- if(!istype(active2, /datum/data/record))
- return 1
- var/a2 = active2
- var/t1 = copytext(trim(sanitize(input("Add Comment:", "Med. records", null, null) as message)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["comments"] += "Made by [authenticated] ([rank]) on [GLOB.current_date_string] [station_time_timestamp()] [t1]"
-
- if(href_list["del_c"])
- var/index = min(max(text2num(href_list["del_c"]) + 1, 1), length(active2.fields["comments"]))
- if(istype(active2, /datum/data/record) && active2.fields["comments"][index])
- active2.fields["comments"] -= active2.fields["comments"][index]
-
- if(href_list["search"])
- var/t1 = clean_input("Search String: (Name, DNA, or ID)", "Med. records", null, null)
- if(!t1 || ..())
- return 1
- active1 = null
- active2 = null
- t1 = lowertext(t1)
- for(var/datum/data/record/R in GLOB.data_core.medical)
- if(t1 == lowertext(R.fields["name"]) || t1 == lowertext(R.fields["id"]) || t1 == lowertext(R.fields["b_dna"]))
+ if("new")
+ if(istype(active1, /datum/data/record) && !istype(active2, /datum/data/record))
+ var/datum/data/record/R = new /datum/data/record()
+ R.fields["name"] = active1.fields["name"]
+ R.fields["id"] = active1.fields["id"]
+ R.name = "Medical Record #[R.fields["id"]]"
+ R.fields["blood_type"] = "Unknown"
+ R.fields["b_dna"] = "Unknown"
+ R.fields["mi_dis"] = "None"
+ R.fields["mi_dis_d"] = "No minor disabilities have been declared."
+ R.fields["ma_dis"] = "None"
+ R.fields["ma_dis_d"] = "No major disabilities have been diagnosed."
+ R.fields["alg"] = "None"
+ R.fields["alg_d"] = "No allergies have been detected in this patient."
+ R.fields["cdi"] = "None"
+ R.fields["cdi_d"] = "No diseases have been diagnosed at the moment."
+ R.fields["notes"] = "No notes."
+ GLOB.data_core.medical += R
active2 = R
- if(!active2)
- setTemp("Could not locate record [t1].")
- else
+ screen = MED_DATA_RECORD
+ set_temp("Medical record created.", "success")
+ if("del_c")
+ var/index = text2num(params["del_c"] || "")
+ if(!index || !istype(active2, /datum/data/record))
+ return
+
+ var/list/comments = active2.fields["comments"]
+ index = clamp(index, 1, length(comments))
+ if(comments[index])
+ comments.Cut(index, index + 1)
+ if("search")
+ active1 = null
+ active2 = null
+ var/t1 = lowertext(params["t1"] || "")
+ if(!length(t1))
+ return
+
+ for(var/datum/data/record/R in GLOB.data_core.medical)
+ if(t1 == lowertext(R.fields["name"]) || t1 == lowertext(R.fields["id"]) || t1 == lowertext(R.fields["b_dna"]))
+ active2 = R
+ break
+ if(!active2)
+ set_temp("Medical record not found. You must enter the person's exact name, ID or DNA.", "danger")
+ return
for(var/datum/data/record/E in GLOB.data_core.general)
if(E.fields["name"] == active2.fields["name"] && E.fields["id"] == active2.fields["id"])
active1 = E
+ break
screen = MED_DATA_RECORD
+ if("print_p")
+ if(!printing)
+ printing = TRUE
+ playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, TRUE)
+ SStgui.update_uis(src)
+ addtimer(CALLBACK(src, .proc/print_finish), 5 SECONDS)
+ else
+ return FALSE
- if(href_list["print_p"])
- if(!printing)
- printing = 1
- playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1)
- sleep(50)
- var/obj/item/paper/P = new /obj/item/paper(loc)
- P.info = "Medical Record "
- if(istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1))
- P.info += {"Name: [active1.fields["name"]] ID: [active1.fields["id"]]
- \nSex: [active1.fields["sex"]]
- \nAge: [active1.fields["age"]]
- \nFingerprint: [active1.fields["fingerprint"]]
- \nPhysical Status: [active1.fields["p_stat"]]
- \nMental Status: [active1.fields["m_stat"]] "}
+/**
+ * Called in tgui_act() to process modal actions
+ *
+ * Arguments:
+ * * action - The action passed by tgui
+ * * params - The params passed by tgui
+ */
+/obj/machinery/computer/med_data/proc/tgui_act_modal(action, params)
+ . = TRUE
+ var/id = params["id"] // The modal's ID
+ var/list/arguments = istext(params["arguments"]) ? json_decode(params["arguments"]) : params["arguments"]
+ switch(tgui_modal_act(src, action, params))
+ if(TGUI_MODAL_OPEN)
+ switch(id)
+ if("edit")
+ var/field = arguments["field"]
+ if(!length(field) || !field_edit_questions[field])
+ return
+ var/question = field_edit_questions[field]
+ var/choices = field_edit_choices[field]
+ if(length(choices))
+ tgui_modal_choice(src, id, question, arguments = arguments, value = arguments["value"], choices = choices)
+ else
+ tgui_modal_input(src, id, question, arguments = arguments, value = arguments["value"])
+ if("add_c")
+ tgui_modal_input(src, id, "Please enter your message:")
else
- P.info += "General Record Lost! "
- if(istype(active2, /datum/data/record) && GLOB.data_core.medical.Find(active2))
- P.info += {" \nMedical Data
- \nBlood Type: [active2.fields["blood_type"]]
- \nDNA: [active2.fields["b_dna"]] \n
- \nMinor Disabilities: [active2.fields["mi_dis"]]
- \nDetails: [active2.fields["mi_dis_d"]] \n
- \nMajor Disabilities: [active2.fields["ma_dis"]]
- \nDetails: [active2.fields["ma_dis_d"]] \n
- \nAllergies: [active2.fields["alg"]]
- \nDetails: [active2.fields["alg_d"]] \n
- \nCurrent Diseases: [active2.fields["cdi"]] (per disease info placed in log/comment section)
- \nDetails: [active2.fields["cdi_d"]] \n
- \nImportant Notes:
- \n\t[active2.fields["notes"]] \n
- \n
- Comments/Log "}
- for(var/c in active2.fields["comments"])
- P.info += "[c] "
- else
- P.info += "Medical Record Lost! "
- P.info += ""
- P.name = "paper- 'Medical Record: [active1.fields["name"]]'"
- printing = 0
- return 1
+ return FALSE
+ if(TGUI_MODAL_ANSWER)
+ var/answer = params["answer"]
+ switch(id)
+ if("edit")
+ var/field = arguments["field"]
+ if(!length(field) || !field_edit_questions[field])
+ return
+ var/list/choices = field_edit_choices[field]
+ if(length(choices) && !(answer in choices))
+ return
-/obj/machinery/computer/med_data/proc/setTemp(text, list/buttons = list())
- temp = list("text" = text, "buttons" = buttons, "has_buttons" = buttons.len > 0)
+ if(field == "age")
+ var/new_age = text2num(answer)
+ if(new_age < AGE_MIN || new_age > AGE_MAX)
+ set_temp("Invalid age. It must be between [AGE_MIN] and [AGE_MAX].", "danger")
+ return
+ answer = new_age
+
+ if(istype(active2) && (field in active2.fields))
+ active2.fields[field] = answer
+ else if(istype(active1) && (field in active1.fields))
+ active1.fields[field] = answer
+ if("add_c")
+ var/datum/tgui_login/state = tgui_login_get()
+ if(!length(answer) || !istype(active2) || !length(state.name))
+ return
+ active2.fields["comments"] += list(list(
+ header = "Made by [state.name] ([state.name]) on [GLOB.current_date_string] [station_time_timestamp()]",
+ text = answer
+ ))
+ else
+ return FALSE
+ else
+ return FALSE
+
+/**
+ * Called when the print timer finishes
+ */
+/obj/machinery/computer/med_data/proc/print_finish()
+ var/obj/item/paper/P = new /obj/item/paper(loc)
+ P.info = "Medical Record "
+ if(istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1))
+ P.info += {"Name: [active1.fields["name"]] ID: [active1.fields["id"]]
+ \nSex: [active1.fields["sex"]]
+ \nAge: [active1.fields["age"]]
+ \nFingerprint: [active1.fields["fingerprint"]]
+ \nPhysical Status: [active1.fields["p_stat"]]
+ \nMental Status: [active1.fields["m_stat"]] "}
+ else
+ P.info += "General Record Lost! "
+ if(istype(active2, /datum/data/record) && GLOB.data_core.medical.Find(active2))
+ P.info += {" \nMedical Data
+ \nBlood Type: [active2.fields["blood_type"]]
+ \nDNA: [active2.fields["b_dna"]] \n
+ \nMinor Disabilities: [active2.fields["mi_dis"]]
+ \nDetails: [active2.fields["mi_dis_d"]] \n
+ \nMajor Disabilities: [active2.fields["ma_dis"]]
+ \nDetails: [active2.fields["ma_dis_d"]] \n
+ \nAllergies: [active2.fields["alg"]]
+ \nDetails: [active2.fields["alg_d"]] \n
+ \nCurrent Diseases: [active2.fields["cdi"]] (per disease info placed in log/comment section)
+ \nDetails: [active2.fields["cdi_d"]] \n
+ \nImportant Notes:
+ \n\t[active2.fields["notes"]] \n
+ \n
+ Comments/Log "}
+ for(var/c in active2.fields["comments"])
+ P.info += "[c] "
+ else
+ P.info += "Medical Record Lost! "
+ P.info += ""
+ P.name = "paper - 'Medical Record: [active1.fields["name"]]'"
+ printing = FALSE
+ SStgui.update_uis(src)
+
+/**
+ * Sets a temporary message to display to the user
+ *
+ * Arguments:
+ * * text - Text to display, null/empty to clear the message from the UI
+ * * style - The style of the message: (color name), info, success, warning, danger, virus
+ */
+/obj/machinery/computer/med_data/proc/set_temp(text = "", style = "info", update_now = FALSE)
+ temp = list(text = text, style = style)
+ if(update_now)
+ SStgui.update_uis(src)
/obj/machinery/computer/med_data/emp_act(severity)
if(stat & (BROKEN|NOPOWER))
@@ -555,6 +447,10 @@
..(severity)
+/obj/machinery/computer/med_data/tgui_login_on_login(datum/tgui_login/state)
+ active1 = null
+ active2 = null
+ screen = MED_DATA_R_LIST
/obj/machinery/computer/med_data/laptop
name = "medical laptop"
@@ -564,9 +460,10 @@
icon_screen = "medlaptop"
density = 0
-#undef MED_DATA_MAIN
#undef MED_DATA_R_LIST
#undef MED_DATA_MAINT
#undef MED_DATA_RECORD
#undef MED_DATA_V_DATA
#undef MED_DATA_MEDBOT
+#undef FIELD
+#undef MED_FIELD
diff --git a/code/game/machinery/computer/power.dm b/code/game/machinery/computer/power.dm
index bb38e59c800..a0bbedf2f73 100644
--- a/code/game/machinery/computer/power.dm
+++ b/code/game/machinery/computer/power.dm
@@ -9,8 +9,17 @@
light_color = LIGHT_COLOR_ORANGE
circuit = /obj/item/circuitboard/powermonitor
var/datum/powernet/powernet = null
- var/datum/nano_module/power_monitor/power_monitor
+ var/datum/tgui_module/power_monitor/power_monitor
+ /// Will this monitor be hidden from viewers?
var/is_secret_monitor = FALSE
+ /// How many records to keep of supply and demand
+ var/record_size = 60
+ /// Interval between power snapshots
+ var/record_interval = 5 SECONDS
+ /// Time to next record power
+ var/next_record = 0
+ /// The history list itself of the power
+ var/list/history = list()
/obj/machinery/computer/monitor/secret //Hides the power monitor (such as ones on ruins & CentCom) from PDA's to prevent metagaming.
name = "outdated power monitoring console"
@@ -28,6 +37,8 @@
..()
GLOB.powermonitor_repository.update_cache()
powernet = find_powernet()
+ history["supply"] = list()
+ history["demand"] = list()
/obj/machinery/computer/monitor/Destroy()
GLOB.power_monitors -= src
@@ -56,10 +67,35 @@
return
// Update the powernet
powernet = find_powernet()
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/monitor/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- power_monitor.ui_interact(user, ui_key, ui, force_open)
+/obj/machinery/computer/monitor/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ power_monitor.tgui_interact(user, ui_key, ui, force_open)
/obj/machinery/computer/monitor/interact(mob/user)
- power_monitor.ui_interact(user)
+ power_monitor.tgui_interact(user)
+
+/obj/machinery/computer/monitor/process()
+ record()
+
+/**
+ * Power snapshot recording proc
+ *
+ * This proc handles recording powernet history for the graph on the TGUI
+ * It is called every process(), but only logs every 5 seconds
+ */
+/obj/machinery/computer/monitor/proc/record()
+ if(world.time >= next_record)
+ next_record = world.time + record_interval
+ if(!powernet)
+ return
+
+ var/list/supply = history["supply"]
+ supply += powernet.viewavail
+ if(length(supply) > record_size)
+ supply.Cut(1, 2)
+
+ var/list/demand = history["demand"]
+ demand += powernet.viewload
+ if(length(demand) > record_size)
+ demand.Cut(1, 2)
diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm
index 6134860733a..f676d861f96 100644
--- a/code/game/machinery/computer/robot.dm
+++ b/code/game/machinery/computer/robot.dm
@@ -20,221 +20,210 @@
return
if(stat & (NOPOWER|BROKEN))
return
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/robotics/proc/is_authenticated(var/mob/user as mob)
+/obj/machinery/computer/robotics/proc/is_authenticated(mob/user)
+ if(!istype(user))
+ return FALSE
if(user.can_admin_interact())
- return 1
- else if(allowed(user))
- return 1
- return 0
+ return TRUE
+ if(allowed(user))
+ return TRUE
+ return FALSE
-/obj/machinery/computer/robotics/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/**
+ * Does this borg show up in the console
+ *
+ * Returns TRUE if a robot will show up in the console
+ * Returns FALSE if a robot will not show up in the console
+ * Arguments:
+ * * R - The [mob/living/silicon/robot] to be checked
+ */
+/obj/machinery/computer/robotics/proc/console_shows(mob/living/silicon/robot/R)
+ if(!istype(R))
+ return FALSE
+ if(istype(R, /mob/living/silicon/robot/drone))
+ return FALSE
+ if(R.scrambledcodes)
+ return FALSE
+ if(!atoms_share_level(get_turf(src), get_turf(R)))
+ return FALSE
+ return TRUE
+
+/**
+ * Check if a user can send a lockdown/detonate command to a specific borg
+ *
+ * Returns TRUE if a user can send the command (does not guarantee it will work)
+ * Returns FALSE if a user cannot
+ * Arguments:
+ * * user - The [mob/user] to be checked
+ * * R - The [mob/living/silicon/robot] to be checked
+ * * telluserwhy - Bool of whether the user should be sent a to_chat message if they don't have access
+ */
+/obj/machinery/computer/robotics/proc/can_control(mob/user, mob/living/silicon/robot/R, telluserwhy = FALSE)
+ if(!istype(user))
+ return FALSE
+ if(!console_shows(R))
+ return FALSE
+ if(isAI(user))
+ if(R.connected_ai != user)
+ if(telluserwhy)
+ to_chat(user, "AIs can only control cyborgs which are linked to them.")
+ return FALSE
+ if(isrobot(user))
+ if(R != user)
+ if(telluserwhy)
+ to_chat(user, "Cyborgs cannot control other cyborgs.")
+ return FALSE
+ return TRUE
+
+/**
+ * Check if the user is the right kind of entity to be able to hack borgs
+ *
+ * Returns TRUE if a user is a traitor AI, or aghost
+ * Returns FALSE otherwise
+ * Arguments:
+ * * user - The [mob/user] to be checked
+ */
+/obj/machinery/computer/robotics/proc/can_hack_any(mob/user)
+ if(!istype(user))
+ return FALSE
+ if(user.can_admin_interact())
+ return TRUE
+ if(!isAI(user))
+ return FALSE
+ return (user.mind.special_role && user.mind.original == user)
+
+/**
+ * Check if the user is allowed to hack a specific borg
+ *
+ * Returns TRUE if a user can hack the specific cyborg
+ * Returns FALSE if a user cannot
+ * Arguments:
+ * * user - The [mob/user] to be checked
+ * * R - The [mob/living/silicon/robot] to be checked
+ */
+/obj/machinery/computer/robotics/proc/can_hack(mob/user, mob/living/silicon/robot/R)
+ if(!can_hack_any(user))
+ return FALSE
+ if(!istype(R))
+ return FALSE
+ if(R.emagged)
+ return FALSE
+ if(R.connected_ai != user)
+ return FALSE
+ return TRUE
+
+/obj/machinery/computer/robotics/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "robot_control.tmpl", "Robotic Control Console", 400, 500)
+ ui = new(user, src, ui_key, "RoboticsControlConsole", name, 500, 460, master_ui, state)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/computer/robotics/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- var/list/robots = get_cyborgs(user)
- if(robots.len)
- data["robots"] = robots
+/obj/machinery/computer/robotics/tgui_data(mob/user)
+ var/list/data = list()
+ data["auth"] = is_authenticated(user)
+ data["can_hack"] = can_hack_any(user)
+ data["cyborgs"] = list()
data["safety"] = safety
- // Also applies for cyborgs. Hides the manual self-destruct button.
- data["is_ai"] = issilicon(user)
- data["allowed"] = is_authenticated(user)
+ for(var/mob/living/silicon/robot/R in GLOB.mob_list)
+ if(!console_shows(R))
+ continue
+ var/area/A = get_area(R)
+ var/turf/T = get_turf(R)
+ var/list/cyborg_data = list(
+ name = R.name,
+ uid = R.UID(),
+ locked_down = R.lockcharge,
+ locstring = "[A.name] ([T.x], [T.y])",
+ status = R.stat,
+ health = round(R.health * 100 / R.maxHealth, 0.1),
+ charge = R.cell ? round(R.cell.percent()) : null,
+ cell_capacity = R.cell ? R.cell.maxcharge : null,
+ module = R.module ? R.module.name : "No Module Detected",
+ synchronization = R.connected_ai,
+ is_hacked = R.connected_ai && R.emagged,
+ hackable = can_hack(user, R),
+ )
+ data["cyborgs"] += list(cyborg_data)
+ data["show_detonate_all"] = (data["auth"] && length(data["cyborgs"]) > 0 && ishuman(user))
return data
-/obj/machinery/computer/robotics/Topic(href, href_list)
+/obj/machinery/computer/robotics/tgui_act(action, params)
if(..())
- return 1
-
- var/mob/user = usr
- if(!is_authenticated(user))
- to_chat(user, "Access denied.")
return
-
- // Destroys the cyborg
- if(href_list["detonate"])
- var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["detonate"])
- if(!target || !istype(target))
- return
- if(isAI(user) && (target.connected_ai != user))
- to_chat(user, "Access Denied. This robot is not linked to you.")
- return
- // Cyborgs may blow up themselves via the console
- if((isrobot(user) && user != target) || !is_authenticated(user))
- to_chat(user, "Access Denied.")
- return
- var/choice = input("Really detonate [target.name]?") in list ("Yes", "No")
- if(choice != "Yes")
- return
- if(!target || !istype(target))
- return
-
- // Antagonistic cyborgs? Left here for downstream
- if(target.mind && target.mind.special_role && target.emagged)
- to_chat(target, "Extreme danger. Termination codes detected. Scrambling security codes and automatic AI unlink triggered.")
- target.ResetSecurityCodes()
- else
- message_admins("[key_name_admin(usr)] detonated [key_name_admin(target)] (JMP)!")
- log_game("\[key_name(usr)] detonated [key_name(target)]!")
- to_chat(target, "Self-destruct command received.")
- if(target.connected_ai)
- to_chat(target.connected_ai, "
ALERT - Cyborg detonation detected: [target.name] ")
- spawn(10)
- target.self_destruct()
-
- // Locks or unlocks the cyborg
- else if(href_list["lockdown"])
- var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["lockdown"])
- if(!target || !istype(target))
- return
-
- if(isAI(user) && (target.connected_ai != user))
- to_chat(user, "Access Denied. This robot is not linked to you.")
- return
-
- if(isrobot(user))
- to_chat(user, "Access Denied.")
- return
-
- var/choice = input("Really [target.lockcharge ? "unlock" : "lockdown"] [target.name] ?") in list ("Yes", "No")
- if(choice != "Yes")
- return
-
- if(!target || !istype(target))
- return
-
- message_admins("[key_name_admin(usr)] [target.canmove ? "locked down" : "released"] [key_name_admin(target)]!")
- log_game("[key_name(usr)] [target.canmove ? "locked down" : "released"] [key_name(target)]!")
- target.SetLockdown(!target.lockcharge)
- to_chat(target, "[!target.lockcharge ? "Your lockdown has been lifted!" : "You have been locked down!"]")
- if(target.connected_ai)
- to_chat(target.connected_ai, "[!target.lockcharge ? "NOTICE - Cyborg lockdown lifted" : "ALERT - Cyborg lockdown detected"]: [target.name] ")
-
- // Remotely hacks the cyborg. Only antag AIs can do this and only to linked cyborgs.
- else if(href_list["hack"])
- var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["hack"])
- if(!target || !istype(target))
- return
-
- // Antag AI checks
- if(!istype(user, /mob/living/silicon/ai) || !(user.mind.special_role && user.mind.original == user))
- to_chat(user, "Access Denied.")
- return
-
- if(target.connected_ai != user)
- to_chat(user, "Access Denied. This robot is not linked to you.")
- return
-
- if(target.emagged)
- to_chat(user, "Robot is already hacked.")
- return
-
- var/choice = input("Really hack [target.name]? This cannot be undone.") in list("Yes", "No")
- if(choice != "Yes")
- return
-
- if(!target || !istype(target))
- return
-
- message_admins("[key_name_admin(usr)] emagged [key_name_admin(target)] using robotic console!")
- log_game("[key_name(usr)] emagged [key_name(target)] using robotic console!")
- target.emagged = 1
- to_chat(target, "Failsafe protocols overriden. New tools available.")
-
- // Arms the emergency self-destruct system
- else if(href_list["arm"])
- if(istype(user, /mob/living/silicon))
- to_chat(user, "Access Denied.")
- return
-
- safety = !safety
- to_chat(user, "You [safety ? "disarm" : "arm"] the emergency self destruct.")
-
- // Destroys all accessible cyborgs if safety is disabled
- else if(href_list["nuke"])
- if(istype(user, /mob/living/silicon))
- to_chat(user, "Access Denied")
- return
- if(safety)
- to_chat(user, "Self-destruct aborted - safety active")
- return
-
- message_admins("[key_name_admin(usr)] detonated all cyborgs!")
- log_game("\[key_name(usr)] detonated all cyborgs!")
-
- for(var/mob/living/silicon/robot/R in GLOB.mob_list)
- if(istype(R, /mob/living/silicon/robot/drone))
- continue
- // Ignore antagonistic cyborgs
- if(R.scrambledcodes)
- continue
+ . = FALSE
+ if(!is_authenticated(usr))
+ to_chat(usr, "Access denied.")
+ return
+ switch(action)
+ if("arm") // Arms the emergency self-destruct system
+ if(issilicon(usr))
+ to_chat(usr, "Access Denied (silicon detected)")
+ return
+ safety = !safety
+ to_chat(usr, "You [safety ? "disarm" : "arm"] the emergency self destruct.")
+ . = TRUE
+ if("nuke") // Destroys all accessible cyborgs if safety is disabled
+ if(issilicon(usr))
+ to_chat(usr, "Access Denied (silicon detected)")
+ return
+ if(safety)
+ to_chat(usr, "Self-destruct aborted - safety active")
+ return
+ message_admins("[key_name_admin(usr)] detonated all cyborgs!")
+ log_game("\[key_name(usr)] detonated all cyborgs!")
+ for(var/mob/living/silicon/robot/R in GLOB.mob_list)
+ if(istype(R, /mob/living/silicon/robot/drone))
+ continue
+ // Ignore antagonistic cyborgs
+ if(R.scrambledcodes)
+ continue
+ to_chat(R, "Self-destruct command received.")
+ if(R.connected_ai)
+ to_chat(R.connected_ai, "
ALERT - Cyborg detonation detected: [R.name] ")
+ R.self_destruct()
+ . = TRUE
+ if("killbot") // destroys one specific cyborg
+ var/mob/living/silicon/robot/R = locateUID(params["uid"])
+ if(!can_control(usr, R, TRUE))
+ return
+ if(R.mind && R.mind.special_role && R.emagged)
+ to_chat(R, "Extreme danger! Termination codes detected. Scrambling security codes and automatic AI unlink triggered.")
+ R.ResetSecurityCodes()
+ . = TRUE
+ return
+ var/turf/T = get_turf(R)
+ message_admins("[key_name_admin(usr)] detonated [key_name_admin(R)] ([ADMIN_COORDJMP(T)])!")
+ log_game("\[key_name(usr)] detonated [key_name(R)]!")
to_chat(R, "Self-destruct command received.")
if(R.connected_ai)
to_chat(R.connected_ai, "
ALERT - Cyborg detonation detected: [R.name] ")
- spawn(10)
- R.self_destruct()
-
-// Proc: get_cyborgs()
-// Parameters: 1 (operator - mob which is operating the console.)
-// Description: Returns NanoUI-friendly list of accessible cyborgs.
-/obj/machinery/computer/robotics/proc/get_cyborgs(var/mob/operator)
- var/list/robots = list()
-
- for(var/mob/living/silicon/robot/R in GLOB.mob_list)
- // Ignore drones
- if(istype(R, /mob/living/silicon/robot/drone))
- continue
- // Ignore antagonistic cyborgs
- if(R.scrambledcodes)
- continue
-
- var/list/robot = list()
- robot["name"] = R.name
- if(R.stat)
- robot["status"] = "Not Responding"
- else if(!R.canmove)
- robot["status"] = "Lockdown"
- else
- robot["status"] = "Operational"
-
- if(R.cell)
- robot["cell"] = 1
- robot["cell_capacity"] = R.cell.maxcharge
- robot["cell_current"] = R.cell.charge
- robot["cell_percentage"] = round(R.cell.percent())
- else
- robot["cell"] = 0
-
- var/turf/pos = get_turf(R)
- var/area/bot_area = get_area(R)
- robot["xpos"] = pos.x
- robot["ypos"] = pos.y
- robot["zpos"] = pos.z
- robot["area"] = format_text(bot_area.name)
-
- robot["health"] = round(R.health * 100 / R.maxHealth,0.1)
-
- robot["module"] = R.module ? R.module.name : "None"
- robot["master_ai"] = R.connected_ai ? R.connected_ai.name : "None"
- robot["hackable"] = 0
- // Antag AIs know whether linked cyborgs are hacked or not.
- if(operator && istype(operator, /mob/living/silicon/ai) && (R.connected_ai == operator) && (operator.mind.special_role && operator.mind.original == operator))
- robot["hacked"] = R.emagged ? 1 : 0
- robot["hackable"] = R.emagged? 0 : 1
- robots.Add(list(robot))
- return robots
-
-// Proc: get_cyborg_by_name()
-// Parameters: 1 (name - Cyborg we are trying to find)
-// Description: Helper proc for finding cyborg by name
-/obj/machinery/computer/robotics/proc/get_cyborg_by_name(var/name)
- if(!name)
- return
- for(var/mob/living/silicon/robot/R in GLOB.mob_list)
- if(R.name == name)
- return R
+ R.self_destruct()
+ . = TRUE
+ if("stopbot") // lock or unlock the borg
+ if(isrobot(usr))
+ to_chat(usr, "Access Denied.")
+ return
+ var/mob/living/silicon/robot/R = locateUID(params["uid"])
+ if(!can_control(usr, R, TRUE))
+ return
+ message_admins("[ADMIN_LOOKUPFLW(usr)] [!R.lockcharge ? "locked down" : "released"] [ADMIN_LOOKUPFLW(R)]!")
+ log_game("[key_name(usr)] [!R.lockcharge ? "locked down" : "released"] [key_name(R)]!")
+ R.SetLockdown(!R.lockcharge)
+ to_chat(R, "[!R.lockcharge ? "Your lockdown has been lifted!" : "You have been locked down!"]")
+ if(R.connected_ai)
+ to_chat(R.connected_ai, "[!R.lockcharge ? "NOTICE - Cyborg lockdown lifted" : "ALERT - Cyborg lockdown detected"]: [R.name] ")
+ . = TRUE
+ if("hackbot") // AIs hacking/emagging a borg
+ var/mob/living/silicon/robot/R = locateUID(params["uid"])
+ if(!can_hack(usr, R))
+ return
+ var/choice = input("Really hack [R.name]? This cannot be undone.") in list("Yes", "No")
+ if(choice != "Yes")
+ return
+ log_game("[key_name(usr)] emagged [key_name(R)] using robotic console!")
+ message_admins("[key_name_admin(usr)] emagged [key_name_admin(R)] using robotic console!")
+ R.emagged = TRUE
+ to_chat(R, "Failsafe protocols overriden. New tools available.")
+ . = TRUE
diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm
index 8c815198d31..d091b07d4cb 100644
--- a/code/game/machinery/computer/security.dm
+++ b/code/game/machinery/computer/security.dm
@@ -2,43 +2,67 @@
#define SEC_DATA_MAINT 2 // Records maintenance
#define SEC_DATA_RECORD 3 // Record
-/obj/machinery/computer/secure_data//TODO:SANITY
+#define SEC_FIELD(N, V, E, LB) list(field = N, value = V, edit = E, line_break = LB)
+
+/obj/machinery/computer/secure_data
name = "security records"
desc = "Used to view and edit personnel's security records."
icon_keyboard = "security_key"
icon_screen = "security"
- req_one_access = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS)
circuit = /obj/item/circuitboard/secure_data
- var/obj/item/card/id/scan = null
- var/authenticated = null
- var/rank = null
- var/list/authcard_access = list()
- var/screen = null
- var/datum/data/record/active1 = null
- var/datum/data/record/active2 = null
- var/temp = null
- var/printing = null
- //Sorting Variables
- var/sortBy = "name"
- var/order = 1 // -1 = Descending - 1 = Ascending
+ /// The current page being viewed.
+ var/current_page = SEC_DATA_R_LIST
+ /// The current general record being viewed.
+ var/datum/data/record/record_general = null
+ /// The current security record being viewed.
+ var/datum/data/record/record_security = null
+ /// Whether the computer is currently printing a paper or not.
+ var/is_printing = FALSE
+ /// The editable fields and their associated question to display to the user.
+ var/static/list/field_edit_questions
+ /// The editable fields and their associated choices to display to the user.
+ var/static/list/field_edit_choices
+ /// The current temporary notice.
+ var/temp_notice
light_color = LIGHT_COLOR_RED
+/obj/machinery/computer/secure_data/Initialize(mapload)
+ . = ..()
+ req_one_access = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS)
+ if(!field_edit_questions)
+ field_edit_questions = list(
+ // General
+ "name" = "Please input new name:",
+ "id" = "Please input new ID:",
+ "sex" = "Please select new sex:",
+ "age" = "Please input new age:",
+ "fingerprint" = "Please input new fingerprint hash:",
+ // Security
+ "criminal" = "Please select new criminal status:",
+ "mi_crim" = "Please input new minor crimes:",
+ "mi_crim_d" = "Please summarize minor crimes:",
+ "ma_crim" = "Please input new major crimes:",
+ "ma_crim_d" = "Please summarize major crimes:",
+ "notes" = "Please input new important notes:",
+ )
+ field_edit_choices = list(
+ // General
+ "sex" = list("Male", "Female"),
+ // Security
+ "criminal" = list(SEC_RECORD_STATUS_NONE, SEC_RECORD_STATUS_ARREST, SEC_RECORD_STATUS_EXECUTE, SEC_RECORD_STATUS_INCARCERATED, SEC_RECORD_STATUS_RELEASED, SEC_RECORD_STATUS_PAROLLED, SEC_RECORD_STATUS_DEMOTE, SEC_RECORD_STATUS_SEARCH, SEC_RECORD_STATUS_MONITOR),
+ )
+
/obj/machinery/computer/secure_data/Destroy()
- active1 = null
- active2 = null
+ record_general = null
+ record_security = null
return ..()
/obj/machinery/computer/secure_data/attackby(obj/item/O, mob/user, params)
- if(istype(O, /obj/item/card/id) && !scan)
- user.drop_item()
- O.forceMove(src)
- scan = O
- ui_interact(user)
+ if(tgui_login_attackby(O, user))
return
return ..()
-//Someone needs to break down the dat += into chunks instead of long ass lines.
/obj/machinery/computer/secure_data/attack_hand(mob/user)
if(..())
return
@@ -46,267 +70,124 @@
to_chat(user, "Unable to establish a connection: You're too far away from the station!")
return
add_fingerprint(user)
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/secure_data/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/secure_data/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "secure_data.tmpl", name, 800, 800)
+ ui = new(user, src, ui_key, "SecurityRecords", name, 800, 800)
ui.open()
+ ui.set_autoupdate(FALSE)
-/obj/machinery/computer/secure_data/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- data["temp"] = temp
- data["scan"] = scan ? scan.name : null
- data["authenticated"] = authenticated
- data["screen"] = screen
- if(authenticated)
- switch(screen)
+/obj/machinery/computer/secure_data/tgui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
+ var/list/data = list()
+ data["currentPage"] = current_page
+ data["isPrinting"] = is_printing
+ tgui_login_data(data, user)
+ data["modal"] = tgui_modal_data()
+ data["temp"] = temp_notice
+ if(data["loginState"]["logged_in"])
+ switch(current_page)
if(SEC_DATA_R_LIST)
- if(!isnull(GLOB.data_core.general))
- for(var/datum/data/record/R in sortRecord(GLOB.data_core.general, sortBy, order))
- var/crimstat = "null"
- for(var/datum/data/record/E in GLOB.data_core.security)
- if(E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"])
- crimstat = E.fields["criminal"]
- break
- var/background = "''"
- switch(crimstat)
- if(SEC_RECORD_STATUS_EXECUTE)
- background = "'background-color:#5E0A1A'"
- if(SEC_RECORD_STATUS_ARREST)
- background = "'background-color:#890E26'"
- if(SEC_RECORD_STATUS_SEARCH)
- background = "'background-color:#999900'"
- if(SEC_RECORD_STATUS_MONITOR)
- background = "'background-color:#004C99'"
- if(SEC_RECORD_STATUS_DEMOTE)
- background = "'background-color:#C2A111'"
- if(SEC_RECORD_STATUS_INCARCERATED)
- background = "'background-color:#743B03'"
- if(SEC_RECORD_STATUS_PAROLLED)
- background = "'background-color:#743B03'"
- if(SEC_RECORD_STATUS_RELEASED)
- background = "'background-color:#216489'"
- if(SEC_RECORD_STATUS_NONE)
- background = "'background-color:#007f47'"
- if("null")
- crimstat = "No record."
- data["records"] += list(list("ref" = "\ref[R]", "id" = R.fields["id"], "name" = R.fields["name"], "rank" = R.fields["rank"], "fingerprint" = R.fields["fingerprint"], "background" = background, "crimstat" = crimstat))
+ // Prepare the list of security records to associate with the general ones.
+ // This is not ideal but datacore code sucks and needs to be rewritten.
+ var/list/sec_records_assoc = list()
+ for(var/datum/data/record/S in GLOB.data_core.security)
+ sec_records_assoc["[S.fields["name"]]|[S.fields["id"]]"] = S
+ // List the general records
+ var/list/records = list()
+ data["records"] = records
+ for(var/datum/data/record/G in GLOB.data_core.general)
+ var/datum/data/record/S = sec_records_assoc["[G.fields["name"]]|[G.fields["id"]]"]
+ var/list/record_line = list("uid_gen" = G.UID(), "id" = G.fields["id"], "name" = G.fields["name"], "rank" = G.fields["rank"], "fingerprint" = G.fields["fingerprint"])
+ record_line["status"] = S?.fields["criminal"] || "No record"
+ record_line["uid_sec"] = S?.UID() // So we don't have to perform the search through a for loop again later
+ records[++records.len] = record_line
if(SEC_DATA_RECORD)
var/list/general = list()
data["general"] = general
- if(istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1))
- var/list/fields = list()
- general["fields"] = fields
- fields[++fields.len] = list("field" = "Name:", "value" = active1.fields["name"], "edit" = "name")
- fields[++fields.len] = list("field" = "ID:", "value" = active1.fields["id"], "edit" = "id")
- fields[++fields.len] = list("field" = "Sex:", "value" = active1.fields["sex"], "edit" = "sex")
- fields[++fields.len] = list("field" = "Age:", "value" = active1.fields["age"], "edit" = "age")
- fields[++fields.len] = list("field" = "Rank:", "value" = active1.fields["rank"], "edit" = "rank")
- fields[++fields.len] = list("field" = "Fingerprint:", "value" = active1.fields["fingerprint"], "edit" = "fingerprint")
- fields[++fields.len] = list("field" = "Physical Status:", "value" = active1.fields["p_stat"], "edit" = null)
- fields[++fields.len] = list("field" = "Mental Status:", "value" = active1.fields["m_stat"], "edit" = null)
- var/list/photos = list()
- general["photos"] = photos
- photos[++photos.len] = list("photo" = active1.fields["photo-south"])
- photos[++photos.len] = list("photo" = active1.fields["photo-west"])
- general["has_photos"] += (active1.fields["photo-south"] || active1.fields["photo-west"] ? 1 : 0)
- general["empty"] = 0
+ if(record_general && GLOB.data_core.general.Find(record_general))
+ var/list/gen_fields = record_general.fields
+ general["fields"] = list(
+ SEC_FIELD("Name", gen_fields["name"], "name", FALSE),
+ SEC_FIELD("ID", gen_fields["id"], "id", TRUE),
+ SEC_FIELD("Sex", gen_fields["sex"], "sex", FALSE),
+ SEC_FIELD("Age", gen_fields["age"], "age", TRUE),
+ SEC_FIELD("Assignment", gen_fields["rank"], null, FALSE),
+ SEC_FIELD("Fingerprint", gen_fields["fingerprint"], "fingerprint", TRUE),
+ SEC_FIELD("Physical Status", gen_fields["p_stat"], null, FALSE),
+ SEC_FIELD("Mental Status", gen_fields["m_stat"], null, TRUE),
+ SEC_FIELD("Important Notes", gen_fields["notes"], null, FALSE),
+ )
+ general["photos"] = list(
+ gen_fields["photo-south"],
+ gen_fields["photo-west"],
+ )
+ general["has_photos"] = (gen_fields["photo-south"] || gen_fields["photo-west"]) ? TRUE : FALSE
+ general["empty"] = FALSE
else
- general["empty"] = 1
+ general["empty"] = TRUE
var/list/security = list()
data["security"] = security
- if(istype(active2, /datum/data/record) && GLOB.data_core.security.Find(active2))
- var/list/fields = list()
- security["fields"] = fields
- fields[++fields.len] = list("field" = "Criminal Status:", "value" = active2.fields["criminal"], "edit" = "criminal", "line_break" = 1)
- fields[++fields.len] = list("field" = "Minor Crimes:", "value" = active2.fields["mi_crim"], "edit" = "mi_crim", "line_break" = 0)
- fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["mi_crim_d"], "edit" = "mi_crim_d", "line_break" = 1)
- fields[++fields.len] = list("field" = "Major Crimes:", "value" = active2.fields["ma_crim"], "edit" = "ma_crim", "line_break" = 0)
- fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["ma_crim_d"], "edit" = "ma_crim_d", "line_break" = 1)
- fields[++fields.len] = list("field" = "Important Notes:", "value" = active2.fields["notes"], "edit" = "notes", "line_break" = 0)
- if(!active2.fields["comments"] || !islist(active2.fields["comments"]))
- active2.fields["comments"] = list()
- security["comments"] = active2.fields["comments"]
- security["empty"] = 0
+ if(record_security && GLOB.data_core.security.Find(record_security))
+ var/list/sec_fields = record_security.fields
+ security["fields"] = list(
+ SEC_FIELD("Criminal Status", sec_fields["criminal"], "criminal", TRUE),
+ SEC_FIELD("Minor Crimes", sec_fields["mi_crim"], "mi_crim", FALSE),
+ SEC_FIELD("Details", sec_fields["mi_crim_d"], "mi_crim_d", TRUE),
+ SEC_FIELD("Major Crimes", sec_fields["ma_crim"], "ma_crim", FALSE),
+ SEC_FIELD("Details", sec_fields["ma_crim_d"], "ma_crim_d", TRUE),
+ SEC_FIELD("Important Notes", sec_fields["notes"], null, FALSE),
+ )
+ if(!islist(sec_fields["comments"]))
+ sec_fields["comments"] = list()
+ security["comments"] = sec_fields["comments"]
+ security["empty"] = FALSE
else
- security["empty"] = 1
+ security["empty"] = TRUE
+
return data
-/obj/machinery/computer/secure_data/Topic(href, href_list)
+/obj/machinery/computer/secure_data/tgui_act(action, list/params)
if(..())
- return 1
+ return
- if(!GLOB.data_core.general.Find(active1))
- active1 = null
- if(!GLOB.data_core.security.Find(active2))
- active2 = null
+ . = TRUE
+ if(tgui_act_modal(action, params))
+ return
+ if(tgui_login_act(action, params))
+ return
- if(href_list["temp"])
- temp = null
-
- if(href_list["temp_action"])
- var/temp_href = splittext(href_list["temp_action"], "=")
- switch(temp_href[1])
- if("del_all2")
- for(var/datum/data/record/R in GLOB.data_core.security)
- qdel(R)
- update_all_mob_security_hud()
- setTemp("All records deleted.")
- if("del_alllogs2")
- if(GLOB.cell_logs.len)
- setTemp("All cell logs deleted.")
- GLOB.cell_logs.Cut()
- else
- to_chat(usr, "Error; No cell logs to delete.")
- if("del_r2")
- if(active2)
- qdel(active2)
- update_all_mob_security_hud()
- if("del_rg2")
- if(active1)
- for(var/datum/data/record/R in GLOB.data_core.medical)
- if(R.fields["name"] == active1.fields["name"] && R.fields["id"] == active1.fields["id"])
- qdel(R)
- QDEL_NULL(active1)
- QDEL_NULL(active2)
- update_all_mob_security_hud()
- screen = SEC_DATA_R_LIST
- if("criminal")
- if(active2)
- var/t1
- if(temp_href[2] == "execute")
- t1 = copytext(trim(sanitize(input("Explain why they are being executed. Include a list of their crimes, and victims.", "EXECUTION ORDER", null, null) as text)), 1, MAX_MESSAGE_LEN)
- else
- t1 = copytext(trim(sanitize(input("Enter Reason:", "Secure. records", null, null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1)
- t1 = "(none)"
- if(!set_criminal_status(usr, active2, temp_href[2], t1, rank, authcard_access))
- setTemp("Error: permission denied.")
- return 1
- if("rank")
- if(active1)
- active1.fields["rank"] = temp_href[2]
- if(temp_href[2] in GLOB.joblist)
- active1.fields["real_rank"] = temp_href[2]
-
- if(href_list["scan"])
- if(scan)
- scan.forceMove(loc)
- if(ishuman(usr) && !usr.get_active_hand())
- usr.put_in_hands(scan)
- scan = null
- else
- var/obj/item/I = usr.get_active_hand()
- if(istype(I, /obj/item/card/id))
- usr.drop_item()
- I.forceMove(src)
- scan = I
-
- if(href_list["login"])
- if(isAI(usr))
- authenticated = usr.name
- rank = "AI"
- else if(isrobot(usr))
- authenticated = usr.name
- var/mob/living/silicon/robot/R = usr
- rank = "[R.modtype] [R.braintype]"
- else if(istype(scan, /obj/item/card/id))
- if(check_access(scan))
- authenticated = scan.registered_name
- rank = scan.assignment
- authcard_access = scan.access
-
- if(authenticated)
- active1 = null
- active2 = null
- screen = SEC_DATA_R_LIST
-
- if(authenticated)
- if(href_list["logout"])
- authenticated = null
- screen = null
- active1 = null
- active2 = null
- authcard_access = list()
-
- else if(href_list["sort"])
- // Reverse the order if clicked twice
- if(sortBy == href_list["sort"])
- if(order == 1)
- order = -1
- else
- order = 1
- else
- sortBy = href_list["sort"]
- order = initial(order)
-
- else if(href_list["screen"])
- screen = text2num(href_list["screen"])
- if(screen < 1)
- screen = SEC_DATA_R_LIST
-
- active1 = null
- active2 = null
-
- else if(href_list["d_rec"])
- var/datum/data/record/R = locate(href_list["d_rec"])
- var/datum/data/record/M = locate(href_list["d_rec"])
- if(!GLOB.data_core.general.Find(R))
- setTemp("Record not found!")
- return 1
- for(var/datum/data/record/E in GLOB.data_core.security)
- if(E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"])
- M = E
- active1 = R
- active2 = M
- screen = SEC_DATA_RECORD
-
- else if(href_list["del_all"])
- var/list/buttons = list()
- buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_all2=1", "status" = null)
- buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null)
- setTemp("Are you sure you wish to delete all records?", buttons)
-
- else if(href_list["del_alllogs"])
- var/list/buttons = list()
- buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_alllogs2=1", "status" = null)
- buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null)
- setTemp("Are you sure you wish to delete all cell logs?", buttons)
-
- else if(href_list["del_rg"])
- if(active1)
- var/list/buttons = list()
- buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_rg2=1", "status" = null)
- buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null)
- setTemp("Are you sure you wish to delete the record (ALL)?", buttons)
-
- else if(href_list["del_r"])
- if(active1)
- var/list/buttons = list()
- buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_r2=1", "status" = null)
- buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null)
- setTemp("Are you sure you wish to delete the record (Security Portion Only)?", buttons)
-
- else if(href_list["new_s"])
- if(istype(active1, /datum/data/record) && !istype(active2, /datum/data/record))
- var/datum/data/record/R = new /datum/data/record()
- R.fields["name"] = active1.fields["name"]
- R.fields["id"] = active1.fields["id"]
- R.name = "Security Record #[R.fields["id"]]"
- R.fields["criminal"] = "None"
- R.fields["mi_crim"] = "None"
- R.fields["mi_crim_d"] = "No minor crime convictions."
- R.fields["ma_crim"] = "None"
- R.fields["ma_crim_d"] = "No major crime convictions."
- R.fields["notes"] = "No notes."
- GLOB.data_core.security += R
- active2 = R
- screen = SEC_DATA_RECORD
-
- else if(href_list["new_g"])
+ var/logged_in = tgui_login_get().logged_in
+ switch(action)
+ if("cleartemp")
+ temp_notice = null
+ if("page") // Select Page
+ if(!logged_in)
+ return
+ var/page_num = clamp(text2num(params["page"]), SEC_DATA_R_LIST, SEC_DATA_MAINT) // SEC_DATA_RECORD cannot be accessed through this act
+ current_page = page_num
+ record_general = null
+ record_security = null
+ if("view") // View Record
+ if(!logged_in)
+ return
+ var/datum/data/record/G = locateUID(params["uid_gen"])
+ var/datum/data/record/S = locateUID(params["uid_sec"])
+ if(!istype(G)) // No general record!
+ set_temp("General record not found!", "danger")
+ return
+ if(istype(S) && !(G.fields["name"] == S.fields["name"] && G.fields["id"] == S.fields["id"])) // General and security records don't match!
+ S = null
+ record_general = G
+ record_security = S
+ current_page = SEC_DATA_RECORD
+ if("new_general") // New General Record
+ if(!logged_in)
+ return
+ if(record_general)
+ return
var/datum/data/record/G = new /datum/data/record()
G.fields["name"] = "New Record"
G.fields["id"] = "[add_zero(num2hex(rand(1, 1.6777215E7)), 6)]"
@@ -318,229 +199,251 @@
G.fields["p_stat"] = "Active"
G.fields["m_stat"] = "Stable"
G.fields["species"] = "Human"
+ G.fields["notes"] = "No notes."
GLOB.data_core.general += G
- active1 = G
- active2 = null
+ record_general = G
+ record_security = null
+ current_page = SEC_DATA_RECORD
+ if("new_security") // New Security Record
+ if(!logged_in)
+ return
+ if(!record_general || record_security)
+ return
+ var/datum/data/record/S = new /datum/data/record()
+ S.fields["name"] = record_general.fields["name"]
+ S.fields["id"] = record_general.fields["id"]
+ S.name = "Security Record #[S.fields["id"]]"
+ S.fields["criminal"] = SEC_RECORD_STATUS_NONE
+ S.fields["mi_crim"] = "None"
+ S.fields["mi_crim_d"] = "No minor crime convictions."
+ S.fields["ma_crim"] = "None"
+ S.fields["ma_crim_d"] = "No major crime convictions."
+ S.fields["notes"] = "No notes."
+ GLOB.data_core.security += S
+ record_security = S
+ update_all_mob_security_hud()
+ if("delete_general") // Delete General, Security and Medical Records
+ if(!logged_in)
+ return
+ if(!record_general)
+ return
+ message_admins("[key_name_admin(usr)] has deleted [record_general.fields["name"]]'s general, security and medical records at [ADMIN_COORDJMP(usr)]")
+ usr.create_log(MISC_LOG, "deleted [record_general.fields["name"]]'s general, security and medical records")
+ for(var/datum/data/record/M in GLOB.data_core.medical)
+ if(M.fields["name"] == record_general.fields["name"] && M.fields["id"] == record_general.fields["id"])
+ qdel(M)
+ QDEL_NULL(record_general)
+ QDEL_NULL(record_security)
+ update_all_mob_security_hud()
+ current_page = SEC_DATA_R_LIST
+ set_temp("General, Security and Medical records deleted.")
+ if("delete_security") // Delete Security Record
+ if(!logged_in)
+ return
+ if(!record_security)
+ return
+ message_admins("[key_name_admin(usr)] has deleted [record_security.fields["name"]]'s security record at [ADMIN_COORDJMP(usr)]")
+ usr.create_log(MISC_LOG, "deleted [record_security.fields["name"]]'s security record")
+ QDEL_NULL(record_security)
+ update_all_mob_security_hud()
+ set_temp("Security record deleted.")
+ if("delete_security_all") // Delete All Security Records
+ if(!logged_in)
+ return
+ for(var/datum/data/record/S in GLOB.data_core.security)
+ qdel(S)
+ message_admins("[key_name_admin(usr)] has deleted all security records at [ADMIN_COORDJMP(usr)]")
+ usr.create_log(MISC_LOG, "deleted all security records")
+ update_all_mob_security_hud()
+ set_temp("All security records deleted.")
+ if("delete_cell_logs") // Delete All Cell Logs
+ if(!logged_in)
+ return
+ if(!length(GLOB.cell_logs))
+ set_temp("There are no cell logs to delete.")
+ return
+ message_admins("[key_name_admin(usr)] has deleted all cell logs at [ADMIN_COORDJMP(usr)]")
+ usr.create_log(MISC_LOG, "deleted all cell logs")
+ GLOB.cell_logs.Cut()
+ set_temp("All cell logs deleted.")
+ if("comment_delete") // Delete Comment
+ if(!logged_in)
+ return
+ var/index = text2num(params["id"])
+ if(!index || !record_security)
+ return
- else if(href_list["print_r"])
- if(!printing)
- printing = 1
- playsound(loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1)
- sleep(50)
- var/obj/item/paper/P = new /obj/item/paper(loc)
- P.info = "Security Record "
- if(istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1))
- P.info += {"Name: [active1.fields["name"]] ID: [active1.fields["id"]]
- \nSex: [active1.fields["sex"]]
- \nAge: [active1.fields["age"]]
- \nFingerprint: [active1.fields["fingerprint"]]
- \nPhysical Status: [active1.fields["p_stat"]]
- \nMental Status: [active1.fields["m_stat"]] "}
- else
- P.info += "General Record Lost! "
- if(istype(active2, /datum/data/record) && GLOB.data_core.security.Find(active2))
- P.info += {" \nSecurity Data
- \nCriminal Status: [active2.fields["criminal"]] \n
- \nMinor Crimes: [active2.fields["mi_crim"]]
- \nDetails: [active2.fields["mi_crim_d"]] \n
- \nMajor Crimes: [active2.fields["ma_crim"]]
- \nDetails: [active2.fields["ma_crim_d"]] \n
- \nImportant Notes:
- \n\t[active2.fields["notes"]] \n \nComments/Log "}
- for(var/c in active2.fields["comments"])
- P.info += "[c] "
- else
- P.info += "Security Record Lost! "
- P.info += ""
- P.name = "paper - 'Security Record: [active1.fields["name"]]'"
- printing = 0
+ var/list/comments = record_security.fields["comments"]
+ if(!length(comments))
+ return
+ index = clamp(index, 1, length(comments))
+ comments.Cut(index, index + 1)
+ if("print_record")
+ if(!logged_in)
+ return
+ if(is_printing)
+ return
+ is_printing = TRUE
+ playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, TRUE)
+ addtimer(CALLBACK(src, .proc/print_record_finish), 5 SECONDS)
+ else
+ return FALSE
-/* Removed due to BYOND issue
- else if(href_list["print_p"])
- if(!printing)
- printing = 1
- playsound(loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1)
- sleep(50)
- if(istype(active1, /datum/data/record) && data_core.general.Find(active1))
- create_record_photo(active1)
- printing = 0
-*/
+ add_fingerprint(usr)
- else if(href_list["printlogs"])
- if(GLOB.cell_logs.len && !printing)
- var/obj/item/paper/P = input(usr, "Select log to print", "Available Cell Logs") as null|anything in GLOB.cell_logs
- if(!P)
- return 0
- printing = 1
- playsound(loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1)
- to_chat(usr, "Printing file [P.name].")
- sleep(50)
- var/obj/item/paper/log = new /obj/item/paper(loc)
- log.name = P.name
- log.info = P.info
- printing = 0
- return 1
- else
- to_chat(usr, "[src] has no logs stored or is already printing.")
-
-
- else if(href_list["add_c"])
- if(istype(active2, /datum/data/record))
- var/a2 = active2
- var/t1 = copytext(trim(sanitize(input("Add Comment:", "Secure. records", null, null) as message)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["comments"] += "Made by [authenticated] ([rank]) on [GLOB.current_date_string] [station_time_timestamp()] [t1]"
-
- else if(href_list["del_c"])
- var/index = min(max(text2num(href_list["del_c"]) + 1, 1), length(active2.fields["comments"]))
- if(istype(active2, /datum/data/record) && active2.fields["comments"][index])
- active2.fields["comments"] -= active2.fields["comments"][index]
-
- if(href_list["field"])
- if(..())
- return 1
- var/a1 = active1
- var/a2 = active2
- switch(href_list["field"])
- if("name")
- if(istype(active1, /datum/data/record))
- var/t1 = reject_bad_name(clean_input("Please input name:", "Secure. records", active1.fields["name"], null))
- if(!t1 || !length(trim(t1)) || ..() || active1 != a1)
- return 1
- active1.fields["name"] = t1
- if(istype(active2, /datum/data/record))
- active2.fields["name"] = t1
- if("id")
- if(istype(active1, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please input id:", "Secure. records", active1.fields["id"], null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active1 != a1)
- return 1
- active1.fields["id"] = t1
- if(istype(active2, /datum/data/record))
- active2.fields["id"] = t1
- if("fingerprint")
- if(istype(active1, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please input fingerprint hash:", "Secure. records", active1.fields["fingerprint"], null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active1 != a1)
- return 1
- active1.fields["fingerprint"] = t1
- if("sex")
- if(istype(active1, /datum/data/record))
- if(active1.fields["sex"] == "Male")
- active1.fields["sex"] = "Female"
- else
- active1.fields["sex"] = "Male"
- if("age")
- if(istype(active1, /datum/data/record))
- var/t1 = input("Please input age:", "Secure. records", active1.fields["age"], null) as num
- if(!t1 || ..() || active1 != a1)
- return 1
- active1.fields["age"] = t1
- if("mi_crim")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please input minor crimes list:", "Secure. records", active2.fields["mi_crim"], null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["mi_crim"] = t1
- if("mi_crim_d")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please summarize minor crimes:", "Secure. records", active2.fields["mi_crim_d"], null) as message)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["mi_crim_d"] = t1
- if("ma_crim")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please input major crimes list:", "Secure. records", active2.fields["ma_crim"], null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["ma_crim"] = t1
- if("ma_crim_d")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please summarize major crimes:", "Secure. records", active2.fields["ma_crim_d"], null) as message)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["ma_crim_d"] = t1
- if("notes")
- if(istype(active2, /datum/data/record))
- var/t1 = copytext(html_encode(trim(input("Please summarize notes:", "Secure. records", html_decode(active2.fields["notes"]), null) as message)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active2 != a2)
- return 1
- active2.fields["notes"] = t1
- if("criminal")
- if(istype(active2, /datum/data/record))
- var/list/buttons = list()
- buttons[++buttons.len] = list("name" = "None", "icon" = "unlock", "href" = "criminal=none", "status" = (active2.fields["criminal"] == SEC_RECORD_STATUS_NONE ? "selected" : null))
- buttons[++buttons.len] = list("name" = "*Arrest*", "icon" = "lock", "href" = "criminal=arrest", "status" = (active2.fields["criminal"] == SEC_RECORD_STATUS_ARREST ? "selected" : null))
- buttons[++buttons.len] = list("name" = "Search", "icon" = "lock", "href" = "criminal=search", "status" = (active2.fields["criminal"] == SEC_RECORD_STATUS_SEARCH ? "selected" : null))
- buttons[++buttons.len] = list("name" = "Monitor", "icon" = "unlock", "href" = "criminal=monitor", "status" = (active2.fields["criminal"] == SEC_RECORD_STATUS_MONITOR ? "selected" : null))
- buttons[++buttons.len] = list("name" = "Demote", "icon" = "lock", "href" = "criminal=demote", "status" = (active2.fields["criminal"] == SEC_RECORD_STATUS_DEMOTE ? "selected" : null))
- buttons[++buttons.len] = list("name" = "Incarcerated", "icon" = "lock", "href" = "criminal=incarcerated", "status" = (active2.fields["criminal"] == SEC_RECORD_STATUS_INCARCERATED ? "selected" : null))
- buttons[++buttons.len] = list("name" = "*Execute*", "icon" = "lock", "href" = "criminal=execute", "status" = (active2.fields["criminal"] == SEC_RECORD_STATUS_EXECUTE ? "selected" : null))
- buttons[++buttons.len] = list("name" = "Parolled", "icon" = "unlock-alt", "href" = "criminal=parolled", "status" = (active2.fields["criminal"] == SEC_RECORD_STATUS_PAROLLED ? "selected" : null))
- buttons[++buttons.len] = list("name" = "Released", "icon" = "unlock", "href" = "criminal=released", "status" = (active2.fields["criminal"] == SEC_RECORD_STATUS_RELEASED ? "selected" : null))
- setTemp("Criminal Status", buttons)
- if("rank")
- var/list/L = list("Head of Personnel", "Captain", "AI")
- //This was so silly before the change. Now it actually works without beating your head against the keyboard. /N
- if(istype(active1, /datum/data/record) && L.Find(rank))
- var/list/buttons = list()
- for(var/rank in GLOB.joblist)
- buttons[++buttons.len] = list("name" = rank, "icon" = null, "href" = "rank=[rank]", "status" = (active1.fields["rank"] == rank ? "selected" : null))
- setTemp("Rank", buttons)
+/**
+ * Called in tgui_act() to process modal actions
+ *
+ * Arguments:
+ * * action - The action passed by tgui
+ * * params - The params passed by tgui
+ */
+/obj/machinery/computer/secure_data/proc/tgui_act_modal(action, list/params)
+ if(!tgui_login_get().logged_in)
+ return
+ . = TRUE
+ var/id = params["id"]
+ var/list/arguments = istext(params["arguments"]) ? json_decode(params["arguments"]) : params["arguments"]
+ switch(tgui_modal_act(src, action, params))
+ if(TGUI_MODAL_OPEN)
+ switch(id)
+ if("edit")
+ var/field = arguments["field"]
+ if(!length(field) || !field_edit_questions[field])
+ return
+ var/question = field_edit_questions[field]
+ var/choices = field_edit_choices[field]
+ if(length(choices))
+ tgui_modal_choice(src, id, question, arguments = arguments, value = arguments["value"], choices = choices)
else
- setTemp("You do not have the required rank to do this!")
- if("species")
- if(istype(active1, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please enter race:", "General records", active1.fields["species"], null) as message)), 1, MAX_MESSAGE_LEN)
- if(!t1 || ..() || active1 != a1)
- return 1
- active1.fields["species"] = t1
- return 1
+ tgui_modal_input(src, id, question, arguments = arguments, value = arguments["value"])
+ if("comment_add")
+ tgui_modal_input(src, id, "Please enter your message:")
+ if("print_cell_log")
+ if(is_printing)
+ return
+ if(!length(GLOB.cell_logs))
+ set_temp("There are no cell logs available to print.")
+ return
+ var/list/choices = list()
+ var/list/already_in = list()
+ for(var/p in GLOB.cell_logs)
+ var/obj/item/paper/P = p
+ if(already_in[P.name])
+ continue
+ choices += P.name
+ already_in[P.name] = TRUE
+ tgui_modal_choice(src, id, "Please select the cell log you would like printed:", choices = choices)
+ else
+ return FALSE
+ if(TGUI_MODAL_ANSWER)
+ var/answer = params["answer"]
+ switch(id)
+ if("edit")
+ var/field = arguments["field"]
+ if(!length(field) || !field_edit_questions[field])
+ return
+ var/list/choices = field_edit_choices[field]
+ if(length(choices) && !(answer in choices))
+ return
-/obj/machinery/computer/secure_data/proc/setTemp(text, list/buttons = list())
- temp = list("text" = text, "buttons" = buttons, "has_buttons" = buttons.len > 0)
+ if(field == "age")
+ var/new_age = text2num(answer)
+ if(new_age < AGE_MIN || new_age > AGE_MAX)
+ set_temp("Invalid age. It must be between [AGE_MIN] and [AGE_MAX].", "danger")
+ return
+ answer = new_age
+ else if(field == "criminal")
+ var/text = "Please enter a reason for the status change to [answer]:"
+ if(answer == SEC_RECORD_STATUS_EXECUTE)
+ text = "Please explain why they are being executed. Include a list of their crimes, and victims."
+ else if(answer == SEC_RECORD_STATUS_DEMOTE)
+ text = "Please explain why they are being demoted. Include a list of their offenses."
+ tgui_modal_input(src, "criminal_reason", text, arguments = list("status" = answer))
+ return
-/* Proc disabled due to BYOND Issue
+ if(record_security && (field in record_security.fields))
+ record_security.fields[field] = answer
+ else if(record_general && (field in record_general.fields))
+ record_general.fields[field] = answer
+ if("criminal_reason")
+ var/status = arguments["status"]
+ if(!record_security || !(status in field_edit_choices["criminal"]))
+ return
+ if((status in list(SEC_RECORD_STATUS_EXECUTE, SEC_RECORD_STATUS_DEMOTE)) && !length(answer))
+ set_temp("A valid reason must be provided for this status.", "danger")
+ return
+ var/datum/tgui_login/state = tgui_login_get()
+ if(!set_criminal_status(usr, record_security, status, answer, state.rank, state.access, state.name))
+ set_temp("Required permissions to set this criminal status not found!", "danger")
+ if("comment_add")
+ var/datum/tgui_login/state = tgui_login_get()
+ if(!length(answer) || !record_security || !length(state.name))
+ return
+ record_security.fields["comments"] += list(list(
+ header = "Made by [state.name] ([state.rank]) on [GLOB.current_date_string] [station_time_timestamp()]",
+ text = answer
+ ))
+ if("print_cell_log")
+ if(is_printing)
+ return
+ var/obj/item/paper/T
+ for(var/obj/item/paper/P in GLOB.cell_logs)
+ if(P.name == answer)
+ T = P
+ break
+ if(!T)
+ set_temp("Cell log not found!", "danger")
+ return
+ is_printing = TRUE
+ playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, TRUE)
+ addtimer(CALLBACK(src, .proc/print_cell_log_finish, T.name, T.info), 5 SECONDS)
+ else
+ return FALSE
+ else
+ return FALSE
-/obj/machinery/computer/secure_data/proc/create_record_photo(datum/data/record/R)
- // basically copy-pasted from the camera code but different enough that it has to be redone
- var/icon/photoimage = get_record_photo(R)
- var/icon/small_img = icon(photoimage)
- var/icon/tiny_img = icon(photoimage)
- var/icon/ic = icon('icons/obj/items.dmi',"photo")
- var/icon/pc = icon('icons/obj/bureaucracy.dmi', "photo")
- small_img.Scale(8, 8)
- tiny_img.Scale(4, 4)
- ic.Blend(small_img, ICON_OVERLAY, 10, 13)
- pc.Blend(tiny_img, ICON_OVERLAY, 12, 19)
+/**
+ * Called when the print record timer finishes
+ */
+/obj/machinery/computer/secure_data/proc/print_record_finish()
+ var/obj/item/paper/P = new(loc)
+ P.info = "Security Record "
+ if(record_general && GLOB.data_core.general.Find(record_general))
+ P.info += {"Name: [record_general.fields["name"]] ID: [record_general.fields["id"]]
+ \nSex: [record_general.fields["sex"]]
+ \nAge: [record_general.fields["age"]]
+ \nFingerprint: [record_general.fields["fingerprint"]]
+ \nPhysical Status: [record_general.fields["p_stat"]]
+ \nMental Status: [record_general.fields["m_stat"]] "}
+ P.name = "paper - 'Security Record: [record_general.fields["name"]]'"
+ else
+ P.info += "General Record Lost! "
+ if(record_security && GLOB.data_core.security.Find(record_security))
+ P.info += {" \nSecurity Data
+ \nCriminal Status: [record_security.fields["criminal"]] \n
+ \nMinor Crimes: [record_security.fields["mi_crim"]]
+ \nDetails: [record_security.fields["mi_crim_d"]] \n
+ \nMajor Crimes: [record_security.fields["ma_crim"]]
+ \nDetails: [record_security.fields["ma_crim_d"]] \n
+ \nImportant Notes:
+ \n\t[record_security.fields["notes"]] \n \nComments/Log "}
+ for(var/c in record_security.fields["comments"])
+ P.info += "[c] "
+ else
+ P.info += "Security Record Lost! "
+ is_printing = FALSE
+ SStgui.update_uis(src)
- var/datum/picture/P = new()
- P.fields["name"] = "File Photo - [R.fields["name"]]"
- P.fields["author"] = "Central Command"
- P.fields["icon"] = ic
- P.fields["tiny"] = pc
- P.fields["img"] = photoimage
- P.fields["desc"] = "You can see [R.fields["name"]] on the photo."
- P.fields["pixel_x"] = rand(-10, 10)
- P.fields["pixel_y"] = rand(-10, 10)
- P.fields["size"] = 2
-
- var/obj/item/photo/PH = new/obj/item/photo(loc)
- PH.construct(P)
-
-*/
-
-/obj/machinery/computer/secure_data/proc/get_record_photo(datum/data/record/R)
- // similar to the code to make a photo, but of course the actual rendering is completely different
- var/icon/res = icon('icons/effects/96x96.dmi', "")
- // will be 2x2 to fit the 2 directions
- res.Scale(2 * 32, 2 * 32)
- // transparent background (it's a plastic transparency, you see) with the front and side icons
- res.Blend(icon(R.fields["photo"], dir = SOUTH), ICON_OVERLAY, 1, 17)
- res.Blend(icon(R.fields["photo"], dir = WEST), ICON_OVERLAY, 33, 17)
-
- return res
+/**
+ * Called when the print cell log timer finishes
+ */
+/obj/machinery/computer/secure_data/proc/print_cell_log_finish(name, info)
+ var/obj/item/paper/P = new(loc)
+ P.name = name
+ P.info = info
+ is_printing = FALSE
+ SStgui.update_uis(src)
/obj/machinery/computer/secure_data/emp_act(severity)
if(stat & (BROKEN|NOPOWER))
@@ -548,8 +451,8 @@
return
for(var/datum/data/record/R in GLOB.data_core.security)
- if(prob(10/severity))
- switch(rand(1,6))
+ if(prob(10 / severity))
+ switch(rand(1, 6))
if(1)
R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]"
if(2)
@@ -570,9 +473,17 @@
..(severity)
-/obj/machinery/computer/secure_data/detective_computer
- icon = 'icons/obj/computer.dmi'
- icon_state = "messyfiles"
+/**
+ * Sets a temporary message to display to the user
+ *
+ * Arguments:
+ * * text - Text to display, null/empty to clear the message from the UI
+ * * style - The style of the message: (color name), info, success, warning, danger
+ */
+/obj/machinery/computer/secure_data/proc/set_temp(text = "", style = "info", update_now = FALSE)
+ temp_notice = list(text = text, style = style)
+ if(update_now)
+ SStgui.update_uis(src)
/obj/machinery/computer/secure_data/laptop
name = "security laptop"
@@ -580,8 +491,9 @@
icon_state = "laptop"
icon_keyboard = "seclaptop_key"
icon_screen = "seclaptop"
- density = 0
+ density = FALSE
#undef SEC_DATA_R_LIST
#undef SEC_DATA_MAINT
#undef SEC_DATA_RECORD
+#undef SEC_FIELD
diff --git a/code/game/machinery/computer/skills.dm b/code/game/machinery/computer/skills.dm
deleted file mode 100644
index b7361db01a3..00000000000
--- a/code/game/machinery/computer/skills.dm
+++ /dev/null
@@ -1,328 +0,0 @@
-#define SKILL_DATA_R_LIST 1 // Record list
-#define SKILL_DATA_MAINT 2 // Records maintenance
-#define SKILL_DATA_RECORD 3 // Record
-
-/obj/machinery/computer/skills//TODO:SANITY
- name = "employment records console"
- desc = "Used to view personnel's employment records"
- icon_state = "laptop"
- icon_keyboard = "laptop_key"
- icon_screen = "medlaptop"
- density = 0
- light_color = LIGHT_COLOR_GREEN
- req_one_access = list(ACCESS_HEADS)
- circuit = /obj/item/circuitboard/skills
- var/obj/item/card/id/scan = null
- var/authenticated = null
- var/rank = null
- var/screen = null
- var/datum/data/record/active1 = null
- var/temp = null
- var/printing = null
- //Sorting Variables
- var/sortBy = "name"
- var/order = 1 // -1 = Descending - 1 = Ascending
-
-/obj/machinery/computer/skills/Destroy()
- active1 = null
- return ..()
-
-/obj/machinery/computer/skills/attackby(obj/item/O, mob/user, params)
- if(istype(O, /obj/item/card/id) && !scan)
- user.drop_item()
- O.forceMove(src)
- scan = O
- ui_interact(user)
- return
- return ..()
-
-//Someone needs to break down the dat += into chunks instead of long ass lines.
-/obj/machinery/computer/skills/attack_hand(mob/user)
- if(..())
- return
- if(is_away_level(z))
- to_chat(user, "Unable to establish a connection: You're too far away from the station!")
- return
- add_fingerprint(user)
- ui_interact(user)
-
-/obj/machinery/computer/skills/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "skills_data.tmpl", name, 800, 380)
- ui.open()
-
-/obj/machinery/computer/skills/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- data["temp"] = temp
- data["scan"] = scan ? scan.name : null
- data["authenticated"] = authenticated
- data["screen"] = screen
- if(authenticated)
- switch(screen)
- if(SKILL_DATA_R_LIST)
- if(!isnull(GLOB.data_core.general))
- for(var/datum/data/record/R in sortRecord(GLOB.data_core.general, sortBy, order))
- data["records"] += list(list("ref" = "\ref[R]", "id" = R.fields["id"], "name" = R.fields["name"], "rank" = R.fields["rank"], "fingerprint" = R.fields["fingerprint"]))
- if(SKILL_DATA_RECORD)
- var/list/general = list()
- data["general"] = general
- if(istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1))
- var/list/fields = list()
- general["fields"] = fields
- fields[++fields.len] = list("field" = "Name:", "value" = active1.fields["name"], "name" = "name")
- fields[++fields.len] = list("field" = "ID:", "value" = active1.fields["id"], "name" = "id")
- fields[++fields.len] = list("field" = "Sex:", "value" = active1.fields["sex"], "name" = "sex")
- fields[++fields.len] = list("field" = "Age:", "value" = active1.fields["age"], "name" = "age")
- fields[++fields.len] = list("field" = "Rank:", "value" = active1.fields["rank"], "name" = "rank")
- fields[++fields.len] = list("field" = "Fingerprint:", "value" = active1.fields["fingerprint"], "name" = "fingerprint")
- fields[++fields.len] = list("field" = "Physical Status:", "value" = active1.fields["p_stat"])
- fields[++fields.len] = list("field" = "Mental Status:", "value" = active1.fields["m_stat"])
- general["notes"] = active1.fields["notes"]
- var/list/photos = list()
- general["photos"] = photos
- photos[++photos.len] = list("photo" = active1.fields["photo-south"])
- photos[++photos.len] = list("photo" = active1.fields["photo-west"])
- general["has_photos"] += (active1.fields["photo-south"] || active1.fields["photo-west"] ? 1 : 0)
- general["empty"] = 0
- else
- general["empty"] = 1
- return data
-
-/obj/machinery/computer/skills/Topic(href, href_list)
- if(..())
- return 1
-
- if(!GLOB.data_core.general.Find(active1))
- active1 = null
-
- if(href_list["temp"])
- temp = null
-
- if(href_list["temp_action"])
- var/temp_list = splittext(href_list["temp_action"], "=")
- switch(temp_list[1])
- if("del_all2")
- if(GLOB.PDA_Manifest && GLOB.PDA_Manifest.len)
- GLOB.PDA_Manifest.Cut()
- for(var/datum/data/record/R in GLOB.data_core.security)
- qdel(R)
- setTemp("All employment records deleted.")
- if("del_rg2")
- if(active1)
- if(GLOB.PDA_Manifest && GLOB.PDA_Manifest.len)
- GLOB.PDA_Manifest.Cut()
- for(var/datum/data/record/R in GLOB.data_core.medical)
- if(R.fields["name"] == active1.fields["name"] && R.fields["id"] == active1.fields["id"])
- qdel(R)
- QDEL_NULL(active1)
- screen = SKILL_DATA_R_LIST
- if("rank")
- if(active1)
- if(GLOB.PDA_Manifest && GLOB.PDA_Manifest.len)
- GLOB.PDA_Manifest.Cut()
- active1.fields["rank"] = temp_list[2]
- if(temp_list[2] in GLOB.joblist)
- active1.fields["real_rank"] = temp_list[2]
-
- if(href_list["scan"])
- if(scan)
- scan.forceMove(loc)
- if(ishuman(usr) && !usr.get_active_hand())
- usr.put_in_hands(scan)
- scan = null
- else
- var/obj/item/I = usr.get_active_hand()
- if(istype(I, /obj/item/card/id))
- usr.drop_item()
- I.forceMove(src)
- scan = I
-
- if(href_list["login"])
- if(isAI(usr))
- authenticated = usr.name
- rank = "AI"
- else if(isrobot(usr))
- authenticated = usr.name
- var/mob/living/silicon/robot/R = usr
- rank = "[R.modtype] [R.braintype]"
- else if(istype(scan, /obj/item/card/id))
- if(check_access(scan))
- authenticated = scan.registered_name
- rank = scan.assignment
-
- if(authenticated)
- active1 = null
- screen = SKILL_DATA_R_LIST
-
- if(authenticated)
- var/incapable = (usr.stat || usr.restrained() || (!in_range(src, usr) && !issilicon(usr)))
- if(href_list["logout"])
- authenticated = null
- screen = null
- active1 = null
-
- else if(href_list["sort"])
- // Reverse the order if clicked twice
- if(sortBy == href_list["sort"])
- if(order == 1)
- order = -1
- else
- order = 1
- else
- sortBy = href_list["sort"]
- order = initial(order)
-
- else if(href_list["screen"])
- screen = text2num(href_list["screen"])
- if(screen < 1)
- screen = SKILL_DATA_R_LIST
-
- active1 = null
-
- else if(href_list["d_rec"])
- var/datum/data/record/R = locate(href_list["d_rec"])
- if(!GLOB.data_core.general.Find(R))
- setTemp("Record not found!")
- return 1
- active1 = R
- screen = SKILL_DATA_RECORD
-
- else if(href_list["del_all"])
- var/list/buttons = list()
- buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "val" = "del_all2=1", "status" = null)
- buttons[++buttons.len] = list("name" = "No", "icon" = "times", "val" = null, "status" = null)
- setTemp("Are you sure you wish to delete all employment records?", buttons)
-
- else if(href_list["del_rg"])
- if(active1)
- var/list/buttons = list()
- buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "val" = "del_rg2=1", "status" = null)
- buttons[++buttons.len] = list("name" = "No", "icon" = "times", "val" = null, "status" = null)
- setTemp("Are you sure you wish to delete the record (ALL)?", buttons)
-
- else if(href_list["new_g"])
- if(GLOB.PDA_Manifest.len)
- GLOB.PDA_Manifest.Cut()
- var/datum/data/record/G = new /datum/data/record()
- G.fields["name"] = "New Record"
- G.fields["id"] = "[add_zero(num2hex(rand(1, 1.6777215E7)), 6)]"
- G.fields["rank"] = "Unassigned"
- G.fields["real_rank"] = "Unassigned"
- G.fields["sex"] = "Male"
- G.fields["age"] = "Unknown"
- G.fields["fingerprint"] = "Unknown"
- G.fields["p_stat"] = "Active"
- G.fields["m_stat"] = "Stable"
- G.fields["species"] = "Human"
- GLOB.data_core.general += G
- active1 = G
-
- else if(href_list["print_r"])
- if(!printing)
- printing = 1
- playsound(loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1)
- sleep(50)
- var/obj/item/paper/P = new /obj/item/paper(loc)
- P.info = "Employment Record "
- if(istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1))
- P.info += {"Name: [active1.fields["name"]] ID: [active1.fields["id"]]
- \nSex: [active1.fields["sex"]]
- \nAge: [active1.fields["age"]]
- \nFingerprint: [active1.fields["fingerprint"]]
- \nPhysical Status: [active1.fields["p_stat"]]
- \nMental Status: [active1.fields["m_stat"]]
- \nEmployment/Skills Summary:[active1.fields["notes"]] "}
- else
- P.info += "General Record Lost! "
- P.info += ""
- P.name = "paper - 'Employment Record: [active1.fields["name"]]'"
- printing = 0
-
- if(href_list["field"])
- if(incapable)
- return 1
- var/a1 = active1
- switch(href_list["field"])
- if("name")
- if(istype(active1, /datum/data/record))
- var/t1 = reject_bad_name(clean_input("Please input name:", "Secure. records", active1.fields["name"], null))
- if(!t1 || !length(trim(t1)) || incapable || active1 != a1)
- return 1
- active1.fields["name"] = t1
- if("id")
- if(istype(active1, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please input id:", "Secure. records", active1.fields["id"], null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1 || incapable || active1 != a1)
- return 1
- active1.fields["id"] = t1
- if("fingerprint")
- if(istype(active1, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please input fingerprint hash:", "Secure. records", active1.fields["fingerprint"], null) as text)), 1, MAX_MESSAGE_LEN)
- if(!t1 || incapable || active1 != a1)
- return 1
- active1.fields["fingerprint"] = t1
- if("sex")
- if(istype(active1, /datum/data/record))
- if(active1.fields["sex"] == "Male")
- active1.fields["sex"] = "Female"
- else
- active1.fields["sex"] = "Male"
- if("age")
- if(istype(active1, /datum/data/record))
- var/t1 = input("Please input age:", "Secure. records", active1.fields["age"], null) as num
- if(!t1 || incapable || active1 != a1)
- return 1
- active1.fields["age"] = t1
- if("rank")
- var/list/L = list("Head of Personnel", "Captain", "AI")
- //This was so silly before the change. Now it actually works without beating your head against the keyboard. /N
- if(istype(active1, /datum/data/record) && L.Find(rank))
- var/list/buttons = list()
- for(var/rank in GLOB.joblist)
- buttons[++buttons.len] = list("name" = rank, "icon" = null, "val" = "rank=[rank]", "status" = (active1.fields["rank"] == rank ? "selected" : null))
- setTemp("Rank", buttons)
- else
- setTemp("You do not have the required rank to do this!")
- if("species")
- if(istype(active1, /datum/data/record))
- var/t1 = copytext(trim(sanitize(input("Please enter race:", "General records", active1.fields["species"], null) as message)), 1, MAX_MESSAGE_LEN)
- if(!t1 || incapable || active1 != a1)
- return 1
- active1.fields["species"] = t1
- return 1
-
-/obj/machinery/computer/skills/proc/setTemp(text, list/buttons = list())
- temp = list("text" = text, "buttons" = buttons, "has_buttons" = buttons.len > 0)
-
-/obj/machinery/computer/skills/emp_act(severity)
- if(stat & (BROKEN|NOPOWER))
- ..(severity)
- return
-
- for(var/datum/data/record/R in GLOB.data_core.security)
- if(prob(10/severity))
- switch(rand(1,6))
- if(1)
- R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]"
- if(2)
- R.fields["sex"] = pick("Male", "Female")
- if(3)
- R.fields["age"] = rand(5, 85)
- if(4)
- R.fields["criminal"] = pick(SEC_RECORD_STATUS_NONE, SEC_RECORD_STATUS_ARREST, SEC_RECORD_STATUS_INCARCERATED, SEC_RECORD_STATUS_PAROLLED, SEC_RECORD_STATUS_RELEASED)
- if(5)
- R.fields["p_stat"] = pick("*Unconcious*", "Active", "Physically Unfit")
- if(6)
- R.fields["m_stat"] = pick("*Insane*", "*Unstable*", "*Watch*", "Stable")
- continue
-
- else if(prob(1))
- qdel(R)
- continue
-
- ..(severity)
-
-#undef SKILL_DATA_R_LIST
-#undef SKILL_DATA_MAINT
-#undef SKILL_DATA_RECORD
diff --git a/code/game/machinery/computer/sm_monitor.dm b/code/game/machinery/computer/sm_monitor.dm
new file mode 100644
index 00000000000..f2e01e6c4fa
--- /dev/null
+++ b/code/game/machinery/computer/sm_monitor.dm
@@ -0,0 +1,147 @@
+/obj/machinery/computer/sm_monitor
+ name = "supermatter monitoring console"
+ desc = "Used to monitor supermatter shards."
+ icon_keyboard = "power_key"
+ icon_screen = "smmon_0"
+ circuit = /obj/item/circuitboard/sm_monitor
+ light_color = LIGHT_COLOR_YELLOW
+ /// Cache-list of all supermatter shards
+ var/list/supermatters
+ /// Last status of the active supermatter for caching purposes
+ var/last_status
+ /// Reference to the active shard
+ var/obj/machinery/power/supermatter_shard/active
+
+/obj/machinery/computer/sm_monitor/Destroy()
+ active = null
+ return ..()
+
+/obj/machinery/computer/sm_monitor/attack_ai(mob/user)
+ attack_hand(user)
+
+/obj/machinery/computer/sm_monitor/attack_hand(mob/user)
+ add_fingerprint(user)
+ if(stat & (BROKEN|NOPOWER))
+ return
+ tgui_interact(user)
+
+/obj/machinery/computer/sm_monitor/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "SupermatterMonitor", name, 600, 325, master_ui, state)
+ ui.open()
+
+/obj/machinery/computer/sm_monitor/tgui_data(mob/user)
+ var/list/data = list()
+
+ if(istype(active))
+ var/turf/T = get_turf(active)
+ // If we somehow delam during this proc, handle it somewhat
+ if(!T)
+ active = null
+ refresh()
+ return
+ var/datum/gas_mixture/air = T.return_air()
+ if(!air)
+ active = null
+ return
+
+ data["active"] = TRUE
+ data["SM_integrity"] = active.get_integrity()
+ data["SM_power"] = active.power
+ data["SM_ambienttemp"] = air.temperature
+ data["SM_ambientpressure"] = air.return_pressure()
+ //data["SM_EPR"] = round((air.total_moles / air.group_multiplier) / 23.1, 0.01)
+ var/other_moles = air.total_trace_moles()
+ var/TM = air.total_moles()
+ if(TM)
+ data["SM_gas_O2"] = round(100*air.oxygen/TM, 0.01)
+ data["SM_gas_CO2"] = round(100*air.carbon_dioxide/TM, 0.01)
+ data["SM_gas_N2"] = round(100*air.nitrogen/TM, 0.01)
+ data["SM_gas_PL"] = round(100*air.toxins/TM, 0.01)
+ if(other_moles)
+ data["SM_gas_OTHER"] = round(100 * other_moles / TM, 0.01)
+ else
+ data["SM_gas_OTHER"] = 0
+ else
+ data["SM_gas_O2"] = 0
+ data["SM_gas_CO2"] = 0
+ data["SM_gas_N2"] = 0
+ data["SM_gas_PH"] = 0
+ data["SM_gas_OTHER"] = 0
+ else
+ var/list/SMS = list()
+ for(var/I in supermatters)
+ var/obj/machinery/power/supermatter_shard/S = I
+ var/area/A = get_area(S)
+ if(!A)
+ continue
+
+ SMS.Add(list(list(
+ "area_name" = A.name,
+ "integrity" = S.get_integrity(),
+ "uid" = S.UID()
+ )))
+
+ data["active"] = FALSE
+ data["supermatters"] = SMS
+
+ return data
+
+/**
+ * Supermatter List Refresher
+ *
+ * This proc loops through the list of supermatters in the atmos SS and adds them to this console's cache list
+ */
+/obj/machinery/computer/sm_monitor/proc/refresh()
+ supermatters = list()
+ var/turf/T = get_turf(tgui_host()) // Get the TGUI host incase this ever turned into a supermatter monitoring module for AIs to use or something
+ if(!T)
+ return
+ for(var/obj/machinery/power/supermatter_shard/S in SSair.atmos_machinery)
+ // Delaminating, not within coverage, not on a tile.
+ if(!(is_station_level(S.z) || is_mining_level(S.z) || atoms_share_level(S, T) || !istype(S.loc, /turf/simulated/)))
+ continue
+ supermatters.Add(S)
+
+ if(!(active in supermatters))
+ active = null
+
+/obj/machinery/computer/sm_monitor/process()
+ if(stat & (NOPOWER|BROKEN))
+ return FALSE
+
+ if(active)
+ var/new_status = active.get_status()
+ if(last_status != new_status)
+ last_status = new_status
+ if(last_status == SUPERMATTER_ERROR)
+ last_status = SUPERMATTER_INACTIVE
+ icon_screen = "smmon_[last_status]"
+ update_icon()
+
+ return TRUE
+
+/obj/machinery/computer/sm_monitor/tgui_act(action, params)
+ if(..())
+ return
+
+ if(stat & (BROKEN|NOPOWER))
+ return
+
+ . = TRUE
+
+ switch(action)
+ if("refresh")
+ refresh()
+
+ if("view")
+ var/newuid = params["view"]
+ for(var/obj/machinery/power/supermatter_shard/S in supermatters)
+ if(S.UID() == newuid)
+ active = S
+ break
+
+ if("back")
+ active = null
+
diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm
index 607918442ea..3b4631b171a 100644
--- a/code/game/machinery/computer/station_alert.dm
+++ b/code/game/machinery/computer/station_alert.dm
@@ -6,48 +6,91 @@
icon_screen = "alert:0"
light_color = LIGHT_COLOR_CYAN
circuit = /obj/item/circuitboard/stationalert_engineering
- var/datum/nano_module/alarm_monitor/alarm_monitor
- var/monitor_type = /datum/nano_module/alarm_monitor/engineering
+ var/ui_x = 325
+ var/ui_y = 500
+ var/list/alarms_listend_for = list("Fire", "Atmosphere", "Power")
-/obj/machinery/computer/station_alert/security
- monitor_type = /datum/nano_module/alarm_monitor/security
- circuit = /obj/item/circuitboard/stationalert_security
-
-/obj/machinery/computer/station_alert/all
- monitor_type = /datum/nano_module/alarm_monitor/all
- circuit = /obj/item/circuitboard/stationalert_all
-
-/obj/machinery/computer/station_alert/New()
- ..()
- alarm_monitor = new monitor_type(src)
- alarm_monitor.register(src, /obj/machinery/computer/station_alert/.proc/update_icon)
+/obj/machinery/computer/station_alert/Initialize(mapload)
+ . = ..()
+ GLOB.alert_consoles += src
+ RegisterSignal(SSalarm, COMSIG_TRIGGERED_ALARM, .proc/alarm_triggered)
+ RegisterSignal(SSalarm, COMSIG_CANCELLED_ALARM, .proc/alarm_cancelled)
/obj/machinery/computer/station_alert/Destroy()
- alarm_monitor.unregister(src)
- QDEL_NULL(alarm_monitor)
+ GLOB.alert_consoles -= src
return ..()
/obj/machinery/computer/station_alert/attack_ai(mob/user)
add_fingerprint(user)
if(stat & (BROKEN|NOPOWER))
return
- interact(user)
+ tgui_interact(user)
/obj/machinery/computer/station_alert/attack_hand(mob/user)
add_fingerprint(user)
if(stat & (BROKEN|NOPOWER))
return
- interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/station_alert/interact(mob/user)
- alarm_monitor.ui_interact(user)
+/obj/machinery/computer/station_alert/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "StationAlertConsole", name, ui_x, ui_y, master_ui, state)
+ ui.open()
+
+/obj/machinery/computer/station_alert/tgui_data(mob/user)
+ var/list/data = list()
+
+ data["alarms"] = list()
+ for(var/class in SSalarm.alarms)
+ if(!(class in alarms_listend_for))
+ continue
+ data["alarms"][class] = list()
+ for(var/area in SSalarm.alarms[class])
+ for(var/thing in SSalarm.alarms[class][area][3])
+ var/atom/A = locateUID(thing)
+ if(atoms_share_level(A, src))
+ data["alarms"][class] += area
+
+ return data
+
+/obj/machinery/computer/station_alert/proc/alarm_triggered(src, class, area/A, list/O, obj/alarmsource)
+ if(!(class in alarms_listend_for))
+ return
+ if(alarmsource.z != z)
+ return
+ if(stat & (BROKEN))
+ return
+ update_icon()
+
+/obj/machinery/computer/station_alert/proc/alarm_cancelled(src, class, area/A, obj/origin, cleared)
+ if(!(class in alarms_listend_for))
+ return
+ if(origin.z != z)
+ return
+ if(stat & (BROKEN))
+ return
+ update_icon()
/obj/machinery/computer/station_alert/update_icon()
- if(alarm_monitor)
- var/list/alarms = alarm_monitor.major_alarms()
- if(alarms.len)
- icon_screen = "alert:2"
- else
- icon_screen = "alert:0"
+ var/active_alarms = FALSE
+ var/list/list/temp_alarm_list = SSalarm.alarms.Copy()
+ for(var/cat in temp_alarm_list)
+ if(!(cat in alarms_listend_for))
+ continue
+ var/list/list/L = temp_alarm_list[cat].Copy()
+ for(var/alarm in L)
+ var/list/list/alm = L[alarm].Copy()
+ var/list/list/sources = alm[3].Copy()
+ for(var/thing in sources)
+ var/atom/A = locateUID(thing)
+ if(A && A.z != z)
+ L -= alarm
+ if(length(L))
+ active_alarms = TRUE
+ if(active_alarms)
+ icon_screen = "alert:2"
+ else
+ icon_screen = "alert:0"
..()
diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm
index ee29504fe99..8ee774d1ae0 100644
--- a/code/game/machinery/constructable_frame.dm
+++ b/code/game/machinery/constructable_frame.dm
@@ -253,7 +253,6 @@ to destroy them and players will be able to make replacements.
/obj/machinery/vending/engineering = "Robco Tool Maker",
/obj/machinery/vending/sovietsoda = "BODA",
/obj/machinery/vending/security = "SecTech",
- /obj/machinery/vending/modularpc = "Deluxe Silicate Selections",
/obj/machinery/vending/crittercare = "CritterCare")
/obj/item/circuitboard/vendor/screwdriver_act(mob/user, obj/item/I)
diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm
index 4213ff517bf..70798c0b92e 100644
--- a/code/game/machinery/cryo.dm
+++ b/code/game/machinery/cryo.dm
@@ -27,12 +27,13 @@
var/running_bob_animation = 0 // This is used to prevent threads from building up if update_icons is called multiple times
light_color = LIGHT_COLOR_WHITE
- power_change()
- ..()
- if(!(stat & (BROKEN|NOPOWER)))
- set_light(2)
- else
- set_light(0)
+
+/obj/machinery/atmospherics/unary/cryo_cell/power_change()
+ ..()
+ if(!(stat & (BROKEN|NOPOWER)))
+ set_light(2)
+ else
+ set_light(0)
/obj/machinery/atmospherics/unary/cryo_cell/New()
..()
@@ -103,7 +104,7 @@
beaker.forceMove(drop_location())
beaker = null
-/obj/machinery/atmospherics/unary/cryo_cell/MouseDrop_T(atom/movable/O as mob|obj, mob/living/user as mob)
+/obj/machinery/atmospherics/unary/cryo_cell/MouseDrop_T(atom/movable/O, mob/living/user)
if(O.loc == user) //no you can't pull things out of your ass
return
if(user.incapacitated()) //are you cuffed, dying, lying, stunned or other
@@ -140,6 +141,7 @@
add_attack_logs(user, L, "put into a cryo cell at [COORD(src)].", ATKLOG_ALL)
if(user.pulling == L)
user.stop_pulling()
+ SStgui.update_uis(src)
/obj/machinery/atmospherics/unary/cryo_cell/process()
..()
@@ -177,14 +179,14 @@
return FALSE
-/obj/machinery/atmospherics/unary/cryo_cell/relaymove(mob/user as mob)
+/obj/machinery/atmospherics/unary/cryo_cell/relaymove(mob/user)
if(user.stat)
return
go_out()
return
/obj/machinery/atmospherics/unary/cryo_cell/attack_ghost(mob/user)
- return attack_hand(user)
+ tgui_interact(user)
/obj/machinery/atmospherics/unary/cryo_cell/attack_hand(mob/user)
if(user == occupant)
@@ -194,36 +196,18 @@
to_chat(usr, "Close the maintenance panel first.")
return
- ui_interact(user)
+ tgui_interact(user)
-
- /**
- * The ui_interact proc is used to open and update Nano UIs
- * If ui_interact is not used then the UI will not update correctly
- * ui_interact is currently defined for /atom/movable (which is inherited by /obj and /mob)
- *
- * @param user /mob The mob who is interacting with this ui
- * @param ui_key string A string key to use for this ui. Allows for multiple unique uis on one obj/mob (defaut value "main")
- * @param ui /datum/nanoui This parameter is passed by the nanoui process() proc when updating an open ui
- *
- * @return nothing
- */
-/obj/machinery/atmospherics/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- // update the ui if it exists, returns null if no ui is passed/found
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/atmospherics/unary/cryo_cell/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- // the ui does not exist, so we'll create a new() one
- // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "cryo.tmpl", "Cryo Cell Control System", 520, 480)
- // open the new ui window
+ ui = new(user, src, ui_key, "Cryo", "Cryo Cell", 520, 490)
ui.open()
- // auto update every Master Controller tick
- ui.set_auto_update(1)
-/obj/machinery/atmospherics/unary/cryo_cell/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
+/obj/machinery/atmospherics/unary/cryo_cell/tgui_data(mob/user)
var/data[0]
data["isOperating"] = on
- data["hasOccupant"] = occupant ? 1 : 0
+ data["hasOccupant"] = occupant ? TRUE : FALSE
var/occupantData[0]
if(occupant)
@@ -246,7 +230,7 @@
else if(air_contents.temperature > TCRYO)
data["cellTemperatureStatus"] = "average"
- data["isBeakerLoaded"] = beaker ? 1 : 0
+ data["isBeakerLoaded"] = beaker ? TRUE : FALSE
data["beakerLabel"] = null
data["beakerVolume"] = 0
if(beaker)
@@ -259,48 +243,44 @@
data["auto_eject_dead"] = (auto_eject_prefs & AUTO_EJECT_DEAD) ? TRUE : FALSE
return data
-/obj/machinery/atmospherics/unary/cryo_cell/Topic(href, href_list)
- if(usr == occupant)
- return 0 // don't update UIs attached to this object
+/obj/machinery/atmospherics/unary/cryo_cell/tgui_act(action, params)
+ if(..() || usr == occupant)
+ return
+ if(stat & (NOPOWER|BROKEN))
+ return
- if(..())
- return 0 // don't update UIs attached to this object
-
- if(href_list["switchOn"])
- on = TRUE
- update_icon()
-
- if(href_list["switchOff"])
- on = FALSE
- update_icon()
-
- if(href_list["auto_eject_healthy_on"])
- auto_eject_prefs |= AUTO_EJECT_HEALTHY
-
- if(href_list["auto_eject_healthy_off"])
- auto_eject_prefs &= ~AUTO_EJECT_HEALTHY
-
- if(href_list["auto_eject_dead_on"])
- auto_eject_prefs |= AUTO_EJECT_DEAD
-
- if(href_list["auto_eject_dead_off"])
- auto_eject_prefs &= ~AUTO_EJECT_DEAD
-
- if(href_list["ejectBeaker"])
- if(beaker)
+ . = TRUE
+ switch(action)
+ if("switchOn")
+ on = TRUE
+ update_icon()
+ if("switchOff")
+ on = FALSE
+ update_icon()
+ if("auto_eject_healthy_on")
+ auto_eject_prefs |= AUTO_EJECT_HEALTHY
+ if("auto_eject_healthy_off")
+ auto_eject_prefs &= ~AUTO_EJECT_HEALTHY
+ if("auto_eject_dead_on")
+ auto_eject_prefs |= AUTO_EJECT_DEAD
+ if("auto_eject_dead_off")
+ auto_eject_prefs &= ~AUTO_EJECT_DEAD
+ if("ejectBeaker")
+ if(!beaker)
+ return
beaker.forceMove(get_step(loc, SOUTH))
beaker = null
-
- if(href_list["ejectOccupant"])
- if(!occupant || isslime(usr) || ispAI(usr))
- return 0 // don't update UIs attached to this object
- add_attack_logs(usr, occupant, "ejected from cryo cell at [COORD(src)]", ATKLOG_ALL)
- go_out()
+ if("ejectOccupant")
+ if(!occupant || isslime(usr) || ispAI(usr))
+ return
+ add_attack_logs(usr, occupant, "ejected from cryo cell at [COORD(src)]", ATKLOG_ALL)
+ go_out()
+ else
+ return FALSE
add_fingerprint(usr)
- return 1 // update UIs attached to this object
-/obj/machinery/atmospherics/unary/cryo_cell/attackby(var/obj/item/G as obj, var/mob/user as mob, params)
+/obj/machinery/atmospherics/unary/cryo_cell/attackby(var/obj/item/G, var/mob/user, params)
if(istype(G, /obj/item/reagent_containers/glass))
var/obj/item/reagent_containers/B = G
if(beaker)
@@ -313,6 +293,7 @@
beaker = B
add_attack_logs(user, null, "Added [B] containing [B.reagents.log_list()] to a cryo cell at [COORD(src)]")
user.visible_message("[user] adds \a [B] to [src]!", "You add \a [B] to [src]!")
+ SStgui.update_uis(src)
return
if(exchange_parts(user, G))
@@ -357,7 +338,7 @@
return
if(occupant)
- var/image/pickle = image(occupant.icon, occupant.icon_state)
+ var/mutable_appearance/pickle = mutable_appearance(occupant.icon, occupant.icon_state)
pickle.overlays = occupant.overlays
pickle.pixel_y = 22
@@ -443,6 +424,9 @@
for(var/atom/movable/A in contents - component_parts - list(beaker))
A.forceMove(get_step(loc, SOUTH))
+/obj/machinery/atmospherics/unary/cryo_cell/force_eject_occupant()
+ go_out()
+
/// Called when either the occupant is dead and the AUTO_EJECT_DEAD flag is present, OR the occupant is alive, has no external damage, and the AUTO_EJECT_HEALTHY flag is present.
/obj/machinery/atmospherics/unary/cryo_cell/proc/auto_eject(eject_flag)
on = FALSE
@@ -452,8 +436,9 @@
playsound(loc, 'sound/machines/ding.ogg', 50, 1)
if(AUTO_EJECT_DEAD)
playsound(loc, 'sound/machines/buzz-sigh.ogg', 40)
+ SStgui.update_uis(src)
-/obj/machinery/atmospherics/unary/cryo_cell/proc/put_mob(mob/living/carbon/M as mob)
+/obj/machinery/atmospherics/unary/cryo_cell/proc/put_mob(mob/living/carbon/M)
if(!istype(M))
to_chat(usr, "The cryo cell cannot handle such a lifeform!")
return
@@ -527,7 +512,7 @@
/datum/data/function/proc/reset()
return
-/datum/data/function/proc/r_input(href, href_list, mob/user as mob)
+/datum/data/function/proc/r_input(href, href_list, mob/user)
return
/datum/data/function/proc/display()
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index 36196ea4eca..a13f87845ff 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -302,6 +302,7 @@
// Eject dead people
if(occupant.stat == DEAD)
go_out()
+ return
// Allow a gap between entering the pod and actually despawning.
if(world.time - time_entered < time_till_despawn)
@@ -768,9 +769,13 @@
return ..()
+
/proc/cryo_ssd(var/mob/living/carbon/person_to_cryo)
if(istype(person_to_cryo.loc, /obj/machinery/cryopod))
return 0
+ if(isobj(person_to_cryo.loc))
+ var/obj/O = person_to_cryo.loc
+ O.force_eject_occupant()
var/list/free_cryopods = list()
for(var/obj/machinery/cryopod/P in GLOB.machines)
if(!P.occupant && istype(get_area(P), /area/crew_quarters/sleep))
@@ -779,6 +784,9 @@
if(free_cryopods.len)
target_cryopod = safepick(free_cryopods)
if(target_cryopod.check_occupant_allowed(person_to_cryo))
+ var/turf/T = get_turf(person_to_cryo)
+ var/obj/effect/portal/SP = new /obj/effect/portal(T, null, null, 40)
+ SP.name = "NT SSD Teleportation Portal"
target_cryopod.take_occupant(person_to_cryo, 1)
return 1
return 0
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 42ff08ff00f..a732348ac3c 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -38,6 +38,11 @@
#define AIRLOCK_DAMAGE_DEFLECTION_N 21 // Normal airlock damage deflection
#define AIRLOCK_DAMAGE_DEFLECTION_R 30 // Reinforced airlock damage deflection
+#define TGUI_GREEN 2
+#define TGUI_ORANGE 1
+#define TGUI_RED 0
+
+
GLOBAL_LIST_EMPTY(airlock_overlays)
/obj/machinery/door/airlock
@@ -51,10 +56,9 @@ GLOBAL_LIST_EMPTY(airlock_overlays)
autoclose = TRUE
explosion_block = 1
assemblytype = /obj/structure/door_assembly
- normalspeed = 1
siemens_strength = 1
var/security_level = 0 //How much are wires secured
- var/aiControlDisabled = FALSE //If TRUE, AI control is disabled until the AI hacks back in and disables the lock. If 2, the AI has bypassed the lock. If -1, the control is enabled but the AI had bypassed it earlier, so if it is disabled again the AI would have no trouble getting back in.
+ var/aiControlDisabled = AICONTROLDISABLED_OFF
var/hackProof = FALSE // if TRUE, this door can't be hacked by the AI
var/electrified_until = 0 // World time when the door is no longer electrified. -1 if it is permanently electrified until someone fixes it.
var/main_power_lost_until = 0 //World time when main power is restored.
@@ -65,7 +69,7 @@ GLOBAL_LIST_EMPTY(airlock_overlays)
var/spawnPowerRestoreRunning = 0
var/lights = TRUE // bolt lights show by default
var/datum/wires/airlock/wires
- var/aiDisabledIdScanner = 0
+ var/aiDisabledIdScanner = FALSE
var/aiHacking = 0
var/obj/machinery/door/airlock/closeOther
var/closeOtherId
@@ -96,7 +100,7 @@ GLOBAL_LIST_EMPTY(airlock_overlays)
var/doorDeni = 'sound/machines/deniedbeep.ogg' // i'm thinkin' Deni's
var/boltUp = 'sound/machines/boltsup.ogg'
var/boltDown = 'sound/machines/boltsdown.ogg'
- var/is_special = 0
+ var/is_special = FALSE
/obj/machinery/door/airlock/welded
welded = TRUE
@@ -142,6 +146,7 @@ About the new airlock wires panel:
break
/obj/machinery/door/airlock/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(electronics)
QDEL_NULL(wires)
QDEL_NULL(note)
@@ -173,7 +178,7 @@ About the new airlock wires panel:
spawn (10)
justzap = 0
return
- else /*if(justzap)*/
+ else
return
else if(user.hallucination > 50 && prob(10) && !operating)
if(user.electrocute_act(50, src, 1, illusion = TRUE)) // We'll just go with a flat 50 damage, instead of doing powernet checks
@@ -188,15 +193,11 @@ About the new airlock wires panel:
return 1
return 0
-/obj/machinery/door/airlock/proc/isWireCut(wireIndex)
- // You can find the wires in the datum folder.
- return wires.IsIndexCut(wireIndex)
-
/obj/machinery/door/airlock/proc/canAIControl()
- return ((aiControlDisabled!=1) && (!isAllPowerLoss()))
+ return ((aiControlDisabled != AICONTROLDISABLED_ON) && (!isAllPowerLoss()))
/obj/machinery/door/airlock/proc/canAIHack()
- return ((aiControlDisabled==1) && (!hackProof) && (!isAllPowerLoss()))
+ return ((aiControlDisabled == AICONTROLDISABLED_ON) && (!hackProof) && (!isAllPowerLoss()))
/obj/machinery/door/airlock/proc/arePowerSystemsOn()
if(stat & (NOPOWER|BROKEN))
@@ -204,27 +205,21 @@ About the new airlock wires panel:
return (main_power_lost_until==0 || backup_power_lost_until==0)
/obj/machinery/door/airlock/requiresID()
- return !(isWireCut(AIRLOCK_WIRE_IDSCAN) || aiDisabledIdScanner)
+ return !(wires.is_cut(WIRE_IDSCAN) || aiDisabledIdScanner)
/obj/machinery/door/airlock/proc/isAllPowerLoss()
if(stat & (NOPOWER|BROKEN))
return 1
- if(mainPowerCablesCut() && backupPowerCablesCut())
+ if(wires.is_cut(WIRE_MAIN_POWER1) && wires.is_cut(WIRE_BACKUP_POWER1))
return 1
return 0
-/obj/machinery/door/airlock/proc/mainPowerCablesCut()
- return isWireCut(AIRLOCK_WIRE_MAIN_POWER1)
-
-/obj/machinery/door/airlock/proc/backupPowerCablesCut()
- return isWireCut(AIRLOCK_WIRE_BACKUP_POWER1)
-
/obj/machinery/door/airlock/proc/loseMainPower()
- main_power_lost_until = mainPowerCablesCut() ? -1 : world.time + 60 SECONDS
+ main_power_lost_until = wires.is_cut(WIRE_MAIN_POWER1) ? -1 : world.time + 60 SECONDS
if(main_power_lost_until > 0)
main_power_timer = addtimer(CALLBACK(src, .proc/regainMainPower), 60 SECONDS, TIMER_UNIQUE | TIMER_STOPPABLE)
// If backup power is permanently disabled then activate in 10 seconds if possible, otherwise it's already enabled or a timer is already running
- if(backup_power_lost_until == -1 && !backupPowerCablesCut())
+ if(backup_power_lost_until == -1 && !wires.is_cut(WIRE_BACKUP_POWER1))
backup_power_lost_until = world.time + 10 SECONDS
backup_power_timer = addtimer(CALLBACK(src, .proc/regainBackupPower), 10 SECONDS, TIMER_UNIQUE | TIMER_STOPPABLE)
// Disable electricity if required
@@ -232,7 +227,7 @@ About the new airlock wires panel:
electrify(0)
/obj/machinery/door/airlock/proc/loseBackupPower()
- backup_power_lost_until = backupPowerCablesCut() ? -1 : world.time + 60 SECONDS
+ backup_power_lost_until = wires.is_cut(WIRE_BACKUP_POWER1) ? -1 : world.time + 60 SECONDS
if(backup_power_lost_until > 0)
backup_power_timer = addtimer(CALLBACK(src, .proc/regainBackupPower), 60 SECONDS, TIMER_UNIQUE | TIMER_STOPPABLE)
@@ -243,7 +238,7 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/proc/regainMainPower()
main_power_timer = null
- if(!mainPowerCablesCut())
+ if(!wires.is_cut(WIRE_MAIN_POWER1))
main_power_lost_until = 0
// If backup power is currently active then disable, otherwise let it count down and disable itself later
if(!backup_power_lost_until)
@@ -253,18 +248,18 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/proc/regainBackupPower()
backup_power_timer = null
- if(!backupPowerCablesCut())
+ if(!wires.is_cut(WIRE_BACKUP_POWER1))
// Restore backup power only if main power is offline, otherwise permanently disable
backup_power_lost_until = main_power_lost_until == 0 ? -1 : 0
update_icon()
-/obj/machinery/door/airlock/proc/electrify(duration, feedback = 0)
+/obj/machinery/door/airlock/proc/electrify(duration, mob/user = usr, feedback = FALSE)
if(electrified_timer)
deltimer(electrified_timer)
electrified_timer = null
var/message = ""
- if(isWireCut(AIRLOCK_WIRE_ELECTRIFY) && arePowerSystemsOn())
+ if(wires.is_cut(WIRE_ELECTRIFY) && arePowerSystemsOn())
message = text("The electrification wire is cut - Door permanently electrified.")
electrified_until = -1
else if(duration && !arePowerSystemsOn())
@@ -274,10 +269,10 @@ About the new airlock wires panel:
message = "The door is now un-electrified."
electrified_until = 0
else if(duration) //electrify door for the given duration seconds
- if(usr)
- shockedby += text("\[[time_stamp()]\] - [usr](ckey:[usr.ckey])")
- usr.create_attack_log("Electrified the [name] at [x] [y] [z]")
- add_attack_logs(usr, src, "Electrified", ATKLOG_ALL)
+ if(user)
+ shockedby += text("\[[time_stamp()]\] - [user](ckey:[user.ckey])")
+ user.create_attack_log("Electrified the [name] at [x] [y] [z]")
+ add_attack_logs(user, src, "Electrified", ATKLOG_ALL)
else
shockedby += text("\[[time_stamp()]\] - EMP)")
message = "The door is now electrified [duration == -1 ? "permanently" : "for [duration] second\s"]."
@@ -285,7 +280,7 @@ About the new airlock wires panel:
if(duration != -1)
electrified_timer = addtimer(CALLBACK(src, .proc/electrify, 0), duration SECONDS, TIMER_UNIQUE | TIMER_STOPPABLE)
if(feedback && message)
- to_chat(usr, message)
+ to_chat(user, message)
// shock user with probability prb (if all connections & power are working)
// returns 1 if shocked, 0 otherwise
@@ -520,6 +515,10 @@ About the new airlock wires panel:
var/obj/machinery/door/airlock/airlock = painter.available_paint_jobs["[painter.paint_setting]"] // get the airlock type path associated with the airlock name the user just chose
var/obj/structure/door_assembly/assembly = initial(airlock.assemblytype)
+ if(assemblytype == assembly)
+ to_chat(user, "This airlock is already painted [painter.paint_setting]!")
+ return
+
if(airlock_material == "glass" && initial(assembly.noglass)) // prevents painting glass airlocks with a paint job that doesn't have a glass version, such as the freezer
to_chat(user, "This paint job can only be applied to non-glass airlocks.")
return
@@ -570,38 +569,58 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/attack_ghost(mob/user)
if(panel_open)
wires.Interact(user)
- ui_interact(user)
+ tgui_interact(user)
/obj/machinery/door/airlock/attack_ai(mob/user)
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/door/airlock/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/door/airlock/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "door_control.tmpl", "Door Controls - [src]", 600, 375)
+ ui = new(user, src, ui_key, "AiAirlock", name, 600, 400, master_ui, state)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/door/airlock/ui_data(mob/user, datum/topic_state/state = GLOB.default_state)
- var/data[0]
- data["main_power_loss"] = round(main_power_lost_until > 0 ? max(main_power_lost_until - world.time, 0) / 10 : main_power_lost_until, 1)
- data["backup_power_loss"] = round(backup_power_lost_until > 0 ? max(backup_power_lost_until - world.time, 0) / 10 : backup_power_lost_until, 1)
- data["electrified"] = round(electrified_until > 0 ? max(electrified_until - world.time, 0) / 10 : electrified_until, 1)
- data["open"] = !density
+/obj/machinery/door/airlock/tgui_data(mob/user)
+ var/list/data = list()
- var/commands[0]
- commands[++commands.len] = list("name" = "IdScan", "command"= "idscan", "active" = !aiDisabledIdScanner,"enabled" = "Enabled", "disabled" = "Disable", "danger" = 0, "act" = 1)
- commands[++commands.len] = list("name" = "Bolts", "command"= "bolts", "active" = !locked, "enabled" = "Raised", "disabled" = "Dropped", "danger" = 0, "act" = 0)
- commands[++commands.len] = list("name" = "Bolt Lights", "command"= "lights", "active" = lights, "enabled" = "Enabled", "disabled" = "Disable", "danger" = 0, "act" = 1)
- commands[++commands.len] = list("name" = "Safeties", "command"= "safeties", "active" = safe, "enabled" = "Nominal", "disabled" = "Overridden", "danger" = 1, "act" = 0)
- commands[++commands.len] = list("name" = "Timing", "command"= "timing", "active" = normalspeed, "enabled" = "Nominal", "disabled" = "Overridden", "danger" = 1, "act" = 0)
- commands[++commands.len] = list("name" = "Door State", "command"= "open", "active" = density, "enabled" = "Closed", "disabled" = "Opened", "danger" = 0, "act" = 0)
- commands[++commands.len] = list("name" = "Emergency Access","command"= "emergency", "active" = !emergency, "enabled" = "Disabled", "disabled" = "Enabled", "danger" = 0, "act" = 0)
+ var/list/power = list()
+ power["main"] = main_power_lost_until ? TGUI_RED : TGUI_GREEN
+ power["main_timeleft"] = max(main_power_lost_until - world.time, 0) / 10
+ power["backup"] = backup_power_lost_until ? TGUI_RED : TGUI_GREEN
+ power["backup_timeleft"] = max(backup_power_lost_until - world.time, 0) / 10
+ data["power"] = power
+ if(electrified_until == -1)
+ data["shock"] = TGUI_RED
+ else if(electrified_until > 0)
+ data["shock"] = TGUI_ORANGE
+ else
+ data["shock"] = TGUI_GREEN
- data["commands"] = commands
+ data["shock_timeleft"] = max(electrified_until - world.time, 0) / 10
+ data["id_scanner"] = !aiDisabledIdScanner
+ data["emergency"] = emergency // access
+ data["locked"] = locked // bolted
+ data["lights"] = lights // bolt lights
+ data["safe"] = safe // safeties
+ data["speed"] = normalspeed // safe speed
+ data["welded"] = welded // welded
+ data["opened"] = !density // opened
+
+ var/list/wire = list()
+ wire["main_power"] = !wires.is_cut(WIRE_MAIN_POWER1)
+ wire["backup_power"] = !wires.is_cut(WIRE_BACKUP_POWER1)
+ wire["shock"] = !wires.is_cut(WIRE_ELECTRIFY)
+ wire["id_scanner"] = !wires.is_cut(WIRE_IDSCAN)
+ wire["bolts"] = !wires.is_cut(WIRE_DOOR_BOLTS)
+ wire["lights"] = !wires.is_cut(WIRE_BOLT_LIGHT)
+ wire["safe"] = !wires.is_cut(WIRE_SAFETY)
+ wire["timing"] = !wires.is_cut(WIRE_SPEED)
+
+ data["wires"] = wire
return data
+
/obj/machinery/door/airlock/proc/hack(mob/user)
set waitfor = 0
if(!aiHacking)
@@ -641,7 +660,7 @@ About the new airlock wires panel:
to_chat(user, "Transfer complete. Forcing airlock to execute program.")
sleep(50)
//disable blocked control
- aiControlDisabled = 2
+ aiControlDisabled = AICONTROLDISABLED_BYPASS
to_chat(user, "Receiving control information from airlock.")
sleep(10)
//bring up airlock dialog
@@ -737,125 +756,136 @@ About the new airlock wires panel:
else
try_to_activate_door(user)
-/obj/machinery/door/airlock/CanUseTopic(mob/user)
- if(!issilicon(user) && !isobserver(user))
- return STATUS_CLOSE
-
+/obj/machinery/door/airlock/proc/ai_control_check(mob/user)
+ if(!issilicon(user))
+ return TRUE
if(emagged)
to_chat(user, "Unable to interface: Internal error.")
- return STATUS_CLOSE
- if(!canAIControl() && !isobserver(user))
+ return FALSE
+ if(!canAIControl())
if(canAIHack(user))
hack(user)
else
- if(isAllPowerLoss()) //don't really like how this gets checked a second time, but not sure how else to do it.
+ if(isAllPowerLoss())
to_chat(user, "Unable to interface: Connection timed out.")
else
to_chat(user, "Unable to interface: Connection refused.")
- return STATUS_CLOSE
+ return FALSE
+ return TRUE
- return ..()
-
-/obj/machinery/door/airlock/Topic(href, href_list, nowindow = 0)
+/obj/machinery/door/airlock/tgui_act(action, params)
if(..())
- return 1
-
- var/activate = text2num(href_list["activate"])
- switch(href_list["command"])
- if("idscan")
- if(isWireCut(AIRLOCK_WIRE_IDSCAN))
- to_chat(usr, "The IdScan wire has been cut - IdScan feature permanently disabled.")
- else if(activate && aiDisabledIdScanner)
- aiDisabledIdScanner = 0
- to_chat(usr, "IdScan feature has been enabled.")
- else if(!activate && !aiDisabledIdScanner)
- aiDisabledIdScanner = 1
- to_chat(usr, "IdScan feature has been disabled.")
- if("main_power")
+ return
+ if(!issilicon(usr) && !usr.can_admin_interact())
+ to_chat(usr, "Access denied. Only silicons may use this interface.")
+ return
+ if(!ai_control_check(usr))
+ return
+ . = TRUE
+ switch(action)
+ if("disrupt-main")
if(!main_power_lost_until)
loseMainPower()
update_icon()
- if("backup_power")
+ else
+ to_chat(usr, "Main power is already offline.")
+ . = FALSE
+ if("disrupt-backup")
if(!backup_power_lost_until)
loseBackupPower()
update_icon()
- if("bolts")
- if(isWireCut(AIRLOCK_WIRE_DOOR_BOLTS))
- to_chat(usr, "The door bolt control wire has been cut - Door bolts permanently dropped.")
- else if(activate && lock())
- to_chat(usr, "The door bolts have been dropped.")
- else if(!activate && unlock())
- to_chat(usr, "The door bolts have been raised.")
- if("electrify_temporary")
- if(activate && isWireCut(AIRLOCK_WIRE_ELECTRIFY))
- to_chat(usr, text("The electrification wire is cut - Door permanently electrified."))
- else if(!activate && electrified_until != 0)
- to_chat(usr, "The door is now un-electrified.")
- electrify(0)
- else if(activate) //electrify door for 30 seconds
- shockedby += text("\[[time_stamp()]\][usr](ckey:[usr.ckey])")
- usr.create_attack_log("Electrified the [name] at [x] [y] [z]")
- add_attack_logs(usr, src, "Electrified", ATKLOG_ALL)
- to_chat(usr, "The door is now electrified for thirty seconds.")
- electrify(30)
- if("electrify_permanently")
- if(isWireCut(AIRLOCK_WIRE_ELECTRIFY))
- to_chat(usr, text("The electrification wire is cut - Cannot electrify the door."))
- else if(!activate && electrified_until != 0)
- to_chat(usr, "The door is now un-electrified.")
- electrify(0)
- else if(activate)
- shockedby += text("\[[time_stamp()]\][usr](ckey:[usr.ckey])")
- usr.create_attack_log("Electrified the [name] at [x] [y] [z]")
- add_attack_logs(usr, src, "Electrified", ATKLOG_ALL)
- to_chat(usr, "The door is now electrified.")
- electrify(-1)
- if("open")
- if(welded)
- to_chat(usr, text("The airlock has been welded shut!"))
- else if(locked)
- to_chat(usr, text("The door bolts are down!"))
- else if(activate && density)
- open()
- else if(!activate && !density)
- close()
- if("safeties")
- // Safeties! We don't need no stinking safeties!
- if(isWireCut(AIRLOCK_WIRE_SAFETY))
- to_chat(usr, text("The safety wire is cut - Cannot secure the door."))
- else if(activate && safe)
- safe = 0
- else if(!activate && !safe)
- safe = 1
- if("timing")
- // Door speed control
- if(isWireCut(AIRLOCK_WIRE_SPEED))
- to_chat(usr, text("The timing wire is cut - Cannot alter timing."))
- else if(activate && normalspeed)
- normalspeed = 0
- else if(!activate && !normalspeed)
- normalspeed = 1
- if("lights")
- // Bolt lights
- if(isWireCut(AIRLOCK_WIRE_LIGHT))
- to_chat(usr, "The bolt lights wire has been cut - The door bolt lights are permanently disabled.")
- else if(!activate && lights)
- lights = 0
- to_chat(usr, "The door bolt lights have been disabled.")
- else if(activate && !lights)
- lights = 1
- to_chat(usr, "The door bolt lights have been enabled.")
- update_icon()
- if("emergency")
- // Emergency access
- if(emergency)
- emergency = 0
- to_chat(usr, "Emergency access has been disabled.")
else
- emergency = 1
- to_chat(usr, "Emergency access has been enabled.")
- update_icon()
- return 1
+ to_chat(usr, "Backup power is already offline.")
+ if("shock-restore")
+ electrify(0, usr, TRUE)
+ if("shock-temp")
+ if(wires.is_cut(WIRE_ELECTRIFY))
+ to_chat(usr, "The electrification wire is cut - Door permanently electrified.")
+ . = FALSE
+ else
+ //electrify door for 30 seconds
+ electrify(30, usr, TRUE)
+ if("shock-perm")
+ if(wires.is_cut(WIRE_ELECTRIFY))
+ to_chat(usr, "The electrification wire is cut - Cannot electrify the door.")
+ . = FALSE
+ else
+ electrify(-1, usr, TRUE)
+ if("idscan-toggle")
+ if(wires.is_cut(WIRE_IDSCAN))
+ to_chat(usr, "The IdScan wire has been cut - IdScan feature permanently disabled.")
+ . = FALSE
+ else if(aiDisabledIdScanner)
+ aiDisabledIdScanner = FALSE
+ to_chat(usr, "IdScan feature has been enabled.")
+ else
+ aiDisabledIdScanner = TRUE
+ to_chat(usr, "IdScan feature has been disabled.")
+ if("emergency-toggle")
+ toggle_emergency_status(usr)
+ if("bolt-toggle")
+ toggle_bolt(usr)
+ if("light-toggle")
+ toggle_light(usr)
+ if("safe-toggle")
+ if(wires.is_cut(WIRE_SAFETY))
+ to_chat(usr, "The safety wire is cut - Cannot secure the door.")
+ else if(safe)
+ safe = 0
+ to_chat(usr, "The door safeties have been disabled.")
+ else
+ safe = 1
+ to_chat(usr, "The door safeties have been enabled.")
+ if("speed-toggle")
+ if(wires.is_cut(WIRE_SPEED))
+ to_chat(usr, "The timing wire is cut - Cannot alter timing.")
+ else if(normalspeed)
+ normalspeed = 0
+ else
+ normalspeed = 1
+ if("open-close")
+ open_close(usr)
+ else
+ . = FALSE
+
+/obj/machinery/door/airlock/proc/open_close(mob/user)
+ if(welded)
+ to_chat(user, "The airlock has been welded shut!")
+ return FALSE
+ else if(locked)
+ to_chat(user, "The door bolts are down!")
+ return FALSE
+ else if(density)
+ return open()
+ else
+ return close()
+
+/obj/machinery/door/airlock/proc/toggle_light(mob/user)
+ if(wires.is_cut(WIRE_BOLT_LIGHT))
+ to_chat(user, "The bolt lights wire has been cut - The door bolt lights are permanently disabled.")
+ else if(lights)
+ lights = FALSE
+ to_chat(user, "The door bolt lights have been disabled.")
+ else if(!lights)
+ lights = TRUE
+ to_chat(user, "The door bolt lights have been enabled.")
+ update_icon()
+
+/obj/machinery/door/airlock/proc/toggle_bolt(mob/user)
+ if(wires.is_cut(WIRE_DOOR_BOLTS))
+ to_chat(user, "The door bolt control wire has been cut - Door bolts permanently dropped.")
+ else if(lock())
+ to_chat(user, "The door bolts have been dropped.")
+ else if(unlock())
+ to_chat(user, "The door bolts have been raised.")
+
+/obj/machinery/door/airlock/proc/toggle_emergency_status(mob/user)
+ emergency = !emergency
+ if(emergency)
+ to_chat(user, "Emergency access has been enabled.")
+ else
+ to_chat(user, "Emergency access has been disabled.")
+ update_icon()
/obj/machinery/door/airlock/attackby(obj/item/C, mob/user, params)
add_fingerprint(user)
@@ -1064,7 +1094,7 @@ About the new airlock wires panel:
return
return TRUE
-/obj/machinery/door/airlock/try_to_crowbar(mob/living/user, obj/item/I) //*scream
+/obj/machinery/door/airlock/try_to_crowbar(mob/living/user, obj/item/I)
if(operating)
return
if(istype(I, /obj/item/twohanded/fireaxe)) //let's make this more specific //FUCK YOU
@@ -1126,7 +1156,7 @@ About the new airlock wires panel:
if(operating || welded || locked || emagged)
return 0
if(!forced)
- if(!arePowerSystemsOn() || isWireCut(AIRLOCK_WIRE_OPEN_DOOR))
+ if(!arePowerSystemsOn() || wires.is_cut(WIRE_OPEN_DOOR))
return 0
use_power(360) //360 W seems much more appropriate for an actuator moving an industrial door capable of crushing people
if(forced)
@@ -1163,7 +1193,7 @@ About the new airlock wires panel:
if(!forced)
//despite the name, this wire is for general door control.
//Bolts are already covered by the check for locked, above
- if(!arePowerSystemsOn() || isWireCut(AIRLOCK_WIRE_OPEN_DOOR))
+ if(!arePowerSystemsOn() || wires.is_cut(WIRE_OPEN_DOOR))
return
if(safe)
for(var/turf/turf in locs)
@@ -1219,7 +1249,7 @@ About the new airlock wires panel:
return
if(!forced)
- if(operating || !arePowerSystemsOn() || isWireCut(AIRLOCK_WIRE_DOOR_BOLTS))
+ if(operating || !arePowerSystemsOn() || wires.is_cut(WIRE_DOOR_BOLTS))
return
locked = 0
@@ -1245,8 +1275,10 @@ About the new airlock wires panel:
return 1
/obj/machinery/door/airlock/emp_act(severity)
- ..()
- if(prob(40/severity))
+ . = ..()
+ if(prob(20 / severity))
+ open()
+ if(prob(40 / severity))
var/duration = world.time + (30 / severity) SECONDS
if(duration > electrified_until)
electrify(duration)
@@ -1314,7 +1346,7 @@ About the new airlock wires panel:
stat |= BROKEN
if(!panel_open)
panel_open = TRUE
- wires.CutAll()
+ wires.cut_all()
update_icon()
/obj/machinery/door/airlock/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
@@ -1407,6 +1439,12 @@ About the new airlock wires panel:
A.name = name
qdel(src)
+/obj/machinery/door/airlock/proc/ai_control_callback()
+ if(aiControlDisabled == AICONTROLDISABLED_ON)
+ aiControlDisabled = AICONTROLDISABLED_OFF
+ else if(aiControlDisabled == AICONTROLDISABLED_BYPASS)
+ aiControlDisabled = AICONTROLDISABLED_PERMA
+
#undef AIRLOCK_CLOSED
#undef AIRLOCK_CLOSING
#undef AIRLOCK_OPEN
@@ -1426,3 +1464,7 @@ About the new airlock wires panel:
#undef AIRLOCK_INTEGRITY_MULTIPLIER
#undef AIRLOCK_DAMAGE_DEFLECTION_N
#undef AIRLOCK_DAMAGE_DEFLECTION_R
+
+#undef TGUI_GREEN
+#undef TGUI_ORANGE
+#undef TGUI_RED
diff --git a/code/game/machinery/doors/airlock_types.dm b/code/game/machinery/doors/airlock_types.dm
index dd9581a5af3..1d194507357 100644
--- a/code/game/machinery/doors/airlock_types.dm
+++ b/code/game/machinery/doors/airlock_types.dm
@@ -365,14 +365,14 @@
assemblytype = /obj/structure/door_assembly/door_assembly_vault
security_level = 6
hackProof = TRUE
- aiControlDisabled = TRUE
+ aiControlDisabled = AICONTROLDISABLED_ON
/obj/machinery/door/airlock/hatch/gamma
name = "gamma level hatch"
- hackProof = 1
- aiControlDisabled = 1
+ hackProof = TRUE
+ aiControlDisabled = AICONTROLDISABLED_ON
resistance_flags = FIRE_PROOF | ACID_PROOF
- is_special = 1
+ is_special = TRUE
/obj/machinery/door/airlock/hatch/gamma/attackby(obj/C, mob/user, params)
if(!issilicon(user))
@@ -432,8 +432,8 @@
/obj/machinery/door/airlock/highsecurity/red
name = "secure armory airlock"
- hackProof = 1
- aiControlDisabled = 1
+ hackProof = TRUE
+ aiControlDisabled = AICONTROLDISABLED_ON
/obj/machinery/door/airlock/highsecurity/red/attackby(obj/C, mob/user, params)
if(!issilicon(user))
@@ -487,7 +487,7 @@
damage_deflection = 30
explosion_block = 3
hackProof = TRUE
- aiControlDisabled = 1
+ aiControlDisabled = AICONTROLDISABLED_ON
normal_integrity = 700
security_level = 1
paintable = FALSE
@@ -504,7 +504,7 @@
assemblytype = /obj/structure/door_assembly/door_assembly_cult
damage_deflection = 10
hackProof = TRUE
- aiControlDisabled = TRUE
+ aiControlDisabled = AICONTROLDISABLED_ON
paintable = FALSE
var/openingoverlaytype = /obj/effect/temp_visual/cult/door
var/friendly = FALSE
diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm
index 9970974e4ff..b2f061e1d67 100644
--- a/code/game/machinery/doors/brigdoors.dm
+++ b/code/game/machinery/doors/brigdoors.dm
@@ -36,9 +36,10 @@
var/crimes = CELL_NONE
var/time = 0
var/officer = CELL_NONE
- var/prisoner_name = ""
- var/prisoner_charge = ""
- var/prisoner_time = ""
+ var/prisoner_name
+ var/prisoner_charge
+ var/prisoner_time
+ var/prisoner_hasrecord = FALSE
/obj/machinery/door_timer/New()
GLOB.celltimers_list += src
@@ -82,12 +83,17 @@
var/datum/data/record/R = find_security_record("name", occupant)
- var/announcetext = "Detainee [occupant] ([prisoner_drank]) has been incarcerated for [seconds_to_time(timetoset / 10)] for the charges of: '[crimes]'. \
+ var/timetext = seconds_to_time(timetoset / 10)
+ var/announcetext = "Detainee [occupant] ([prisoner_drank]) has been incarcerated for [timetext] for the crime of: '[crimes]'. \
Arresting Officer: [usr.name].[R ? "" : " Detainee record not found, manual record update required."]"
Radio.autosay(announcetext, name, "Security", list(z))
- if(prisoner_trank != "unknown")
- notify_dept_head(prisoner_trank, announcetext)
+ // Notify the actual criminal being brigged. This is a QOL thing to ensure they always know the charges against them.
+ // Announcing it on radio isn't enough, as they're unlikely to have sec radio.
+ notify_prisoner("You have been incarcerated for [timetext] for the crime of: '[crimes]'.")
+
+ if(prisoner_trank != "unknown" && prisoner_trank != "Civilian")
+ SSjobs.notify_dept_head(prisoner_trank, announcetext)
if(R)
prisoner = R
@@ -104,30 +110,12 @@
update_all_mob_security_hud()
return 1
-
-/obj/machinery/door_timer/proc/notify_dept_head(jobtitle, antext)
- if(!jobtitle || !antext)
- return
- if(jobtitle == "Civilian")
- // Don't notify the HoP about greytiding civilians
- return
- var/datum/job/brigged_job = SSjobs.GetJob(jobtitle)
- if(!brigged_job)
- return
- if(!brigged_job.department_head[1])
- return
- var/boss_title = brigged_job.department_head[1]
-
- var/obj/item/pda/target_pda
- for(var/obj/item/pda/check_pda in GLOB.PDAs)
- if(check_pda.ownrank == boss_title)
- target_pda = check_pda
- if(!target_pda)
- return
- var/datum/data/pda/app/messenger/PM = target_pda.find_program(/datum/data/pda/app/messenger)
- if(PM && PM.can_receive())
- PM.notify("Message from Brig Timer (Automated), \"[antext]\" (Unable to Reply)")
-
+/obj/machinery/door_timer/proc/notify_prisoner(notifytext)
+ for(var/mob/living/carbon/human/H in range(4, get_turf(src)))
+ if(occupant == H.name)
+ to_chat(H, "[src] beeps, \"[notifytext]\"")
+ return
+ atom_say("[src] beeps, \"[occupant]: [notifytext]\"")
/obj/machinery/door_timer/Initialize()
..()
@@ -238,6 +226,7 @@
occupant = CELL_NONE
crimes = CELL_NONE
time = 0
+ timetoset = 0
officer = CELL_NONE
releasetime = 0
printed = 0
@@ -249,8 +238,7 @@
for(var/obj/machinery/door/window/brigdoor/door in targets)
if(!door.density)
continue
- spawn(0)
- door.open()
+ INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/open)
for(var/obj/structure/closet/secure_closet/brig/C in targets)
if(C.broken)
@@ -286,13 +274,11 @@
return
-//Allows AIs to use door_timer, see human attack_hand function below
/obj/machinery/door_timer/attack_ai(mob/user)
- attack_hand(user)
- ui_interact(user)
+ tgui_interact(user)
/obj/machinery/door_timer/attack_ghost(mob/user)
- ui_interact(user)
+ tgui_interact(user)
//Allows humans to use door_timer
//Opens dialog window when someone clicks on door timer
@@ -301,71 +287,109 @@
/obj/machinery/door_timer/attack_hand(mob/user)
if(..())
return
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/door_timer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/door_timer/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "brig_timer.tmpl", "Brig Timer", 500, 400)
+ ui = new(user, src, ui_key, "BrigTimer", name, 500, 450, master_ui, state)
ui.open()
- ui.set_auto_update(TRUE)
-/obj/machinery/door_timer/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
+/obj/machinery/door_timer/tgui_static_data(mob/user)
+ var/list/data = list()
+ data["spns"] = list()
+ for(var/mob/living/carbon/human/H in range(4, get_turf(src)))
+ if(H.handcuffed)
+ data["spns"] += H.name
+ return data
+
+/obj/machinery/door_timer/tgui_data(mob/user)
+ var/list/data = list()
data["cell_id"] = name
data["occupant"] = occupant
data["crimes"] = crimes
data["brigged_by"] = officer
- data["time_set"] = seconds_to_clock(time / 10)
+ data["time_set"] = seconds_to_clock(timetoset / 10)
data["time_left"] = seconds_to_clock(timeleft())
data["timing"] = timing
data["isAllowed"] = allowed(user)
data["prisoner_name"] = prisoner_name
data["prisoner_charge"] = prisoner_charge
data["prisoner_time"] = prisoner_time
-
+ data["prisoner_hasrec"] = prisoner_hasrecord
return data
-/obj/machinery/door_timer/Topic(href, href_list)
- if(!allowed(usr) && !usr.can_admin_interact())
- return 1
+/obj/machinery/door_timer/allowed(mob/user)
+ if(user.can_admin_interact())
+ return TRUE
+ return ..()
- if(href_list["flash"])
- for(var/obj/machinery/flasher/F in targets)
- if(F.last_flash && (F.last_flash + 150) > world.time)
- to_chat(usr, "Flash still charging.")
+/obj/machinery/door_timer/tgui_act(action, params)
+ if(..())
+ return
+ if(!allowed(usr))
+ to_chat(usr, "Access denied.")
+ return
+ . = TRUE
+ switch(action)
+ if("prisoner_name")
+ if(params["prisoner_name"])
+ prisoner_name = params["prisoner_name"]
else
- F.flash()
-
- if(href_list["release"])
- if(timing)
- timer_end()
- Radio.autosay("Timer stopped manually from cell control.", name, "Security", list(z))
- ui_interact(usr)
-
- if(href_list["prisoner_name"])
- prisoner_name = input("Prisoner Name:", name, prisoner_name) as text|null
-
- if(href_list["prisoner_charge"])
- prisoner_charge = input("Prisoner Charge:", name, prisoner_charge) as text|null
-
- if(href_list["prisoner_time"])
- prisoner_time = input("Prisoner Time (in minutes):", name, prisoner_time) as num|null
- prisoner_time = min(max(round(prisoner_time), 0), 60)
-
- if(href_list["set_timer"])
- if(!prisoner_name || !prisoner_charge || !prisoner_time)
- return
- timeset(prisoner_time * 60)
- occupant = prisoner_name
- crimes = prisoner_charge
- prisoner_name = ""
- prisoner_charge = ""
- prisoner_time = ""
- timing = TRUE
- timer_start()
- ui_interact(usr)
- update_icon()
+ prisoner_name = input("Prisoner Name:", name, prisoner_name) as text|null
+ if(prisoner_name)
+ var/datum/data/record/R = find_security_record("name", prisoner_name)
+ if(istype(R))
+ prisoner_hasrecord = TRUE
+ else
+ prisoner_hasrecord = FALSE
+ if("prisoner_charge")
+ prisoner_charge = input("Prisoner Charge:", name, prisoner_charge) as text|null
+ if("prisoner_time")
+ prisoner_time = input("Prisoner Time (in minutes):", name, prisoner_time) as num|null
+ prisoner_time = min(max(round(prisoner_time), 0), 60)
+ if("start")
+ if(!prisoner_name || !prisoner_charge || !prisoner_time)
+ return FALSE
+ timeset(prisoner_time * 60)
+ occupant = prisoner_name
+ crimes = prisoner_charge
+ prisoner_name = null
+ prisoner_charge = null
+ prisoner_time = null
+ timing = TRUE
+ timer_start()
+ update_icon()
+ if("restart_timer")
+ if(timing)
+ var/reset_reason = sanitize(copytext(input(usr, "Reason for resetting timer:", name, "") as text|null, 1, MAX_MESSAGE_LEN))
+ if(!reset_reason)
+ to_chat(usr, "Cancelled reset: reason field is required.")
+ return FALSE
+ releasetime = world.timeofday + timetoset
+ var/resettext = isobserver(usr) ? "for: [reset_reason]." : "by [usr.name] for: [reset_reason]."
+ Radio.autosay("Prisoner [occupant] had their timer reset [resettext]", name, "Security", list(z))
+ notify_prisoner("Your brig timer has been reset for: '[reset_reason]'.")
+ var/datum/data/record/R = find_security_record("name", occupant)
+ if(istype(R))
+ R.fields["comments"] += "Autogenerated by [name] on [GLOB.current_date_string] [station_time_timestamp()] Timer reset [resettext]"
+ else
+ . = FALSE
+ if("stop")
+ if(timing)
+ timer_end()
+ var/stoptext = isobserver(usr) ? "from cell control." : "by [usr.name]."
+ Radio.autosay("Timer stopped manually [stoptext]", name, "Security", list(z))
+ else
+ . = FALSE
+ if("flash")
+ for(var/obj/machinery/flasher/F in targets)
+ if(F.last_flash && (F.last_flash + 150) > world.time)
+ to_chat(usr, "Flash still charging.")
+ else
+ F.flash()
+ else
+ . = FALSE
//icon update function
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 2d6fac3bb81..a86ea7d4a6b 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -20,7 +20,7 @@
var/locked = FALSE //whether the door is bolted or not.
var/glass = FALSE
var/welded = FALSE
- var/normalspeed = 1
+ var/normalspeed = TRUE
var/auto_close_time = 150
var/auto_close_time_dangerous = 15
var/assemblytype //the type of door frame to drop during deconstruction
@@ -234,12 +234,6 @@
emagged = 1
return 1
-/obj/machinery/door/emp_act(severity)
- if(prob(20/severity) && (istype(src,/obj/machinery/door/airlock) || istype(src,/obj/machinery/door/window)) )
- spawn(0)
- open()
- ..()
-
/obj/machinery/door/update_icon()
if(density)
icon_state = "door1"
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index 37dbdb6c9c0..c509675868b 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -28,6 +28,11 @@
var/nextstate = null
var/boltslocked = TRUE
var/active_alarm = FALSE
+ var/list/affecting_areas
+
+/obj/machinery/door/firedoor/Initialize(mapload)
+ . = ..()
+ CalculateAffectingAreas()
/obj/machinery/door/firedoor/examine(mob/user)
. = ..()
@@ -40,11 +45,31 @@
else
. += "The bolt locks have been unscrewed, but the bolts themselves are still wrenched to the floor."
+/obj/machinery/door/firedoor/proc/CalculateAffectingAreas()
+ remove_from_areas()
+ affecting_areas = get_adjacent_open_areas(src) | get_area(src)
+ for(var/I in affecting_areas)
+ var/area/A = I
+ LAZYADD(A.firedoors, src)
+
/obj/machinery/door/firedoor/closed
icon_state = "door_closed"
opacity = TRUE
density = TRUE
+//see also turf/AfterChange for adjacency shennanigans
+
+/obj/machinery/door/firedoor/proc/remove_from_areas()
+ if(affecting_areas)
+ for(var/I in affecting_areas)
+ var/area/A = I
+ LAZYREMOVE(A.firedoors, src)
+
+/obj/machinery/door/firedoor/Destroy()
+ remove_from_areas()
+ affecting_areas.Cut()
+ return ..()
+
/obj/machinery/door/firedoor/Bumped(atom/AM)
if(panel_open || operating)
return
diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm
index e423f92683c..f27d4bf34a8 100644
--- a/code/game/machinery/doors/windowdoor.dm
+++ b/code/game/machinery/doors/windowdoor.dm
@@ -57,6 +57,11 @@
if(emagged)
. += "Its access panel is smoking slightly."
+/obj/machinery/door/window/emp_act(severity)
+ . = ..()
+ if(prob(20 / severity))
+ open()
+
/obj/machinery/door/window/proc/open_and_close()
open()
if(check_access(null))
diff --git a/code/game/machinery/embedded_controller/airlock_program.dm b/code/game/machinery/embedded_controller/airlock_program.dm
index 8ad85a608f0..d9500e30808 100644
--- a/code/game/machinery/embedded_controller/airlock_program.dm
+++ b/code/game/machinery/embedded_controller/airlock_program.dm
@@ -305,7 +305,7 @@
signalDoor(tag_exterior_door, command)
signalDoor(tag_interior_door, command)
-datum/computer/file/embedded_program/airlock/proc/signal_mech_sensor(var/command, var/sensor)
+/datum/computer/file/embedded_program/airlock/proc/signal_mech_sensor(var/command, var/sensor)
var/datum/signal/signal = new
signal.data["tag"] = sensor
signal.data["command"] = command
diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm
index f12361f1664..137dcb50110 100644
--- a/code/game/machinery/firealarm.dm
+++ b/code/game/machinery/firealarm.dm
@@ -25,7 +25,11 @@ FIRE ALARM
active_power_usage = 6
power_channel = ENVIRON
resistance_flags = FIRE_PROOF
- var/last_process = 0
+
+ light_power = 0
+ light_range = 7
+ light_color = "#ff3232"
+
var/wiresexposed = 0
var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone
@@ -79,7 +83,8 @@ FIRE ALARM
return attack_hand(user)
/obj/machinery/firealarm/attack_ghost(mob/user)
- ui_interact(user)
+ if(user.can_admin_interact())
+ toggle_alarm(user)
/obj/machinery/firealarm/emp_act(severity)
if(prob(50/severity))
@@ -191,6 +196,7 @@ FIRE ALARM
/obj/machinery/firealarm/obj_break(damage_flag)
if(!(stat & BROKEN) && !(flags & NODECONSTRUCT) && buildstage != 0) //can't break the electronics if there isn't any inside.
stat |= BROKEN
+ LAZYREMOVE(myArea.firealarms, src)
update_icon()
/obj/machinery/firealarm/deconstruct(disassembled = TRUE)
@@ -203,20 +209,13 @@ FIRE ALARM
new /obj/item/stack/cable_coil(loc, 3)
qdel(src)
-/obj/machinery/firealarm/process()//Note: this processing was mostly phased out due to other code, and only runs when needed
- if(stat & (NOPOWER|BROKEN))
- return
-
- if(timing)
- if(time > 0)
- time = time - ((world.timeofday - last_process)/10)
- else
- alarm()
- time = 0
- timing = 0
- STOP_PROCESSING(SSobj, src)
- updateDialog()
- last_process = world.timeofday
+/obj/machinery/firealarm/proc/update_fire_light(fire)
+ if(fire == !!light_power)
+ return // do nothing if we're already active
+ if(fire)
+ set_light(l_power = 0.8)
+ else
+ set_light(l_power = 0)
/obj/machinery/firealarm/power_change()
if(powered(ENVIRON))
@@ -234,78 +233,33 @@ FIRE ALARM
if(user.incapacitated())
return 1
- ui_interact(user)
+ toggle_alarm(user)
-/obj/machinery/firealarm/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, var/master_ui = null, var/datum/topic_state/state = GLOB.default_state)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "firealarm.tmpl", name, 400, 400, state = state)
- ui.open()
- ui.set_auto_update(1)
-
-/obj/machinery/firealarm/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
+/obj/machinery/firealarm/proc/toggle_alarm(mob/user)
var/area/A = get_area(src)
- data["fire"] = A.fire
- data["timing"] = timing
+ if(istype(A))
+ add_fingerprint(user)
+ if(A.fire)
+ reset()
+ else
+ alarm()
- data["sec_level"] = get_security_level()
-
- var/second = round(time % 60)
- var/minute = round(time / 60)
-
- data["time_left"] = "[minute ? "[minute]:" : ""][add_zero(num2text(second), 2)]"
- return data
-
-/obj/machinery/firealarm/Topic(href, href_list)
- if(..())
- return 1
-
- if(buildstage != 2)
- return 1
-
- add_fingerprint(usr)
-
- if(href_list["reset"])
- reset()
- else if(href_list["alarm"])
- alarm()
- else if(href_list["time"])
- var/oldTiming = timing
- timing = text2num(href_list["time"])
- last_process = world.timeofday
- if(oldTiming != timing)
- if(timing)
- START_PROCESSING(SSobj, src)
- else
- STOP_PROCESSING(SSobj, src)
- else if(href_list["tp"])
- var/tp = text2num(href_list["tp"])
- time += tp
- time = min(max(round(time), 0), 120)
+/obj/machinery/firealarm/examine(mob/user)
+ . = ..()
+ . += "It shows the alert level as: [capitalize(get_security_level())]."
/obj/machinery/firealarm/proc/reset()
- if(!working)
+ if(!working || !report_fire_alarms)
return
var/area/A = get_area(src)
- A.fire_reset()
+ A.firereset(src)
- for(var/obj/machinery/firealarm/FA in A)
- if(is_station_contact(z) && FA.report_fire_alarms)
- SSalarms.fire_alarm.clearAlarm(loc, FA)
-
-/obj/machinery/firealarm/proc/alarm(var/duration = 0)
- if(!working)
+/obj/machinery/firealarm/proc/alarm()
+ if(!working || !report_fire_alarms)
return
-
var/area/A = get_area(src)
- for(var/obj/machinery/firealarm/FA in A)
- if(is_station_contact(z) && FA.report_fire_alarms)
- SSalarms.fire_alarm.triggerAlarm(loc, FA, duration)
- else
- A.fire_alert() // Manually trigger alarms if the alarm isn't reported
-
+ A.firealert(src) // Manually trigger alarms if the alarm isn't reported
update_icon()
/obj/machinery/firealarm/New(location, direction, building)
@@ -323,8 +277,14 @@ FIRE ALARM
else
overlays += image('icons/obj/monitors.dmi', "overlay_green")
+ myArea = get_area(src)
+ LAZYADD(myArea.firealarms, src)
update_icon()
+/obj/machinery/firealarm/Destroy()
+ LAZYREMOVE(myArea.firealarms, src)
+ return ..()
+
/*
FIRE ALARM CIRCUIT
Just a object used in constructing fire alarms
diff --git a/code/game/machinery/flasher.dm b/code/game/machinery/flasher.dm
index 37766db58fb..369d43e5d94 100644
--- a/code/game/machinery/flasher.dm
+++ b/code/game/machinery/flasher.dm
@@ -25,20 +25,17 @@
base_state = "pflash"
density = 1
-/*
-/obj/machinery/flasher/New()
- sleep(4) //<--- What the fuck are you doing? D=
- sd_set_light(2)
-*/
+/obj/machinery/flasher/portable/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/proximity_monitor)
+
/obj/machinery/flasher/power_change()
if( powered() )
stat &= ~NOPOWER
icon_state = "[base_state]1"
-// sd_set_light(2)
else
stat |= ~NOPOWER
icon_state = "[base_state]1-p"
-// sd_set_light(0)
//Let the AI trigger them directly.
/obj/machinery/flasher/attack_ai(mob/user)
@@ -81,7 +78,7 @@
flash()
..(severity)
-/obj/machinery/flasher/portable/HasProximity(atom/movable/AM as mob|obj)
+/obj/machinery/flasher/portable/HasProximity(atom/movable/AM)
if((disable) || (last_flash && world.time < last_flash + 150))
return
@@ -109,7 +106,7 @@
if(anchored)
WRENCH_ANCHOR_MESSAGE
overlays.Cut()
- else if(anchored)
+ else
WRENCH_UNANCHOR_MESSAGE
overlays += "[base_state]-s"
diff --git a/code/game/machinery/floodlight.dm b/code/game/machinery/floodlight.dm
index 0cc6dda7098..b4babfbabfd 100644
--- a/code/game/machinery/floodlight.dm
+++ b/code/game/machinery/floodlight.dm
@@ -146,3 +146,4 @@
/obj/machinery/floodlight/extinguish_light()
on = 0
set_light(0)
+ update_icon()
diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm
index f02e037e8c5..ad164fe720a 100644
--- a/code/game/machinery/machinery.dm
+++ b/code/game/machinery/machinery.dm
@@ -310,6 +310,12 @@ Class Procs:
return ..()
+/obj/machinery/tgui_status(mob/user, datum/tgui_state/state)
+ if(!interact_offline && (stat & (NOPOWER|BROKEN)))
+ return STATUS_CLOSE
+
+ return ..()
+
/obj/machinery/CouldUseTopic(var/mob/user)
..()
user.set_machine(src)
diff --git a/code/game/machinery/overview.dm b/code/game/machinery/overview.dm
index 7165cceb1ff..7d8910d6ef5 100644
--- a/code/game/machinery/overview.dm
+++ b/code/game/machinery/overview.dm
@@ -342,13 +342,13 @@
return
-proc/getr(col)
+/proc/getr(col)
return hex2num( copytext(col, 2,4))
-proc/getg(col)
+/proc/getg(col)
return hex2num( copytext(col, 4,6))
-proc/getb(col)
+/proc/getb(col)
return hex2num( copytext(col, 6))
diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm
index 42452a9f8e2..a8c9710ff6c 100644
--- a/code/game/machinery/pipe/construction.dm
+++ b/code/game/machinery/pipe/construction.dm
@@ -106,10 +106,6 @@
else if(istype(make_from, /obj/machinery/atmospherics/unary/passive_vent))
src.pipe_type = PIPE_PASV_VENT
- else if(istype(make_from, /obj/machinery/atmospherics/omni/mixer))
- src.pipe_type = PIPE_OMNI_MIXER
- else if(istype(make_from, /obj/machinery/atmospherics/omni/filter))
- src.pipe_type = PIPE_OMNI_FILTER
else if(istype(make_from, /obj/machinery/atmospherics/binary/circulator))
src.pipe_type = PIPE_CIRCULATOR
@@ -259,7 +255,7 @@
return dir //dir|acw
if(PIPE_CONNECTOR, PIPE_UVENT, PIPE_PASV_VENT, PIPE_SCRUBBER, PIPE_HEAT_EXCHANGE, PIPE_INJECTOR)
return dir|flip
- if(PIPE_MANIFOLD4W, PIPE_SUPPLY_MANIFOLD4W, PIPE_SCRUBBERS_MANIFOLD4W, PIPE_OMNI_MIXER, PIPE_OMNI_FILTER)
+ if(PIPE_MANIFOLD4W, PIPE_SUPPLY_MANIFOLD4W, PIPE_SCRUBBERS_MANIFOLD4W)
return dir|flip|cw|acw
if(PIPE_MANIFOLD, PIPE_SUPPLY_MANIFOLD, PIPE_SCRUBBERS_MANIFOLD)
return flip|cw|acw
@@ -317,7 +313,7 @@
dir = 1
else if(dir==8)
dir = 4
- else if(pipe_type in list(PIPE_MANIFOLD4W, PIPE_SUPPLY_MANIFOLD4W, PIPE_SCRUBBERS_MANIFOLD4W, PIPE_OMNI_MIXER, PIPE_OMNI_FILTER))
+ else if(pipe_type in list(PIPE_MANIFOLD4W, PIPE_SUPPLY_MANIFOLD4W, PIPE_SCRUBBERS_MANIFOLD4W))
dir = 2
/obj/item/pipe/attack_self(mob/user as mob)
@@ -499,14 +495,6 @@
P.name = pipename
P.on_construction(dir, pipe_dir, color)
- if(PIPE_OMNI_MIXER)
- var/obj/machinery/atmospherics/omni/mixer/P = new(loc)
- P.on_construction(dir, pipe_dir, color)
-
- if(PIPE_OMNI_FILTER)
- var/obj/machinery/atmospherics/omni/filter/P = new(loc)
- P.on_construction(dir, pipe_dir, color)
-
user.visible_message( \
"[user] fastens the [src].", \
"You have fastened the [src].", \
diff --git a/code/game/machinery/poolcontroller.dm b/code/game/machinery/poolcontroller.dm
index b910b1272ff..777aa155a62 100644
--- a/code/game/machinery/poolcontroller.dm
+++ b/code/game/machinery/poolcontroller.dm
@@ -15,7 +15,6 @@
var/decalinpool = list() // List containing all of the cleanable decals in pool
var/linked_area = null
var/temperature = NORMAL //The temperature of the pool, starts off on normal, which has no effects.
- var/temperaturecolor = "" //used for nanoUI fancyness
var/srange = 5 //The range of the search for pool turfs, change this for bigger or smaller pools.
var/list/linkedmist = list() //Used to keep track of created mist
var/deep_water = FALSE //set to 1 to drown even standing people
@@ -67,8 +66,8 @@
else
to_chat(user, "Nothing happens.")//If not emagged, don't do anything, and don't tell the user that it can be emagged.
-/obj/machinery/poolcontroller/attack_hand(mob/user as mob)
- ui_interact(user)
+/obj/machinery/poolcontroller/attack_hand(mob/user)
+ tgui_interact(user)
/obj/machinery/poolcontroller/process()
processMob() //Call the mob affecting proc
@@ -151,64 +150,74 @@
linkedmist.Cut()
-/obj/machinery/poolcontroller/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/poolcontroller/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "poolcontroller.tmpl", "Pool Controller Interface", 520, 410)
+ ui = new(user, src, ui_key, "PoolController", "Pool Controller Interface", 520, 410)
ui.open()
-/obj/machinery/poolcontroller/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- var/currenttemp
- switch(temperature) //So we can output nice things like "Cool" to nanoUI
+/obj/machinery/poolcontroller/proc/temp_to_str(temp)
+ switch(temp) //So we can output nice things like "Cool" to tgUI
if(FRIGID)
- currenttemp = "frigid"
+ return "frigid"
if(COOL)
- currenttemp = "cool"
+ return "cool"
if(NORMAL)
- currenttemp = "normal"
+ return "normal"
if(WARM)
- currenttemp = "warm"
+ return "warm"
if(SCALDING)
- currenttemp = "scalding"
- data["currentTemp"] = currenttemp
+ return "scalding"
+
+/obj/machinery/poolcontroller/proc/set_temp(val)
+ if (val != WARM && val != NORMAL && val != COOL && !(emagged && (val == SCALDING || val == FRIGID)))
+ return
+
+ if(val == SCALDING)
+ miston()
+ else
+ mistoff()
+
+ temperature = val
+
+
+/obj/machinery/poolcontroller/proc/str_to_temp(str)
+ switch(str)
+ if("frigid")
+ return FRIGID
+ if("cool")
+ return COOL
+ if("normal")
+ return NORMAL
+ if("warm")
+ return WARM
+ if("scalding")
+ return SCALDING
+
+/obj/machinery/poolcontroller/proc/set_temp_str(target)
+ var/temp = str_to_temp(target)
+ if(temp)
+ set_temp(temp)
+
+
+/obj/machinery/poolcontroller/tgui_data(mob/user)
+ var/list/data = list()
+ data["currentTemp"] = temp_to_str(temperature)
data["emagged"] = emagged
- data["TempColor"] = temperaturecolor
return data
-/obj/machinery/poolcontroller/Topic(href, href_list)
+/obj/machinery/poolcontroller/tgui_act(action, list/params)
if(..())
- return 1
+ return
- switch(href_list["temp"])
- if("Scalding")
- if(!emagged)
- return 0
- temperature = SCALDING
- temperaturecolor = "#FF0000"
- miston()
- if("Frigid")
- if(!emagged)
- return 0
- temperature = FRIGID
- temperaturecolor = "#00CCCC"
- mistoff()
- if("Warm")
- temperature = WARM
- temperaturecolor = "#990000"
- mistoff()
- if("Cool")
- temperature = COOL
- temperaturecolor = "#009999"
- mistoff()
- if("Normal")
- temperature = NORMAL
- temperaturecolor = ""
- mistoff()
+ switch(action)
+ if("setTemp")
+ set_temp_str(params["temp"])
+ return TRUE
- return 1
+ return FALSE
#undef FRIGID
#undef COOL
diff --git a/code/game/machinery/portable_tag_turret.dm b/code/game/machinery/portable_tag_turret.dm
index 12bae1bbf5a..4b22315f387 100644
--- a/code/game/machinery/portable_tag_turret.dm
+++ b/code/game/machinery/portable_tag_turret.dm
@@ -6,6 +6,14 @@
// Reasonable defaults, in case someone manually spawns us
var/lasercolor = "r" //Something to do with lasertag turrets, blame Sieve for not adding a comment.
installation = /obj/item/gun/energy/laser/tag/red
+ targetting_is_configurable = FALSE
+ lethal_is_configurable = FALSE
+ shot_delay = 30
+ iconholder = 1
+ has_cover = FALSE
+ always_up = TRUE
+ raised = TRUE
+ req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE)
/obj/machinery/porta_turret/tag/red
@@ -18,43 +26,15 @@
icon_state = "[lasercolor]grey_target_prism"
/obj/machinery/porta_turret/tag/weapon_setup(var/obj/item/gun/energy/E)
- switch(E.type)
- if(/obj/item/gun/energy/laser/tag/blue)
- eprojectile = /obj/item/gun/energy/laser/tag/blue
- lasercolor = "b"
- req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE)
- check_arrest = 0
- check_records = 0
- check_weapons = 1
- check_access = 0
- check_anomalies = 0
- shot_delay = 30
+ return
- if(/obj/item/gun/energy/laser/tag/red)
- eprojectile = /obj/item/gun/energy/laser/tag/red
- lasercolor = "r"
- req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE)
- check_arrest = 0
- check_records = 0
- check_weapons = 1
- check_access = 0
- check_anomalies = 0
- shot_delay = 30
- iconholder = 1
-
-/obj/machinery/porta_turret/tag/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "turret_control.tmpl", "Turret Controls", 500, 300)
- ui.open()
- ui.set_auto_update(1)
-
-/obj/machinery/porta_turret/tag/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- data["access"] = !isLocked(user)
- data["locked"] = locked
- data["enabled"] = enabled
- data["is_lethal"] = 0
+/obj/machinery/porta_turret/tag/tgui_data(mob/user)
+ var/list/data = list(
+ "locked" = isLocked(user), // does the current user have access?
+ "on" = enabled, // is turret turned on?
+ "lethal" = FALSE,
+ "lethal_is_configurable" = lethal_is_configurable
+ )
return data
/obj/machinery/porta_turret/tag/update_icon()
diff --git a/code/game/machinery/portable_turret.dm b/code/game/machinery/portable_turret.dm
index 79f1eb83e23..346008fce3f 100644
--- a/code/game/machinery/portable_turret.dm
+++ b/code/game/machinery/portable_turret.dm
@@ -7,18 +7,18 @@
name = "turret"
icon = 'icons/obj/turrets.dmi'
icon_state = "turretCover"
- anchored = 1
- density = 0
+ anchored = TRUE
+ density = FALSE
use_power = IDLE_POWER_USE //this turret uses and requires power
idle_power_usage = 50 //when inactive, this turret takes up constant 50 Equipment power
active_power_usage = 300 //when active, this turret takes up constant 300 Equipment power
power_channel = EQUIP //drains power from the EQUIPMENT channel
armor = list(melee = 50, bullet = 30, laser = 30, energy = 30, bomb = 30, bio = 0, rad = 0, fire = 90, acid = 90)
- var/raised = 0 //if the turret cover is "open" and the turret is raised
- var/raising= 0 //if the turret is currently opening or closing its cover
+ var/raised = FALSE //if the turret cover is "open" and the turret is raised
+ var/raising= FALSE //if the turret is currently opening or closing its cover
var/health = 80 //the turret's health
- var/locked = 1 //if the turret's behaviour control access is locked
- var/controllock = 0 //if the turret responds to control panels
+ var/locked = TRUE //if the turret's behaviour control access is locked
+ var/controllock = FALSE //if the turret responds to control panels. TRUE = does NOT respond
var/installation = /obj/item/gun/energy/gun/turret //the type of weapon installed
var/gun_charge = 0 //the charge of the gun inserted
@@ -31,68 +31,49 @@
var/last_fired = 0 //1: if the turret is cooling down from a shot, 0: turret is ready to fire
var/shot_delay = 15 //1.5 seconds between each shot
- var/check_arrest = 1 //checks if the perp is set to arrest
- var/check_records = 1 //checks if a security record exists at all
- var/check_weapons = 0 //checks if it can shoot people that have a weapon they aren't authorized to have
- var/check_access = 1 //if this is active, the turret shoots everything that does not meet the access requirements
- var/check_anomalies = 1 //checks if it can shoot at unidentified lifeforms (ie xenos)
- var/check_synth = 0 //if active, will shoot at anything not an AI or cyborg
- var/ailock = 0 // AI cannot use this
+ var/targetting_is_configurable = TRUE // if false, you cannot change who this turret attacks via its UI
+ var/check_arrest = TRUE //checks if the perp is set to arrest
+ var/check_records = TRUE //checks if a security record exists at all
+ var/check_weapons = FALSE //checks if it can shoot people that have a weapon they aren't authorized to have
+ var/check_access = TRUE //if this is active, the turret shoots everything that does not meet the access requirements
+ var/check_anomalies = TRUE //checks if it can shoot at unidentified lifeforms (ie xenos)
+ var/check_synth = FALSE //if active, will shoot at anything not an AI or cyborg
+ var/check_borgs = FALSE //if TRUE, target all cyborgs.
+ var/ailock = FALSE // if TRUE, AI cannot use this
- var/attacked = 0 //if set to 1, the turret gets pissed off and shoots at people nearby (unless they have sec access!)
+ var/attacked = FALSE //if set to 1, the turret gets pissed off and shoots at people nearby (unless they have sec access!)
- var/enabled = 1 //determines if the turret is on
- var/lethal = 0 //whether in lethal or stun mode
- var/disabled = 0
+ var/enabled = TRUE //determines if the turret is on
+ var/lethal = FALSE //whether in lethal or stun mode
+ var/lethal_is_configurable = TRUE // if false, its lethal setting cannot be changed
+ var/disabled = FALSE
var/shot_sound //what sound should play when the turret fires
var/eshot_sound //what sound should play when the emagged turret fires
var/datum/effect_system/spark_spread/spark_system //the spark system, used for generating... sparks?
- var/wrenching = 0
+ var/wrenching = FALSE
var/last_target //last target fired at, prevents turrets from erratically firing at all valid targets in range
- var/screen = 0 // Screen 0: main control, screen 1: access levels
- var/one_access = 0 // Determines if access control is set to req_one_access or req_access
+ var/one_access = FALSE // Determines if access control is set to req_one_access or req_access
+ var/region_min = REGION_GENERAL
+ var/region_max = REGION_COMMAND
- var/syndicate = 0 //is the turret a syndicate turret?
+ var/syndicate = FALSE //is the turret a syndicate turret?
var/faction = ""
- var/emp_vulnerable = 1 // Can be empd
+ var/emp_vulnerable = TRUE // Can be empd
var/scan_range = 7
- var/always_up = 0 //Will stay active
- var/has_cover = 1 //Hides the cover
+ var/always_up = FALSE //Will stay active
+ var/has_cover = TRUE //Hides the cover
-/obj/machinery/porta_turret/centcom
- name = "Centcom Turret"
- enabled = 0
- ailock = 1
- check_synth = 0
- check_access = 1
- check_arrest = 1
- check_records = 1
- check_weapons = 1
- check_anomalies = 1
-
-/obj/machinery/porta_turret/centcom/pulse
- name = "Pulse Turret"
- health = 200
- enabled = 1
- lethal = 1
- req_access = list(ACCESS_CENT_COMMANDER)
- installation = /obj/item/gun/energy/pulse/turret
-
-/obj/machinery/porta_turret/stationary
- ailock = 1
- lethal = 1
- installation = /obj/item/gun/energy/laser
/obj/machinery/porta_turret/Initialize(mapload)
. = ..()
if(req_access && req_access.len)
req_access.Cut()
req_one_access = list(ACCESS_SECURITY, ACCESS_HEADS)
- one_access = 1
+ one_access = TRUE
//Sets up a spark system
spark_system = new /datum/effect_system/spark_spread
@@ -110,7 +91,7 @@
if(req_one_access && req_one_access.len)
req_one_access.Cut()
req_access = list(ACCESS_CENT_SPECOPS)
- one_access = 0
+ one_access = FALSE
/obj/machinery/porta_turret/proc/setup()
var/obj/item/gun/energy/E = new installation //All energy-based weapons are applicable
@@ -185,152 +166,151 @@ GLOBAL_LIST_EMPTY(turret_icons)
else
icon_state = "turretCover"
-/obj/machinery/porta_turret/proc/isLocked(mob/user)
- if(ailock && (isrobot(user) || isAI(user)))
- to_chat(user, "There seems to be a firewall preventing you from accessing this device.")
- return 1
-
- if(locked && !(isrobot(user) || isAI(user) || isobserver(user)))
- to_chat(user, "Access denied.")
- return 1
-
- return 0
-
-/obj/machinery/porta_turret/attack_ai(mob/user)
- if(isLocked(user))
- return
-
- ui_interact(user)
-
-/obj/machinery/porta_turret/attack_ghost(mob/user)
- ui_interact(user)
-
-/obj/machinery/porta_turret/attack_hand(mob/user)
- if(isLocked(user))
- return
-
- ui_interact(user)
-
-/obj/machinery/porta_turret/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "turret_control.tmpl", "Turret Controls", 500, 320)
- ui.open()
- ui.set_auto_update(1)
-
-/obj/machinery/porta_turret/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- data["access"] = !isLocked(user)
- data["screen"] = screen
- data["locked"] = locked
- data["enabled"] = enabled
- data["lethal_control"] = !syndicate ? 1 : 0
- data["lethal"] = lethal
-
- if(data["access"] && !syndicate)
- var/settings[0]
- settings[++settings.len] = list("category" = "Neutralize All Non-Synthetics", "setting" = "check_synth", "value" = check_synth)
- settings[++settings.len] = list("category" = "Check Weapon Authorization", "setting" = "check_weapons", "value" = check_weapons)
- settings[++settings.len] = list("category" = "Check Security Records", "setting" = "check_records", "value" = check_records)
- settings[++settings.len] = list("category" = "Check Arrest Status", "setting" = "check_arrest", "value" = check_arrest)
- settings[++settings.len] = list("category" = "Check Access Authorization", "setting" = "check_access", "value" = check_access)
- settings[++settings.len] = list("category" = "Check Misc. Lifeforms", "setting" = "check_anomalies", "value" = check_anomalies)
- data["settings"] = settings
-
- if(!syndicate)
- data["one_access"] = one_access
- var/accesses[0]
- var/list/access_list = get_all_accesses()
- for(var/access in access_list)
- var/name = get_access_desc(access)
- var/active
- if(one_access)
- active = (access in req_one_access)
- else
- active = (access in req_access)
- accesses[++accesses.len] = list("name" = name, "active" = active, "number" = access)
- data["accesses"] = accesses
- return data
-
/obj/machinery/porta_turret/proc/HasController()
var/area/A = get_area(src)
return A && A.turret_controls.len > 0
-/obj/machinery/porta_turret/CanUseTopic(var/mob/user)
+/obj/machinery/porta_turret/proc/access_is_configurable()
+ return targetting_is_configurable && !HasController()
+
+/obj/machinery/porta_turret/proc/isLocked(mob/user)
if(HasController())
- to_chat(user, "Turrets can only be controlled using the assigned turret controller.")
- return STATUS_CLOSE
+ return TRUE
+ if(isrobot(user) || isAI(user))
+ if(ailock)
+ to_chat(user, "There seems to be a firewall preventing you from accessing this device.")
+ return TRUE
+ else
+ return FALSE
+ if(isobserver(user))
+ if(user.can_admin_interact())
+ return FALSE
+ else
+ return TRUE
+ if(locked)
+ return TRUE
+ return FALSE
- if(isLocked(user))
- return STATUS_CLOSE
+/obj/machinery/porta_turret/attack_ai(mob/user)
+ tgui_interact(user)
- if(!anchored)
- to_chat(usr, "\The [src] has to be secured first!")
- return STATUS_CLOSE
+/obj/machinery/porta_turret/attack_ghost(mob/user)
+ tgui_interact(user)
- return ..()
+/obj/machinery/porta_turret/attack_hand(mob/user)
+ tgui_interact(user)
-/obj/machinery/porta_turret/Topic(href, href_list, var/nowindow = 0)
- if(..())
- return 1
-
- if(href_list["command"] && href_list["value"])
- var/value = text2num(href_list["value"])
- if(href_list["command"] == "enable")
- enabled = value
- else if(syndicate)
- return 1
- else if(href_list["command"] == "screen")
- screen = value
- else if(href_list["command"] == "lethal")
- lethal = value
- else if(href_list["command"] == "check_synth")
- check_synth = value
- else if(href_list["command"] == "check_weapons")
- check_weapons = value
- else if(href_list["command"] == "check_records")
- check_records = value
- else if(href_list["command"] == "check_arrest")
- check_arrest = value
- else if(href_list["command"] == "check_access")
- check_access = value
- else if(href_list["command"] == "check_anomalies")
- check_anomalies = value
-
- if(!syndicate)
- if(href_list["one_access"])
- toggle_one_access(href_list["one_access"])
-
- if(href_list["access"])
- toggle_access(href_list["access"])
-
- return 1
-
-/obj/machinery/porta_turret/proc/toggle_one_access(var/access)
- one_access = text2num(access)
-
- if(one_access == 1)
- req_one_access = req_access.Copy()
- req_access.Cut()
- else if(one_access == 0)
- req_access = req_one_access.Copy()
- req_one_access.Cut()
-
-/obj/machinery/porta_turret/proc/toggle_access(var/access)
- var/required = text2num(access)
- if(!(required in get_all_accesses()))
+/obj/machinery/porta_turret/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ if(HasController())
+ to_chat(user, "[src] can only be controlled using the assigned turret controller.")
return
+ if(!anchored)
+ to_chat(user, "[src] has to be secured first!")
+ return
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "PortableTurret", name, 500, access_is_configurable() ? 800 : 400)
+ ui.open()
- if(one_access)
- if((required in req_one_access))
- req_one_access -= required
- else
- req_one_access += required
- else
- if((required in req_access))
- req_access -= required
- else
- req_access += required
+/obj/machinery/porta_turret/tgui_data(mob/user)
+ var/list/data = list(
+ "locked" = isLocked(user), // does the current user have access?
+ "on" = enabled,
+ "targetting_is_configurable" = targetting_is_configurable, // If false, targetting settings don't show up
+ "lethal" = lethal,
+ "lethal_is_configurable" = lethal_is_configurable,
+ "check_weapons" = check_weapons,
+ "neutralize_noaccess" = check_access,
+ "one_access" = one_access,
+ "selectedAccess" = one_access ? req_one_access : req_access,
+ "access_is_configurable" = access_is_configurable(),
+ "neutralize_norecord" = check_records,
+ "neutralize_criminals" = check_arrest,
+ "neutralize_all" = check_synth,
+ "neutralize_unidentified" = check_anomalies,
+ "neutralize_cyborgs" = check_borgs
+ )
+ return data
+
+/obj/machinery/porta_turret/tgui_static_data(mob/user)
+ var/list/data = list()
+ data["regions"] = get_accesslist_static_data(region_min, region_max)
+ return data
+
+/obj/machinery/porta_turret/tgui_act(action, params)
+ if (..())
+ return
+ if(isLocked(usr))
+ return
+ . = TRUE
+ switch(action)
+ if("power")
+ enabled = !enabled
+ if("lethal")
+ if(lethal_is_configurable)
+ lethal = !lethal
+ if(targetting_is_configurable)
+ switch(action)
+ if("authweapon")
+ check_weapons = !check_weapons
+ if("authaccess")
+ check_access = !check_access
+ if("authnorecord")
+ check_records = !check_records
+ if("autharrest")
+ check_arrest = !check_arrest
+ if("authxeno")
+ check_anomalies = !check_anomalies
+ if("authsynth")
+ check_synth = !check_synth
+ if("authborgs")
+ check_borgs = !check_borgs
+ if("set")
+ var/access = text2num(params["access"])
+ if(one_access)
+ if(!(access in req_one_access))
+ req_one_access += access
+ else
+ req_one_access -= access
+ else
+ if(!(access in req_access))
+ req_access += access
+ else
+ req_access -= access
+ if(access_is_configurable())
+ switch(action)
+ if("grant_region")
+ var/region = text2num(params["region"])
+ if(isnull(region))
+ return
+ if(one_access)
+ req_one_access |= get_region_accesses(region)
+ else
+ req_access |= get_region_accesses(region)
+ if("deny_region")
+ var/region = text2num(params["region"])
+ if(isnull(region))
+ return
+ if(one_access)
+ req_one_access -= get_region_accesses(region)
+ else
+ req_access -= get_region_accesses(region)
+ if("clear_all")
+ if(one_access)
+ req_one_access = list()
+ else
+ req_access = list()
+ if("grant_all")
+ if(one_access)
+ req_one_access = get_all_accesses()
+ else
+ req_access = get_all_accesses()
+ if("one_access")
+ if(one_access)
+ req_one_access = list()
+ else
+ req_access = list()
+ one_access = !one_access
/obj/machinery/porta_turret/power_change()
if(powered() || !use_power)
@@ -379,23 +359,25 @@ GLOBAL_LIST_EMPTY(turret_icons)
"You begin [anchored ? "un" : ""]securing the turret." \
)
- wrenching = 1
+ wrenching = TRUE
if(do_after(user, 50 * I.toolspeed, target = src))
//This code handles moving the turret around. After all, it's a portable turret!
if(!anchored)
playsound(loc, I.usesound, 100, 1)
- anchored = 1
+ anchored = TRUE
update_icon()
to_chat(user, "You secure the exterior bolts on the turret.")
else if(anchored)
playsound(loc, I.usesound, 100, 1)
- anchored = 0
+ anchored = FALSE
to_chat(user, "You unsecure the exterior bolts on the turret.")
update_icon()
- wrenching = 0
+ wrenching = FALSE
else if(istype(I, /obj/item/card/id) || istype(I, /obj/item/pda))
- if(allowed(user))
+ if(HasController())
+ to_chat(user, "Turrets regulated by a nearby turret controller are not unlockable.")
+ else if(allowed(user))
locked = !locked
to_chat(user, "Controls are now [locked ? "locked" : "unlocked"].")
updateUsrDialog()
@@ -409,9 +391,9 @@ GLOBAL_LIST_EMPTY(turret_icons)
playsound(src.loc, 'sound/weapons/smash.ogg', 60, 1)
if(I.force * 0.5 > 1) //if the force of impact dealt at least 1 damage, the turret gets pissed off
if(!attacked && !emagged)
- attacked = 1
+ attacked = TRUE
spawn(60)
- attacked = 0
+ attacked = FALSE
..()
@@ -445,12 +427,12 @@ GLOBAL_LIST_EMPTY(turret_icons)
if(user)
to_chat(user, "You short out [src]'s threat assessment circuits.")
visible_message("[src] hums oddly...")
- emagged = 1
+ emagged = TRUE
iconholder = 1
- controllock = 1
- enabled = 0 //turns off the turret temporarily
+ controllock = TRUE
+ enabled = FALSE //turns off the turret temporarily
sleep(60) //6 seconds for the traitor to gtfo of the area before the turret decides to ruin his shit
- enabled = 1 //turns it back on. The cover popUp() popDown() are automatically called in process(), no need to define it here
+ enabled = TRUE //turns it back on. The cover popUp() popDown() are automatically called in process(), no need to define it here
/obj/machinery/porta_turret/take_damage(force)
if(!raised && !raising)
@@ -470,9 +452,9 @@ GLOBAL_LIST_EMPTY(turret_icons)
if(enabled)
if(!attacked && !emagged)
- attacked = 1
+ attacked = TRUE
spawn(60)
- attacked = 0
+ attacked = FALSE
..()
@@ -489,12 +471,12 @@ GLOBAL_LIST_EMPTY(turret_icons)
check_access = prob(20) // check_access is a pretty big deal, so it's least likely to get turned on
check_anomalies = prob(50)
if(prob(5))
- emagged = 1
+ emagged = TRUE
enabled=0
- spawn(rand(60,600))
+ spawn(rand(60, 600))
if(!enabled)
- enabled=1
+ enabled = TRUE
..()
@@ -585,8 +567,8 @@ GLOBAL_LIST_EMPTY(turret_icons)
if(get_turf(L) == get_turf(src))
return TURRET_NOT_TARGET
- if(!emagged && !syndicate && (issilicon(L) || isbot(L))) // Don't target silica
- return TURRET_NOT_TARGET
+ if(!emagged && !syndicate && (issilicon(L) || isbot(L)))
+ return (check_borgs && isrobot(L)) ? TURRET_PRIORITY_TARGET : TURRET_NOT_TARGET
if(L.stat && !emagged) //if the perp is dead/dying, no need to bother really
return TURRET_NOT_TARGET //move onto next potential victim!
@@ -643,7 +625,7 @@ GLOBAL_LIST_EMPTY(turret_icons)
return
if(stat & BROKEN)
return
- set_raised_raising(raised, 1)
+ set_raised_raising(raised, TRUE)
playsound(get_turf(src), 'sound/effects/turret/open.wav', 60, 1)
update_icon()
@@ -653,7 +635,7 @@ GLOBAL_LIST_EMPTY(turret_icons)
sleep(10)
qdel(flick_holder)
- set_raised_raising(1, 0)
+ set_raised_raising(TRUE, FALSE)
update_icon()
/obj/machinery/porta_turret/proc/popDown() //pops the turret down
@@ -664,7 +646,7 @@ GLOBAL_LIST_EMPTY(turret_icons)
return
if(stat & BROKEN)
return
- set_raised_raising(raised, 1)
+ set_raised_raising(raised, TRUE)
playsound(get_turf(src), 'sound/effects/turret/open.wav', 60, 1)
update_icon()
@@ -674,7 +656,7 @@ GLOBAL_LIST_EMPTY(turret_icons)
sleep(10)
qdel(flick_holder)
- set_raised_raising(0, 0)
+ set_raised_raising(FALSE, FALSE)
update_icon()
/obj/machinery/porta_turret/on_assess_perp(mob/living/carbon/human/perp)
@@ -739,6 +721,27 @@ GLOBAL_LIST_EMPTY(turret_icons)
A.throw_at(target, scan_range, 1)
return A
+/obj/machinery/porta_turret/centcom
+ name = "Centcom Turret"
+ enabled = FALSE
+ ailock = TRUE
+ check_synth = FALSE
+ check_access = TRUE
+ check_arrest = TRUE
+ check_records = TRUE
+ check_weapons = TRUE
+ check_anomalies = TRUE
+ region_max = REGION_CENTCOMM // Non-turretcontrolled turrets at CC can have their access customized to check for CC accesses.
+
+/obj/machinery/porta_turret/centcom/pulse
+ name = "Pulse Turret"
+ health = 200
+ enabled = TRUE
+ lethal = TRUE
+ lethal_is_configurable = FALSE
+ req_access = list(ACCESS_CENT_COMMANDER)
+ installation = /obj/item/gun/energy/pulse/turret
+
/datum/turret_checks
var/enabled
var/lethal
@@ -748,6 +751,7 @@ GLOBAL_LIST_EMPTY(turret_icons)
var/check_arrest
var/check_weapons
var/check_anomalies
+ var/check_borgs
var/ailock
/obj/machinery/porta_turret/proc/setState(var/datum/turret_checks/TC)
@@ -763,6 +767,7 @@ GLOBAL_LIST_EMPTY(turret_icons)
check_arrest = TC.check_arrest
check_weapons = TC.check_weapons
check_anomalies = TC.check_anomalies
+ check_borgs = TC.check_borgs
ailock = TC.ailock
power_change()
@@ -791,7 +796,7 @@ GLOBAL_LIST_EMPTY(turret_icons)
if(istype(I, /obj/item/wrench) && !anchored)
playsound(loc, I.usesound, 100, 1)
to_chat(user, "You secure the external bolts.")
- anchored = 1
+ anchored = TRUE
build_step = 1
return
@@ -816,7 +821,7 @@ GLOBAL_LIST_EMPTY(turret_icons)
else if(istype(I, /obj/item/wrench))
playsound(loc, I.usesound, 75, 1)
to_chat(user, "You unfasten the external bolts.")
- anchored = 0
+ anchored = FALSE
build_step = 0
return
@@ -841,8 +846,10 @@ GLOBAL_LIST_EMPTY(turret_icons)
gun_charge = E.cell.charge //the gun's charge is stored in gun_charge
to_chat(user, "You add [I] to the turret.")
- if(istype(installation, /obj/item/gun/energy/laser/tag/blue) || istype(installation, /obj/item/gun/energy/laser/tag/red))
- target_type = /obj/machinery/porta_turret/tag
+ if(istype(E, /obj/item/gun/energy/laser/tag/blue))
+ target_type = /obj/machinery/porta_turret/tag/blue
+ else if(istype(E, /obj/item/gun/energy/laser/tag/red))
+ target_type = /obj/machinery/porta_turret/tag/red
else
target_type = /obj/machinery/porta_turret
@@ -934,7 +941,7 @@ GLOBAL_LIST_EMPTY(turret_icons)
Turret.name = finish_name
Turret.installation = installation
Turret.gun_charge = gun_charge
- Turret.enabled = 0
+ Turret.enabled = FALSE
Turret.setup()
qdel(src)
@@ -981,25 +988,27 @@ GLOBAL_LIST_EMPTY(turret_icons)
var/icon_state_active = "syndieturret1"
var/icon_state_destroyed = "syndieturret2"
- syndicate = 1
+ syndicate = TRUE
installation = null
- always_up = 1
+ always_up = TRUE
use_power = NO_POWER_USE
- has_cover = 0
- raised = 1
+ has_cover = FALSE
+ raised = TRUE
scan_range = 9
faction = "syndicate"
- emp_vulnerable = 0
+ emp_vulnerable = FALSE
- lethal = 1
- check_arrest = 0
- check_records = 0
- check_weapons = 0
- check_access = 0
- check_anomalies = 1
- check_synth = 1
- ailock = 1
+ lethal = TRUE
+ lethal_is_configurable = FALSE
+ targetting_is_configurable = FALSE
+ check_arrest = FALSE
+ check_records = FALSE
+ check_weapons = FALSE
+ check_access = FALSE
+ check_anomalies = TRUE
+ check_synth = TRUE
+ ailock = TRUE
var/area/syndicate_depot/core/depotarea
/obj/machinery/porta_turret/syndicate/die()
@@ -1018,7 +1027,7 @@ GLOBAL_LIST_EMPTY(turret_icons)
if(req_one_access && req_one_access.len)
req_one_access.Cut()
req_access = list(ACCESS_SYNDICATE)
- one_access = 0
+ one_access = FALSE
/obj/machinery/porta_turret/syndicate/update_icon()
if(stat & BROKEN)
diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm
index 82b473b50f0..85fe8fcf843 100644
--- a/code/game/machinery/recharger.dm
+++ b/code/game/machinery/recharger.dm
@@ -1,22 +1,27 @@
+#define RECHARGER_POWER_USAGE_GUN 250
+#define RECHARGER_POWER_USAGE_MISC 200
+
/obj/machinery/recharger
name = "recharger"
icon = 'icons/obj/stationobjs.dmi'
icon_state = "recharger0"
desc = "A charging dock for energy based weaponry."
- anchored = 1
+ anchored = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 4
active_power_usage = 200
pass_flags = PASSTABLE
- var/obj/item/charging = null
- var/using_power = FALSE
- var/list/allowed_devices = list(/obj/item/gun/energy, /obj/item/melee/baton, /obj/item/modular_computer, /obj/item/rcs, /obj/item/bodyanalyzer)
+
+ var/list/allowed_devices = list(/obj/item/gun/energy, /obj/item/melee/baton, /obj/item/rcs, /obj/item/bodyanalyzer)
var/icon_state_off = "rechargeroff"
var/icon_state_charged = "recharger2"
var/icon_state_charging = "recharger1"
var/icon_state_idle = "recharger0"
var/recharge_coeff = 1
+ var/obj/item/charging = null // The item that is being charged
+ var/using_power = FALSE // Whether the recharger is actually transferring power or not, used for icon
+
/obj/machinery/recharger/New()
..()
component_parts = list()
@@ -34,32 +39,32 @@
if(allowed)
if(anchored)
if(charging)
- return 1
+ return TRUE
//Checks to make sure he's not in space doing it, and that the area got proper power.
var/area/a = get_area(src)
- if(!isarea(a) || a.power_equip == 0)
+ if(!isarea(a) || !a.power_equip)
to_chat(user, "[src] blinks red as you try to insert [G].")
- return 1
+ return TRUE
if(istype(G, /obj/item/gun/energy))
var/obj/item/gun/energy/E = G
if(!E.can_charge)
to_chat(user, "Your gun has no external power connector.")
- return 1
+ return TRUE
if(!user.drop_item())
- return 1
+ return TRUE
G.forceMove(src)
charging = G
use_power = ACTIVE_POWER_USE
+ using_power = check_cell_needs_recharging(get_cell_from(G))
update_icon()
else
to_chat(user, "[src] isn't connected to anything!")
- return 1
+ return TRUE
return ..()
-
/obj/machinery/recharger/crowbar_act(mob/user, obj/item/I)
if(panel_open && !charging && default_deconstruction_crowbar(user, I))
return TRUE
@@ -106,57 +111,15 @@
if(stat & (NOPOWER|BROKEN) || !anchored)
return
- using_power = FALSE
- if(charging)
- if(istype(charging, /obj/item/gun/energy))
- var/obj/item/gun/energy/E = charging
- if(E.cell.charge < E.cell.maxcharge)
- E.cell.give(E.cell.chargerate * recharge_coeff)
- E.on_recharge()
- use_power(250)
- using_power = TRUE
-
-
- if(istype(charging, /obj/item/melee/baton))
- var/obj/item/melee/baton/B = charging
- if(B.cell)
- if(B.cell.give(B.cell.chargerate))
- use_power(200)
- using_power = TRUE
-
- if(istype(charging, /obj/item/modular_computer))
- var/obj/item/modular_computer/C = charging
- var/obj/item/computer_hardware/battery/battery_module = C.all_components[MC_CELL]
- if(battery_module)
- var/obj/item/computer_hardware/battery/B = battery_module
- if(B.battery)
- if(B.battery.charge < B.battery.maxcharge)
- B.battery.give(B.battery.chargerate)
- use_power(200)
- using_power = TRUE
-
- if(istype(charging, /obj/item/rcs))
- var/obj/item/rcs/R = charging
- if(R.rcell)
- if(R.rcell.give(R.rcell.chargerate))
- use_power(200)
- using_power = TRUE
-
- if(istype(charging, /obj/item/bodyanalyzer))
- var/obj/item/bodyanalyzer/B = charging
- if(B.cell)
- if(B.cell.give(B.cell.chargerate))
- use_power(200)
- using_power = TRUE
-
- update_icon(using_power)
+ using_power = try_recharging_if_possible()
+ update_icon()
/obj/machinery/recharger/emp_act(severity)
if(stat & (NOPOWER|BROKEN) || !anchored)
..(severity)
return
- if(istype(charging, /obj/item/gun/energy))
+ if(istype(charging, /obj/item/gun/energy))
var/obj/item/gun/energy/E = charging
if(E.cell)
E.cell.emp_act(severity)
@@ -167,7 +130,11 @@
B.cell.charge = 0
..(severity)
-/obj/machinery/recharger/update_icon(using_power = FALSE) //we have an update_icon() in addition to the stuff in process to make it feel a tiny bit snappier.
+/obj/machinery/recharger/power_change()
+ ..()
+ update_icon()
+
+/obj/machinery/recharger/update_icon() //we have an update_icon() in addition to the stuff in process to make it feel a tiny bit snappier.
if(stat & (NOPOWER|BROKEN) || !anchored)
icon_state = icon_state_off
return
@@ -179,6 +146,49 @@
return
icon_state = icon_state_idle
+/obj/machinery/recharger/proc/get_cell_from(obj/item/I)
+ if(istype(I, /obj/item/gun/energy))
+ var/obj/item/gun/energy/E = I
+ return E.cell
+
+ if(istype(I, /obj/item/melee/baton))
+ var/obj/item/melee/baton/B = I
+ return B.cell
+
+ if(istype(I, /obj/item/rcs))
+ var/obj/item/rcs/R = I
+ return R.rcell
+
+ if(istype(I, /obj/item/bodyanalyzer))
+ var/obj/item/bodyanalyzer/B = I
+ return B.cell
+
+ return null
+
+/obj/machinery/recharger/proc/check_cell_needs_recharging(obj/item/stock_parts/cell/C)
+ if(!C || C.charge >= C.maxcharge)
+ return FALSE
+ return TRUE
+
+/obj/machinery/recharger/proc/recharge_cell(obj/item/stock_parts/cell/C, power_usage)
+ C.give(C.chargerate * recharge_coeff)
+ use_power(power_usage)
+
+/obj/machinery/recharger/proc/try_recharging_if_possible()
+ var/obj/item/stock_parts/cell/C = get_cell_from(charging)
+ if(!check_cell_needs_recharging(C))
+ return FALSE
+
+ if(istype(charging, /obj/item/gun/energy))
+ recharge_cell(C, RECHARGER_POWER_USAGE_GUN)
+
+ var/obj/item/gun/energy/E = charging
+ E.on_recharge()
+ else
+ recharge_cell(C, RECHARGER_POWER_USAGE_MISC)
+
+ return TRUE
+
/obj/machinery/recharger/examine(mob/user)
. = ..()
if(charging && (!in_range(user, src) && !issilicon(user) && !isobserver(user)))
@@ -204,3 +214,6 @@
icon_state_idle = "wrecharger0"
icon_state_charging = "wrecharger1"
icon_state_charged = "wrecharger2"
+
+#undef RECHARGER_POWER_USAGE_GUN
+#undef RECHARGER_POWER_USAGE_MISC
diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm
index e4e37ae1bcb..5d26f0e946e 100644
--- a/code/game/machinery/recycler.dm
+++ b/code/game/machinery/recycler.dm
@@ -8,8 +8,8 @@
layer = MOB_LAYER+1 // Overhead
anchored = 1
density = 1
- damage_deflection = 10
- var/safety_mode = 0 // Temporarily stops machine if it detects a mob
+ damage_deflection = 15
+ var/emergency_mode = FALSE // Temporarily stops machine if it detects a mob
var/icon_name = "grinder-o"
var/blood = 0
var/eat_dir = WEST
@@ -42,9 +42,9 @@
/obj/machinery/recycler/examine(mob/user)
. = ..()
- . += "The power light is [(stat & NOPOWER) ? "off" : "on"]."
- . += "The safety-mode light is [safety_mode ? "on" : "off"]."
- . += "The safety-sensors status light is [emagged ? "off" : "on"]."
+ . += "The power light is [(stat & NOPOWER) ? "off" : "on"]."
+ . += "The operation light is [emergency_mode ? "off. [src] has detected a forbidden object with its sensors, and has shut down temporarily." : "on. [src] is active."]"
+ . += "The safety sensor light is [emagged ? "off!" : "on."]"
/obj/machinery/recycler/power_change()
..()
@@ -73,8 +73,8 @@
/obj/machinery/recycler/emag_act(mob/user)
if(!emagged)
emagged = 1
- if(safety_mode)
- safety_mode = 0
+ if(emergency_mode)
+ emergency_mode = FALSE
update_icon()
playsound(loc, "sparks", 75, 1, -1)
to_chat(user, "You use the cryptographic sequencer on the [name].")
@@ -82,7 +82,7 @@
/obj/machinery/recycler/update_icon()
..()
var/is_powered = !(stat & (BROKEN|NOPOWER))
- if(safety_mode)
+ if(emergency_mode)
is_powered = 0
icon_state = icon_name + "[is_powered]" + "[(blood ? "bld" : "")]" // add the blood tag at the end
@@ -92,14 +92,13 @@
if(AM)
Bumped(AM)
-
/obj/machinery/recycler/Bumped(atom/movable/AM)
if(stat & (BROKEN|NOPOWER))
return
if(!anchored)
return
- if(safety_mode)
+ if(emergency_mode)
return
var/move_dir = get_dir(loc, AM.loc)
@@ -146,14 +145,14 @@
/obj/machinery/recycler/proc/emergency_stop(mob/living/L)
playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
- safety_mode = 1
+ emergency_mode = TRUE
update_icon()
L.loc = loc
addtimer(CALLBACK(src, .proc/reboot), SAFETY_COOLDOWN)
/obj/machinery/recycler/proc/reboot()
playsound(loc, 'sound/machines/ping.ogg', 50, 0)
- safety_mode = 0
+ emergency_mode = FALSE
update_icon()
/obj/machinery/recycler/proc/crush_living(mob/living/L)
diff --git a/code/game/machinery/slotmachine.dm b/code/game/machinery/slotmachine.dm
index 5952108b6a9..1624ef4bb92 100644
--- a/code/game/machinery/slotmachine.dm
+++ b/code/game/machinery/slotmachine.dm
@@ -12,29 +12,26 @@
var/resultlvl = null
/obj/machinery/slot_machine/attack_hand(mob/user as mob)
+ tgui_interact(user)
+
+/obj/machinery/slot_machine/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "SlotMachine", name, 350, 200, master_ui, state)
+ ui.open()
+
+/obj/machinery/slot_machine/tgui_data(mob/user)
+ var/list/data = list()
+ // Get account
account = user.get_worn_id_account()
if(!account)
if(istype(user.get_active_hand(), /obj/item/card/id))
account = get_card_account(user.get_active_hand())
else
account = null
- ui_interact(user)
-/obj/machinery/slot_machine/wrench_act(mob/user, obj/item/I)
- . = TRUE
- if(!I.tool_use_check(user, 0))
- return
- default_unfasten_wrench(user, I)
-/obj/machinery/slot_machine/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "slotmachine.tmpl", name, 350, 200)
- ui.open()
- ui.set_auto_update(1)
-
-/obj/machinery/slot_machine/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
+ // Send data
data["working"] = working
data["money"] = account ? account.money : null
data["plays"] = plays
@@ -42,22 +39,23 @@
data["resultlvl"] = resultlvl
return data
-/obj/machinery/slot_machine/Topic(href, href_list)
+/obj/machinery/slot_machine/tgui_act(action, params)
+ if(..())
+ return
add_fingerprint(usr)
- if(href_list["ops"])
- if(text2num(href_list["ops"])) // Play
- if(working)
- return
- if(!account || account.money < 10)
- return
- if(!account.charge(10, null, "Bet", "Slot Machine", "Slot Machine"))
- return
- plays++
- working = 1
- icon_state = "slots-on"
- playsound(src.loc, 'sound/machines/ding.ogg', 50, 1)
- addtimer(CALLBACK(src, .proc/spin_slots, usr.name), 25)
+ if(action == "spin")
+ if(working)
+ return
+ if(!account || account.money < 10)
+ return
+ if(!account.charge(10, null, "Bet", "Slot Machine", "Slot Machine"))
+ return
+ plays++
+ working = TRUE
+ icon_state = "slots-on"
+ playsound(src.loc, 'sound/machines/ding.ogg', 50, 1)
+ addtimer(CALLBACK(src, .proc/spin_slots, usr.name), 25)
/obj/machinery/slot_machine/proc/spin_slots(userName)
switch(rand(1,4050))
@@ -65,44 +63,45 @@
atom_say("JACKPOT! [userName] has won a MILLION CREDITS!")
GLOB.event_announcement.Announce("Congratulations to [userName] on winning the Jackpot of ONE MILLION CREDITS!", "Jackpot Winner")
result = "JACKPOT! You win one million credits!"
- resultlvl = "highlight"
+ resultlvl = "teal"
win_money(1000000, 'sound/goonstation/misc/airraid_loop.ogg')
if(2 to 5) // .07%
atom_say("Big Winner! [userName] has won a hundred thousand credits!")
GLOB.event_announcement.Announce("Congratulations to [userName] on winning a hundred thousand credits!", "Big Winner")
result = "Big Winner! You win a hundred thousand credits!"
- resultlvl = "good"
+ resultlvl = "green"
win_money(100000, 'sound/goonstation/misc/klaxon.ogg')
if(6 to 50) // 1.08%
atom_say("Big Winner! [userName] has won ten thousand credits!")
result = "You win ten thousand credits!"
- resultlvl = "good"
+ resultlvl = "green"
win_money(10000, 'sound/goonstation/misc/klaxon.ogg')
if(51 to 100) // 1.21%
atom_say("Winner! [userName] has won a thousand credits!")
result = "You win a thousand credits!"
- resultlvl = "good"
+ resultlvl = "green"
win_money(1000, 'sound/goonstation/misc/bell.ogg')
if(101 to 200) // 2.44%
atom_say("Winner! [userName] has won a hundred credits!")
result = "You win a hundred credits!"
- resultlvl = "good"
+ resultlvl = "green"
win_money(100, 'sound/goonstation/misc/bell.ogg')
if(201 to 300) // 2.44%
atom_say("Winner! [userName] has won fifty credits!")
result = "You win fifty credits!"
- resultlvl = "good"
+ resultlvl = "green"
win_money(50)
if(301 to 1000) // 17.26%
atom_say("Winner! [userName] has won ten credits!")
result = "You win ten credits!"
- resultlvl = "good"
+ resultlvl = "green"
win_money(10)
else // 75.31%
- result = "No luck!"
- resultlvl = "average"
- working = 0
+ result = "No luck!"
+ resultlvl = "orange"
+ working = FALSE
icon_state = "slots-off"
+ SStgui.update_uis(src) // Push a UI update
/obj/machinery/slot_machine/proc/win_money(amt, sound='sound/machines/ping.ogg')
if(sound)
@@ -110,3 +109,9 @@
if(!account)
return
account.credit(amt, "Slot Winnings", "Slot Machine", account.owner_name)
+
+/obj/machinery/slot_machine/wrench_act(mob/user, obj/item/I)
+ . = TRUE
+ if(!I.tool_use_check(user, 0))
+ return
+ default_unfasten_wrench(user, I)
diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm
index da4df4f8382..59865dee52a 100644
--- a/code/game/machinery/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage_unit.dm
@@ -96,7 +96,7 @@
name = "atmospherics suit storage unit"
suit_type = /obj/item/clothing/suit/space/hardsuit/engine/atmos
mask_type = /obj/item/clothing/mask/gas
- magboots_type = /obj/item/clothing/shoes/magboots
+ magboots_type = /obj/item/clothing/shoes/magboots/atmos
req_access = list(ACCESS_ATMOSPHERICS)
/obj/machinery/suit_storage_unit/atmos/secure
@@ -255,6 +255,7 @@
occupant_typecache = typecacheof(occupant_typecache)
/obj/machinery/suit_storage_unit/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(suit)
QDEL_NULL(helmet)
QDEL_NULL(mask)
@@ -740,10 +741,11 @@
if(!occupant)
return
- if(user != occupant)
- to_chat(occupant, "The machine kicks you out!")
- if(user.loc != loc)
- to_chat(occupant, "You leave the not-so-cozy confines of the SSU.")
+ if(user)
+ if(user != occupant)
+ to_chat(occupant, "The machine kicks you out!")
+ if(user.loc != loc)
+ to_chat(occupant, "You leave the not-so-cozy confines of [src].")
occupant.forceMove(loc)
occupant = null
if(!state_open)
@@ -751,6 +753,8 @@
update_icon()
return
+/obj/machinery/suit_storage_unit/force_eject_occupant()
+ eject_occupant()
/obj/machinery/suit_storage_unit/verb/get_out()
set name = "Eject Suit Storage Unit"
@@ -799,3 +803,7 @@
/obj/machinery/suit_storage_unit/attack_ai(mob/user as mob)
return attack_hand(user)
+
+/obj/machinery/suit_storage_unit/proc/check_electrified_callback()
+ if(!wires.is_cut(WIRE_ELECTRIFY))
+ shocked = FALSE
diff --git a/code/game/machinery/syndicatebomb.dm b/code/game/machinery/syndicatebomb.dm
index 181d5e494da..37dd2c6d439 100644
--- a/code/game/machinery/syndicatebomb.dm
+++ b/code/game/machinery/syndicatebomb.dm
@@ -91,6 +91,7 @@
..()
/obj/machinery/syndicatebomb/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(wires)
QDEL_NULL(countdown)
STOP_PROCESSING(SSfastprocess, src)
@@ -174,7 +175,7 @@
. = TRUE
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
- if(open_panel && wires.IsAllCut())
+ if(open_panel && wires.is_all_cut())
if(payload)
to_chat(user, "You carefully pry out [payload].")
payload.loc = user.loc
@@ -188,7 +189,7 @@
/obj/machinery/syndicatebomb/welder_act(mob/user, obj/item/I)
. = TRUE
- if(payload || !wires.IsAllCut() || !open_panel)
+ if(payload || !wires.is_all_cut() || !open_panel)
return
if(!I.tool_use_check(user, 0))
return
@@ -259,14 +260,11 @@
var/turf/bombturf = get_turf(src)
var/area/A = get_area(bombturf)
if(payload && !istype(payload, /obj/item/bombcore/training))
- msg_admin_attack("[key_name_admin(user)] has primed a [name] ([payload]) for detonation at [A.name] (JMP).", ATKLOG_FEW)
log_game("[key_name(user)] has primed a [name] ([payload]) for detonation at [A.name] [COORD(bombturf)]")
investigate_log("[key_name(user)] has has primed a [name] ([payload]) for detonation at [A.name] [COORD(bombturf)]", INVESTIGATE_BOMB)
+ add_attack_logs(user, src, "has primed a [name] ([payload]) for detonation", ATKLOG_FEW)
payload.adminlog = "\The [src] that [key_name(user)] had primed detonated!"
-/obj/machinery/syndicatebomb/proc/isWireCut(var/index)
- return wires.IsIndexCut(index)
-
///Bomb Subtypes///
/obj/machinery/syndicatebomb/training
@@ -297,7 +295,7 @@
/obj/machinery/syndicatebomb/empty/New()
..()
- wires.CutAll()
+ wires.cut_all()
/obj/machinery/syndicatebomb/self_destruct
name = "self destruct device"
@@ -368,7 +366,7 @@
var/obj/machinery/syndicatebomb/holder = loc
if(istype(holder))
if(holder.wires)
- holder.wires.Shuffle()
+ holder.wires.shuffle_wires()
holder.defused = 0
holder.open_panel = 0
holder.delayedbig = FALSE
@@ -654,8 +652,8 @@
var/turf/T = get_turf(src)
var/area/A = get_area(T)
detonated--
- message_admins("[key_name_admin(user)] has remotely detonated [detonated ? "syndicate bombs" : "a syndicate bomb"] using a [name] at [A.name] (JMP).")
investigate_log("[key_name(user)] has remotely detonated [detonated ? "syndicate bombs" : "a syndicate bomb"] using a [name] at [A.name] ([T.x],[T.y],[T.z])", INVESTIGATE_BOMB)
+ add_attack_logs(user, src, "has remotely detonated [detonated ? "syndicate bombs" : "a syndicate bomb"] using", ATKLOG_FEW)
log_game("[key_name(user)] has remotely detonated [detonated ? "syndicate bombs" : "a syndicate bomb"] using a [name] at [A.name] ([T.x],[T.y],[T.z])")
detonated = 0
existant = 0
diff --git a/code/game/machinery/tcomms/_base.dm b/code/game/machinery/tcomms/_base.dm
index dc933099faa..892392a8c54 100644
--- a/code/game/machinery/tcomms/_base.dm
+++ b/code/game/machinery/tcomms/_base.dm
@@ -40,7 +40,7 @@ GLOBAL_LIST_EMPTY(tcomms_machines)
var/network_id = "None"
/// Is the machine active
var/active = TRUE
- /// Has the machine been hit by an ionspheric anomalie
+ /// Has the machine been hit by an ionospheric anomaly
var/ion = FALSE
/**
@@ -52,6 +52,19 @@ GLOBAL_LIST_EMPTY(tcomms_machines)
. = ..()
GLOB.tcomms_machines += src
update_icon()
+ if((!mapload) && (usr))
+ // To the person who asks "Hey affected, why are you using this massive operator when you can use AREACOORD?" Well, ill tell you
+ // get_area_name is fucking broken and uses a for(x in world) search
+ // It doesnt even work, is expensive, and returns 0
+ // Im not refactoring one thing which could risk breaking all admin location logs
+ // Fight me
+ log_action(usr, "constructed a new [src] at [src ? "[get_location_name(src, TRUE)] [COORD(src)]" : "nonexistent location"] [ADMIN_JMP(src)]", adminmsg = TRUE)
+ // Add in component parts for the sake of deconstruction
+ component_parts = list()
+ component_parts += new /obj/item/stock_parts/manipulator(null)
+ component_parts += new /obj/item/stock_parts/manipulator(null)
+ component_parts += new /obj/item/stack/cable_coil(null, 1)
+ component_parts += new /obj/item/stack/cable_coil(null, 1)
/**
* Base Destructor
@@ -60,6 +73,8 @@ GLOBAL_LIST_EMPTY(tcomms_machines)
*/
/obj/machinery/tcomms/Destroy()
GLOB.tcomms_machines -= src
+ if(usr)
+ log_action(usr, "destroyed a [src] at [src ? "[get_location_name(src, TRUE)] [COORD(src)]" : "nonexistent location"] [ADMIN_JMP(src)]", adminmsg = TRUE)
return ..()
/**
@@ -79,35 +94,49 @@ GLOBAL_LIST_EMPTY(tcomms_machines)
// Attack overrides. These are needed so the UIs can be opened up //
/obj/machinery/tcomms/attack_ai(mob/user)
add_hiddenprint(user)
- ui_interact(user)
+ tgui_interact(user)
/obj/machinery/tcomms/attack_ghost(mob/user)
- ui_interact(user)
+ tgui_interact(user)
/obj/machinery/tcomms/attack_hand(mob/user)
if(..(user))
return
- ui_interact(user)
+ tgui_interact(user)
/**
- * Start of Ion Anomalie Event
+ * Start of Ion Anomaly Event
*
- * Proc to easily start an Ion Anomalie's effects, and update the icon
+ * Proc to easily start an Ion Anomaly's effects, and update the icon
*/
/obj/machinery/tcomms/proc/start_ion()
ion = TRUE
update_icon()
/**
- * End of Ion Anomalie Event
+ * End of Ion Anomaly Event
*
- * Proc to easily stop an Ion Anomalie's effects, and update the icon
+ * Proc to easily stop an Ion Anomaly's effects, and update the icon
*/
/obj/machinery/tcomms/proc/end_ion()
ion = FALSE
update_icon()
+/**
+ * Z-Level transit change helper
+ *
+ * Proc to make sure you cant have two of these active on a Z-level at once. It also makes sure to update the linkage
+ */
+/obj/machinery/tcomms/onTransitZ(old_z, new_z)
+ . = ..()
+ if(active)
+ active = FALSE
+ // This needs a timer because otherwise its on the shuttle Z and the message is missed
+ addtimer(CALLBACK(src, /atom.proc/visible_message, "Radio equipment on [src] has been overloaded by heavy bluespace interference. Please restart the machine."), 5)
+ update_icon()
+
+
/**
* Logging helper
*
@@ -462,3 +491,20 @@ GLOBAL_LIST_EMPTY(tcomms_machines)
break
return ..()
+/**
+ * Screwdriver Act Handler
+ *
+ * Handles the screwdriver action for all tcomms machines, so they can be open and closed to be deconstructed
+ */
+/obj/machinery/tcomms/screwdriver_act(mob/user, obj/item/I)
+ . = TRUE
+ default_deconstruction_screwdriver(user, icon_state, icon_state, I)
+
+/**
+ * Crowbar Act Handler
+ *
+ * Handles the crowbar action for all tcomms machines, so they can be deconstructed
+ */
+/obj/machinery/tcomms/crowbar_act(mob/user, obj/item/I)
+ . = TRUE
+ default_deconstruction_crowbar(user, I)
diff --git a/code/game/machinery/tcomms/core.dm b/code/game/machinery/tcomms/core.dm
index 88846a5de33..9f84f3af963 100644
--- a/code/game/machinery/tcomms/core.dm
+++ b/code/game/machinery/tcomms/core.dm
@@ -14,6 +14,8 @@
name = "Telecommunications Core"
desc = "A large rack full of communications equipment. Looks important."
icon_state = "core"
+ // This starts as off so you cant make cores as hot spares
+ active = FALSE
/// The NTTC config for this device
var/datum/nttc_configuration/nttc = new()
/// List of all reachable devices
@@ -34,6 +36,12 @@
. = ..()
link_password = GenerateKey()
reachable_zlevels |= loc.z
+ component_parts += new /obj/item/circuitboard/tcomms/core(null)
+ if(check_power_on())
+ active = TRUE
+ else
+ visible_message("Error: Another core is already active in this sector. Power-up cancelled due to radio interference.")
+ update_icon()
/**
* Destructor for the core.
@@ -120,102 +128,126 @@
reachable_zlevels |= R.loc.z
+/**
+ * Z-Level transit change helper
+ *
+ * Handles parent call of disabling the machine if it changes Z-level, but also rebuilds the list of reachable levels
+ */
+/obj/machinery/tcomms/core/onTransitZ(old_z, new_z)
+ . = ..()
+ refresh_zlevels()
+
+/**
+ * Power-on checker
+ *
+ * Checks the z-level to see if an existing core is already powered on, and deny this one turning on if there is one. Returns TRUE if it can power on, or FALSE if it cannot
+ */
+/obj/machinery/tcomms/core/proc/check_power_on()
+ // Cancel if we are already on
+ if(active)
+ return TRUE
+
+ for(var/obj/machinery/tcomms/core/C in GLOB.tcomms_machines)
+ // Make sure we dont check ourselves
+ if(C == src)
+ continue
+ // We dont care about ones on other zlevels
+ if(!atoms_share_level(C, src))
+ continue
+ // If another core is active, return FALSE
+ if(C.active)
+ return FALSE
+ // If we got here there isnt an active core on this Z-level. So return true
+ return TRUE
+
//////////////
// UI STUFF //
//////////////
-/obj/machinery/tcomms/core/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- // this is silly but it has to be done because NTTC inits before languages do
- if(nttc.valid_languages.len == 1)
+/obj/machinery/tcomms/core/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ // This needs to happen here because of how late the language datum initializes. I dont like it
+ if(length(nttc.valid_languages) == 1)
nttc.update_languages()
- // Now the actual UI stuff
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "tcomms_core.tmpl", "Telecommunications Core", 900, 600)
+ ui = new(user, src, ui_key, "TcommsCore", name, 900, 600, master_ui, state)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/tcomms/core/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- // What tab are we on
- data["tab"] = ui_tab
+/obj/machinery/tcomms/core/tgui_data(mob/user)
+ var/list/data = list()
data["ion"] = ion
- // Only send NTTC settings if were on the right tab. This saves on sending overhead.
- if(ui_tab == UI_TAB_CONFIG)
- // Z-level list. Note that this will also show sectors with hidden relay links, but you cant see the relays themselves
- // This allows the crew to realise that sectors have hidden relays
- data["sectors_available"] = "Count: [length(reachable_zlevels)] | List: [jointext(reachable_zlevels, " ")]"
- // Toggles
- data["active"] = active
- data["nttc_toggle_jobs"] = nttc.toggle_jobs
- data["nttc_toggle_job_color"] = nttc.toggle_job_color
- data["nttc_toggle_name_color"] = nttc.toggle_name_color
- data["nttc_toggle_command_bold"] = nttc.toggle_command_bold
- // Strings
- data["nttc_setting_language"] = nttc.setting_language
- data["nttc_job_indicator_type"] = nttc.job_indicator_type
- // Network ID
- data["network_id"] = network_id
+ // Z-level list. Note that this will also show sectors with hidden relay links, but you cant see the relays themselves
+ // This allows the crew to realise that sectors have hidden relays
+ data["sectors_available"] = "Count: [length(reachable_zlevels)] | List: [jointext(reachable_zlevels, " ")]"
+ // Toggles
+ data["active"] = active
+ data["nttc_toggle_jobs"] = nttc.toggle_jobs
+ data["nttc_toggle_job_color"] = nttc.toggle_job_color
+ data["nttc_toggle_name_color"] = nttc.toggle_name_color
+ data["nttc_toggle_command_bold"] = nttc.toggle_command_bold
+ // Strings
+ data["nttc_setting_language"] = nttc.setting_language
+ data["nttc_job_indicator_type"] = nttc.job_indicator_type
+ // Network ID
+ data["network_id"] = network_id
- if(ui_tab == UI_TAB_LINKS)
- data["link_password"] = link_password
- // You ready to see some shit?
- for(var/obj/machinery/tcomms/relay/R in linked_relays)
- // Dont show relays with a hidden link
- if(R.hidden_link)
- continue
- // Assume false
- var/status = FALSE
- var/status_color = "'background-color: #eb4034'" // Red
- if(R.active && !(R.stat & NOPOWER))
- status = TRUE
- status_color = "'background-color: #32a852'" // Green
+ data["link_password"] = link_password
+ // You ready to see some awful shit?
+ var/list/relays = list()
+ for(var/obj/machinery/tcomms/relay/R in linked_relays)
+ // Dont show relays with a hidden link
+ if(R.hidden_link)
+ continue
+ // Assume false
+ var/status = FALSE
+ if(R.active && !(R.stat & NOPOWER))
+ status = TRUE
+ relays += list(list("addr" = "\ref[R]", "net_id" = R.network_id, "sector" = R.loc.z, "status" = status))
- data["entries"] += list(list("addr" = "\ref[R]", "net_id" = R.network_id, "sector" = R.loc.z, "status" = status, "status_color" = status_color))
- // End the shit
+ data["relay_entries"] = relays
+ // End the shit
- if(ui_tab == UI_TAB_FILTER)
- data["filtered_users"] = nttc.filtering
+ data["filtered_users"] = nttc.filtering
return data
-/obj/machinery/tcomms/core/Topic(href, href_list)
+/obj/machinery/tcomms/core/tgui_act(action, list/params)
// Check against href exploits
if(..())
return
- if(href_list["tab"])
- // Make sure its a valid tab
- if(href_list["tab"] in list(UI_TAB_CONFIG, UI_TAB_LINKS, UI_TAB_FILTER))
- ui_tab = href_list["tab"]
+ . = TRUE
- // Check if they did a href, but only for that current tab
- if(ui_tab == UI_TAB_CONFIG)
+ switch(action)
// All the toggle on/offs go here
- if(href_list["toggle_active"])
- active = !active
- update_icon()
+ if("toggle_active")
+ if(check_power_on())
+ active = !active
+ update_icon()
+ else
+ to_chat(usr, "Error: Another core is already active in this sector. Power-up cancelled due to radio interference.")
+
// NTTC Toggles
- if(href_list["nttc_toggle_jobs"])
+ if("nttc_toggle_jobs")
nttc.toggle_jobs = !nttc.toggle_jobs
log_action(usr, "toggled job tags (Now [nttc.toggle_jobs])")
- if(href_list["nttc_toggle_job_color"])
+ if("nttc_toggle_job_color")
nttc.toggle_job_color = !nttc.toggle_job_color
log_action(usr, "toggled job colors (Now [nttc.toggle_job_color])")
- if(href_list["nttc_toggle_name_color"])
+ if("nttc_toggle_name_color")
nttc.toggle_name_color = !nttc.toggle_name_color
log_action(usr, "toggled name colors (Now [nttc.toggle_name_color])")
- if(href_list["nttc_toggle_command_bold"])
+ if("nttc_toggle_command_bold")
nttc.toggle_command_bold = !nttc.toggle_command_bold
log_action(usr, "toggled command bold (Now [nttc.toggle_command_bold])")
// We need to be a little more fancy for the others
// Job Format
- if(href_list["nttc_job_indicator_type"])
+ if("nttc_job_indicator_type")
var/card_style = input(usr, "Pick a job card format.", "Job Card Format") as null|anything in nttc.job_card_styles
if(!card_style)
return
@@ -224,7 +256,7 @@
log_action(usr, "has set NTTC job card format to [card_style]")
// Language Settings
- if(href_list["nttc_setting_language"])
+ if("nttc_setting_language")
var/new_language = input(usr, "Pick a language to convert messages to.", "Language Conversion") as null|anything in nttc.valid_languages
if(!new_language)
return
@@ -238,24 +270,23 @@
log_action(usr, new_language == "--DISABLE--" ? "disabled NTTC language conversion" : "set NTTC language conversion to [new_language]", TRUE)
// Imports and exports
- if(href_list["import"])
+ if("import")
var/json = input(usr, "Provide configuration JSON below.", "Load Config", nttc.nttc_serialize()) as message
if(nttc.nttc_deserialize(json, usr.ckey))
log_action(usr, "has uploaded a NTTC JSON configuration: [ADMIN_SHOWDETAILS("Show", json)]", TRUE)
- if(href_list["export"])
+ if("export")
usr << browse(nttc.nttc_serialize(), "window=save_nttc")
// Set network ID
- if(href_list["network_id"])
+ if("network_id")
var/new_id = input(usr, "Please enter a new network ID", "Network ID", network_id)
log_action(usr, "renamed core with ID [network_id] to [new_id]")
to_chat(usr, "Device ID changed from [network_id] to [new_id].")
network_id = new_id
- if(ui_tab == UI_TAB_LINKS)
- if(href_list["unlink"])
- var/obj/machinery/tcomms/relay/R = locate(href_list["unlink"])
+ if("unlink")
+ var/obj/machinery/tcomms/relay/R = locate(params["addr"])
if(istype(R, /obj/machinery/tcomms/relay))
var/confirm = alert("Are you sure you want to unlink this relay?\nID: [R.network_id]\nADDR: \ref[R]", "Relay Unlink", "Yes", "No")
if(confirm == "Yes")
@@ -264,14 +295,13 @@
else
to_chat(usr, "ERROR: Relay not found. Please file an issue report.")
- if(href_list["change_password"])
+ if("change_password")
var/new_password = input(usr, "Please enter a new password","New Password", link_password)
log_action(usr, "has changed the password on core with ID [network_id] from [link_password] to [new_password]")
to_chat(usr, "Successfully changed password from [link_password] to [new_password].")
link_password = new_password
- if(ui_tab == UI_TAB_FILTER)
- if(href_list["add_filter"])
+ if("add_filter")
// This is a stripped input because I did NOT come this far for this system to be abused by HTML injection
var/name_to_add = stripped_input(usr, "Enter a name to add to the filtering list", "Name Entry")
if(name_to_add == "")
@@ -283,9 +313,8 @@
log_action(usr, "has added [name_to_add] to the NTTC filter list on core with ID [network_id]", TRUE)
to_chat(usr, "Successfully added [name_to_add] to the NTTC filtering list.")
-
- if(href_list["remove_filter"])
- var/name_to_remove = href_list["remove_filter"]
+ if("remove_filter")
+ var/name_to_remove = params["user"]
if(!(name_to_remove in nttc.filtering))
to_chat(usr, "ERROR: Name does not exist in filter list. Please file an issue report.")
else
@@ -296,9 +325,6 @@
to_chat(usr, "Successfully removed [name_to_remove] from the NTTC filtering list.")
- // Hack to speed update the nanoUI
- SSnanoui.update_uis(src)
-
#undef UI_TAB_CONFIG
#undef UI_TAB_LINKS
#undef UI_TAB_FILTER
diff --git a/code/game/machinery/tcomms/relay.dm b/code/game/machinery/tcomms/relay.dm
index 2cd2a44b1af..27d4e750bc5 100644
--- a/code/game/machinery/tcomms/relay.dm
+++ b/code/game/machinery/tcomms/relay.dm
@@ -9,6 +9,8 @@
name = "Telecommunications Relay"
desc = "A large device with several radio antennas on it."
icon_state = "relay"
+ // This starts as off so you cant make cores as hot spares
+ active = FALSE
/// The host core for this relay
var/obj/machinery/tcomms/core/linked_core
/// ID of the hub to auto link to
@@ -25,6 +27,12 @@
*/
/obj/machinery/tcomms/relay/Initialize(mapload)
. = ..()
+ component_parts += new /obj/item/circuitboard/tcomms/relay(null)
+ if(check_power_on())
+ active = TRUE
+ else
+ visible_message("Error: Another relay is already active in this sector. Power-up cancelled due to radio interference.")
+ update_icon()
if(mapload && autolink_id)
return INITIALIZE_HINT_LATELOAD
@@ -50,6 +58,40 @@
// Only ONE of these with one ID should exist per world
break
+/**
+ * Z-Level transit change helper
+ *
+ * Handles parent call of disabling the machine if it changes Z-level, but also rebuilds the list of reachable levels on the linked core
+ */
+/obj/machinery/tcomms/relay/onTransitZ(old_z, new_z)
+ . = ..()
+ if(linked_core)
+ linked_core.refresh_zlevels()
+
+
+/**
+ * Power-on checker
+ *
+ * Checks the z-level to see if an existing relay is already powered on, and deny this one turning on if there is one. Returns TRUE if it can power on, or FALSE if it cannot
+ */
+/obj/machinery/tcomms/relay/proc/check_power_on()
+ // Cancel if we are already on
+ if(active)
+ return TRUE
+
+ for(var/obj/machinery/tcomms/relay/R in GLOB.tcomms_machines)
+ // Make sure we dont check ourselves
+ if(R == src)
+ continue
+ // We dont care about ones on other zlevels
+ if(!atoms_share_level(R, src))
+ continue
+ // If another relay is active, return FALSE
+ if(R.active)
+ return FALSE
+ // If we got here there isnt an active relay on this Z-level. So return TRUE
+ return TRUE
+
/**
* Proc to link the relay to the core.
*
@@ -82,23 +124,22 @@
* Proc which ensures the host core has its zlevels updated (icons are updated by parent call)
*/
/obj/machinery/tcomms/relay/power_change()
- ..()
- if(linked_core)
- linked_core.refresh_zlevels()
+ ..()
+ if(linked_core)
+ linked_core.refresh_zlevels()
//////////////
// UI STUFF //
//////////////
-/obj/machinery/tcomms/relay/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/tcomms/relay/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "tcomms_relay.tmpl", "Telecommunications Relay", 600, 400)
+ ui = new(user, src, ui_key, "TcommsRelay", name, 600, 400, master_ui, state)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/tcomms/relay/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
+/obj/machinery/tcomms/relay/tgui_data(mob/user)
+ var/list/data = list()
// Are we on or not
data["active"] = active
// What is our network ID
@@ -112,47 +153,58 @@
if(linked)
data["linked_core_id"] = linked_core.network_id
data["linked_core_addr"] = "\ref[linked_core]"
-
else
+ var/list/cores = list()
for(var/obj/machinery/tcomms/core/C in GLOB.tcomms_machines)
- data["entries"] += list(list("addr" = "\ref[C]", "net_id" = C.network_id, "sector" = C.loc.z))
+ cores += list(list("addr" = "\ref[C]", "net_id" = C.network_id, "sector" = C.loc.z))
+ data["cores"] = cores
return data
-/obj/machinery/tcomms/relay/Topic(href, href_list)
+/obj/machinery/tcomms/relay/tgui_act(action, list/params)
// Check against href exploits
if(..())
return
- // All the toggle on/offs go here
- if(href_list["toggle_active"])
- active = !active
- update_icon()
- if(linked_core)
- linked_core.refresh_zlevels()
+ . = TRUE
- // Set network ID
- if(href_list["network_id"])
- var/new_id = input(usr, "Please enter a new network ID", "Network ID", network_id)
- log_action(usr, "renamed core with ID [network_id] to [new_id]")
- to_chat(usr, "Device ID changed from [network_id] to [new_id].")
- network_id = new_id
+ switch(action)
+ if("toggle_active")
+ if(check_power_on())
+ active = !active
+ update_icon()
+ if(linked_core)
+ linked_core.refresh_zlevels()
+ else
+ to_chat(usr, "Error: Another relay is already active in this sector. Power-up cancelled due to radio interference.")
+
+ // Set network ID
+ if("network_id")
+ var/new_id = input(usr, "Please enter a new network ID", "Network ID", network_id)
+ log_action(usr, "renamed core with ID [network_id] to [new_id]")
+ to_chat(usr, "Device ID changed from [network_id] to [new_id].")
+ network_id = new_id
- if(linked)
// Only do these hrefs if we are linked to prevent bugs/exploits
- if(href_list["toggle_hidden_link"])
+ if("toggle_hidden_link")
+ if(!linked)
+ return
hidden_link = !hidden_link
log_action(usr, "Modified hidden link for [network_id] (Now [hidden_link])")
- if(href_list["unlink"])
+ if("unlink")
+ if(!linked)
+ return
var/choice = alert(usr, "Are you SURE you want to unlink this relay?\nYou wont be able to re-link without the core password", "Unlink","Yes","No")
if(choice == "Yes")
log_action(usr, "Unlinked [network_id] from [linked_core.network_id]")
Reset()
- else
+
// You should only be able to link if its not linked, to prevent weirdness
- if(href_list["link"])
- var/obj/machinery/tcomms/core/C = locate(href_list["link"])
+ if("link")
+ if(linked)
+ return
+ var/obj/machinery/tcomms/core/C = locate(params["addr"])
if(istype(C, /obj/machinery/tcomms/core))
var/user_pass = input(usr, "Please enter core password","Password Entry")
// Check the password
@@ -165,5 +217,3 @@
to_chat(usr, "ERROR: Core not found. Please file an issue report.")
- // Hack to speed update the nanoUI
- SSnanoui.update_uis(src)
diff --git a/code/game/machinery/teleporter.dm b/code/game/machinery/teleporter.dm
index b92e3c78bb8..005e6720b8b 100644
--- a/code/game/machinery/teleporter.dm
+++ b/code/game/machinery/teleporter.dm
@@ -1,16 +1,24 @@
+#define REGIME_TELEPORT 0
+#define REGIME_GATE 1
+#define REGIME_GPS 2
+
/obj/machinery/computer/teleporter
name = "teleporter control console"
desc = "Used to control a linked teleportation Hub and Station."
icon_screen = "teleport"
icon_keyboard = "teleport_key"
circuit = /obj/item/circuitboard/teleporter
- var/obj/item/gps/locked = null
- var/regime_set = "Teleporter"
+ var/obj/item/gps/locked = null /// A GPS with a locked destination
+ var/regime = REGIME_TELEPORT /// Switches mode between teleporter, gate and gps
var/id = null
- var/obj/machinery/teleport/station/power_station
- var/calibrating
- var/turf/target //Used for one-time-use teleport cards (such as clown planet coordinates.)
- //Setting this to 1 will set src.locked to null after a player enters the portal and will not allow hand-teles to open portals to that location.
+ var/obj/machinery/teleport/station/power_station /// The power station that's connected to the console
+ var/calibrating = FALSE /// Whether calibration is in progress or not. Calibration prevents changes.
+ var/turf/target ///The target turf of the teleporter
+ var/target_list ///lists of suitable teleport targets, dependent on regime. Used in the UI
+
+ /* var/area_bypass is for one-time-use teleport cards (such as clown planet coordinates.)
+ Setting this to TRUE will set var/obj/item/gps/locked to null after a player enters the portal and will not allow hand-teles to open portals to that location.
+ */
var/area_bypass = FALSE
var/cc_beacon = FALSE
@@ -24,6 +32,7 @@
..()
link_power_station()
update_icon()
+ target_list = targets_teleport()
/obj/machinery/computer/teleporter/Destroy()
if(power_station)
@@ -55,202 +64,238 @@
/obj/machinery/computer/teleporter/emag_act(mob/user)
if(!emagged)
- emagged = 1
+ emagged = TRUE
to_chat(user, "The teleporter can now lock on to Syndicate beacons!")
else
- ui_interact(user)
+ tgui_interact(user)
/obj/machinery/computer/teleporter/attack_ai(mob/user)
- src.attack_hand(user)
+ attack_hand(user)
/obj/machinery/computer/teleporter/attack_hand(mob/user)
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/teleporter/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
+
+/obj/machinery/computer/teleporter/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
if(stat & (NOPOWER|BROKEN))
return
-
- // Set up the Nano UI
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "teleporter_console.tmpl", "Teleporter Console UI", 400, 400)
+ ui = new(user, src, ui_key, "Teleporter", "Teleporter Console", 380, 260)
ui.open()
-/obj/machinery/computer/teleporter/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
+/obj/machinery/computer/teleporter/tgui_data(mob/user)
+ var/list/data = list()
data["powerstation"] = power_station
- if(power_station)
+ if(power_station?.teleporter_hub)
data["teleporterhub"] = power_station.teleporter_hub
data["calibrated"] = power_station.teleporter_hub.calibrated
- data["accurate"] = power_station.teleporter_hub.accurate
else
data["teleporterhub"] = null
data["calibrated"] = null
- data["accurate"] = null
- data["regime"] = regime_set
+ data["regime"] = regime
var/area/targetarea = get_area(target)
data["target"] = (!target || !targetarea) ? "None" : sanitize(targetarea.name)
data["calibrating"] = calibrating
- data["locked"] = locked
+ data["locked"] = locked ? TRUE : FALSE
+ data["targetsTeleport"] = target_list
return data
-/obj/machinery/computer/teleporter/Topic(href, href_list)
+/obj/machinery/computer/teleporter/tgui_act(action, params)
if(..())
- return 1
-
- if(href_list["eject"])
- eject()
- SSnanoui.update_uis(src)
return
if(!check_hub_connection())
- to_chat(usr, "Error: Unable to detect hub.")
- SSnanoui.update_uis(src)
+ atom_say("Error: Unable to detect hub.")
return
if(calibrating)
- to_chat(usr, "Error: Calibration in progress. Stand by.")
- SSnanoui.update_uis(src)
+ atom_say("Error: Calibration in progress. Stand by.")
return
- if(href_list["regimeset"])
- power_station.engaged = 0
- power_station.teleporter_hub.update_icon()
- power_station.teleporter_hub.calibrated = 0
- reset_regime()
- SSnanoui.update_uis(src)
- if(href_list["settarget"])
- power_station.engaged = 0
- power_station.teleporter_hub.update_icon()
- power_station.teleporter_hub.calibrated = 0
- set_target(usr)
- SSnanoui.update_uis(src)
- if(href_list["lock"])
- power_station.engaged = 0
- power_station.teleporter_hub.update_icon()
- power_station.teleporter_hub.calibrated = 0
- target = get_turf(locked.locked_location)
- SSnanoui.update_uis(src)
- if(href_list["calibrate"])
- if(!target)
- to_chat(usr, "Error: No target set to calibrate to.")
- SSnanoui.update_uis(src)
- return
- if(power_station.teleporter_hub.calibrated || power_station.teleporter_hub.accurate >= 3)
- to_chat(usr, "Hub is already calibrated.")
- SSnanoui.update_uis(src)
- return
- src.visible_message("Processing hub calibration to target...")
+ . = TRUE
- calibrating = 1
- SSnanoui.update_uis(src)
- spawn(50 * (3 - power_station.teleporter_hub.accurate)) //Better parts mean faster calibration
- calibrating = 0
- if(check_hub_connection())
- power_station.teleporter_hub.calibrated = 1
- src.visible_message("Calibration complete.")
- else
- src.visible_message("Error: Unable to detect hub.")
- SSnanoui.update_uis(src)
+ switch(action)
+ if("eject") //eject gps device
+ eject()
+ if("load") //load gps coordinates
+ target = locate(locked.locked_location.x,locked.locked_location.y,locked.locked_location.z)
+ if("setregime")
+ regime = text2num(params["regime"])
+ if(regime == REGIME_TELEPORT)
+ target_list = targets_teleport()
+ if(regime == REGIME_GATE)
+ target_list = targets_gate()
+ if(regime == REGIME_GPS)
+ target_list = null //clears existing entries, target is added by load action
+ resetPowerstation()
+ target = null
+ if("settarget")
+ resetPowerstation()
+ var/turf/tmpTarget = locate(text2num(params["x"]),text2num(params["y"]),text2num(params["z"]))
+ if(!istype(tmpTarget, /turf))
+ atom_say("No valid targets available.")
+ return
+ target = tmpTarget
+ if(regime == REGIME_TELEPORT)
+ teleport_helper()
+ if(regime == REGIME_GATE)
+ gate_helper()
+ if("calibrate")
+ if(!target)
+ atom_say("Error: No target set to calibrate to.")
+ return
+ if(power_station.teleporter_hub.calibrated || power_station.teleporter_hub.accurate >= 3)
+ atom_say("Hub is already calibrated.")
+ return
- SSnanoui.update_uis(src)
+ atom_say("Processing hub calibration to target...")
+ calibrating = TRUE
+ addtimer(CALLBACK(src, .proc/calibrateCallback), 50 * (3 - power_station.teleporter_hub.accurate)) //Better parts mean faster calibration
+
+/**
+* Resets the connected powerstation to initial values. Helper function of tgui_act
+*/
+/obj/machinery/computer/teleporter/proc/resetPowerstation()
+ power_station.engaged = FALSE
+ power_station.teleporter_hub.calibrated = FALSE
+ power_station.teleporter_hub.update_icon()
+
+/**
+* Calibrates the hub. Helper function of tgui_act
+*/
+/obj/machinery/computer/teleporter/proc/calibrateCallback()
+ calibrating = FALSE
+ if(check_hub_connection())
+ power_station.teleporter_hub.calibrated = TRUE
+ atom_say("Calibration complete.")
+ else
+ atom_say("Error: Unable to detect hub.")
/obj/machinery/computer/teleporter/proc/check_hub_connection()
if(!power_station)
return
if(!power_station.teleporter_hub)
return
- return 1
-
-/obj/machinery/computer/teleporter/proc/reset_regime()
- target = null
- if(regime_set == "Teleporter")
- regime_set = "Gate"
- else
- regime_set = "Teleporter"
+ return TRUE
+/**
+* Helper function of tgui_act
+*
+* Triggered when ejecting a gps device. Sets the gps to the ground and resets the console
+*/
/obj/machinery/computer/teleporter/proc/eject()
if(locked)
locked.loc = loc
locked = null
+ regime = REGIME_TELEPORT
+ target_list = targets_teleport()
-/obj/machinery/computer/teleporter/proc/set_target(mob/user)
- area_bypass = FALSE
- if(regime_set == "Teleporter")
- var/list/L = list()
- var/list/areaindex = list()
+/**
+* Creates a list of viable targets for the teleport. Helper function of tgui_data
+*/
+/obj/machinery/computer/teleporter/proc/targets_teleport()
+ var/list/L = list()
+ var/list/areaindex = list()
- for(var/obj/item/radio/beacon/R in GLOB.beacons)
- var/turf/T = get_turf(R)
- if(!T)
- continue
- if(!is_teleport_allowed(T.z) && !R.cc_beacon)
- continue
- if(R.syndicate == 1 && emagged == 0)
- continue
- var/tmpname = T.loc.name
+ for(var/obj/item/radio/beacon/R in GLOB.beacons)
+ var/turf/T = get_turf(R)
+ if(!T)
+ continue
+ if(!is_teleport_allowed(T.z) && !R.cc_beacon)
+ continue
+ if(R.syndicate && !emagged)
+ continue
+ var/tmpname = T.loc.name
+ if(areaindex[tmpname])
+ tmpname = "[tmpname] ([++areaindex[tmpname]])"
+ else
+ areaindex[tmpname] = 1
+ L[tmpname] = list(
+ "name" = tmpname,
+ "x" = T.x,
+ "y" = T.y,
+ "z" = T.z)
+
+ for(var/obj/item/implant/tracking/I in GLOB.tracked_implants)
+ if(!I.implanted || !ismob(I.loc))
+ continue
+ else
+ var/mob/M = I.loc
+ if(M.stat == DEAD)
+ if(M.timeofdeath + 6000 < world.time)
+ continue
+ var/turf/T = get_turf(M)
+ if(!T) continue
+ if(!is_teleport_allowed(T.z)) continue
+ var/tmpname = M.real_name
if(areaindex[tmpname])
tmpname = "[tmpname] ([++areaindex[tmpname]])"
else
areaindex[tmpname] = 1
- L[tmpname] = R
+ L[tmpname] = list(
+ "name" = tmpname,
+ "x" = T.x,
+ "y" = T.y,
+ "z" = T.z)
+ return L
- for(var/obj/item/implant/tracking/I in GLOB.tracked_implants)
- if(!I.implanted || !ismob(I.loc))
- continue
- else
- var/mob/M = I.loc
- if(M.stat == DEAD)
- if(M.timeofdeath + 6000 < world.time)
- continue
- var/turf/T = get_turf(M)
- if(!T) continue
- if(!is_teleport_allowed(T.z)) continue
- var/tmpname = M.real_name
- if(areaindex[tmpname])
- tmpname = "[tmpname] ([++areaindex[tmpname]])"
- else
- areaindex[tmpname] = 1
- L[tmpname] = I
+/**
+* Creates a list of viable targets for the gate. Helper function of tgui_data
+*/
+/obj/machinery/computer/teleporter/proc/targets_gate(mob/users)
+ var/list/L = list()
+ var/list/areaindex = list()
+ var/list/S = power_station.linked_stations
+ if(!S.len)
+ return L
+ for(var/obj/machinery/teleport/station/R in S)
+ var/turf/T = get_turf(R)
+ if(!T || !R.teleporter_hub || !R.teleporter_console)
+ continue
+ if(!is_teleport_allowed(T.z))
+ continue
+ var/tmpname = T.loc.name
+ if(areaindex[tmpname])
+ tmpname = "[tmpname] ([++areaindex[tmpname]])"
+ else
+ areaindex[tmpname] = 1
+ L[tmpname] = list(
+ "name" = tmpname,
+ "x" = T.x,
+ "y" = T.y,
+ "z" = T.z)
+ return L
- var/desc = input("Please select a location to lock in.", "Locking Computer") in L
- target = L[desc]
- if(istype(target, /obj/item/radio/beacon))
- var/obj/item/radio/beacon/B = target
+/**
+* Helper function of tgui_act.
+*
+* Called after selecting a target for the gate in the UI. Sets area_bypass and cc_beacon.
+*/
+/obj/machinery/computer/teleporter/proc/teleport_helper()
+ area_bypass = FALSE
+ for(var/item in target.contents)
+ if(istype(item, /obj/item/radio/beacon))
+ var/obj/item/radio/beacon/B = item
if(B.area_bypass)
area_bypass = TRUE
cc_beacon = B.cc_beacon
- else
- var/list/L = list()
- var/list/areaindex = list()
- var/list/S = power_station.linked_stations
- if(!S.len)
- to_chat(user, "No connected stations located.")
- return
- for(var/obj/machinery/teleport/station/R in S)
- var/turf/T = get_turf(R)
- if(!T || !R.teleporter_hub || !R.teleporter_console)
- continue
- if(!is_teleport_allowed(T.z))
- continue
- var/tmpname = T.loc.name
- if(areaindex[tmpname])
- tmpname = "[tmpname] ([++areaindex[tmpname]])"
- else
- areaindex[tmpname] = 1
- L[tmpname] = R
- var/desc = input("Please select a station to lock in.", "Locking Computer") in L
- target = L[desc]
- if(target)
- var/obj/machinery/teleport/station/trg = target
- trg.linked_stations |= power_station
- trg.stat &= ~NOPOWER
- if(trg.teleporter_hub)
- trg.teleporter_hub.stat &= ~NOPOWER
- trg.teleporter_hub.update_icon()
- if(trg.teleporter_console)
- trg.teleporter_console.stat &= ~NOPOWER
- trg.teleporter_console.update_icon()
- return
+
+/**
+* Helper function of tgui_act.
+*
+* Called after selecting a target for the teleporter in the UI.
+*/
+/obj/machinery/computer/teleporter/proc/gate_helper()
+ area_bypass = FALSE
+ var/obj/machinery/teleport/station/trg = target
+ trg.linked_stations |= power_station
+ trg.stat &= ~NOPOWER
+ if(trg.teleporter_hub)
+ trg.teleporter_hub.stat &= ~NOPOWER
+ trg.teleporter_hub.update_icon()
+ if(trg.teleporter_console)
+ trg.teleporter_console.stat &= ~NOPOWER
+ trg.teleporter_console.update_icon()
/proc/find_loc(obj/R as obj)
if(!R) return null
@@ -263,20 +308,20 @@
/obj/machinery/teleport
name = "teleport"
icon = 'icons/obj/stationobjs.dmi'
- density = 1
- anchored = 1.0
+ density = TRUE
+ anchored = TRUE
/obj/machinery/teleport/hub
name = "teleporter hub"
desc = "It's the hub of a teleporting machine."
icon_state = "tele0"
- var/accurate = 0
+ var/accurate = FALSE
use_power = IDLE_POWER_USE
idle_power_usage = 10
active_power_usage = 2000
var/obj/machinery/teleport/station/power_station
var/calibrated //Calibration prevents mutation
- var/admin_usage = 0 // if 1, works on z2. If 0, doesn't. Used for admin room teleport.
+ var/admin_usage = FALSE // if 1, works on z2. If 0, doesn't. Used for admin room teleport.
/obj/machinery/teleport/hub/New()
..()
@@ -317,6 +362,7 @@
for(dir in list(NORTH,EAST,SOUTH,WEST))
power_station = locate(/obj/machinery/teleport/station, get_step(src, dir))
if(power_station)
+ power_station.link_console_and_hub()
break
return power_station
@@ -324,27 +370,12 @@
if(!is_teleport_allowed(z) && !admin_usage)
to_chat(M, "You can't use this here.")
return
- if(power_station && power_station.engaged && !panel_open)
- //--FalseIncarnate
- //Prevents AI cores from using the teleporter, prints out failure messages for clarity
- if(istype(M, /mob/living/silicon/ai) || istype(M, /obj/structure/AIcore))
- visible_message("The teleporter rejects the AI unit.")
- if(istype(M, /mob/living/silicon/ai))
- var/mob/living/silicon/ai/T = M
- var/list/TPError = list("Firmware instructions dictate you must remain on your assigned station!",
- "You cannot interface with this technology and get rejected!",
- "External firewalls prevent you from utilizing this machine!",
- "Your AI core's anti-bluespace failsafes trigger and prevent teleportation!")
- to_chat(T, "[pick(TPError)]")
- return
- else
- if(!teleport(M) && isliving(M)) // the isliving(M) is needed to avoid triggering errors if a spark bumps the telehub
- visible_message("[src] emits a loud buzz, as its teleport portal flickers and fails!")
- playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
- power_station.toggle() // turn off the portal.
-
- use_power(5000)
- //--FalseIncarnate
+ if(power_station && power_station.engaged && !panel_open && !blockAI(M) && !istype(M, /obj/spacepod))
+ if(!teleport(M) && isliving(M)) // the isliving(M) is needed to avoid triggering errors if a spark bumps the telehub
+ visible_message("[src] emits a loud buzz, as its teleport portal flickers and fails!")
+ playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
+ power_station.toggle() // turn off the portal.
+ use_power(5000)
return
/obj/machinery/teleport/hub/attackby(obj/item/I, mob/user, params)
@@ -375,7 +406,7 @@
. = do_teleport(M, locate(rand((2*TRANSITIONEDGE), world.maxx - (2*TRANSITIONEDGE)), rand((2*TRANSITIONEDGE), world.maxy - (2*TRANSITIONEDGE)), 3), 2, bypass_area_flag = com.area_bypass)
else
. = do_teleport(M, com.target, bypass_area_flag = com.area_bypass)
- calibrated = 0
+ calibrated = FALSE
/obj/machinery/teleport/hub/update_icon()
if(panel_open)
@@ -389,7 +420,7 @@
name = "permanent teleporter"
desc = "A teleporter with the target pre-set on the circuit board."
icon_state = "tele0"
- var/recalibrating = 0
+ var/recalibrating = FALSE
use_power = IDLE_POWER_USE
idle_power_usage = 10
active_power_usage = 2000
@@ -406,37 +437,42 @@
tele_delay = max(A, 0)
update_icon()
-/obj/machinery/teleport/perma/Bumped(M as mob|obj)
+/**
+ Internal helper function
+
+ Prevents AI from using the teleporter, prints out failure messages for clarity
+*/
+/obj/machinery/teleport/proc/blockAI(atom/A)
+ if(istype(A, /mob/living/silicon/ai) || istype(A, /obj/structure/AIcore))
+ visible_message("The teleporter rejects the AI unit.")
+ if(istype(A, /mob/living/silicon/ai))
+ var/mob/living/silicon/ai/T = A
+ var/list/TPError = list("Firmware instructions dictate you must remain on your assigned station!",
+ "You cannot interface with this technology and get rejected!",
+ "External firewalls prevent you from utilizing this machine!",
+ "Your AI core's anti-bluespace failsafes trigger and prevent teleportation!")
+ to_chat(T, "[pick(TPError)]")
+ return TRUE
+ return FALSE
+
+/obj/machinery/teleport/perma/Bumped(atom/A)
if(stat & (BROKEN|NOPOWER))
return
if(!is_teleport_allowed(z))
- to_chat(M, "You can't use this here.")
+ to_chat(A, "You can't use this here.")
return
- if(target && !recalibrating && !panel_open)
- //--FalseIncarnate
- //Prevents AI cores from using the teleporter, prints out failure messages for clarity
- if(istype(M, /mob/living/silicon/ai) || istype(M, /obj/structure/AIcore))
- visible_message("The teleporter rejects the AI unit.")
- if(istype(M, /mob/living/silicon/ai))
- var/mob/living/silicon/ai/T = M
- var/list/TPError = list("Firmware instructions dictate you must remain on your assigned station!",
- "You cannot interface with this technology and get rejected!",
- "External firewalls prevent you from utilizing this machine!",
- "Your AI core's anti-bluespace failsafes trigger and prevent teleportation!")
- to_chat(T, "[pick(TPError)]")
- return
- else
- do_teleport(M, target)
- use_power(5000)
- if(tele_delay)
- recalibrating = 1
- update_icon()
- spawn(tele_delay)
- recalibrating = 0
- update_icon()
- //--FalseIncarnate
- return
+ if(target && !recalibrating && !panel_open && !blockAI(A))
+ do_teleport(A, target)
+ use_power(5000)
+ if(tele_delay)
+ recalibrating = TRUE
+ update_icon()
+ addtimer(CALLBACK(src, .proc/BumpedCallback), tele_delay)
+
+/obj/machinery/teleport/perma/proc/BumpedCallback()
+ recalibrating = FALSE
+ update_icon()
/obj/machinery/teleport/perma/power_change()
..()
@@ -467,7 +503,7 @@
name = "station"
desc = "The power control station for a bluespace teleporter."
icon_state = "controller"
- var/engaged = 0
+ var/engaged = FALSE
use_power = IDLE_POWER_USE
idle_power_usage = 10
active_power_usage = 2000
@@ -568,7 +604,7 @@
/obj/machinery/teleport/station/attack_ai()
- src.attack_hand()
+ attack_hand()
/obj/machinery/teleport/station/attack_hand(mob/user)
if(!panel_open)
@@ -583,12 +619,12 @@
to_chat(user, "Close the hub's maintenance panel first.")
return
if(teleporter_console.target)
- src.engaged = !src.engaged
+ engaged = !engaged
use_power(5000)
visible_message("Teleporter [engaged ? "" : "dis"]engaged!")
else
visible_message("No target detected.")
- src.engaged = 0
+ engaged = FALSE
teleporter_hub.update_icon()
if(istype(user))
add_fingerprint(user)
diff --git a/code/game/machinery/transformer.dm b/code/game/machinery/transformer.dm
index cc9fc3e4666..18abeba7400 100644
--- a/code/game/machinery/transformer.dm
+++ b/code/game/machinery/transformer.dm
@@ -6,17 +6,45 @@
layer = MOB_LAYER+1 // Overhead
anchored = 1
density = 1
- var/transform_dead = 0
- var/transform_standing = 0
- var/cooldown_duration = 600 // 1 minute
- var/cooldown = 0
- var/robot_cell_charge = 5000
+ /// TRUE if the factory can transform dead mobs.
+ var/transform_dead = TRUE
+ /// TRUE if the mob can be standing and still be transformed.
+ var/transform_standing = TRUE
+ /// Cooldown between each transformation, in deciseconds.
+ var/cooldown_duration = 1 MINUTES
+ /// If the factory is currently on cooldown from its last transformation.
+ var/is_on_cooldown = FALSE
+ /// The type of cell that newly created borgs get.
+ var/robot_cell_type = /obj/item/stock_parts/cell/high/plus
+ /// The direction that mobs must moving in to get transformed.
var/acceptdir = EAST
+ /// The AI who placed this factory.
+ var/mob/living/silicon/ai/masterAI
-/obj/machinery/transformer/New()
- // On us
- ..()
- new /obj/machinery/conveyor/auto(loc, WEST)
+/obj/machinery/transformer/Initialize(mapload, mob/living/silicon/ai/_ai = null)
+ . = ..()
+ if(_ai)
+ masterAI = _ai
+ initialize_belts()
+
+/// Used to create all of the belts the transformer will be using. All belts should be pushing `WEST`.
+/obj/machinery/transformer/proc/initialize_belts()
+ var/turf/T = get_turf(src)
+ if(!T)
+ return
+
+ // Belt under the factory.
+ new /obj/machinery/conveyor/auto(T, WEST)
+
+ // Get the turf 1 tile to the EAST.
+ var/turf/east = locate(T.x + 1, T.y, T.z)
+ if(istype(east, /turf/simulated/floor))
+ new /obj/machinery/conveyor/auto(east, WEST)
+
+ // Get the turf 1 tile to the WEST.
+ var/turf/west = locate(T.x - 1, T.y, T.z)
+ if(istype(west, /turf/simulated/floor))
+ new /obj/machinery/conveyor/auto(west, WEST)
/obj/machinery/transformer/power_change()
..()
@@ -24,7 +52,7 @@
/obj/machinery/transformer/update_icon()
..()
- if(stat & (BROKEN|NOPOWER) || cooldown == 1)
+ if(is_on_cooldown || stat & (BROKEN|NOPOWER))
icon_state = "separator-AO0"
else
icon_state = initial(icon_state)
@@ -35,120 +63,76 @@
C.setDir(newdir)
acceptdir = turn(newdir, 180)
-/obj/machinery/transformer/Bumped(var/atom/movable/AM)
+/// Resets `is_on_cooldown` to `FALSE` and updates our icon. Used in a callback after the transformer does a transformation.
+/obj/machinery/transformer/proc/reset_cooldown()
+ is_on_cooldown = FALSE
+ update_icon()
- if(cooldown == 1)
+/obj/machinery/transformer/Bumped(atom/movable/AM)
+ // They have to be human to be transformed.
+ if(is_on_cooldown || !ishuman(AM))
return
- // Crossed didn't like people lying down.
- if(ishuman(AM))
- // Only humans can enter from the west side, while lying down.
- var/move_dir = get_dir(loc, AM.loc)
- var/mob/living/carbon/human/H = AM
- if((transform_standing || H.lying) && move_dir == acceptdir)// || move_dir == WEST)
- AM.loc = src.loc
- do_transform(AM)
+ var/mob/living/carbon/human/H = AM
+ var/move_dir = get_dir(loc, H.loc)
-/obj/machinery/transformer/proc/do_transform(var/mob/living/carbon/human/H)
- if(stat & (BROKEN|NOPOWER))
- return
- if(cooldown == 1)
+ if((transform_standing || H.lying) && move_dir == acceptdir)
+ H.forceMove(drop_location())
+ do_transform(H)
+
+/// Transforms a human mob into a cyborg, connects them to the malf AI which placed the factory.
+/obj/machinery/transformer/proc/do_transform(mob/living/carbon/human/H)
+ if(is_on_cooldown || stat & (BROKEN|NOPOWER))
return
if(!transform_dead && H.stat == DEAD)
- playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
+ playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
return
- playsound(src.loc, 'sound/items/welder.ogg', 50, 1)
- H.emote("scream") // It is painful
- H.adjustBruteLoss(max(0, 80 - H.getBruteLoss())) // Hurt the human, don't try to kill them though.
-
- // Sleep for a couple of ticks to allow the human to see the pain
- sleep(5)
-
+ playsound(loc, 'sound/items/welder.ogg', 50, 1)
use_power(5000) // Use a lot of power.
- var/mob/living/silicon/robot/R = H.Robotize(1) // Delete the items or they'll all pile up in a single tile and lag
-
- R.cell.maxcharge = robot_cell_charge
- R.cell.charge = robot_cell_charge
-
- // So he can't jump out the gate right away.
- R.lockcharge = !R.lockcharge
- spawn(50)
- playsound(src.loc, 'sound/machines/ping.ogg', 50, 0)
- sleep(30)
- if(R)
- R.lockcharge = !R.lockcharge
- R.notify_ai(1)
// Activate the cooldown
- cooldown = 1
+ is_on_cooldown = TRUE
update_icon()
- spawn(cooldown_duration)
- cooldown = 0
- update_icon()
-
-/obj/machinery/transformer/conveyor/New()
- ..()
- var/turf/T = loc
- if(T)
- // Spawn Conveyour Belts
-
- //East
- var/turf/east = locate(T.x + 1, T.y, T.z)
- if(istype(east, /turf/simulated/floor))
- new /obj/machinery/conveyor/auto(east, WEST)
-
- // West
- var/turf/west = locate(T.x - 1, T.y, T.z)
- if(istype(west, /turf/simulated/floor))
- new /obj/machinery/conveyor/auto(west, WEST)
+ addtimer(CALLBACK(src, .proc/reset_cooldown), cooldown_duration)
+ addtimer(CALLBACK(null, .proc/playsound, loc, 'sound/machines/ping.ogg', 50, 0), 3 SECONDS)
+ H.emote("scream")
+ if(!masterAI) // If the factory was placed via admin spawning or other means, it wont have an owner_AI.
+ H.Robotize(robot_cell_type)
+ return
+ var/mob/living/silicon/robot/R = H.Robotize(robot_cell_type, FALSE, masterAI)
+ if(R.mind && !R.client && !R.grab_ghost()) // Make sure this is an actual player first and not just a humanized monkey or something.
+ message_admins("[key_name_admin(R)] was just transformed by a borg factory, but they were SSD. Polling ghosts for a replacement.")
+ var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a malfunctioning cyborg?", ROLE_TRAITOR, poll_time = 15 SECONDS)
+ if(!length(candidates))
+ return
+ var/mob/dead/observer/O = pick(candidates)
+ R.key= O.key
/obj/machinery/transformer/mime
name = "Mimetech Greyscaler"
desc = "Turns anything placed inside black and white."
-
-/obj/machinery/transformer/mime/conveyor/New()
- ..()
- var/turf/T = loc
- if(T)
- // Spawn Conveyour Belts
-
- //East
- var/turf/east = locate(T.x + 1, T.y, T.z)
- if(istype(east, /turf/simulated/floor))
- new /obj/machinery/conveyor/auto(east, WEST)
-
- // West
- var/turf/west = locate(T.x - 1, T.y, T.z)
- if(istype(west, /turf/simulated/floor))
- new /obj/machinery/conveyor/auto(west, WEST)
-
-/obj/machinery/transformer/mime/Bumped(var/atom/movable/AM)
-
- if(cooldown == 1)
+/obj/machinery/transformer/mime/Bumped(atom/movable/AM)
+ if(is_on_cooldown)
return
// Crossed didn't like people lying down.
- if(isatom(AM))
- AM.loc = src.loc
+ if(istype(AM))
+ AM.forceMove(drop_location())
do_transform_mime(AM)
else
to_chat(AM, "Only items can be greyscaled.")
return
-/obj/machinery/transformer/proc/do_transform_mime(var/obj/item/I)
- if(stat & (BROKEN|NOPOWER))
- return
- if(cooldown == 1)
+/obj/machinery/transformer/proc/do_transform_mime(obj/item/I)
+ if(is_on_cooldown || stat & (BROKEN|NOPOWER))
return
- playsound(src.loc, 'sound/items/welder.ogg', 50, 1)
- // Sleep for a couple of ticks to allow the human to see the pain
- sleep(5)
+ playsound(loc, 'sound/items/welder.ogg', 50, 1)
use_power(5000) // Use a lot of power.
var/icon/newicon = new(I.icon, I.icon_state)
@@ -156,42 +140,27 @@
I.icon = newicon
// Activate the cooldown
- cooldown = 1
+ is_on_cooldown = TRUE
update_icon()
- spawn(cooldown_duration)
- cooldown = 0
- update_icon()
+ addtimer(CALLBACK(src, .proc/reset_cooldown), cooldown_duration)
/obj/machinery/transformer/xray
name = "Automatic X-Ray 5000"
desc = "A large metalic machine with an entrance and an exit. A sign on the side reads, 'backpack go in, backpack come out', 'human go in, irradiated human come out'."
+ acceptdir = WEST
-/obj/machinery/transformer/xray/Initialize(mapload)
- . = ..()
- // On us
- new /obj/machinery/conveyor/auto(loc, EAST)
-
-/obj/machinery/transformer/xray/conveyor/New()
- ..()
- var/turf/T = loc
+/obj/machinery/transformer/xray/initialize_belts()
+ var/turf/T = get_turf(src)
if(T)
- // Spawn Conveyour Belts
+ // This handles the belt under the transformer and 1 tile to the left and right.
+ . = ..()
- //East
- var/turf/east = locate(T.x + 1, T.y, T.z)
- if(istype(east, /turf/simulated/floor))
- new /obj/machinery/conveyor/auto(east, EAST)
- //East2
+ // Get the turf 2 tiles to the EAST.
var/turf/east2 = locate(T.x + 2, T.y, T.z)
if(istype(east2, /turf/simulated/floor))
new /obj/machinery/conveyor/auto(east2, EAST)
- // West
- var/turf/west = locate(T.x - 1, T.y, T.z)
- if(istype(west, /turf/simulated/floor))
- new /obj/machinery/conveyor/auto(west, EAST)
-
- // West2
+ // Get the turf 2 tiles to the WEST.
var/turf/west2 = locate(T.x - 2, T.y, T.z)
if(istype(west2, /turf/simulated/floor))
new /obj/machinery/conveyor/auto(west2, EAST)
@@ -207,30 +176,30 @@
else
icon_state = initial(icon_state)
-/obj/machinery/transformer/xray/Bumped(var/atom/movable/AM)
-
- if(cooldown == 1)
+/obj/machinery/transformer/xray/Bumped(atom/movable/AM)
+ if(is_on_cooldown)
return
// Crossed didn't like people lying down.
if(ishuman(AM))
// Only humans can enter from the west side, while lying down.
- var/move_dir = get_dir(loc, AM.loc)
var/mob/living/carbon/human/H = AM
- if(H.lying && move_dir == WEST)// || move_dir == WEST)
- AM.loc = src.loc
- irradiate(AM)
+ var/move_dir = get_dir(loc, H.loc)
- else if(isatom(AM))
- AM.loc = src.loc
+ if(H.lying && move_dir == acceptdir)
+ H.forceMove(drop_location())
+ irradiate(H)
+
+ else if(istype(AM))
+ AM.forceMove(drop_location())
scan(AM)
-/obj/machinery/transformer/xray/proc/irradiate(var/mob/living/carbon/human/H)
+/obj/machinery/transformer/xray/proc/irradiate(mob/living/carbon/human/H)
if(stat & (BROKEN|NOPOWER))
return
flick("separator-AO0",src)
- playsound(src.loc, 'sound/effects/alert.ogg', 50, 0)
+ playsound(loc, 'sound/effects/alert.ogg', 50, 0)
sleep(5)
H.apply_effect((rand(150,200)),IRRADIATE,0)
if(prob(5))
@@ -242,15 +211,15 @@
domutcheck(H,null,1)
-/obj/machinery/transformer/xray/proc/scan(var/obj/item/I)
+/obj/machinery/transformer/xray/proc/scan(obj/item/I)
if(scan_rec(I))
- playsound(src.loc, 'sound/effects/alert.ogg', 50, 0)
+ playsound(loc, 'sound/effects/alert.ogg', 50, 0)
flick("separator-AO0",src)
else
- playsound(src.loc, 'sound/machines/ping.ogg', 50, 0)
+ playsound(loc, 'sound/machines/ping.ogg', 50, 0)
sleep(30)
-/obj/machinery/transformer/xray/proc/scan_rec(var/obj/item/I)
+/obj/machinery/transformer/xray/proc/scan_rec(obj/item/I)
if(istype(I, /obj/item/gun))
return TRUE
if(istype(I, /obj/item/transfer_valve))
diff --git a/code/game/machinery/turntable.dm b/code/game/machinery/turntable.dm
deleted file mode 100644
index 70d7adff2be..00000000000
--- a/code/game/machinery/turntable.dm
+++ /dev/null
@@ -1,280 +0,0 @@
-/sound/turntable/test
- file = 'sound/turntable/testloop1.ogg'
- falloff = 2
- repeat = 1
-
-/mob/var/music = 0
-
-/obj/machinery/party/turntable
- name = "turntable"
- desc = "A turntable used for parties and shit."
- icon = 'icons/effects/lasers2.dmi'
- icon_state = "turntable"
- var/playing = 0
- anchored = 1
-
-/obj/machinery/party/mixer
- name = "mixer"
- desc = "A mixing board for mixing music"
- icon = 'icons/effects/lasers2.dmi'
- icon_state = "mixer"
- anchored = 1
-
-
-/obj/machinery/party/turntable/New()
- ..()
- sleep(2)
- new /sound/turntable/test(src)
- return
-
-/obj/machinery/party/turntable/attack_hand(mob/user as mob)
-
- var/t = "Turntable Interface
"
- //t += "On "
- t += "Off
"
- t += "One "
- t += "TestLoop2 "
- t += "TestLoop3 "
-
- user << browse(t, "window=turntable;size=420x700")
-
-
-/obj/machinery/party/turntable/Topic(href, href_list)
- ..()
- if( href_list["on1"] )
- if(src.playing == 0)
-// to_chat(world, "Should be working...")
- var/sound/S = sound('sound/turntable/testloop1.ogg')
- S.repeat = 1
- S.channel = 10
- S.falloff = 2
- S.wait = 1
- S.environment = 0
- //for(var/mob/M in world)
- // if(M.loc.loc == src.loc.loc && M.music == 0)
-// to_chat(world, "Found the song...")
-// M << S
- // M.music = 1
- var/area/A = src.loc.loc
-
- for(var/obj/machinery/party/lasermachine/L in A)
- L.turnon()
- playing = 1
- while(playing == 1)
- for(var/mob/M in world)
- if((M.loc.loc in A) && M.music == 0)
-// to_chat(world, "Found the song...")
- M << S
- M.music = 1
- else if(!(M.loc.loc in A) && M.music == 1)
- var/sound/Soff = sound(null)
- Soff.channel = 10
- M << Soff
- M.music = 0
- sleep(10)
- return
- if( href_list["on2"] )
- if(src.playing == 0)
-// to_chat(world, "Should be working...")
- var/sound/S = sound('sound/turntable/testloop2.ogg')
- S.repeat = 1
- S.channel = 10
- S.falloff = 2
- S.wait = 1
- S.environment = 0
- //for(var/mob/M in world)
- // if(M.loc.loc == src.loc.loc && M.music == 0)
-// to_chat(world, "Found the song...")
-// M << S
- // M.music = 1
- var/area/A = src.loc.loc
- for(var/obj/machinery/party/lasermachine/L in A)
- L.turnon()
- playing = 1
- while(playing == 1)
- for(var/mob/M in world)
- if(M.loc.loc == src.loc.loc && M.music == 0)
-// to_chat(world, "Found the song...")
- M << S
- M.music = 1
- else if(M.loc.loc != src.loc.loc && M.music == 1)
- var/sound/Soff = sound(null)
- Soff.channel = 10
- M << Soff
- M.music = 0
- sleep(10)
- return
- if( href_list["on3"] )
- if(src.playing == 0)
-// to_chat(world, "Should be working...")
- var/sound/S = sound('sound/turntable/testloop3.ogg')
- S.repeat = 1
- S.channel = 10
- S.falloff = 2
- S.wait = 1
- S.environment = 0
- //for(var/mob/M in world)
- // if(M.loc.loc == src.loc.loc && M.music == 0)
-// to_chat(world, "Found the song...")
-// M << S
- // M.music = 1
- var/area/A = src.loc.loc
- for(var/obj/machinery/party/lasermachine/L in A)
- L.turnon()
- playing = 1
- while(playing == 1)
- for(var/mob/M in world)
- if(M.loc.loc == src.loc.loc && M.music == 0)
-// to_chat(world, "Found the song...")
- M << S
- M.music = 1
- else if(M.loc.loc != src.loc.loc && M.music == 1)
- var/sound/Soff = sound(null)
- Soff.channel = 10
- M << Soff
- M.music = 0
- sleep(10)
- return
-
-
- if( href_list["off"] )
- if(src.playing == 1)
- var/sound/S = sound(null)
- S.channel = 10
- S.wait = 1
- for(var/mob/M in world)
- M << S
- M.music = 0
- playing = 0
- var/area/A = src.loc.loc
- for(var/obj/machinery/party/lasermachine/L in A)
- L.turnoff()
-
-
-
-/obj/machinery/party/lasermachine
- name = "laser machine"
- desc = "A laser machine that shoots lasers."
- icon = 'icons/effects/lasers2.dmi'
- icon_state = "lasermachine"
- anchored = 1
- var/mirrored = 0
-
-/obj/effect/turntable_laser
- name = "laser"
- desc = "A laser..."
- icon = 'icons/effects/lasers2.dmi'
- icon_state = "laserred1"
- anchored = 1
- layer = 4
-
-/obj/item/lasermachine/New()
- ..()
-
-/obj/machinery/party/lasermachine/proc/turnon()
- var/wall = 0
- var/cycle = 1
- var/area/A = get_area(src)
- var/X = 1
- var/Y = 0
- if(mirrored == 0)
- while(wall == 0)
- if(cycle == 1)
- var/obj/effect/turntable_laser/F = new(src)
- F.x = src.x+X
- F.y = src.y+Y
- F.z = src.z
- F.icon_state = "laserred1"
- var/area/AA = get_area(F)
- var/turf/T = get_turf(F)
- if(T.density == 1 || AA.name != A.name)
- qdel(F)
- return
- cycle++
- if(cycle > 3)
- cycle = 1
- X++
- if(cycle == 2)
- var/obj/effect/turntable_laser/F = new(src)
- F.x = src.x+X
- F.y = src.y+Y
- F.z = src.z
- F.icon_state = "laserred2"
- var/area/AA = get_area(F)
- var/turf/T = get_turf(F)
- if(T.density == 1 || AA.name != A.name)
- qdel(F)
- return
- cycle++
- if(cycle > 3)
- cycle = 1
- Y++
- if(cycle == 3)
- var/obj/effect/turntable_laser/F = new(src)
- F.x = src.x+X
- F.y = src.y+Y
- F.z = src.z
- F.icon_state = "laserred3"
- var/area/AA = get_area(F)
- var/turf/T = get_turf(F)
- if(T.density == 1 || AA.name != A.name)
- qdel(F)
- return
- cycle++
- if(cycle > 3)
- cycle = 1
- X++
- if(mirrored == 1)
- while(wall == 0)
- if(cycle == 1)
- var/obj/effect/turntable_laser/F = new(src)
- F.x = src.x+X
- F.y = src.y-Y
- F.z = src.z
- F.icon_state = "laserred1m"
- var/area/AA = get_area(F)
- var/turf/T = get_turf(F)
- if(T.density == 1 || AA.name != A.name)
- qdel(F)
- return
- cycle++
- if(cycle > 3)
- cycle = 1
- Y++
- if(cycle == 2)
- var/obj/effect/turntable_laser/F = new(src)
- F.x = src.x+X
- F.y = src.y-Y
- F.z = src.z
- F.icon_state = "laserred2m"
- var/area/AA = get_area(F)
- var/turf/T = get_turf(F)
- if(T.density == 1 || AA.name != A.name)
- qdel(F)
- return
- cycle++
- if(cycle > 3)
- cycle = 1
- X++
- if(cycle == 3)
- var/obj/effect/turntable_laser/F = new(src)
- F.x = src.x+X
- F.y = src.y-Y
- F.z = src.z
- F.icon_state = "laserred3m"
- var/area/AA = get_area(F)
- var/turf/T = get_turf(F)
- if(T.density == 1 || AA.name != A.name)
- qdel(F)
- return
- cycle++
- if(cycle > 3)
- cycle = 1
- X++
-
-
-
-/obj/machinery/party/lasermachine/proc/turnoff()
- var/area/A = src.loc.loc
- for(var/obj/effect/turntable_laser/F in A)
- qdel(F)
diff --git a/code/game/machinery/turret_control.dm b/code/game/machinery/turret_control.dm
index 9dda3b193bb..71cb58c6478 100644
--- a/code/game/machinery/turret_control.dm
+++ b/code/game/machinery/turret_control.dm
@@ -11,51 +11,56 @@
desc = "Used to control a room's automated defenses."
icon = 'icons/obj/machines/turret_control.dmi'
icon_state = "control_standby"
- anchored = 1
- density = 0
+ anchored = TRUE
+ density = FALSE
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
- var/enabled = 0
- var/lethal = 0
- var/locked = 1
+ var/enabled = FALSE
+ var/lethal = FALSE
+ var/lethal_is_configurable = TRUE
+ var/locked = TRUE
var/area/control_area //can be area name, path or nothing.
- var/check_arrest = 1 //checks if the perp is set to arrest
- var/check_records = 1 //checks if a security record exists at all
- var/check_weapons = 0 //checks if it can shoot people that have a weapon they aren't authorized to have
- var/check_access = 1 //if this is active, the turret shoots everything that does not meet the access requirements
- var/check_anomalies = 1 //checks if it can shoot at unidentified lifeforms (ie xenos)
- var/check_synth = 0 //if active, will shoot at anything not an AI or cyborg
- var/ailock = 0 //Silicons cannot use this
+ var/targetting_is_configurable = TRUE // if false, you cannot change who this turret attacks via its UI
+ var/check_arrest = TRUE //checks if the perp is set to arrest
+ var/check_records = TRUE //checks if a security record exists at all
+ var/check_weapons = FALSE //checks if it can shoot people that have a weapon they aren't authorized to have
+ var/check_access = TRUE //if this is active, the turret shoots everything that does not meet the access requirements
+ var/check_anomalies = TRUE //checks if it can shoot at unidentified lifeforms (ie xenos)
+ var/check_synth = FALSE //if active, will shoot at anything not an AI or cyborg
+ var/check_borgs = FALSE //if TRUE, target all cyborgs.
+ var/ailock = FALSE //Silicons cannot use this
- var/syndicate = 0
+ var/syndicate = FALSE
var/faction = "" // Turret controls can only access turrets that are in the same faction
req_access = list(ACCESS_AI_UPLOAD)
/obj/machinery/turretid/stun
- enabled = 1
+ enabled = TRUE
icon_state = "control_stun"
/obj/machinery/turretid/lethal
- enabled = 1
- lethal = 1
+ enabled = TRUE
+ lethal = TRUE
icon_state = "control_kill"
/obj/machinery/turretid/syndicate
- enabled = 1
- lethal = 1
+ enabled = TRUE
+ lethal = TRUE
+ lethal_is_configurable = FALSE
+ targetting_is_configurable = FALSE
icon_state = "control_kill"
- lethal = 1
- check_arrest = 0
- check_records = 0
- check_weapons = 0
- check_access = 0
- check_anomalies = 1
- check_synth = 1
- ailock = 1
+ check_arrest = FALSE
+ check_records = FALSE
+ check_weapons = FALSE
+ check_access = FALSE
+ check_anomalies = TRUE
+ check_synth = TRUE
+ check_borgs = FALSE
+ ailock = TRUE
- syndicate = 1
+ syndicate = TRUE
faction = "syndicate"
req_access = list(ACCESS_SYNDICATE_LEADER)
@@ -87,21 +92,23 @@
return
/obj/machinery/turretid/proc/isLocked(mob/user)
- if(ailock && (isrobot(user) || isAI(user)))
- to_chat(user, "There seems to be a firewall preventing you from accessing this device.")
- return 1
+ if(isrobot(user) || isAI(user))
+ if(ailock)
+ to_chat(user, "There seems to be a firewall preventing you from accessing this device.")
+ return TRUE
+ else
+ return FALSE
- if(locked && !(isrobot(user) || isAI(user) || isobserver(user)))
- to_chat(user, "Access denied.")
- return 1
+ if(isobserver(user))
+ if(user.can_admin_interact())
+ return FALSE
+ else
+ return TRUE
- return 0
+ if(locked)
+ return TRUE
-/obj/machinery/turretid/CanUseTopic(mob/user)
- if(isLocked(user))
- return STATUS_CLOSE
-
- return ..()
+ return FALSE
/obj/machinery/turretid/attackby(obj/item/W, mob/user)
if(stat & BROKEN)
@@ -120,89 +127,82 @@
/obj/machinery/turretid/emag_act(user as mob)
if(!emagged)
to_chat(user, "You short out the turret controls' access analysis module.")
- emagged = 1
- locked = 0
- ailock = 0
+ emagged = TRUE
+ locked = FALSE
+ ailock = FALSE
return
/obj/machinery/turretid/attack_ai(mob/user as mob)
- if(isLocked(user))
- return
-
- ui_interact(user)
+ tgui_interact(user)
/obj/machinery/turretid/attack_ghost(mob/user as mob)
- ui_interact(user)
+ tgui_interact(user)
/obj/machinery/turretid/attack_hand(mob/user as mob)
- if(isLocked(user))
- return
+ tgui_interact(user)
- ui_interact(user)
-
-/obj/machinery/turretid/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/turretid/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "turret_control.tmpl", "Turret Controls", 500, 350)
+ ui = new(user, src, ui_key, "PortableTurret", name, 500, 400)
ui.open()
- ui.set_auto_update(1)
-
-/obj/machinery/turretid/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- data["access"] = !isLocked(user)
- data["locked"] = locked
- data["enabled"] = enabled
- data["lethal_control"] = !syndicate ? 1 : 0
- data["lethal"] = lethal
-
- if(data["access"] && !syndicate)
- var/settings[0]
- settings[++settings.len] = list("category" = "Neutralize All Non-Synthetics", "setting" = "check_synth", "value" = check_synth)
- settings[++settings.len] = list("category" = "Check Weapon Authorization", "setting" = "check_weapons", "value" = check_weapons)
- settings[++settings.len] = list("category" = "Check Security Records", "setting" = "check_records", "value" = check_records)
- settings[++settings.len] = list("category" = "Check Arrest Status", "setting" = "check_arrest", "value" = check_arrest)
- settings[++settings.len] = list("category" = "Check Access Authorization", "setting" = "check_access", "value" = check_access)
- settings[++settings.len] = list("category" = "Check Misc. Lifeforms", "setting" = "check_anomalies", "value" = check_anomalies)
- data["settings"] = settings
+/obj/machinery/turretid/tgui_data(mob/user)
+ var/list/data = list(
+ "locked" = isLocked(user), // does the current user have access?
+ "on" = enabled,
+ "targetting_is_configurable" = targetting_is_configurable, // If false, targetting settings don't show up
+ "lethal" = lethal,
+ "lethal_is_configurable" = lethal_is_configurable,
+ "check_weapons" = check_weapons,
+ "neutralize_noaccess" = check_access,
+ "one_access" = FALSE,
+ "selectedAccess" = list(),
+ "access_is_configurable" = FALSE,
+ "neutralize_norecord" = check_records,
+ "neutralize_criminals" = check_arrest,
+ "neutralize_all" = check_synth,
+ "neutralize_unidentified" = check_anomalies,
+ "neutralize_cyborgs" = check_borgs
+ )
return data
-/obj/machinery/turretid/Topic(href, href_list, var/nowindow = 0)
- if(..())
- return 1
-
+/obj/machinery/turretid/tgui_act(action, params)
+ if (..())
+ return
if(isLocked(usr))
- return 1
-
- if(href_list["command"] && href_list["value"])
- var/value = text2num(href_list["value"])
- if(href_list["command"] == "enable")
- enabled = value
- else if(syndicate)
- return 1
- else if(href_list["command"] == "lethal")
- lethal = value
- else if(href_list["command"] == "check_synth")
- check_synth = value
- else if(href_list["command"] == "check_weapons")
- check_weapons = value
- else if(href_list["command"] == "check_records")
- check_records = value
- else if(href_list["command"] == "check_arrest")
- check_arrest = value
- else if(href_list["command"] == "check_access")
- check_access = value
- else if(href_list["command"] == "check_anomalies")
- check_anomalies = value
-
- updateTurrets()
- return 1
+ return
+ . = TRUE
+ switch(action)
+ if("power")
+ enabled = !enabled
+ if("lethal")
+ if(lethal_is_configurable)
+ lethal = !lethal
+ if(targetting_is_configurable)
+ switch(action)
+ if("authweapon")
+ check_weapons = !check_weapons
+ if("authaccess")
+ check_access = !check_access
+ if("authnorecord")
+ check_records = !check_records
+ if("autharrest")
+ check_arrest = !check_arrest
+ if("authxeno")
+ check_anomalies = !check_anomalies
+ if("authsynth")
+ check_synth = !check_synth
+ if("authborgs")
+ check_borgs = !check_borgs
+ updateTurrets()
/obj/machinery/turretid/proc/updateTurrets()
var/datum/turret_checks/TC = new
TC.enabled = enabled
TC.lethal = lethal
TC.check_synth = check_synth
+ TC.check_borgs = check_borgs
TC.check_access = check_access
TC.check_records = check_records
TC.check_arrest = check_arrest
diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm
index 7aa5661febe..20fd93547eb 100644
--- a/code/game/machinery/vending.dm
+++ b/code/game/machinery/vending.dm
@@ -11,9 +11,6 @@
var/max_amount = 0
var/price = 0 // Price to buy one
-/**
- * A vending machine
- */
/obj/machinery/vending
name = "\improper Vendomat"
desc = "A generic vending machine."
@@ -25,8 +22,10 @@
max_integrity = 300
integrity_failure = 100
armor = list(melee = 20, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 50, acid = 70)
- var/icon_vend //Icon_state when vending
- var/icon_deny //Icon_state when denying access
+ /// Icon_state when vending
+ var/icon_vend
+ /// Icon_state when denying access
+ var/icon_deny
// Power
use_power = IDLE_POWER_USE
@@ -34,12 +33,14 @@
var/vend_power_usage = 150
// Vending-related
- var/active = 1 //No sales pitches if off!
- var/vend_ready = 1 //Are we ready to vend?? Is it time??
- var/vend_delay = 10 //How long does it take to vend?
- var/datum/data/vending_product/currently_vending = null // What we're requesting payment for right now
- var/status_message = "" // Status screen messages like "insufficient funds", displayed in NanoUI
- var/status_error = 0 // Set to 1 if status_message is an error
+ /// No sales pitches if off
+ var/active = 1
+ /// If off, vendor is busy and unusable until current action finishes
+ var/vend_ready = TRUE
+ /// How long vendor takes to vend one item.
+ var/vend_delay = 10
+ /// Item currently being bought
+ var/datum/data/vending_product/currently_vending = null
// To be filled out at compile time
var/list/products = list() // For each, use the following pattern:
@@ -51,16 +52,19 @@
var/list/product_records = list()
var/list/hidden_records = list()
var/list/coin_records = list()
+ var/list/imagelist = list()
-
- var/list/ads_list = list() //Small ad messages in the vending screen - random chance, TODO: implementation
+ /// Unimplemented list of ads that are meant to show up somewhere, but don't.
+ var/list/ads_list = list()
// Stuff relating vocalizations
- var/list/slogan_list = list() //List of slogans the vendor will say, optional
+ /// List of slogans the vendor will say, optional
+ var/list/slogan_list = list()
var/vend_reply //Thank you for shopping!
- var/shut_up = 0 //Stop spouting those godawful pitches!
+ /// If true, prevent saying sales pitches
+ var/shut_up = FALSE
///can we access the hidden inventory?
- var/extended_inventory = 0
+ var/extended_inventory = FALSE
var/last_reply = 0
var/last_slogan = 0 //When did we last pitch?
var/slogan_delay = 6000 //How long until we can pitch again?
@@ -69,17 +73,26 @@
var/obj/item/vending_refill/refill_canister = null
// Things that can go wrong
- emagged = 0 //Ignores if somebody doesn't have card access to that machine.
- var/seconds_electrified = 0 //Shock customers like an airlock.
- var/shoot_inventory = 0 //Fire items at customers! We're broken!
- var/shoot_speed = 3 //How hard are we firing the items?
- var/shoot_chance = 2 //How often are we firing the items?
+ /// Allows people to access a vendor that's normally access restricted.
+ emagged = 0
+ /// Shocks people like an airlock
+ var/seconds_electrified = 0
+ /// Fire items at customers! We're broken!
+ var/shoot_inventory = FALSE
+ /// How hard are we firing the items?
+ var/shoot_speed = 3
+ /// How often are we firing the items? (prob(...))
+ var/shoot_chance = 2
- var/scan_id = 1
+ /// If true, enforce access checks on customers. Disabled by messing with wires.
+ var/scan_id = TRUE
+ /// Holder for a coin inserted into the vendor
var/obj/item/coin/coin
var/datum/wires/vending/wires = null
+ /// boolean, whether this vending machine can accept people inserting items into it, used for coffee vendors
var/item_slot = FALSE
+ /// the actual item inserted
var/obj/item/inserted_item = null
/obj/machinery/vending/Initialize(mapload)
@@ -92,6 +105,10 @@
build_inventory(products, product_records)
build_inventory(contraband, hidden_records)
build_inventory(premium, coin_records)
+ for (var/datum/data/vending_product/R in (product_records + coin_records + hidden_records))
+ var/obj/item/I = R.product_path
+ var/pp = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-")
+ imagelist[pp] = "[icon2base64(icon(initial(I.icon), initial(I.icon_state)))]"
if(LAZYLEN(slogan_list))
// So not all machines speak at the exact same time.
// The first time this machine says something will be at slogantime + this random value,
@@ -101,6 +118,7 @@
power_change()
/obj/machinery/vending/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(wires)
QDEL_NULL(coin)
QDEL_NULL(inserted_item)
@@ -213,37 +231,16 @@
..()
/obj/machinery/vending/attackby(obj/item/I, mob/user, params)
- if(currently_vending && GLOB.vendor_account && !GLOB.vendor_account.suspended)
- var/paid = 0
- var/handled = 0
- if(istype(I, /obj/item/card/id))
- var/obj/item/card/id/C = I
- paid = pay_with_card(C)
- handled = 1
- if(istype(I, /obj/item/pda))
- var/obj/item/pda/PDA = I
- if(PDA.id)
- paid = pay_with_card(PDA.id)
- handled = 1
- else if(istype(I, /obj/item/stack/spacecash))
- var/obj/item/stack/spacecash/C = I
- paid = pay_with_cash(C, user)
- handled = 1
-
- if(paid)
- vend(currently_vending, usr)
+ if(istype(I, /obj/item/coin))
+ if(!premium.len)
+ to_chat(user, "[src] does not accept coins.")
return
- else if(handled)
- SSnanoui.update_uis(src)
- return // don't smack that machine with your 2 thalers
-
- if(istype(I, /obj/item/coin) && premium.len)
if(!user.drop_item())
return
I.forceMove(src)
coin = I
to_chat(user, "You insert the [I] into the [src]")
- SSnanoui.update_uis(src)
+ SStgui.update_uis(src)
return
if(refill_canister && istype(I, refill_canister))
if(!panel_open)
@@ -294,7 +291,7 @@
else
SCREWDRIVER_CLOSE_PANEL_MESSAGE
overlays.Cut()
- SSnanoui.update_uis(src) // Speaker switch is on the main UI, not wires UI
+ SStgui.update_uis(src)
/obj/machinery/vending/wirecutter_act(mob/user, obj/item/I)
. = TRUE
@@ -358,12 +355,10 @@
if(!user.drop_item())
to_chat(user, "[I] is stuck to your hand, you can't seem to put it down!")
return
-
inserted_item = I
I.forceMove(src)
-
to_chat(user, "You insert [I] into [src].")
- SSnanoui.update_uis(src)
+ SStgui.update_uis(src)
/obj/machinery/vending/proc/eject_item(mob/user)
if(!item_slot || !inserted_item)
@@ -376,23 +371,19 @@
var/turf/T = get_turf(src)
inserted_item.forceMove(T)
inserted_item = null
- SSnanoui.update_uis(src)
+ SStgui.update_uis(src)
/obj/machinery/vending/emag_act(user as mob)
emagged = TRUE
to_chat(user, "You short out the product lock on [src]")
-/**
- * Receive payment with cashmoney.
- *
- * usr is the mob who gets the change.
- */
+
/obj/machinery/vending/proc/pay_with_cash(obj/item/stack/spacecash/cashmoney, mob/user)
if(currently_vending.price > cashmoney.amount)
// This is not a status display message, since it's something the character
// themselves is meant to see BEFORE putting the money in
to_chat(usr, "[bicon(cashmoney)] That is not enough money.")
- return 0
+ return FALSE
// Bills (banknotes) cannot really have worth different than face value,
// so we have to eat the bill and spit out change in a bundle
@@ -403,66 +394,38 @@
cashmoney.use(currently_vending.price)
// Vending machines have no idea who paid with cash
- credit_purchase("(cash)")
- return 1
+ GLOB.vendor_account.credit(currently_vending.price, "Sale of [currently_vending.name]", name, "(cash)")
+ return TRUE
-/**
- * Scan a card and attempt to transfer payment from associated account.
- *
- * Takes payment for whatever is the currently_vending item. Returns 1 if
- * successful, 0 if failed
- */
-/obj/machinery/vending/proc/pay_with_card(var/obj/item/card/id/I)
- visible_message("[usr] swipes a card through [src].")
- return pay_with_account(get_card_account(I))
-/obj/machinery/vending/proc/pay_with_account(var/datum/money_account/customer_account)
+/obj/machinery/vending/proc/pay_with_card(obj/item/card/id/I, mob/M)
+ visible_message("[M] swipes a card through [src].")
+ return pay_with_account(get_card_account(I), M)
+
+/obj/machinery/vending/proc/pay_with_account(datum/money_account/customer_account, mob/M)
if(!customer_account)
- src.status_message = "Error: Unable to access account. Please contact technical support if problem persists."
- src.status_error = 1
- return 0
-
+ to_chat(M, "Error: Unable to access account. Please contact technical support if problem persists.")
+ return FALSE
if(customer_account.suspended)
- src.status_message = "Unable to access account: account suspended."
- src.status_error = 1
- return 0
-
- // Have the customer punch in the PIN before checking if there's enough money. Prevents people from figuring out acct is
- // empty at high security levels
- if(customer_account.security_level != 0) //If card requires pin authentication (ie seclevel 1 or 2)
+ to_chat(M, "Unable to access account: account suspended.")
+ return FALSE
+ // Have the customer punch in the PIN before checking if there's enough money.
+ // Prevents people from figuring out acct is empty at high security levels
+ if(customer_account.security_level != 0)
+ // If card requires pin authentication (ie seclevel 1 or 2)
var/attempt_pin = input("Enter pin code", "Vendor transaction") as num
-
if(!attempt_account_access(customer_account.account_number, attempt_pin, 2))
- src.status_message = "Unable to access account: incorrect credentials."
- src.status_error = 1
- return 0
-
+ to_chat(M, "Unable to access account: incorrect credentials.")
+ return FALSE
if(currently_vending.price > customer_account.money)
- src.status_message = "Insufficient funds in account."
- src.status_error = 1
- return 0
- else
- // Okay to move the money at this point
- var/paid = customer_account.charge(currently_vending.price, GLOB.vendor_account,
- "Purchase of [currently_vending.name]", name, GLOB.vendor_account.owner_name,
- "Sale of [currently_vending.name]", customer_account.owner_name)
+ to_chat(M, "Your bank account has insufficient money to purchase this.")
+ return FALSE
+ // Okay to move the money at this point
+ customer_account.charge(currently_vending.price, GLOB.vendor_account,
+ "Purchase of [currently_vending.name]", name, GLOB.vendor_account.owner_name,
+ "Sale of [currently_vending.name]", customer_account.owner_name)
+ return TRUE
- if(paid)
- // Give the vendor the money. We use the account owner name, which means
- // that purchases made with stolen/borrowed card will look like the card
- // owner made them
- credit_purchase(customer_account.owner_name)
- return paid
-
-/**
- * Add money for current purchase to the vendor account.
- *
- * Called after the money has already been taken from the customer.
- */
-/obj/machinery/vending/proc/credit_purchase(var/target as text)
- GLOB.vendor_account.money += currently_vending.price
- GLOB.vendor_account.credit(currently_vending.price, "Sale of [currently_vending.name]",
- name, target)
/obj/machinery/vending/attack_ai(mob/user)
return attack_hand(user)
@@ -478,174 +441,238 @@
if(src.shock(user, 100))
return
- ui_interact(user)
+ tgui_interact(user)
wires.Interact(user)
-/**
- * Display the NanoUI window for the vending machine.
- *
- * See NanoUI documentation for details.
- */
-/obj/machinery/vending/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- user.set_machine(src)
-
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/vending/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "vending_machine.tmpl", src.name, 440, 600)
+ var/estimated_height = 100 + min(length(product_records) * 34, 500)
+ if(length(prices) > 0)
+ estimated_height += 100 // to account for the "current user" interface
+ ui = new(user, src, ui_key, "Vending", name, 470, estimated_height, master_ui, state)
ui.open()
-/obj/machinery/vending/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
+/obj/machinery/vending/tgui_data(mob/user)
var/list/data = list()
- if(currently_vending)
- data["mode"] = 1
- data["product"] = sanitize(currently_vending.name)
- data["price"] = currently_vending.price
- data["message_err"] = 0
- data["message"] = src.status_message
- data["message_err"] = src.status_error
- else
- data["mode"] = 0
- var/list/listed_products = list()
-
- var/list/display_records = product_records + coin_records
- if(extended_inventory)
- display_records = product_records + coin_records + hidden_records
-
- for(var/key = 1 to display_records.len)
- var/datum/data/vending_product/I = display_records[key]
-
- if(coin_records.Find(I) && !coin)
- continue
-
- if(hidden_records.Find(I) && !extended_inventory)
- continue
-
- listed_products.Add(list(list(
- "key" = key,
- "name" = sanitize(I.name),
- "price" = I.price,
- "amount" = I.amount)))
-
- data["products"] = listed_products
-
- if(coin)
- data["coin"] = coin.name
-
- if(item_slot)
- data["item_slot"] = 1
- if(inserted_item)
- data["inserted_item"] = inserted_item
- else
- data["inserted_item"] = null
- else
- data["item_slot"] = 0
-
- if(panel_open)
- data["panel"] = 1
- data["speaker"] = shut_up ? 0 : 1
- else
- data["panel"] = 0
+ var/mob/living/carbon/human/H
+ var/obj/item/card/id/C
+ data["guestNotice"] = "No valid ID card detected. Wear your ID, or present cash.";
+ data["userMoney"] = 0
+ data["user"] = null
+ if(ishuman(user))
+ H = user
+ C = H.get_idcard(TRUE)
+ var/obj/item/stack/spacecash/S = H.get_active_hand()
+ if(istype(S))
+ data["userMoney"] = S.amount
+ data["guestNotice"] = "Accepting Cash. You have: [S.amount] credits."
+ else if(istype(C))
+ var/datum/money_account/A = get_card_account(C)
+ if(istype(A))
+ data["user"] = list()
+ data["user"]["name"] = A.owner_name
+ data["userMoney"] = A.money
+ data["user"]["job"] = (istype(C) && C.rank) ? C.rank : "No Job"
+ else
+ data["guestNotice"] = "Unlinked ID detected. Present cash to pay.";
+ data["stock"] = list()
+ for (var/datum/data/vending_product/R in product_records + coin_records + hidden_records)
+ data["stock"][R.name] = R.amount
+ data["extended_inventory"] = extended_inventory
+ data["vend_ready"] = vend_ready
+ data["coin_name"] = coin ? coin.name : FALSE
+ data["panel_open"] = panel_open ? TRUE : FALSE
+ data["speaker"] = shut_up ? FALSE : TRUE
+ data["item_slot"] = item_slot // boolean
+ data["inserted_item_name"] = inserted_item ? inserted_item.name : FALSE
return data
-/obj/machinery/vending/Topic(href, href_list)
- if(..())
- return 1
- if(href_list["remove_coin"] && !istype(usr,/mob/living/silicon))
- if(!coin)
- to_chat(usr, "There is no coin in this machine.")
- return
+/obj/machinery/vending/tgui_static_data(mob/user)
+ var/list/data = list()
+ data["chargesMoney"] = length(prices) > 0 ? TRUE : FALSE
+ data["product_records"] = list()
+ var/i = 1
+ for (var/datum/data/vending_product/R in product_records)
+ var/list/data_pr = list(
+ path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"),
+ name = R.name,
+ price = (R.product_path in prices) ? prices[R.product_path] : 0,
+ max_amount = R.max_amount,
+ req_coin = FALSE,
+ is_hidden = FALSE,
+ inum = i
+ )
+ data["product_records"] += list(data_pr)
+ i++
+ data["coin_records"] = list()
+ for (var/datum/data/vending_product/R in coin_records)
+ var/list/data_cr = list(
+ path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"),
+ name = R.name,
+ price = (R.product_path in prices) ? prices[R.product_path] : 0,
+ max_amount = R.max_amount,
+ req_coin = TRUE,
+ is_hidden = FALSE,
+ inum = i,
+ premium = TRUE
+ )
+ data["coin_records"] += list(data_cr)
+ i++
+ data["hidden_records"] = list()
+ for (var/datum/data/vending_product/R in hidden_records)
+ var/list/data_hr = list(
+ path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"),
+ name = R.name,
+ price = (R.product_path in prices) ? prices[R.product_path] : 0,
+ max_amount = R.max_amount,
+ req_coin = FALSE,
+ is_hidden = TRUE,
+ inum = i,
+ premium = TRUE
+ )
+ data["hidden_records"] += list(data_hr)
+ i++
+ data["imagelist"] = imagelist
+ return data
- usr.put_in_hands(coin)
- coin = null
- to_chat(usr, "You remove [coin] from [src].")
+/obj/machinery/vending/tgui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ if(issilicon(usr) && !isrobot(usr))
+ to_chat(usr, "The vending machine refuses to interface with you, as you are not in its target demographic!")
+ return
+ switch(action)
+ if("toggle_voice")
+ if(panel_open)
+ shut_up = !shut_up
+ . = TRUE
+ if("eject_item")
+ eject_item(usr)
+ . = TRUE
+ if("remove_coin")
+ if(!coin)
+ to_chat(usr, "There is no coin in this machine.")
+ return
+ if(istype(usr, /mob/living/silicon))
+ to_chat(usr, "You lack hands.")
+ return
+ to_chat(usr, "You remove [coin] from [src].")
+ usr.put_in_hands(coin)
+ coin = null
+ . = TRUE
+ if("vend")
+ if(!vend_ready)
+ to_chat(usr, "The vending machine is busy!")
+ return
+ if(panel_open)
+ to_chat(usr, "The vending machine cannot dispense products while its service panel is open!")
+ return
+ var/key = text2num(params["inum"])
+ var/list/display_records = product_records + coin_records
+ if(extended_inventory)
+ display_records = product_records + coin_records + hidden_records
+ if(key < 1 || key > length(display_records))
+ to_chat(usr, "ERROR: invalid inum passed to vendor. Report this bug.")
+ return
+ var/datum/data/vending_product/R = display_records[key]
+ if(!istype(R))
+ to_chat(usr, "ERROR: unknown vending_product record. Report this bug.")
+ return
+ var/list/record_to_check = product_records + coin_records
+ if(extended_inventory)
+ record_to_check = product_records + coin_records + hidden_records
+ if(!R || !istype(R) || !R.product_path)
+ to_chat(usr, "ERROR: unknown product record. Report this bug.")
+ return
+ if(R in hidden_records)
+ if(!extended_inventory)
+ // Exploit prevention, stop the user purchasing hidden stuff if they haven't hacked the machine.
+ to_chat(usr, "ERROR: machine does not allow extended_inventory in current state. Report this bug.")
+ return
+ else if (!(R in record_to_check))
+ // Exploit prevention, stop the user
+ message_admins("Vending machine exploit attempted by [ADMIN_LOOKUPFLW(usr)]!")
+ return
+ if (R.amount <= 0)
+ to_chat(usr, "Sold out of [R.name].")
+ flick(icon_deny, src)
+ return
- if(href_list["remove_item"])
- eject_item(usr)
+ vend_ready = FALSE // From this point onwards, vendor is locked to performing this transaction only, until it is resolved.
- if(href_list["pay"])
- if(currently_vending && GLOB.vendor_account && !GLOB.vendor_account.suspended)
- var/paid = 0
- var/handled = 0
- var/datum/money_account/A = usr.get_worn_id_account()
- if(A)
- paid = pay_with_account(A)
- handled = 1
- else if(istype(usr.get_active_hand(), /obj/item/card))
- paid = pay_with_card(usr.get_active_hand())
- handled = 1
- else if(usr.can_admin_interact())
- paid = 1
- handled = 1
+ if(!ishuman(usr) || R.price <= 0)
+ // Either the purchaser is not human, or the item is free.
+ // Skip all payment logic.
+ vend(R, usr)
+ add_fingerprint(usr)
+ vend_ready = TRUE
+ . = TRUE
+ return
+
+ // --- THE REST OF THIS PROC IS JUST PAYMENT LOGIC ---
+
+ var/mob/living/carbon/human/H = usr
+ var/obj/item/card/id/C = H.get_idcard(TRUE)
+
+ if(!GLOB.vendor_account || GLOB.vendor_account.suspended)
+ to_chat(usr, "Vendor account offline. Unable to process transaction.")
+ flick(icon_deny, src)
+ vend_ready = TRUE
+ return
+
+ currently_vending = R
+ var/paid = FALSE
+
+ if(istype(usr.get_active_hand(), /obj/item/stack/spacecash))
+ var/obj/item/stack/spacecash/S = usr.get_active_hand()
+ paid = pay_with_cash(S)
+ else if(istype(C, /obj/item/card))
+ // Because this uses H.get_idcard(TRUE), it will attempt to use:
+ // active hand, inactive hand, pda.id, and then wear_id ID in that order
+ // this is important because it lets people buy stuff with someone else's ID by holding it while using the vendor
+ paid = pay_with_card(C, usr)
+ else if(usr.can_advanced_admin_interact())
+ to_chat(usr, "Vending object due to admin interaction.")
+ paid = TRUE
+ else
+ to_chat(usr, "Payment failure: you have no ID or other method of payment.")
+ vend_ready = TRUE
+ flick(icon_deny, src)
+ . = TRUE // we set this because they shouldn't even be able to get this far, and we want the UI to update.
+ return
if(paid)
vend(currently_vending, usr)
- return
- else if(handled)
- SSnanoui.update_uis(src)
- return // don't smack that machine with your 2 credits
-
- if((href_list["vend"]) && vend_ready && !currently_vending)
-
- if(issilicon(usr) && !isrobot(usr))
- to_chat(usr, "The vending machine refuses to interface with you, as you are not in its target demographic!")
- return
-
- if(!allowed(usr) && !usr.can_admin_interact() && !emagged && scan_id) //For SECURE VENDING MACHINES YEAH
- to_chat(usr, "Access denied.") //Unless emagged of course
- flick(icon_deny,src)
- return
-
- var/key = text2num(href_list["vend"])
- var/list/display_records = product_records + coin_records
- if(extended_inventory)
- display_records = product_records + coin_records + hidden_records
- var/datum/data/vending_product/R = display_records[key]
-
- // This should not happen unless the request from NanoUI was bad
- if(coin_records.Find(R) && !coin)
- return
-
- if(hidden_records.Find(R) && !extended_inventory)
- return
-
- if(R.price <= 0)
- vend(R, usr)
- else
- currently_vending = R
- if(!GLOB.vendor_account || GLOB.vendor_account.suspended)
- status_message = "This machine is currently unable to process payments due to problems with the associated account."
- status_error = 1
+ . = TRUE
else
- status_message = "Please swipe a card or insert cash to pay for the item."
- status_error = 0
+ to_chat(usr, "Payment failure: unable to process payment.")
+ vend_ready = TRUE
+ if(.)
+ add_fingerprint(usr)
- else if(href_list["cancelpurchase"])
- currently_vending = null
- else if(href_list["togglevoice"] && panel_open)
- shut_up = !src.shut_up
- add_fingerprint(usr)
- SSnanoui.update_uis(src)
/obj/machinery/vending/proc/vend(datum/data/vending_product/R, mob/user)
- if(!allowed(usr) && !usr.can_admin_interact() && !emagged && scan_id) //For SECURE VENDING MACHINES YEAH
- to_chat(usr, "Access denied.")//Unless emagged of course
- flick(icon_deny,src)
+ if(!allowed(user) && !user.can_admin_interact() && !emagged && scan_id) //For SECURE VENDING MACHINES YEAH
+ to_chat(user, "Access denied.")//Unless emagged of course
+ flick(icon_deny, src)
+ vend_ready = TRUE
return
if(!R.amount)
to_chat(user, "The vending machine has ran out of that product.")
+ vend_ready = TRUE
return
- vend_ready = 0 //One thing at a time!!
- status_message = "Vending..."
- status_error = 0
- SSnanoui.update_uis(src)
+ vend_ready = FALSE //One thing at a time!!
if(coin_records.Find(R))
if(!coin)
to_chat(user, "You need to insert a coin to get this item.")
+ vend_ready = TRUE
return
if(coin.string_attached)
if(prob(50))
@@ -665,15 +692,13 @@
use_power(vend_power_usage) //actuators and stuff
if(icon_vend) //Show the vending animation if needed
flick(icon_vend, src)
+ playsound(get_turf(src), 'sound/machines/machine_vend.ogg', 50, TRUE)
addtimer(CALLBACK(src, .proc/delayed_vend, R, user), vend_delay)
/obj/machinery/vending/proc/delayed_vend(datum/data/vending_product/R, mob/user)
do_vend(R, user)
- status_message = ""
- status_error = 0
- vend_ready = 1
+ vend_ready = TRUE
currently_vending = null
- SSnanoui.update_uis(src)
//override this proc to add handling for what to do with the vended product when you have a inserted item and remember to include a parent call for this generic handling
/obj/machinery/vending/proc/do_vend(datum/data/vending_product/R, mob/user)
@@ -809,17 +834,6 @@
*/
-/*
-/obj/machinery/vending/atmospherics //Commenting this out until someone ponies up some actual working, broken, and unpowered sprites - Quarxink
- name = "\improper Tank Vendor"
- desc = "A vendor with a wide variety of masks and gas tanks."
- icon = 'icons/obj/objects.dmi'
- icon_state = "dispenser"
- product_paths = "/obj/item/tank/oxygen;/obj/item/tank/plasma;/obj/item/tank/emergency_oxygen;/obj/item/tank/emergency_oxygen/engi;/obj/item/clothing/mask/breath"
- product_amounts = "10;10;10;5;25"
- vend_delay = 0
-*/
-
/obj/machinery/vending/assist
products = list( /obj/item/assembly/prox_sensor = 5,/obj/item/assembly/igniter = 3,/obj/item/assembly/signaler = 4,
/obj/item/wirecutters = 1, /obj/item/cartridge/signal = 4)
@@ -1258,7 +1272,7 @@
ads_list = list("We like plants!","Don't you want some?","The greenest thumbs ever.","We like big plants.","Soft soil...")
icon_state = "nutri"
icon_deny = "nutri-deny"
- products = list(/obj/item/reagent_containers/glass/bottle/nutrient/ez = 30,/obj/item/reagent_containers/glass/bottle/nutrient/l4z = 20,/obj/item/reagent_containers/glass/bottle/nutrient/rh = 10,/obj/item/reagent_containers/spray/pestspray = 20,
+ products = list(/obj/item/reagent_containers/glass/bottle/nutrient/ez = 20,/obj/item/reagent_containers/glass/bottle/nutrient/l4z = 13,/obj/item/reagent_containers/glass/bottle/nutrient/rh = 6,/obj/item/reagent_containers/spray/pestspray = 20,
/obj/item/reagent_containers/syringe = 5,/obj/item/storage/bag/plants = 5,/obj/item/cultivator = 3,/obj/item/shovel/spade = 3,/obj/item/plant_analyzer = 4)
contraband = list(/obj/item/reagent_containers/glass/bottle/ammonia = 10,/obj/item/reagent_containers/glass/bottle/diethylamine = 5)
refill_canister = /obj/item/vending_refill/hydronutrients
@@ -1385,7 +1399,6 @@
/obj/item/clothing/glasses/gglasses = 1,
/obj/item/clothing/shoes/jackboots = 1,
/obj/item/clothing/under/schoolgirl = 1,
- /obj/item/clothing/head/kitty = 1,
/obj/item/clothing/under/blackskirt = 1,
/obj/item/clothing/suit/toggle/owlwings = 1,
/obj/item/clothing/under/owl = 1,
@@ -1480,6 +1493,7 @@
/obj/item/clothing/under/victsuit/redblk = 1,
/obj/item/clothing/under/victsuit/red = 1,
/obj/item/clothing/suit/tailcoat = 1,
+ /obj/item/clothing/under/tourist_suit = 1,
/obj/item/clothing/suit/draculacoat = 1,
/obj/item/clothing/head/zepelli = 1,
/obj/item/clothing/under/redhawaiianshirt = 1,
@@ -1566,7 +1580,6 @@
desc = "Tools for tools."
icon_state = "tool"
icon_deny = "tool-deny"
- //req_access_txt = "12" //Maintenance access
products = list(/obj/item/stack/cable_coil/random = 10,/obj/item/crowbar = 5,/obj/item/weldingtool = 3,/obj/item/wirecutters = 5,
/obj/item/wrench = 5,/obj/item/analyzer = 5,/obj/item/t_scanner = 5,/obj/item/screwdriver = 5)
contraband = list(/obj/item/weldingtool/hugetank = 2,/obj/item/clothing/gloves/color/fyellow = 2)
@@ -1894,46 +1907,3 @@
component_parts += new /obj/item/vending_refill/crittercare(null)
RefreshParts()
return ..()
-
-/obj/machinery/vending/modularpc
- name = "\improper Deluxe Silicate Selections"
- desc = "All the parts you need to build your own custom pc."
- icon_state = "modularpc"
- icon_deny = "modularpc-deny"
- ads_list = list("Get your gamer gear!","The best GPUs for all of your space-crypto needs!","The most robust cooling!","The finest RGB in space!")
- vend_reply = "Game on!"
- products = list(/obj/item/modular_computer/laptop = 4,
- /obj/item/modular_computer/tablet = 4,
- /obj/item/computer_hardware/hard_drive = 4,
- /obj/item/computer_hardware/hard_drive/small = 4,
- /obj/item/computer_hardware/network_card = 8,
- /obj/item/computer_hardware/hard_drive/portable = 8,
- /obj/item/computer_hardware/battery = 8,
- /obj/item/stock_parts/cell/computer = 8,
- /obj/item/computer_hardware/processor_unit = 4,
- /obj/item/computer_hardware/processor_unit/small = 4)
- premium = list(/obj/item/computer_hardware/card_slot = 2,
- /obj/item/computer_hardware/ai_slot = 2,
- /obj/item/computer_hardware/printer/mini = 2,
- /obj/item/computer_hardware/recharger/APC = 2,
- /obj/item/paicard = 2)
- prices = list(/obj/item/modular_computer/laptop = 300,
- /obj/item/modular_computer/tablet = 300,
- /obj/item/computer_hardware/hard_drive = 100,
- /obj/item/computer_hardware/hard_drive/small = 50,
- /obj/item/computer_hardware/network_card = 100,
- /obj/item/computer_hardware/hard_drive/portable = 100,
- /obj/item/computer_hardware/battery = 100,
- /obj/item/stock_parts/cell/computer = 100,
- /obj/item/computer_hardware/processor_unit = 100,
- /obj/item/computer_hardware/processor_unit/small = 100)
- refill_canister = /obj/item/vending_refill/modularpc
-
-/obj/machinery/vending/modularpc/Initialize(mapload)
- component_parts = list()
- var/obj/item/circuitboard/vendor/V = new(null)
- V.set_type(type)
- component_parts += V
- component_parts += new /obj/item/vending_refill/modularpc(null)
- RefreshParts()
- return ..()
diff --git a/code/game/mecha/combat/honker.dm b/code/game/mecha/combat/honker.dm
index 1bb73856f1b..eb7bfad8942 100644
--- a/code/game/mecha/combat/honker.dm
+++ b/code/game/mecha/combat/honker.dm
@@ -137,7 +137,7 @@
squeak = 0
return result
-obj/mecha/combat/honker/Topic(href, href_list)
+/obj/mecha/combat/honker/Topic(href, href_list)
..()
if(href_list["play_sound"])
switch(href_list["play_sound"])
diff --git a/code/game/mecha/equipment/tools/mining_tools.dm b/code/game/mecha/equipment/tools/mining_tools.dm
index 5916d0f96c7..2248c37f518 100644
--- a/code/game/mecha/equipment/tools/mining_tools.dm
+++ b/code/game/mecha/equipment/tools/mining_tools.dm
@@ -94,7 +94,7 @@
/obj/item/mecha_parts/mecha_equipment/drill/proc/drill_mob(mob/living/target, mob/user)
target.visible_message("[chassis] is drilling [target] with [src]!",
"[chassis] is drilling you with [src]!")
- add_attack_logs(user, target, "DRILLED with [src] (INTENT: [uppertext(user.a_intent)]) (DAMTYPE: [uppertext(damtype)])")
+ add_attack_logs(user, target, "DRILLED with [src] ([uppertext(user.a_intent)]) ([uppertext(damtype)])")
if(target.stat == DEAD && target.getBruteLoss() >= 200)
add_attack_logs(user, target, "gibbed")
if(LAZYLEN(target.butcher_results))
diff --git a/code/game/mecha/equipment/tools/other_tools.dm b/code/game/mecha/equipment/tools/other_tools.dm
index 61ba683ad18..8fdbf0880df 100644
--- a/code/game/mecha/equipment/tools/other_tools.dm
+++ b/code/game/mecha/equipment/tools/other_tools.dm
@@ -87,11 +87,15 @@
energy_drain = 100
range = MECHA_MELEE | MECHA_RANGED
var/atom/movable/locked
+ var/cooldown_timer = 0
var/mode = 1 //1 - gravsling 2 - gravpush
/obj/item/mecha_parts/mecha_equipment/gravcatapult/action(atom/movable/target)
if(!action_checks(target))
return
+ if(cooldown_timer > world.time)
+ occupant_message("[src] is still recharging.")
+ return
switch(mode)
if(1)
if(!locked)
@@ -106,6 +110,7 @@
locked.throw_at(target, 14, 1.5)
locked = null
send_byjax(chassis.occupant,"exosuit.browser","\ref[src]",get_equip_info())
+ cooldown_timer = world.time + 3 SECONDS
return 1
else
locked = null
@@ -125,6 +130,7 @@
step_away(A,target)
sleep(2)
var/turf/T = get_turf(target)
+ cooldown_timer = world.time + 3 SECONDS
log_game("[key_name(chassis.occupant)] used a Gravitational Catapult in ([T.x],[T.y],[T.z])")
return 1
diff --git a/code/game/mecha/equipment/tools/work_tools.dm b/code/game/mecha/equipment/tools/work_tools.dm
index 63eeb21e165..5962a1eccbb 100644
--- a/code/game/mecha/equipment/tools/work_tools.dm
+++ b/code/game/mecha/equipment/tools/work_tools.dm
@@ -60,7 +60,7 @@
target.visible_message("[chassis] squeezes [target].", \
"[chassis] squeezes [target].",\
"You hear something crack.")
- add_attack_logs(chassis.occupant, M, "Squeezed with [src] (INTENT: [uppertext(chassis.occupant.a_intent)]) (DAMTYE: [uppertext(damtype)])")
+ add_attack_logs(chassis.occupant, M, "Squeezed with [src] ([uppertext(chassis.occupant.a_intent)]) ([uppertext(damtype)])")
start_cooldown()
else
step_away(M,chassis)
diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm
index 9acdcba48de..e8db08bf37c 100644
--- a/code/game/mecha/equipment/weapons/weapons.dm
+++ b/code/game/mecha/equipment/weapons/weapons.dm
@@ -59,9 +59,7 @@
sleep(max(0, projectile_delay))
set_ready_state(0)
log_message("Fired from [name], targeting [target].")
- var/turf/T = get_turf(src)
- msg_admin_attack("[key_name_admin(chassis.occupant)] fired a [src] in ([T.x], [T.y], [T.z] - [ADMIN_JMP(T)])")
- log_game("[key_name(chassis.occupant)] fired a [src] in [T.x], [T.y], [T.z]")
+ add_attack_logs(chassis.occupant, target, "fired a [src]")
do_after_cooldown()
return
@@ -209,7 +207,7 @@
for(var/mob/living/carbon/M in ohearers(6, chassis))
if(istype(M, /mob/living/carbon/human))
var/mob/living/carbon/human/H = M
- if(istype(H.l_ear, /obj/item/clothing/ears/earmuffs) || istype(H.r_ear, /obj/item/clothing/ears/earmuffs))
+ if(H.check_ear_prot() >= HEARING_PROTECTION_TOTAL)
continue
to_chat(M, "HONK")
M.SetSleeping(0)
@@ -238,7 +236,7 @@
chassis.use_power(energy_drain)
log_message("Honked from [name]. HONK!")
var/turf/T = get_turf(src)
- msg_admin_attack("[key_name_admin(chassis.occupant)] used a Mecha Honker in ([T.x], [T.y], [T.z] - [ADMIN_JMP(T)])")
+ add_attack_logs(chassis.occupant, target, "used a Mecha Honker", ATKLOG_MOST)
log_game("[key_name(chassis.occupant)] used a Mecha Honker in [T.x], [T.y], [T.z]")
do_after_cooldown()
return
@@ -357,7 +355,7 @@
projectiles--
log_message("Fired from [name], targeting [target].")
var/turf/T = get_turf(src)
- msg_admin_attack("[key_name_admin(chassis.occupant)] fired a [src] in ([T.x], [T.y], [T.z] - [ADMIN_JMP(T)])")
+ add_attack_logs(chassis.occupant, target, "fired a [src]", ATKLOG_FEW)
log_game("[key_name(chassis.occupant)] fired a [src] in [T.x], [T.y], [T.z]")
do_after_cooldown()
return
@@ -385,7 +383,7 @@
return
/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/flashbang
- name = "SGL-6 Grenade Launcher"
+ name = "SGL-6 Flashbang Launcher"
icon_state = "mecha_grenadelnchr"
origin_tech = "combat=4;engineering=4"
projectile = /obj/item/grenade/flashbang
@@ -411,7 +409,7 @@
return
/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/flashbang/clusterbang//Because I am a heartless bastard -Sieve
- name = "SOB-3 Grenade Launcher"
+ name = "SOB-3 Clusterbang Launcher"
desc = "A weapon for combat exosuits. Launches primed clusterbangs. You monster."
origin_tech = "combat=4;materials=4"
projectiles = 3
diff --git a/code/game/mecha/mech_bay.dm b/code/game/mecha/mech_bay.dm
index 896ce886887..34e8648c59e 100644
--- a/code/game/mecha/mech_bay.dm
+++ b/code/game/mecha/mech_bay.dm
@@ -158,36 +158,36 @@
/obj/machinery/computer/mech_bay_power_console/attack_hand(mob/user as mob)
if(..())
return
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/mech_bay_power_console/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/mech_bay_power_console/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- // the ui does not exist, so we'll create a new() one
- // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "mech_bay_console.tmpl", "Mech Bay Control Console", 500, 325)
- // open the new ui window
+ ui = new(user, src, ui_key, "MechBayConsole", name, 400, 150, master_ui, state)
ui.open()
- // auto update every Master Controller tick
- ui.set_auto_update(1)
-/obj/machinery/computer/mech_bay_power_console/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
+/obj/machinery/computer/mech_bay_power_console/tgui_act(action, params)
+ if(..())
+ return
+ switch(action)
+ if("reconnect")
+ reconnect()
+ . = TRUE
+ update_icon()
+
+/obj/machinery/computer/mech_bay_power_console/tgui_data(mob/user)
+ var/data = list()
if(!recharge_port)
reconnect()
if(recharge_port && !QDELETED(recharge_port))
data["recharge_port"] = list("mech" = null)
if(recharge_port.recharging_mecha && !QDELETED(recharge_port.recharging_mecha))
- data["recharge_port"]["mech"] = list("health" = recharge_port.recharging_mecha.obj_integrity, "maxhealth" = initial(recharge_port.recharging_mecha.max_integrity), "cell" = null)
+ data["recharge_port"]["mech"] = list("health" = recharge_port.recharging_mecha.obj_integrity, "maxhealth" = recharge_port.recharging_mecha.max_integrity, "cell" = null, "name" = recharge_port.recharging_mecha.name)
if(recharge_port.recharging_mecha.cell && !QDELETED(recharge_port.recharging_mecha.cell))
- data["has_mech"] = 1
- data["mecha_name"] = recharge_port.recharging_mecha || "None"
- data["mecha_charge"] = isnull(recharge_port.recharging_mecha) ? 0 : recharge_port.recharging_mecha.cell.charge
- data["mecha_maxcharge"] = isnull(recharge_port.recharging_mecha) ? 0 : recharge_port.recharging_mecha.cell.maxcharge
- data["mecha_charge_percentage"] = isnull(recharge_port.recharging_mecha) ? 0 : round(recharge_port.recharging_mecha.cell.percent())
- else
- data["has_mech"] = 0
-
+ data["recharge_port"]["mech"]["cell"] = list(
+ "charge" = recharge_port.recharging_mecha.cell.charge,
+ "maxcharge" = recharge_port.recharging_mecha.cell.maxcharge
+ )
return data
/obj/machinery/computer/mech_bay_power_console/Initialize()
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 52973c9e285..fe032e0f927 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -1,3 +1,5 @@
+#define OCCUPANT_LOGGING occupant ? occupant : "empty mech"
+
/obj/mecha
name = "Mecha"
desc = "Exosuit"
@@ -157,7 +159,6 @@
radio.name = "[src] radio"
radio.icon = icon
radio.icon_state = icon_state
- radio.subspace_transmission = 1
/obj/mecha/examine(mob/user)
. = ..()
@@ -329,6 +330,8 @@
else
occupant.clear_alert("mechaport")
if(leg_overload_mode)
+ log_message("Leg Overload damage.")
+ take_damage(1, BRUTE, FALSE, FALSE)
if(obj_integrity < max_integrity - max_integrity / 3)
leg_overload_mode = FALSE
step_in = initial(step_in)
@@ -421,12 +424,7 @@
return
if(isobj(obstacle))
var/obj/O = obstacle
- if(istype(O, /obj/effect/portal)) //derpfix
- anchored = 0
- O.Bumped(src)
- spawn(0) //countering portal teleport spawn(0), hurr
- anchored = 1
- else if(!O.anchored)
+ if(!O.anchored)
step(obstacle, dir)
else if(ismob(obstacle))
step(obstacle, dir)
@@ -500,7 +498,7 @@
check_for_internal_damage(list(MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST))
else
check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT))
- if(. >= 5 || prob(33))
+ if((. >= 5 || prob(33)) && !(. == 1 && leg_overload_mode)) //If it takes 1 damage and leg_overload_mode is true, do not say TAKING DAMAGE! to the user several times a second.
occupant_message("Taking damage!")
log_message("Took [damage_amount] points of damage. Damage type: [damage_type]")
@@ -538,12 +536,13 @@
user.changeNext_move(CLICK_CD_MELEE)
user.do_attack_animation(src, ATTACK_EFFECT_PUNCH)
playsound(loc, 'sound/weapons/tap.ogg', 40, 1, -1)
- user.visible_message("[user] hits [name]. Nothing happens", "You hit [name] with no visible effect.")
+ user.visible_message("[user] hits [name]. Nothing happens", "You hit [name] with no visible effect.")
log_message("Attack by hand/paw. Attacker - [user].")
/obj/mecha/attack_alien(mob/living/user)
log_message("Attack by alien. Attacker - [user].", TRUE)
+ add_attack_logs(user, OCCUPANT_LOGGING, "Alien attacked mech [src]")
playsound(src.loc, 'sound/weapons/slash.ogg', 100, TRUE)
attack_generic(user, 15, BRUTE, "melee", 0)
@@ -561,8 +560,8 @@
if(user.obj_damage)
animal_damage = user.obj_damage
animal_damage = min(animal_damage, 20*user.environment_smash)
- user.create_attack_log("attacked [name]")
- add_attack_logs(user, src, "Attacked")
+ if(animal_damage)
+ add_attack_logs(user, OCCUPANT_LOGGING, "Animal attacked mech [src]")
attack_generic(user, animal_damage, user.melee_damage_type, "melee", play_soundeffect)
return TRUE
@@ -573,7 +572,7 @@
. = ..()
if(.)
log_message("Attack by hulk. Attacker - [user].", 1)
- add_attack_logs(user, src, "Punched with hulk powers")
+ add_attack_logs(user, OCCUPANT_LOGGING, "Hulk punched mech [src]")
/obj/mecha/blob_act(obj/structure/blob/B)
log_message("Attack by blob. Attacker - [B].")
@@ -584,10 +583,14 @@
/obj/mecha/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) //wrapper
log_message("Hit by [AM].")
+ if(isitem(AM))
+ var/obj/item/I = AM
+ add_attack_logs(I.thrownby, OCCUPANT_LOGGING, "threw [AM] at mech [src]")
. = ..()
/obj/mecha/bullet_act(obj/item/projectile/Proj) //wrapper
log_message("Hit by projectile. Type: [Proj.name]([Proj.flag]).")
+ add_attack_logs(Proj.firer, OCCUPANT_LOGGING, "shot [Proj.name]([Proj.flag]) at mech [src]")
..()
/obj/mecha/ex_act(severity, target)
@@ -774,6 +777,8 @@
to_chat(user, "You stop installing [M].")
else
+ if(W.force)
+ add_attack_logs(user, OCCUPANT_LOGGING, "attacked mech '[src]' using [W]")
return ..()
@@ -861,7 +866,7 @@
return 0
use_power(melee_energy_drain)
if(M.damtype == BRUTE || M.damtype == BURN)
- add_attack_logs(M.occupant, src, "Mecha-attacked with [M] (INTENT: [uppertext(M.occupant.a_intent)]) (DAMTYPE: [uppertext(M.damtype)])")
+ add_attack_logs(M.occupant, src, "Mecha-attacked with [M] ([uppertext(M.occupant.a_intent)]) ([uppertext(M.damtype)])")
. = ..()
/obj/mecha/emag_act(mob/user)
@@ -1264,6 +1269,13 @@
L.client.RemoveViewMod("mecha")
zoom_mode = FALSE
+ if(ishuman(L))
+ var/mob/living/carbon/human/H = L
+ H.regenerate_icons() // workaround for 14457
+
+/obj/mecha/force_eject_occupant()
+ go_out()
+
/////////////////////////
////// Access stuff /////
/////////////////////////
@@ -1534,3 +1546,5 @@
if(L.incapacitated())
return FALSE
return TRUE
+
+#undef OCCUPANT_LOGGING
diff --git a/code/game/mecha/mecha_control_console.dm b/code/game/mecha/mecha_control_console.dm
index f72770a8ea3..72c8985a126 100644
--- a/code/game/mecha/mecha_control_console.dm
+++ b/code/game/mecha/mecha_control_console.dm
@@ -8,68 +8,65 @@
circuit = /obj/item/circuitboard/mecha_control
var/list/located = list()
var/screen = 0
- var/stored_data
+ var/stored_data = list()
/obj/machinery/computer/mecha/attack_ai(mob/user)
return attack_hand(user)
/obj/machinery/computer/mecha/attack_hand(mob/user)
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/mecha/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/mecha/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "exosuit_control.tmpl", "Exosuit Control Console", 420, 500)
+ ui = new(user, src, ui_key, "MechaControlConsole", name, 420, 500, master_ui, state)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/computer/mecha/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- data["screen"] = screen
- if(screen == 0)
- var/list/mechas[0]
- var/list/trackerlist = list()
- for(var/stompy in GLOB.mechas_list)
- var/obj/mecha/MC = stompy
- trackerlist += MC.trackers
- for(var/thing in trackerlist)
- var/obj/item/mecha_parts/mecha_tracking/TR = thing
- var/answer = TR.get_mecha_info()
- if(answer)
- mechas[++mechas.len] = answer
- data["mechas"] = mechas
- if(screen == 1)
- data["log"] = stored_data
+/obj/machinery/computer/mecha/tgui_data(mob/user)
+ var/list/data = list()
+ data["beacons"] = list()
+ var/list/trackerlist = list()
+ for(var/stompy in GLOB.mechas_list)
+ var/obj/mecha/MC = stompy
+ trackerlist += MC.trackers
+ for(var/thing in trackerlist)
+ var/obj/item/mecha_parts/mecha_tracking/TR = thing
+ var/list/tr_data = TR.retrieve_data()
+ if(tr_data)
+ data["beacons"] += list(tr_data)
+
+ data["stored_data"] = stored_data
+
return data
-/obj/machinery/computer/mecha/Topic(href, href_list)
+
+/obj/machinery/computer/mecha/tgui_act(action, params)
if(..())
- return 1
-
- var/datum/topic_input/afilter = new /datum/topic_input(href,href_list)
- if(href_list["send_message"])
- var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("send_message")
- var/message = strip_html_simple(input(usr,"Input message","Transmit message") as text)
- if(!trim(message) || ..())
- return 1
- var/obj/mecha/M = MT.in_mecha()
- if(M)
- M.occupant_message(message)
-
- if(href_list["shock"])
- var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("shock")
- MT.shock()
-
- if(href_list["get_log"])
- var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("get_log")
- stored_data = MT.get_mecha_log()
- screen = 1
-
- if(href_list["return"])
- screen = 0
-
- SSnanoui.update_uis(src)
- return
+ return
+ switch(action)
+ if("send_message")
+ var/obj/item/mecha_parts/mecha_tracking/MT = locateUID(params["mt"])
+ if(istype(MT))
+ var/message = strip_html_simple(input(usr, "Input message", "Transmit message") as text)
+ if(!message || !trim(message) || ..())
+ return FALSE
+ var/obj/mecha/M = MT.in_mecha()
+ if(M)
+ M.occupant_message(message)
+ return TRUE
+ if("shock")
+ var/obj/item/mecha_parts/mecha_tracking/MT = locateUID(params["mt"])
+ if(istype(MT))
+ MT.shock()
+ return TRUE
+ if("get_log")
+ var/obj/item/mecha_parts/mecha_tracking/MT = locateUID(params["mt"])
+ if(istype(MT))
+ stored_data = MT.get_mecha_log()
+ return TRUE
+ if("clear_log")
+ stored_data = list()
+ return TRUE
/obj/item/mecha_parts/mecha_tracking
name = "Exosuit tracking beacon"
@@ -103,7 +100,7 @@
if(istype(M, /obj/mecha/working/ripley))
var/obj/mecha/working/ripley/RM = M
answer["hascargo"] = 1
- answer["cargo"] = RM.cargo.len/RM.cargo_capacity*100
+ answer["cargo"] = length(RM.cargo) / RM.cargo_capacity * 100
return answer
@@ -122,10 +119,34 @@
Active equipment: [M.selected||"None"] "}
if(istype(M, /obj/mecha/working/ripley))
var/obj/mecha/working/ripley/RM = M
- answer += "Used cargo space: [RM.cargo.len/RM.cargo_capacity*100]% "
+ answer += "Used cargo space: [length(RM.cargo) / RM.cargo_capacity * 100]% "
return answer
+/obj/item/mecha_parts/mecha_tracking/proc/retrieve_data()
+ var/list/data = list()
+ if(!in_mecha())
+ return FALSE
+ var/obj/mecha/M = loc
+ data["uid"] = UID()
+ data["charge"] = M.get_charge()
+ data["name"] = M.name
+ data["health"] = M.obj_integrity
+ data["maxHealth"] = M.max_integrity
+ data["cell"] = M.cell
+ if(M.cell)
+ data["cellCharge"] = M.cell.charge
+ data["cellMaxCharge"] = M.cell.charge
+ data["airtank"] = M.return_pressure()
+ data["pilot"] = M.occupant
+ data["location"] = get_area(M)
+ data["active"] = M.selected
+ if(istype(M, /obj/mecha/working/ripley))
+ var/obj/mecha/working/ripley/RM = M
+ data["cargoUsed"] = length(RM.cargo)
+ data["cargoMax"] = RM.cargo_capacity
+ return data
+
/obj/item/mecha_parts/mecha_tracking/emp_act()
qdel(src)
@@ -142,9 +163,9 @@
/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_log()
if(!in_mecha())
- return 0
+ return list()
var/obj/mecha/M = loc
- return M.get_log_html()
+ return M.get_log_tgui()
/obj/item/mecha_parts/mecha_tracking/ai_control
name = "exosuit AI control beacon"
diff --git a/code/game/mecha/mecha_topic.dm b/code/game/mecha/mecha_topic.dm
index fc8f38a971c..8c9a74c0f8d 100644
--- a/code/game/mecha/mecha_topic.dm
+++ b/code/game/mecha/mecha_topic.dm
@@ -158,6 +158,14 @@
output += " |