diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm
index b9b7744744..92fd1ac820 100644
--- a/code/game/machinery/buttons.dm
+++ b/code/game/machinery/buttons.dm
@@ -189,6 +189,12 @@
..()
update_icon()
+/obj/machinery/button/vv_edit_var(vname, vval)
+ . = ..()
+ if(vname == NAMEOF(src, id))
+ var/obj/item/assembly/control/controller = device
+ if(istype(controller))
+ controller.id = vval
/obj/machinery/button/door
name = "door button"
@@ -285,3 +291,17 @@
icon_state = "button"
result_path = /obj/machinery/button
custom_materials = list(/datum/material/iron = MINERAL_MATERIAL_AMOUNT)
+
+/obj/machinery/button/elevator
+ name = "elevator button"
+ desc = "Go back. Go back. Go back. Can you operate the elevator."
+ icon_state = "launcher"
+ skin = "launcher"
+ device_type = /obj/item/assembly/control/elevator
+ req_access = list()
+ id = 1
+
+/obj/machinery/button/elevator/examine(mob/user)
+ . = ..()
+ . += "There's a small inscription on the button..."
+ . += "THIS CALLS THE ELEVATOR! IT DOES NOT OPERATE IT! Interact with the elevator itself to use it!"
diff --git a/code/game/objects/structures/industrial_lift.dm b/code/game/objects/structures/industrial_lift.dm
index 82af7e242a..9f04d1b710 100644
--- a/code/game/objects/structures/industrial_lift.dm
+++ b/code/game/objects/structures/industrial_lift.dm
@@ -1,3 +1,8 @@
+
+//Booleans in arguments are confusing, so I made them defines.
+#define LOCKED 1
+#define UNLOCKED 0
+
///Collect and command
/datum/lift_master
var/list/lift_platforms
@@ -43,18 +48,33 @@
possible_expansions |= lift_platform
possible_expansions -= borderline
-///Move all platforms together
+/**
+ * Moves the lift UP or DOWN, this is what users invoke with their hand.
+ * This is a SAFE proc, ensuring every part of the lift moves SANELY.
+ * It also locks controls for the (miniscule) duration of the movement, so the elevator cannot be broken by spamming.
+ * Arguments:
+ * going - UP or DOWN directions, where the lift should go. Keep in mind by this point checks of whether it should go up or down have already been done.
+ * user - Whomever made the lift movement.
+ */
/datum/lift_master/proc/MoveLift(going, mob/user)
+ set_controls(LOCKED)
for(var/p in lift_platforms)
var/obj/structure/industrial_lift/lift_platform = p
lift_platform.travel(going)
+ set_controls(UNLOCKED)
+/**
+ * Moves the lift, this is what users invoke with their hand.
+ * This is a SAFE proc, ensuring every part of the lift moves SANELY.
+ * It also locks controls for the (miniscule) duration of the movement, so the elevator cannot be broken by spamming.
+ */
/datum/lift_master/proc/MoveLiftHorizontal(going, z)
var/max_x = 1
var/max_y = 1
var/min_x = world.maxx
var/min_y = world.maxy
+ set_controls(LOCKED)
for(var/p in lift_platforms)
var/obj/structure/industrial_lift/lift_platform = p
max_x = max(max_x, lift_platform.x)
@@ -89,12 +109,14 @@
for(var/y in min_y to max_y)
var/obj/structure/industrial_lift/lift_platform = locate(/obj/structure/industrial_lift, locate(x, y, z))
lift_platform.travel(going)
+ set_controls(UNLOCKED)
///Check destination turfs
/datum/lift_master/proc/Check_lift_move(check_dir)
- for(var/lift_platform in lift_platforms)
+ for(var/l in lift_platforms)
+ var/obj/structure/industrial_lift/lift_platform = l
var/turf/T = get_step_multiz(lift_platform, check_dir)
- if(!T)
+ if(!T)//the edges of multi-z maps
return FALSE
if(check_dir == UP && !istype(T, /turf/open/openspace)) // We don't want to go through the ceiling!
return FALSE
@@ -102,6 +124,15 @@
return FALSE
return TRUE
+/**
+ * Sets all lift parts's controls_locked variable. Used to prevent moving mid movement, or cooldowns.
+ */
+/datum/lift_master/proc/set_controls(state)
+ for(var/l in lift_platforms)
+ var/obj/structure/industrial_lift/lift_platform = l
+ lift_platform.controls_locked = state
+
+GLOBAL_LIST_EMPTY(lifts)
/obj/structure/industrial_lift
name = "lift platform"
desc = "A lightweight lift platform. It moves up and down."
@@ -117,12 +148,18 @@
canSmoothWith = null
obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN
+ var/id = null //ONLY SET THIS TO ONE OF THE LIFT'S PARTS. THEY'RE CONNECTED! ONLY ONE NEEDS THE SIGNAL!
+ var/pass_through_floors = FALSE //if true, the elevator works through floors
+ var/controls_locked = FALSE //if true, the lift cannot be manually moved.
var/list/atom/movable/lift_load //things to move
var/datum/lift_master/lift_master_datum //control from
+/obj/structure/industrial_lift/New()
+ GLOB.lifts.Add(src)
+ ..()
+
/obj/structure/industrial_lift/Initialize(mapload)
. = ..()
-
RegisterSignal(src, COMSIG_MOVABLE_CROSSED, PROC_REF(AddItemOnLift))
RegisterSignal(loc, COMSIG_ATOM_CREATED, PROC_REF(AddItemOnLift))//For atoms created on platform
RegisterSignal(src, COMSIG_MOVABLE_UNCROSSED, PROC_REF(RemoveItemFromLift))
@@ -133,7 +170,7 @@
/obj/structure/industrial_lift/Move(atom/newloc, direct)
UnregisterSignal(loc, COMSIG_ATOM_CREATED)
. = ..()
- RegisterSignal(newloc, COMSIG_ATOM_CREATED, PROC_REF(AddItemOnLift))//For atoms created on platform
+ RegisterSignal(loc, COMSIG_ATOM_CREATED, PROC_REF(AddItemOnLift))//For atoms created on platform
/obj/structure/industrial_lift/proc/RemoveItemFromLift(datum/source, atom/movable/AM)
if(!(AM in lift_load))
@@ -162,51 +199,46 @@
destination = get_step_multiz(src, going)
else
destination = going
+ if(going == DOWN)//make sure this stays pre-item moving, or you'll crush anything on the lift under the lift.
+ for(var/mob/living/crushed in destination.contents)
+ to_chat(crushed, span_userdanger("You are crushed by [src]!"))
+ crushed.gib(FALSE,FALSE,FALSE)//the nicest kind of gibbing, keeping everything intact.
forceMove(destination)
for(var/am in things2move)
var/atom/movable/thing = am
thing.forceMove(destination)
/obj/structure/industrial_lift/proc/use(mob/user, is_ghost=FALSE)
- if (is_ghost && !in_range(src, user))
+ if(is_ghost && !in_range(src, user))
return
- var/static/list/tool_list = list(
- "Up" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH),
- "Down" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH)
- )
-
- var/turf/can_move_up = lift_master_datum.Check_lift_move(UP)
- var/turf/can_move_up_down = lift_master_datum.Check_lift_move(DOWN)
-
- if (!can_move_up && !can_move_up_down)
- to_chat(user, "[src] doesn't seem to able move anywhere!")
+ var/list/tool_list = list()
+ if(lift_master_datum.Check_lift_move(UP))
+ tool_list["Up"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH)
+ if(lift_master_datum.Check_lift_move(DOWN))
+ tool_list["Down"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH)
+ if(!length(tool_list))
+ to_chat(user, span_warning("[src] doesn't seem to able to move anywhere!"))
+ add_fingerprint(user)
+ return
+ if(controls_locked)
+ to_chat(user, span_warning("[src] has its controls locked! It must already be trying to do something!"))
add_fingerprint(user)
return
-
var/result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE)
- if (!is_ghost && !in_range(src, user))
+ if(!is_ghost && !in_range(src, user))
return // nice try
switch(result)
if("Up")
- if(can_move_up)
- lift_master_datum.MoveLift(UP, user)
- show_fluff_message(TRUE, user)
- use(user)
- else
- to_chat(user, "[src] doesn't seem to able move up!")
- use(user)
+ lift_master_datum.MoveLift(UP, user)
+ show_fluff_message(TRUE, user)
+ use(user)
if("Down")
- if(can_move_up_down)
- lift_master_datum.MoveLift(DOWN, user)
- show_fluff_message(FALSE, user)
- use(user)
- else
- to_chat(user, "[src] doesn't seem to able move down!")
- use(user)
+ lift_master_datum.MoveLift(DOWN, user)
+ show_fluff_message(FALSE, user)
+ use(user)
if("Cancel")
return
-
add_fingerprint(user)
/obj/structure/industrial_lift/proc/check_menu(mob/user)
@@ -217,6 +249,14 @@
/obj/structure/industrial_lift/on_attack_hand(mob/user)
return use(user)
+//ai probably shouldn't get to use lifts but they sure are great for admins to crush people with
+/obj/structure/industrial_lift/attack_ghost(mob/user)
+ . = ..()
+ if(.)
+ return
+ if(IsAdminGhost(user))
+ use(user)
+
/obj/structure/industrial_lift/attack_paw(mob/user)
return use(user)
@@ -229,11 +269,12 @@
/obj/structure/industrial_lift/proc/show_fluff_message(going_up, mob/user)
if(going_up)
- user.visible_message("[user] move lift up.", "Lift move up.")
+ user.visible_message(span_notice("[user] moves the lift upwards."), span_notice("You move the lift upwards."))
else
- user.visible_message("[user] move lift down.", "Lift move down.")
+ user.visible_message(span_notice("[user] moves the lift downwards."), span_notice("You move the lift downwards."))
/obj/structure/industrial_lift/Destroy()
+ GLOB.lifts.Remove(src)
QDEL_NULL(lift_master_datum)
var/list/border_lift_platforms = lift_platform_expansion()
moveToNullspace()
diff --git a/code/game/objects/structures/ladders.dm b/code/game/objects/structures/ladders.dm
index 9fefe0c676..7b6d1ff5c6 100644
--- a/code/game/objects/structures/ladders.dm
+++ b/code/game/objects/structures/ladders.dm
@@ -87,6 +87,12 @@
if(AM)
user.start_pulling(AM)
+ //reopening ladder radial menu ahead
+ T = get_turf(user)
+ var/obj/structure/ladder/ladder_structure = locate() in T
+ if (ladder_structure && (up && down))
+ ladder_structure.use(user)
+
/obj/structure/ladder/proc/use(mob/user, is_ghost=FALSE)
if (!is_ghost && !in_range(src, user))
return
diff --git a/code/modules/assembly/doorcontrol.dm b/code/modules/assembly/doorcontrol.dm
index cc4533f118..7e05f73770 100644
--- a/code/modules/assembly/doorcontrol.dm
+++ b/code/modules/assembly/doorcontrol.dm
@@ -20,9 +20,9 @@
/obj/item/assembly/control/examine(mob/user)
. = ..()
if(id && show_id)
- . += "Its channel ID is '[id]'."
+ . += span_notice("Its channel ID is '[id]'.")
if(can_change_id)
- . += "Use in hand to change ID."
+ . += span_notice("Use in hand to change ID.")
/obj/item/assembly/control/attack_self(mob/living/user)
. = ..()
@@ -165,3 +165,44 @@
/obj/item/assembly/control/electrochromatic/activate()
on = !on
do_electrochromatic_toggle(on, id)
+
+//how long it spends on each floor when moving somewhere, so it'd take 4 seconds to reach you if it had to travel up 2 floors
+#define FLOOR_TRAVEL_TIME 2 SECONDS
+/obj/item/assembly/control/elevator
+ name = "elevator controller"
+ desc = "A small device used to call elevators to the current floor."
+
+/obj/item/assembly/control/elevator/activate()
+ if(cooldown)
+ return
+ cooldown = TRUE
+ var/obj/structure/industrial_lift/lift
+ for(var/l in GLOB.lifts)
+ var/obj/structure/industrial_lift/possible_lift = l
+ if(possible_lift.id != id || possible_lift.z == z || possible_lift.controls_locked)
+ continue
+ lift = possible_lift
+ break
+ if(!lift)
+ addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 2 SECONDS)
+ return
+ lift.visible_message(span_notice("[src] clinks and whirrs into automated motion, locking controls."))
+ lift.lift_master_datum.set_controls(LOCKED)
+ ///The z level to which the elevator should travel
+ var/targetZ = (abs(loc.z)) //The target Z (where the elevator should move to) is not our z level (we are just some assembly in nullspace) but actually the Z level of whatever we are contained in (e.g. elevator button)
+ ///The amount of z levels between the our and targetZ
+ var/difference = abs(targetZ - lift.z)
+ ///Direction (up/down) needed to go to reach targetZ
+ var/direction = lift.z < targetZ ? UP : DOWN
+ ///How long it will/should take us to reach the target Z level
+ var/travel_duration = FLOOR_TRAVEL_TIME * difference //100 / 2 floors up = 50 seconds on every floor, will always reach destination in the same time
+ addtimer(VARSET_CALLBACK(src, cooldown, FALSE), travel_duration)
+ for(var/i in 1 to difference)
+ sleep(FLOOR_TRAVEL_TIME)//hey this should be alright... right?
+ if(QDELETED(lift) || QDELETED(src))//elevator control or button gone = don't go up anymore
+ return
+ lift.lift_master_datum.MoveLift(direction, null)
+ lift.visible_message(span_notice("[src] clicks, ready to be manually operated again."))
+ lift.lift_master_datum.set_controls(UNLOCKED)
+
+#undef FLOOR_TRAVEL_TIME