diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
index 5a69f26460..a1b7a74384 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/_onclick/hud/_defines.dm
@@ -117,8 +117,8 @@
#define ui_borg_camera "CENTER+3:21,SOUTH:5"
#define ui_borg_alerts "CENTER+4:21,SOUTH:5"
#define ui_borg_language_menu "CENTER+4:21,SOUTH+1:5"
-#define ui_borg_sensor "CENTER-3:15, SOUTH:5" //LEGACY
-#define ui_borg_thrusters "CENTER-5:15, SOUTH:5" //LEGACY
+#define ui_borg_sensor "CENTER-6:16, SOUTH:5" //LEGACY
+#define ui_borg_thrusters "CENTER-5:16, SOUTH:5" //LEGACY
//Aliens
#define ui_alien_health "EAST,CENTER-1:15"
diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm
index 66aa12ac1a..92620f597b 100644
--- a/code/controllers/subsystem/shuttle.dm
+++ b/code/controllers/subsystem/shuttle.dm
@@ -676,7 +676,7 @@ SUBSYSTEM_DEF(shuttle)
if(!preview_shuttle)
load_template(loading_template)
- preview_shuttle.linkup(loading_template, destination_port)
+ // preview_shuttle.linkup(loading_template, destination_port)
preview_template = loading_template
// get the existing shuttle information, if any
diff --git a/code/datums/shuttles.dm b/code/datums/shuttles.dm
index 17116b922b..f8fe430961 100644
--- a/code/datums/shuttles.dm
+++ b/code/datums/shuttles.dm
@@ -454,12 +454,12 @@
return SSshuttle.shuttle_purchase_requirements_met["emagged"]
-/datum/map_template/shuttle/emergency/cruise
- suffix = "cruise"
- name = "The NTSS Independence"
- description = "Ordinarily reserved for special functions and events, the Cruise Shuttle Independence can bring a summery cheer to your next station evacuation for a 'modest' fee!"
- admin_notes = "This motherfucker is BIG. You might need to force dock it."
- credit_cost = 8000
+// /datum/map_template/shuttle/emergency/cruise
+// suffix = "cruise"
+// name = "The NTSS Independence"
+// description = "Ordinarily reserved for special functions and events, the Cruise Shuttle Independence can bring a summery cheer to your next station evacuation for a 'modest' fee!"
+// admin_notes = "This motherfucker is BIG. You might need to force dock it."
+// credit_cost = 8000
/datum/map_template/shuttle/emergency/monkey
suffix = "nature"
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 03a5a17493..74d50117ac 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -1,3 +1,5 @@
+#define DEFAULT_MAP_SIZE 15
+
/obj/machinery/computer/security
name = "security camera console"
desc = "Used to access the various cameras on the station."
@@ -8,15 +10,19 @@
var/list/network = list("ss13")
var/obj/machinery/camera/active_camera
+ /// The turf where the camera was last updated.
+ var/turf/last_camera_turf
var/list/concurrent_users = list()
// Stuff needed to render the map
var/map_name
- var/const/default_map_size = 15
- var/obj/screen/cam_screen
- var/obj/screen/plane_master/lighting/cam_plane_master
+ 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
+ interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_SET_MACHINE | INTERACT_MACHINE_REQUIRES_SIGHT
+
/obj/machinery/computer/security/Initialize()
. = ..()
// Map name has to start and end with an A-Z character,
@@ -33,18 +39,20 @@
cam_screen.assigned_map = map_name
cam_screen.del_on_map_removal = FALSE
cam_screen.screen_loc = "[map_name]:1,1"
- cam_plane_master = new
- cam_plane_master.name = "plane_master"
- cam_plane_master.assigned_map = map_name
- cam_plane_master.del_on_map_removal = FALSE
- cam_plane_master.screen_loc = "[map_name]:CENTER"
+ 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()
qdel(cam_screen)
- qdel(cam_plane_master)
+ QDEL_LIST(cam_plane_masters)
qdel(cam_background)
return ..()
@@ -56,9 +64,10 @@
/obj/machinery/computer/security/ui_interact(mob/user, datum/tgui/ui)
// Update UI
ui = SStgui.try_update_ui(user, src, ui)
- // Show static if can't use the camera
- if(!active_camera?.can_use())
- show_camera_static()
+
+ // Update the camera, showing static if necessary and updating data if the location has moved.
+ update_active_camera_screen()
+
if(!ui)
var/user_ref = REF(user)
var/is_living = isliving(user)
@@ -72,7 +81,7 @@
use_power(active_power_usage)
// Register map objects
user.client.register_map_obj(cam_screen)
- for(var/plane in cam_plane_master)
+ for(var/plane in cam_plane_masters)
user.client.register_map_obj(plane)
user.client.register_map_obj(cam_background)
// Open UI
@@ -100,6 +109,7 @@
data["cameras"] += list(list(
name = C.c_tag,
))
+
return data
/obj/machinery/computer/security/ui_act(action, params)
@@ -110,31 +120,51 @@
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
+ var/obj/machinery/camera/selected_camera = cameras[c_tag]
+ active_camera = selected_camera
playsound(src, get_sfx("terminal_type"), 25, FALSE)
- // Show static if can't use the camera
- if(!active_camera?.can_use())
- show_camera_static()
+ if(!selected_camera)
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)
+ update_active_camera_screen()
return TRUE
+/obj/machinery/computer/security/proc/update_active_camera_screen()
+ // Show static if can't use the camera
+ if(!active_camera?.can_use())
+ show_camera_static()
+ return
+
+ var/list/visible_turfs = list()
+
+ // Is this camera located in or attached to a living thing? If so, assume the camera's loc is the living thing.
+ var/cam_location = isliving(active_camera.loc) ? active_camera.loc : active_camera
+
+ // If we're not forcing an update for some reason and the cameras are in the same location,
+ // we don't need to update anything.
+ // Most security cameras will end here as they're not moving.
+ var/newturf = get_turf(cam_location)
+ if(last_camera_turf == newturf)
+ return
+
+ // Cameras that get here are moving, and are likely attached to some moving atom such as cyborgs.
+ last_camera_turf = get_turf(cam_location)
+
+ var/list/visible_things = active_camera.isXRay() ? range(active_camera.view_range, cam_location) : view(active_camera.view_range, cam_location)
+
+ for(var/turf/visible_turf in visible_things)
+ visible_turfs += visible_turf
+
+ 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)
+
/obj/machinery/computer/security/ui_close(mob/user)
var/user_ref = REF(user)
var/is_living = isliving(user)
@@ -151,7 +181,7 @@
/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)
+ cam_background.fill_rect(1, 1, DEFAULT_MAP_SIZE, DEFAULT_MAP_SIZE)
// Returns the list of cameras accessible from this computer
/obj/machinery/computer/security/proc/get_available_cameras()
@@ -179,7 +209,7 @@
name = "security camera monitor"
desc = "An old TV hooked into the station's camera network."
icon_state = "television"
- icon_keyboard = null
+ icon_keyboard = "no_keyboard"
icon_screen = "detective_tv"
pass_flags = PASSTABLE
@@ -211,7 +241,7 @@
/obj/machinery/computer/security/qm
name = "\improper Quartermaster's camera console"
- desc = "A console with access to the mining, auxillary base and vault camera networks."
+ desc = "A console with access to the mining, auxiliary base and vault camera networks."
network = list("mine", "auxbase", "vault")
circuit = null
@@ -222,17 +252,12 @@
desc = "Used for watching an empty arena."
icon = 'icons/obj/stationobjs.dmi'
icon_state = "telescreen"
+ layer = SIGN_LAYER
network = list("thunder")
density = FALSE
circuit = null
light_power = 0
-/obj/machinery/computer/security/telescreen/Initialize()
- . = ..()
- var/turf/T = get_turf_pixel(src)
- if(iswallturf(T))
- plane = ABOVE_WALL_PLANE
-
/obj/machinery/computer/security/telescreen/update_icon_state()
icon_state = initial(icon_state)
if(stat & BROKEN)
@@ -246,21 +271,19 @@
network = list("thunder")
density = FALSE
circuit = null
- //interaction_flags_atom = NONE // interact() is called by BigClick()
+ interaction_flags_atom = NONE // interact() is called by BigClick()
var/icon_state_off = "entertainment_blank"
var/icon_state_on = "entertainment"
-/* If someone would like to try to get this long-distance viewing thing working, be my guest. I tried everything I could possibly think of and it just refused to operate correctly.
-
/obj/machinery/computer/security/telescreen/entertainment/Initialize()
. = ..()
RegisterSignal(src, COMSIG_CLICK, .proc/BigClick)
// Bypass clickchain to allow humans to use the telescreen from a distance
/obj/machinery/computer/security/telescreen/entertainment/proc/BigClick()
- interact(usr)
+ SIGNAL_HANDLER
-*/
+ INVOKE_ASYNC(src, /atom.proc/interact, usr)
/obj/machinery/computer/security/telescreen/entertainment/proc/notify(on)
if(on && icon_state == icon_state_off)
@@ -279,8 +302,8 @@
network = list("rd", "aicore", "aiupload", "minisat", "xeno", "test")
/obj/machinery/computer/security/telescreen/circuitry
- name = "circuitry telescreen"
- desc = "Used for watching the other eggheads from the safety of the circuitry lab."
+ name = "research telescreen"
+ desc = "A telescreen with access to the research division's camera network."
network = list("rd")
/obj/machinery/computer/security/telescreen/ce
@@ -324,8 +347,8 @@
network = list("prison")
/obj/machinery/computer/security/telescreen/auxbase
- name = "auxillary base monitor"
- desc = "A telescreen that connects to the auxillary base's camera."
+ name = "auxiliary base monitor"
+ desc = "A telescreen that connects to the auxiliary base's camera."
network = list("auxbase")
/obj/machinery/computer/security/telescreen/minisat
@@ -346,3 +369,5 @@
for(var/i in network)
network -= i
network += "[idnum][i]"
+
+#undef DEFAULT_MAP_SIZE
diff --git a/code/game/machinery/computer/pod.dm b/code/game/machinery/computer/pod.dm
index ca64d538b9..2eb1aab925 100644
--- a/code/game/machinery/computer/pod.dm
+++ b/code/game/machinery/computer/pod.dm
@@ -1,21 +1,37 @@
/obj/machinery/computer/pod
name = "mass driver launch control"
desc = "A combined blastdoor and mass driver control unit."
+ // processing_flags = START_PROCESSING_MANUALLY
+ /// Connected mass driver
var/obj/machinery/mass_driver/connected = null
- var/title = "Mass Driver Controls"
+ /// ID of the launch control
var/id = 1
- var/timing = 0
+ /// If the launch timer counts down
+ var/timing = FALSE
+ /// Time before auto launch
var/time = 30
+ /// Range in which we search for a mass drivers and poddoors nearby
var/range = 4
-
+ /// Countdown timer for the mass driver's delayed launch functionality.
+ COOLDOWN_DECLARE(massdriver_countdown)
/obj/machinery/computer/pod/Initialize()
. = ..()
for(var/obj/machinery/mass_driver/M in range(range, src))
if(M.id == id)
connected = M
+ break
+/obj/machinery/computer/pod/process(delta_time)
+ if(COOLDOWN_FINISHED(src, massdriver_countdown))
+ timing = FALSE
+ // alarm() sleeps, so we want to end processing first and can't rely on return PROCESS_KILL
+ end_processing()
+ alarm()
+/**
+ * Initiates launching sequence by checking if all components are functional, opening poddoors, firing mass drivers and then closing poddoors
+ */
/obj/machinery/computer/pod/proc/alarm()
if(stat & (NOPOWER|BROKEN))
return
@@ -39,92 +55,110 @@
if(M.id == id)
M.close()
-/obj/machinery/computer/pod/ui_interact(mob/user)
+/obj/machinery/computer/pod/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "MassDriverControl", name)
+ ui.open()
+
+/obj/machinery/computer/pod/ui_data(mob/user)
+ var/list/data = list()
+ // If the cooldown has finished, just display the time. If the cooldown hasn't finished, display the cooldown.
+ var/display_time = COOLDOWN_FINISHED(src, massdriver_countdown) ? time : COOLDOWN_TIMELEFT(src, massdriver_countdown) * 0.1
+ data["connected"] = connected ? TRUE : FALSE
+ data["seconds"] = round(display_time % 60)
+ data["minutes"] = round((display_time - data["seconds"]) / 60)
+ data["timing"] = timing
+ data["power"] = connected ? connected.power : 0.25
+ data["poddoor"] = FALSE
+ for(var/obj/machinery/door/poddoor/door in range(range, src))
+ if(door.id == id)
+ data["poddoor"] = TRUE
+ break
+ return data
+
+/obj/machinery/computer/pod/ui_act(action, list/params)
. = ..()
- if(!allowed(user))
- to_chat(user, "Access denied.")
+ if(.)
+ return
+ if(!allowed(usr))
+ to_chat(usr, "Access denied.")
return
- var/dat = ""
- if(connected)
- var/d2
- if(timing) //door controls do not need timers.
- d2 = "Stop Time Launch"
- else
- d2 = "Initiate Time Launch"
- dat += "
\nTimer System: [d2]\nTime Left: [DisplayTimeText(time)] - - + +"
- var/temp = ""
- var/list/L = list( 0.25, 0.5, 1, 2, 4, 8, 16 )
- for(var/t in L)
- if(t == connected.power)
- temp += "[t] "
+ switch(action)
+ if("set_power")
+ if(!connected)
+ return
+ var/value = text2num(params["power"])
+ if(!value)
+ return
+ value = clamp(value, 0.25, 16)
+ connected.power = value
+ return TRUE
+ if("launch")
+ alarm()
+ return TRUE
+ if("time")
+ timing = !timing
+ if(timing)
+ COOLDOWN_START(src, massdriver_countdown, time SECONDS)
+ begin_processing()
else
- temp += "[t] "
- dat += "
\nPower Level: [temp]
\nFiring Sequence
\nTest Fire Driver
\nToggle Outer Door
"
- else
- dat += "
\nToggle Outer Door
"
- dat += "
Close"
- add_fingerprint(usr)
- var/datum/browser/popup = new(user, "computer", title, 400, 500)
- popup.set_content(dat)
- popup.open()
-
-/obj/machinery/computer/pod/process()
- if(!..())
- return
- if(timing)
- if(time > 0)
- time = round(time) - 1
- else
- alarm()
- time = 0
- timing = 0
- updateDialog()
-
-
-/obj/machinery/computer/pod/Topic(href, href_list)
- if(..())
- return
- if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || hasSiliconAccessInArea(usr))
- usr.set_machine(src)
- if(href_list["power"])
- var/t = text2num(href_list["power"])
- t = min(max(0.25, t), 16)
- if(connected)
- connected.power = t
- if(href_list["alarm"])
- alarm()
- if(href_list["time"])
- timing = text2num(href_list["time"])
- if(href_list["tp"])
- var/tp = text2num(href_list["tp"])
- time += tp
- time = min(max(round(time), 0), 120)
- if(href_list["door"])
+ time = COOLDOWN_TIMELEFT(src, massdriver_countdown) * 0.1
+ COOLDOWN_RESET(src, massdriver_countdown)
+ end_processing()
+ return TRUE
+ if("input")
+ var/value = text2num(params["adjust"])
+ if(!value)
+ return
+ value = round(time + value)
+ time = clamp(value, 0, 120)
+ return TRUE
+ if("door")
for(var/obj/machinery/door/poddoor/M in range(range, src))
if(M.id == id)
if(M.density)
M.open()
else
M.close()
- if(href_list["drive"])
+ return TRUE
+ if("driver_test")
for(var/obj/machinery/mass_driver/M in range(range, src))
if(M.id == id)
- M.power = connected.power
+ M.power = connected?.power
M.drive()
- updateUsrDialog()
+ return TRUE
/obj/machinery/computer/pod/old
name = "\improper DoorMex control console"
- title = "Door Controls"
icon_state = "oldcomp"
icon_screen = "library"
- icon_keyboard = null
+ icon_keyboard = "no_keyboard"
+
+// /obj/machinery/computer/pod/old/mass_driver_controller
+// name = "\improper Mass Driver Controller"
+// icon = 'icons/obj/airlock_machines.dmi'
+// icon_state = "airlock_control_standby"
+// icon_keyboard = "no_keyboard"
+// density = FALSE
+
+// /obj/machinery/computer/pod/old/mass_driver_controller/toxinsdriver
+// id = MASSDRIVER_TOXINS
+
+// //for maps where pod doors are outside of the standard 4 tile controller detection range (ie Pubbystation)
+// /obj/machinery/computer/pod/old/mass_driver_controller/toxinsdriver/longrange
+// range = 6
+
+// /obj/machinery/computer/pod/old/mass_driver_controller/chapelgun
+// id = MASSDRIVER_CHAPEL
+
+// /obj/machinery/computer/pod/old/mass_driver_controller/trash
+// id = MASSDRIVER_DISPOSALS
/obj/machinery/computer/pod/old/syndicate
name = "\improper ProComp Executive IIc"
desc = "The Syndicate operate on a tight budget. Operates external airlocks."
- title = "External Airlock Controls"
req_access = list(ACCESS_SYNDICATE)
/obj/machinery/computer/pod/old/swf
diff --git a/code/game/machinery/mass_driver.dm b/code/game/machinery/mass_driver.dm
index b39c6d350f..d42c955115 100644
--- a/code/game/machinery/mass_driver.dm
+++ b/code/game/machinery/mass_driver.dm
@@ -11,8 +11,24 @@
var/id = 1
var/drive_range = 50 //this is mostly irrelevant since current mass drivers throw into space, but you could make a lower-range mass driver for interstation transport or something I guess.
-/obj/machinery/mass_driver/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
- id = "[idnum][id]"
+// /obj/machinery/mass_driver/chapelgun
+// name = "holy driver"
+// id = MASSDRIVER_CHAPEL
+
+// /obj/machinery/mass_driver/toxins
+// id = MASSDRIVER_TOXINS
+
+// /obj/machinery/mass_driver/trash
+// id = MASSDRIVER_DISPOSALS
+
+// /obj/machinery/mass_driver/Destroy()
+// for(var/obj/machinery/computer/pod/control in GLOB.machines)
+// if(control.id == id)
+// control.connected = null
+// return ..()
+
+/obj/machinery/mass_driver/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ id = "[port.id]_[id]"
/obj/machinery/mass_driver/proc/drive(amount)
if(stat & (BROKEN|NOPOWER))
@@ -22,6 +38,8 @@
var/atom/target = get_edge_target_turf(src, dir)
for(var/atom/movable/O in loc)
if(!O.anchored || ismecha(O)) //Mechs need their launch platforms.
+ // if(ismob(O) && !isliving(O))
+ // continue
O_limit++
if(O_limit >= 20)
audible_message("[src] lets out a screech, it doesn't seem to be able to handle the load.")
@@ -30,7 +48,6 @@
O.throw_at(target, drive_range * power, power)
flick("mass_driver1", src)
-
/obj/machinery/mass_driver/emp_act(severity)
. = ..()
if (. & EMP_PROTECT_SELF)
diff --git a/code/game/mecha/mecha_control_console.dm b/code/game/mecha/mecha_control_console.dm
index 0e47872221..29298247cd 100644
--- a/code/game/mecha/mecha_control_console.dm
+++ b/code/game/mecha/mecha_control_console.dm
@@ -29,7 +29,7 @@
integrity = round((M.obj_integrity / M.max_integrity) * 100),
charge = M.cell ? round(M.cell.percent()) : null,
airtank = M.internal_tank ? M.return_pressure() : null,
- pilot = M.occupant,
+ pilot = list(M.occupant),
location = get_area_name(M, TRUE),
active_equipment = M.selected,
emp_recharging = MT.recharging,
@@ -38,7 +38,7 @@
if(istype(M, /obj/mecha/working/ripley))
var/obj/mecha/working/ripley/RM = M
mech_data += list(
- cargo_space = round((RM.cargo.len / RM.cargo_capacity) * 100)
+ cargo_space = round((LAZYLEN(RM.cargo) / RM.cargo_capacity) * 100)
)
data["mechs"] += list(mech_data)
@@ -46,7 +46,8 @@
return data
/obj/machinery/computer/mecha/ui_act(action, params)
- if(..())
+ . = ..()
+ if(.)
return
switch(action)
@@ -57,7 +58,7 @@
var/message = stripped_input(usr, "Input message", "Transmit message")
var/obj/mecha/M = MT.chassis
if(trim(message) && M)
- M.occupant_message(message)
+ to_chat(M.occupant, message)
to_chat(usr, "Message sent.")
. = TRUE
if("shock")
@@ -67,8 +68,8 @@
var/obj/mecha/M = MT.chassis
if(M)
MT.shock()
- log_game("[key_name(usr)] has activated remote EMP on exosuit [M], located at [loc_name(M)], which is currently [M.occupant? "being piloted by [key_name(M.occupant)]." : "without a pilot."] ")
- message_admins("[key_name_admin(usr)][ADMIN_FLW(usr)] has activated remote EMP on exosuit [M][ADMIN_JMP(M)], which is currently [M.occupant ? "being piloted by [key_name_admin(M.occupant)][ADMIN_FLW(M.occupant)]." : "without a pilot."] ")
+ log_game("[key_name(usr)] has activated remote EMP on exosuit [M], located at [loc_name(M)], which [M.occupant ? "has the occupants [M.occupant]." : "without a pilot."] ")
+ message_admins("[key_name_admin(usr)][ADMIN_FLW(usr)] has activated remote EMP on exosuit [M][ADMIN_JMP(M)], which is currently [M.occupants ? "occupied by [M.occupant][ADMIN_FLW(M)]." : "without a pilot."] ")
. = TRUE
/obj/item/mecha_parts/mecha_tracking
@@ -85,8 +86,8 @@
var/obj/mecha/chassis
/**
- * Returns a html formatted string describing attached mech status
- */
+ * Returns a html formatted string describing attached mech status
+ */
/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info()
if(!chassis)
return FALSE
@@ -101,7 +102,7 @@
Active Equipment: [chassis.selected || "None"]"}
if(istype(chassis, /obj/mecha/working/ripley))
var/obj/mecha/working/ripley/RM = chassis
- answer += "
Used Cargo Space: [round((RM.cargo.len / RM.cargo_capacity * 100), 0.01)]%"
+ answer += "
Used Cargo Space: [round((LAZYLEN(RM.cargo) / RM.cargo_capacity * 100), 0.01)]%"
return answer
@@ -125,8 +126,8 @@
chassis = M
/**
- * Attempts to EMP mech that the tracker is attached to, if there is one and tracker is not on cooldown
- */
+ * Attempts to EMP mech that the tracker is attached to, if there is one and tracker is not on cooldown
+ */
/obj/item/mecha_parts/mecha_tracking/proc/shock()
if(recharging)
return
@@ -136,8 +137,8 @@
recharging = TRUE
/**
- * Resets recharge variable, allowing tracker to be EMP pulsed again
- */
+ * Resets recharge variable, allowing tracker to be EMP pulsed again
+ */
/obj/item/mecha_parts/mecha_tracking/proc/recharge()
recharging = FALSE
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index 503a183064..b79d1d3cbc 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -439,6 +439,8 @@
if(zero_amount())
return
return split_stack(user, 1)
+ else
+ . = ..()
/obj/item/stack/AltClick(mob/living/user)
. = ..()
diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm
index d57f0cc51f..5bd1f67bee 100644
--- a/code/game/objects/items/tanks/tanks.dm
+++ b/code/game/objects/items/tanks/tanks.dm
@@ -1,10 +1,12 @@
/obj/item/tank
name = "tank"
icon = 'icons/obj/tank.dmi'
+ icon_state = "generic"
lefthand_file = 'icons/mob/inhands/equipment/tanks_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/tanks_righthand.dmi'
flags_1 = CONDUCT_1
slot_flags = ITEM_SLOT_BACK
+ // worn_icon = 'icons/mob/clothing/back.dmi' //since these can also get thrown into suit storage slots. if something goes on the belt, set this to null.
hitsound = 'sound/weapons/smash.ogg'
pressure_resistance = ONE_ATMOSPHERE * 5
force = 5
@@ -18,12 +20,14 @@
var/distribute_pressure = ONE_ATMOSPHERE
var/integrity = 3
var/volume = 70
+ /// Icon state when in a tank holder. Null makes it incompatible with tank holder.
+ var/tank_holder_icon_state = "holder_generic"
/obj/item/tank/ui_action_click(mob/user)
toggle_internals(user)
/obj/item/tank/proc/toggle_internals(mob/user)
- var/mob/living/carbon/H = user
+ var/mob/living/carbon/human/H = user
if(!istype(H))
return
@@ -84,18 +88,23 @@
/obj/item/tank/Destroy()
if(air_contents)
- qdel(air_contents)
+ QDEL_NULL(air_contents)
STOP_PROCESSING(SSobj, src)
. = ..()
+// /obj/item/tank/ComponentInitialize()
+// . = ..()
+// if(tank_holder_icon_state)
+// AddComponent(/datum/component/container_item/tank_holder, tank_holder_icon_state)
+
/obj/item/tank/examine(mob/user)
var/obj/icon = src
. = ..()
if(istype(src.loc, /obj/item/assembly))
icon = src.loc
if(!in_range(src, user) && !isobserver(user))
- if (icon == src)
+ if(icon == src)
. += "If you want any more information you'll need to get closer."
return
@@ -140,14 +149,14 @@
if(T)
T.assume_air(air_contents)
air_update_turf()
- playsound(src.loc, 'sound/effects/spray.ogg', 10, 1, -3)
+ playsound(src.loc, 'sound/effects/spray.ogg', 10, TRUE, -3)
qdel(src)
/obj/item/tank/suicide_act(mob/user)
var/mob/living/carbon/human/H = user
user.visible_message("[user] is putting [src]'s valve to [user.p_their()] lips! It looks like [user.p_theyre()] trying to commit suicide!")
- playsound(loc, 'sound/effects/spray.ogg', 10, 1, -3)
- if (!QDELETED(H) && air_contents && air_contents.return_pressure() >= 1000)
+ playsound(loc, 'sound/effects/spray.ogg', 10, TRUE, -3)
+ if(!QDELETED(H) && air_contents && air_contents.return_pressure() >= 1000)
for(var/obj/item/W in H)
H.dropItemToGround(W)
if(prob(50))
@@ -159,12 +168,10 @@
H.spawn_gibs()
H.spill_organs()
H.spread_bodyparts()
-
- return (BRUTELOSS)
-
-/obj/item/tank/attack_ghost(mob/dead/observer/O)
- . = ..()
- atmosanalyzer_scan(air_contents, O, src, FALSE)
+ return MANUAL_SUICIDE
+ else
+ to_chat(user, "There isn't enough pressure in [src] to commit suicide with...")
+ return SHAME
/obj/item/tank/attackby(obj/item/W, mob/user, params)
add_fingerprint(user)
@@ -182,27 +189,30 @@
ui = new(user, src, "Tank", name)
ui.open()
+/obj/item/tank/ui_static_data(mob/user)
+ . = list (
+ "defaultReleasePressure" = round(TANK_DEFAULT_RELEASE_PRESSURE),
+ "minReleasePressure" = round(TANK_MIN_RELEASE_PRESSURE),
+ "maxReleasePressure" = round(TANK_MAX_RELEASE_PRESSURE),
+ "leakPressure" = round(TANK_LEAK_PRESSURE),
+ "fragmentPressure" = round(TANK_FRAGMENT_PRESSURE)
+ )
+
/obj/item/tank/ui_data(mob/user)
- var/list/data = list()
- data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
- data["releasePressure"] = round(distribute_pressure ? distribute_pressure : 0)
- data["defaultReleasePressure"] = round(TANK_DEFAULT_RELEASE_PRESSURE)
- data["minReleasePressure"] = round(TANK_MIN_RELEASE_PRESSURE)
- data["maxReleasePressure"] = round(TANK_MAX_RELEASE_PRESSURE)
+ . = list(
+ "tankPressure" = round(air_contents.return_pressure()),
+ "releasePressure" = round(distribute_pressure)
+ )
var/mob/living/carbon/C = user
if(!istype(C))
C = loc.loc
- if(!istype(C))
- return data
-
- if(C.internal == src)
- data["connected"] = TRUE
-
- return data
+ if(istype(C) && C.internal == src)
+ .["connected"] = TRUE
/obj/item/tank/ui_act(action, params)
- if(..())
+ . = ..()
+ if(.)
return
switch(action)
if("pressure")
@@ -228,6 +238,9 @@
/obj/item/tank/return_air()
return air_contents
+// /obj/item/tank/return_analyzable_air()
+// return air_contents
+
/obj/item/tank/assume_air(datum/gas_mixture/giver)
air_contents.merge(giver)
@@ -239,10 +252,9 @@
return null
var/tank_pressure = air_contents.return_pressure()
- if(tank_pressure < distribute_pressure)
- distribute_pressure = tank_pressure
+ var/actual_distribute_pressure = clamp(tank_pressure, 0, distribute_pressure)
- var/moles_needed = distribute_pressure*volume_to_return/(R_IDEAL_GAS_EQUATION*air_contents.return_temperature())
+ var/moles_needed = actual_distribute_pressure*volume_to_return/(R_IDEAL_GAS_EQUATION*air_contents.return_temperature())
return remove_air(moles_needed)
@@ -266,7 +278,7 @@
log_game("Explosive tank rupture! Last key to touch the tank was [src.fingerprintslast].")
//Give the gas a chance to build up more pressure through reacting
air_contents.react(src)
- air_contents.react(src)
+ // air_contents.react(src)
//Citadel Edit: removing extra react for "balance"
pressure = air_contents.return_pressure()
var/range = (pressure-TANK_FRAGMENT_PRESSURE)/TANK_FRAGMENT_SCALE
@@ -285,7 +297,7 @@
if(!T)
return
T.assume_air(air_contents)
- playsound(src.loc, 'sound/effects/spray.ogg', 10, 1, -3)
+ playsound(src.loc, 'sound/effects/spray.ogg', 10, TRUE, -3)
qdel(src)
else
integrity--
diff --git a/code/game/objects/structures/safe.dm b/code/game/objects/structures/safe.dm
index e6f77c85a1..55eb100b21 100644
--- a/code/game/objects/structures/safe.dm
+++ b/code/game/objects/structures/safe.dm
@@ -4,6 +4,11 @@ SAFES
FLOOR SAFES
*/
+/// Chance for a sound clue
+#define SOUND_CHANCE 10
+/// Explosion number threshold for opening safe
+#define BROKEN_THRESHOLD 3
+
//SAFES
/obj/structure/safe
name = "safe"
@@ -14,31 +19,36 @@ FLOOR SAFES
density = TRUE
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT
- var/open = FALSE //is the safe open?
- var/tumbler_1_pos //the tumbler position- from 0 to 72
- var/tumbler_1_open //the tumbler position to open at- 0 to 72
- var/tumbler_2_pos
- var/tumbler_2_open
- var/dial = 0 //where is the dial pointing?
- var/space = 0 //the combined w_class of everything in the safe
- var/maxspace = 24 //the maximum combined w_class of stuff in the safe
- var/explosion_count = 0 //Tough, but breakable
-
-/obj/structure/safe/New()
- ..()
- tumbler_1_pos = rand(0, 71)
- tumbler_1_open = rand(0, 71)
-
- tumbler_2_pos = rand(0, 71)
- tumbler_2_open = rand(0, 71)
-
+ /// The maximum combined w_class of stuff in the safe
+ var/maxspace = 24
+ /// The amount of tumblers that will be generated
+ var/number_of_tumblers = 2
+ /// Whether the safe is open or not
+ var/open = FALSE
+ /// Whether the safe is locked or not
+ var/locked = TRUE
+ /// The position the dial is pointing to
+ var/dial = 0
+ /// The list of tumbler dial positions that need to be hit
+ var/list/tumblers = list()
+ /// The index in the tumblers list of the tumbler dial position that needs to be hit
+ var/current_tumbler_index = 1
+ /// The combined w_class of everything in the safe
+ var/space = 0
+ /// Tough, but breakable if explosion counts reaches set value
+ var/explosion_count = 0
/obj/structure/safe/Initialize(mapload)
. = ..()
+ // Combination generation
+ for(var/i in 1 to number_of_tumblers)
+ tumblers.Add(rand(0, 99))
+
if(!mapload)
return
+ // Put as many items on our turf inside as possible
for(var/obj/item/I in loc)
if(space >= maxspace)
return
@@ -46,141 +56,36 @@ FLOOR SAFES
space += I.w_class
I.forceMove(src)
-
-/obj/structure/safe/proc/check_unlocked(mob/user, canhear)
- if(explosion_count > 2)
- return 1
- if(user && canhear)
- if(tumbler_1_pos == tumbler_1_open)
- to_chat(user, "You hear a [pick("tonk", "krunk", "plunk")] from [src].")
- if(tumbler_2_pos == tumbler_2_open)
- to_chat(user, "You hear a [pick("tink", "krink", "plink")] from [src].")
- if(tumbler_1_pos == tumbler_1_open && tumbler_2_pos == tumbler_2_open)
- if(user)
- visible_message("[pick("Spring", "Sprang", "Sproing", "Clunk", "Krunk")]!")
- return TRUE
- return FALSE
-
-/obj/structure/safe/proc/decrement(num)
- num -= 1
- if(num < 0)
- num = 71
- return num
-
-/obj/structure/safe/proc/increment(num)
- num += 1
- if(num > 71)
- num = 0
- return num
-
/obj/structure/safe/update_icon_state()
if(open)
icon_state = "[initial(icon_state)]-open"
else
icon_state = initial(icon_state)
-/obj/structure/safe/ui_interact(mob/user)
- user.set_machine(src)
- var/dat = ""
- dat += "[open ? "Close" : "Open"] [src] | - [dial] +"
- if(open)
- dat += ""
- for(var/i = contents.len, i>=1, i--)
- var/obj/item/P = contents[i]
- dat += "| [P.name] |
"
- dat += "
"
- user << browse("[name][dat]", "window=safe;size=350x300")
-
-/obj/structure/safe/Topic(href, href_list)
- if(!ishuman(usr))
- return
- var/mob/living/carbon/human/user = usr
-
- if(!user.canUseTopic(src))
- return
-
- var/canhear = FALSE
- if(user.is_holding_item_of_type(/obj/item/clothing/neck/stethoscope))
- canhear = TRUE
-
- if(href_list["open"])
- if(check_unlocked())
- to_chat(user, "You [open ? "close" : "open"] [src].")
- open = !open
- update_icon()
- updateUsrDialog()
- return
- else
- to_chat(user, "You can't [open ? "close" : "open"] [src], the lock is engaged!")
- return
-
- if(href_list["decrement"])
- dial = decrement(dial)
- if(dial == tumbler_1_pos + 1 || dial == tumbler_1_pos - 71)
- tumbler_1_pos = decrement(tumbler_1_pos)
- if(canhear)
- to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].")
- if(tumbler_1_pos == tumbler_2_pos + 37 || tumbler_1_pos == tumbler_2_pos - 35)
- tumbler_2_pos = decrement(tumbler_2_pos)
- if(canhear)
- to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].")
- check_unlocked(user, canhear)
- updateUsrDialog()
- return
-
- if(href_list["increment"])
- dial = increment(dial)
- if(dial == tumbler_1_pos - 1 || dial == tumbler_1_pos + 71)
- tumbler_1_pos = increment(tumbler_1_pos)
- if(canhear)
- to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].")
- if(tumbler_1_pos == tumbler_2_pos - 37 || tumbler_1_pos == tumbler_2_pos + 35)
- tumbler_2_pos = increment(tumbler_2_pos)
- if(canhear)
- to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].")
- check_unlocked(user, canhear)
- updateUsrDialog()
- return
-
- if(href_list["retrieve"])
- user << browse("", "window=safe") // Close the menu
-
- var/obj/item/P = locate(href_list["retrieve"]) in src
- if(open)
- if(P && in_range(src, user))
- user.put_in_hands(P)
- space -= P.w_class
- updateUsrDialog()
-
-
/obj/structure/safe/attackby(obj/item/I, mob/user, params)
if(open)
- . = 1 //no afterattack
+ . = TRUE //no afterattack
if(I.w_class + space <= maxspace)
space += I.w_class
if(!user.transferItemToLoc(I, src))
to_chat(user, "\The [I] is stuck to your hand, you cannot put it in the safe!")
return
to_chat(user, "You put [I] in [src].")
- updateUsrDialog()
+ else
+ to_chat(user, "[I] won't fit in [src].")
+ else
+ if(istype(I, /obj/item/clothing/neck/stethoscope))
+ attack_hand(user)
return
else
- to_chat(user, "[I] won't fit in [src].")
+ to_chat(user, "You can't put [I] into the safe while it is closed!")
return
- else if(istype(I, /obj/item/clothing/neck/stethoscope))
- to_chat(user, "Hold [I] in one of your hands while you manipulate the dial!")
- else
- return ..()
-
-
-/obj/structure/safe/handle_atom_del(atom/A)
- updateUsrDialog()
/obj/structure/safe/blob_act(obj/structure/blob/B)
return
/obj/structure/safe/ex_act(severity, target)
- if(((severity == 2 && target == src) || severity == 1) && explosion_count < 3)
+ if(((severity == 2 && target == src) || severity == 1) && explosion_count < BROKEN_THRESHOLD)
explosion_count++
switch(explosion_count)
if(1)
@@ -190,6 +95,144 @@ FLOOR SAFES
if(3)
desc = initial(desc) + "\nThe lock seems to be broken."
+/obj/structure/safe/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/simple/safe),
+ )
+
+/obj/structure/safe/ui_state(mob/user)
+ return GLOB.physical_state
+
+/obj/structure/safe/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Safe", name)
+ ui.open()
+
+/obj/structure/safe/ui_data(mob/user)
+ var/list/data = list()
+ data["dial"] = dial
+ data["open"] = open
+ data["locked"] = locked
+ data["broken"] = check_broken()
+
+ if(open)
+ var/list/contents_names = list()
+ data["contents"] = contents_names
+ for(var/obj/O in contents)
+ contents_names[++contents_names.len] = list("name" = O.name, "sprite" = O.icon_state)
+ user << browse_rsc(icon(O.icon, O.icon_state), "[O.icon_state].png")
+
+ return data
+
+/obj/structure/safe/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ if(!ishuman(usr))
+ return
+ var/mob/living/carbon/human/user = usr
+ if(!user.canUseTopic(src, BE_CLOSE))
+ return
+
+ var/canhear = FALSE
+ if(user.is_holding_item_of_type(/obj/item/clothing/neck/stethoscope))
+ canhear = TRUE
+
+ switch(action)
+ if("open")
+ if(!check_unlocked() && !open && !broken)
+ to_chat(user, "You cannot open [src], as its lock is engaged!")
+ return
+ to_chat(user, "You [open ? "close" : "open"] [src].")
+ open = !open
+ update_icon()
+ return TRUE
+ if("turnright")
+ if(open)
+ return
+ if(broken)
+ to_chat(user, "The dial will not turn, as the mechanism is destroyed!")
+ return
+ var/ticks = text2num(params["num"])
+ for(var/i = 1 to ticks)
+ dial = WRAP(dial - 1, 0, 100)
+
+ var/invalid_turn = current_tumbler_index % 2 == 0 || current_tumbler_index > number_of_tumblers
+ if(invalid_turn) // The moment you turn the wrong way or go too far, the tumblers reset
+ current_tumbler_index = 1
+
+ if(!invalid_turn && dial == tumblers[current_tumbler_index])
+ notify_user(user, canhear, list("tink", "krink", "plink"), ticks, i)
+ current_tumbler_index++
+ else
+ notify_user(user, canhear, list("clack", "scrape", "clank"), ticks, i)
+ check_unlocked()
+ return TRUE
+ if("turnleft")
+ if(open)
+ return
+ if(broken)
+ to_chat(user, "The dial will not turn, as the mechanism is destroyed!")
+ return
+ var/ticks = text2num(params["num"])
+ for(var/i = 1 to ticks)
+ dial = WRAP(dial + 1, 0, 100)
+
+ var/invalid_turn = current_tumbler_index % 2 != 0 || current_tumbler_index > number_of_tumblers
+ if(invalid_turn) // The moment you turn the wrong way or go too far, the tumblers reset
+ current_tumbler_index = 1
+
+ if(!invalid_turn && dial == tumblers[current_tumbler_index])
+ notify_user(user, canhear, list("tonk", "krunk", "plunk"), ticks, i)
+ current_tumbler_index++
+ else
+ notify_user(user, canhear, list("click", "chink", "clink"), ticks, i)
+ check_unlocked()
+ return TRUE
+ if("retrieve")
+ if(!open)
+ return
+ var/index = text2num(params["index"])
+ if(!index)
+ return
+ var/obj/item/I = contents[index]
+ if(!I || !in_range(src, user))
+ return
+ user.put_in_hands(I)
+ space -= I.w_class
+ return TRUE
+
+/**
+ * Checks if safe is considered in a broken state for force-opening the safe
+ */
+/obj/structure/safe/proc/check_broken()
+ return broken || explosion_count >= BROKEN_THRESHOLD
+
+/**
+ * Called every dial turn to determine whether the safe should unlock or not.
+ */
+/obj/structure/safe/proc/check_unlocked()
+ if(check_broken())
+ return TRUE
+ if(current_tumbler_index > number_of_tumblers)
+ locked = FALSE
+ visible_message("[pick("Spring", "Sprang", "Sproing", "Clunk", "Krunk")]!")
+ return TRUE
+ locked = TRUE
+ return FALSE
+
+/**
+ * Called every dial turn to provide feedback if possible.
+ */
+/obj/structure/safe/proc/notify_user(user, canhear, sounds, total_ticks, current_tick)
+ if(!canhear)
+ return
+ if(current_tick == 2)
+ to_chat(user, "The sounds from [src] are too fast and blend together.")
+ if(total_ticks == 1 || prob(SOUND_CHANCE))
+ to_chat(user, "You hear a [pick(sounds)] from [src].")
//FLOOR SAFES
/obj/structure/safe/floor
@@ -199,13 +242,11 @@ FLOOR SAFES
level = 1 //underfloor
layer = LOW_OBJ_LAYER
-
/obj/structure/safe/floor/Initialize(mapload)
. = ..()
if(mapload)
var/turf/T = loc
hide(T.intact)
-
-/obj/structure/safe/floor/hide(var/intact)
- invisibility = intact ? INVISIBILITY_MAXIMUM : 0
+#undef SOUND_CHANCE
+#undef BROKEN_THRESHOLD
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 84702898ec..e5390bc457 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -1547,22 +1547,17 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
msg += "