mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-28 07:38:05 +01:00
[MIRROR] Adds support for "realistic" public elevators (elevator doors, elevator panel, etc) [MDB IGNORE] (#15515)
* Adds support for "realistic" public elevators (elevator doors, elevator panel, etc) (#68888) * Elevators are a bit more friendly part 1 - Emaggable elevator buttons / support for emaggable device assemblies - Delayed travel between floors - Elevators can now show a warning sign to areas below while travelling - Elevators can now optionally wound heavily instead of gibbing * Adds supoprt for "lift doors" * Comment tweak * Splitting these variables * Functional prototype * multiz debug * Elevator button framework * Unifies these behaviors * Emergency doors * Lift button framework * UI closes on floor change * Testing changes * Fix * UI Tweaks * Panel works pretty swell * Minor tweaks * Move to static * Bonus tram change * User experience * Slight tweak to mapload stuff * This is silly * Some UI tweaks, need to update css * CSS and ui overhaul * Documentation updates. * Multi-z lift support * Multitile lift fix * Adds support for "realistic" public elevators (elevator doors, elevator panel, etc) Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
This commit is contained in:
+373
-179
File diff suppressed because it is too large
Load Diff
@@ -405,7 +405,7 @@
|
||||
///from base of obj/item/attack_qdeleted(): (atom/target, mob/user, params)
|
||||
#define COMSIG_ITEM_ATTACK_QDELETED "item_attack_qdeleted"
|
||||
|
||||
///from /obj/item/assembly/proc/pulsed()
|
||||
///from /obj/item/assembly/proc/pulsed(mob/pulser)
|
||||
#define COMSIG_ASSEMBLY_PULSED "assembly_pulsed"
|
||||
|
||||
///from base of /obj/item/mmi/set_brainmob(): (mob/living/brain/new_brainmob)
|
||||
|
||||
@@ -18,3 +18,7 @@
|
||||
#define MAIN_STATION_TRAM "main station tram"
|
||||
///the specific_lift_id of the tram on the hilbert research station
|
||||
#define HILBERT_TRAM "tram_hilbert"
|
||||
|
||||
// Defines for update_lift_doors
|
||||
#define OPEN_DOORS "open"
|
||||
#define CLOSE_DOORS "close"
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
/obj/effect/temp_visual/telegraphing/vending_machine_tilt
|
||||
duration = 1 SECONDS
|
||||
|
||||
/obj/effect/temp_visual/telegraphing/lift_travel
|
||||
|
||||
/obj/effect/temp_visual/telegraphing/lift_travel/Initialize(mapload, duration)
|
||||
src.duration = duration
|
||||
return ..()
|
||||
|
||||
/obj/effect/temp_visual/telegraphing/thunderbolt
|
||||
icon = 'icons/mob/telegraphing/telegraph.dmi'
|
||||
icon_state = "target_circle"
|
||||
|
||||
@@ -230,12 +230,13 @@
|
||||
var/list/stored_decals = list()
|
||||
|
||||
|
||||
/obj/item/assembly/trapdoor/pulsed(radio)
|
||||
/obj/item/assembly/trapdoor/pulsed(radio, mob/pulser)
|
||||
. = ..()
|
||||
if(linked)
|
||||
return
|
||||
if(!COOLDOWN_FINISHED(src, search_cooldown))
|
||||
visible_message(span_warning("[src] cannot attempt another trapdoor linkup so soon!"))
|
||||
if(loc && pulser)
|
||||
loc.balloon_alert(pulser, "linking on cooldown!")
|
||||
return
|
||||
attempt_link_up()
|
||||
COOLDOWN_START(src, search_cooldown, search_cooldown_time)
|
||||
@@ -305,23 +306,33 @@
|
||||
/obj/item/trapdoor_remote/attack_self(mob/user, modifiers)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
return TRUE
|
||||
|
||||
if(!internals)
|
||||
to_chat(user, span_warning("[src] has no internals!"))
|
||||
return
|
||||
user.balloon_alert(user, "no device!")
|
||||
return TRUE
|
||||
|
||||
if(!internals.linked)
|
||||
to_chat(user, span_notice("You activate [src]."))
|
||||
internals.pulsed()
|
||||
return
|
||||
internals.pulsed(pulser = user)
|
||||
// The pulse linked successfully
|
||||
if(internals.linked)
|
||||
user.balloon_alert(user, "linked")
|
||||
// The pulse failed to link
|
||||
else
|
||||
user.balloon_alert(user, "link failed!")
|
||||
return TRUE
|
||||
|
||||
if(!COOLDOWN_FINISHED(src, trapdoor_cooldown))
|
||||
to_chat(user, span_warning("[src] is on a short cooldown."))
|
||||
return
|
||||
to_chat(user, span_notice("You activate [src]."))
|
||||
user.balloon_alert(user, "on cooldown!")
|
||||
return TRUE
|
||||
|
||||
user.balloon_alert(user, "trapdoor triggered")
|
||||
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
|
||||
icon_state = "trapdoor_pressed"
|
||||
addtimer(VARSET_CALLBACK(src, icon_state, initial(icon_state)), trapdoor_cooldown_time)
|
||||
COOLDOWN_START(src, trapdoor_cooldown, trapdoor_cooldown_time)
|
||||
internals.pulsed()
|
||||
internals.pulsed(pulser = user)
|
||||
return TRUE
|
||||
|
||||
#undef TRAPDOOR_LINKING_SEARCH_RANGE
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@
|
||||
return ..()
|
||||
|
||||
/obj/machinery/button/emag_act(mob/user)
|
||||
. = ..()
|
||||
if(obj_flags & EMAGGED)
|
||||
return
|
||||
req_access = list()
|
||||
@@ -114,6 +115,12 @@
|
||||
playsound(src, SFX_SPARKS, 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
|
||||
obj_flags |= EMAGGED
|
||||
|
||||
// The device inside can be emagged by swiping the button
|
||||
// returning TRUE will prevent feedback (so we can do our own)
|
||||
if(device?.emag_act(user))
|
||||
return
|
||||
balloon_alert(user, "access overridden")
|
||||
|
||||
/obj/machinery/button/attack_ai(mob/user)
|
||||
if(!silicon_access_disabled && !panel_open)
|
||||
return attack_hand(user)
|
||||
@@ -175,7 +182,7 @@
|
||||
icon_state = "[skin]1"
|
||||
|
||||
if(device)
|
||||
device.pulsed()
|
||||
device.pulsed(pulser = user)
|
||||
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_BUTTON_PRESSED,src)
|
||||
|
||||
addtimer(CALLBACK(src, /atom/.proc/update_appearance), 15)
|
||||
@@ -309,20 +316,6 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/button/door, 24)
|
||||
custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT)
|
||||
pixel_shift = 24
|
||||
|
||||
/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)
|
||||
. = ..()
|
||||
. += span_notice("There's a small inscription on the button...")
|
||||
. += span_notice("THIS CALLS THE ELEVATOR! IT DOES NOT OPERATE IT! Interact with the elevator itself to use it!")
|
||||
|
||||
/obj/machinery/button/tram
|
||||
name = "tram caller"
|
||||
desc = "A button for calling the tram. It has a speakerbox in it with some internals."
|
||||
|
||||
@@ -63,11 +63,11 @@
|
||||
return TRUE
|
||||
|
||||
///Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs
|
||||
/obj/item/assembly/proc/pulsed(radio = FALSE)
|
||||
/obj/item/assembly/proc/pulsed(radio = FALSE, mob/pulser)
|
||||
if(wire_type & WIRE_RECEIVE)
|
||||
INVOKE_ASYNC(src, .proc/activate)
|
||||
INVOKE_ASYNC(src, .proc/activate, pulser)
|
||||
if(radio && (wire_type & WIRE_RADIO_RECEIVE))
|
||||
INVOKE_ASYNC(src, .proc/activate)
|
||||
INVOKE_ASYNC(src, .proc/activate, pulser)
|
||||
SEND_SIGNAL(src, COMSIG_ASSEMBLY_PULSED)
|
||||
return TRUE
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
return TRUE
|
||||
|
||||
/// What the device does when turned on
|
||||
/obj/item/assembly/proc/activate()
|
||||
/obj/item/assembly/proc/activate(mob/activator)
|
||||
if(QDELETED(src) || !secured || (next_activate > world.time))
|
||||
return FALSE
|
||||
next_activate = world.time + 30
|
||||
|
||||
@@ -175,59 +175,6 @@
|
||||
|
||||
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 50)
|
||||
|
||||
//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/datum/lift_master/lift
|
||||
for(var/datum/lift_master/possible_match as anything in GLOB.active_lifts_by_type[BASIC_LIFT_ID])
|
||||
if(possible_match.specific_lift_id != id || !check_z(possible_match) || possible_match.controls_locked)
|
||||
continue
|
||||
|
||||
lift = possible_match
|
||||
break
|
||||
|
||||
if(!lift)
|
||||
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 2 SECONDS)
|
||||
return
|
||||
|
||||
var/obj/structure/industrial_lift/target = lift.lift_platforms[1]
|
||||
var/target_z = target.z
|
||||
|
||||
lift.set_controls(LIFT_PLATFORM_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 - target_z)
|
||||
///Direction (up/down) needed to go to reach targetZ
|
||||
var/direction = target_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.MoveLift(direction, null)
|
||||
lift.set_controls(LIFT_PLATFORM_UNLOCKED)
|
||||
|
||||
///check if any of the lift platforms are already here
|
||||
/obj/item/assembly/control/elevator/proc/check_z(datum/lift_master/lift)
|
||||
for(var/obj/structure/industrial_lift/platform as anything in lift.lift_platforms)
|
||||
if(platform.z == loc.z)
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
#undef FLOOR_TRAVEL_TIME
|
||||
|
||||
/obj/item/assembly/control/tram
|
||||
name = "tram call button"
|
||||
desc = "A small device used to bring trams to you."
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/obj/item/assembly/control/elevator
|
||||
name = "elevator controller"
|
||||
desc = "A small device used to call elevators to the current floor."
|
||||
/// A weakref to the lift_master datum we control
|
||||
var/datum/weakref/lift_weakref
|
||||
|
||||
/obj/item/assembly/control/elevator/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
if(mapload)
|
||||
return INITIALIZE_HINT_LATELOAD
|
||||
|
||||
var/datum/lift_master/lift = get_lift()
|
||||
if(!lift)
|
||||
return
|
||||
|
||||
lift_weakref = WEAKREF(lift)
|
||||
|
||||
/obj/item/assembly/control/elevator/LateInitialize()
|
||||
var/datum/lift_master/lift = get_lift()
|
||||
if(!lift)
|
||||
log_mapping("Elevator call button at [AREACOORD(src)] found no associated lift to link with, this may be a mapping error.")
|
||||
return
|
||||
|
||||
lift_weakref = WEAKREF(lift)
|
||||
|
||||
// Emagging elevator buttons will disable safeties
|
||||
/obj/item/assembly/control/elevator/emag_act(mob/user, obj/item/card/emag/emag_card)
|
||||
if(obj_flags & EMAGGED)
|
||||
return
|
||||
|
||||
var/datum/lift_master/lift = lift_weakref?.resolve()
|
||||
if(!lift)
|
||||
return
|
||||
|
||||
for(var/obj/structure/industrial_lift/lift_platform as anything in lift.lift_platforms)
|
||||
lift_platform.violent_landing = TRUE
|
||||
lift_platform.warns_on_down_movement = FALSE
|
||||
lift_platform.elevator_vertical_speed = initial(lift_platform.elevator_vertical_speed) * 0.5
|
||||
|
||||
// Note that we can either be emagged by having the button we are inside swiped,
|
||||
// or by someone emagging the assembly directly after removing it (to be cheeky)
|
||||
var/atom/balloon_alert_loc = get(src, /obj/machinery/button) || src
|
||||
balloon_alert_loc.balloon_alert(user, "safeties overridden")
|
||||
obj_flags |= EMAGGED
|
||||
return TRUE
|
||||
|
||||
// Multitooling emagged elevator buttons will fix the safeties
|
||||
/obj/item/assembly/control/elevator/multitool_act(mob/living/user)
|
||||
if(!(obj_flags & EMAGGED))
|
||||
return ..()
|
||||
|
||||
var/datum/lift_master/lift = lift_weakref?.resolve()
|
||||
if(!lift)
|
||||
return ..()
|
||||
|
||||
for(var/obj/structure/industrial_lift/lift_platform as anything in lift.lift_platforms)
|
||||
lift_platform.violent_landing = initial(lift_platform.violent_landing)
|
||||
lift_platform.warns_on_down_movement = initial(lift_platform.warns_on_down_movement)
|
||||
lift_platform.elevator_vertical_speed = initial(lift_platform.elevator_vertical_speed)
|
||||
|
||||
// We can only be multitooled directly so just throw up the balloon alert
|
||||
balloon_alert(user, "safeties reset")
|
||||
obj_flags &= ~EMAGGED
|
||||
return TRUE
|
||||
|
||||
/obj/item/assembly/control/elevator/activate(mob/activator)
|
||||
if(cooldown)
|
||||
return
|
||||
|
||||
cooldown = TRUE
|
||||
// Actually try to call the elevator - this sleeps.
|
||||
// If we failed to call it, play a buzz sound.
|
||||
if(!call_elevator(activator))
|
||||
playsound(loc, 'sound/machines/buzz-two.ogg', 50, TRUE)
|
||||
|
||||
// Finally, give people a chance to get off after it's done before going back off cooldown
|
||||
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 2 SECONDS)
|
||||
|
||||
/// Actually calls the elevator.
|
||||
/// Returns FALSE if we failed to setup the move.
|
||||
/// Returns TRUE if the move setup was a success, EVEN IF the move itself fails afterwards
|
||||
/obj/item/assembly/control/elevator/proc/call_elevator(mob/activator)
|
||||
// We can't call an elevator that doesn't exist
|
||||
var/datum/lift_master/lift = lift_weakref?.resolve()
|
||||
if(!lift)
|
||||
loc.balloon_alert(activator, "no elevator connected!")
|
||||
return FALSE
|
||||
|
||||
// We can't call an elevator that's moving. You may say "you totally can do that", but that's not modelled
|
||||
if(lift.controls_locked == LIFT_PLATFORM_LOCKED)
|
||||
loc.balloon_alert(activator, "elevator is moving!")
|
||||
return FALSE
|
||||
|
||||
// We can't call an elevator if it's already at this destination
|
||||
var/obj/structure/industrial_lift/prime_lift = lift.return_closest_platform_to_z(loc.z)
|
||||
if(prime_lift.z == loc.z)
|
||||
loc.balloon_alert(activator, "elevator is here!")
|
||||
return FALSE
|
||||
|
||||
// At this point, we can start moving.
|
||||
|
||||
// Give the user, if supplied, a balloon alert.
|
||||
if(activator)
|
||||
loc.balloon_alert(activator, "elevator called")
|
||||
|
||||
// Actually try to move the lift. This will sleep.
|
||||
if(!lift.move_to_zlevel(loc.z, CALLBACK(src, .proc/check_button)))
|
||||
loc.balloon_alert(activator, "elevator out of service!")
|
||||
return FALSE
|
||||
|
||||
// From here on all returns are TRUE, as we successfully moved the lift, even if we maybe didn't reach our floor
|
||||
|
||||
// Our button was destroyed mid transit.
|
||||
if(!check_button())
|
||||
return TRUE
|
||||
|
||||
// Our lift platform survived, but it didn't reach our landing z.
|
||||
if(!QDELETED(prime_lift) && prime_lift.z != loc.z)
|
||||
if(!QDELETED(activator))
|
||||
loc.balloon_alert(activator, "elevator out of service!")
|
||||
playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
|
||||
return TRUE
|
||||
|
||||
// Everything went according to plan
|
||||
playsound(loc, 'sound/machines/ping.ogg', 50, TRUE)
|
||||
if(!QDELETED(activator))
|
||||
loc.balloon_alert(activator, "elevator arrived")
|
||||
|
||||
return TRUE
|
||||
|
||||
/// Callback for move_to_zlevel / general proc to check if we're still in a button
|
||||
/obj/item/assembly/control/elevator/proc/check_button()
|
||||
if(QDELETED(src))
|
||||
return FALSE
|
||||
if(!istype(loc, /obj/machinery/button))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/// Gets the lift associated with our assembly / button
|
||||
/obj/item/assembly/control/elevator/proc/get_lift()
|
||||
for(var/datum/lift_master/possible_match as anything in GLOB.active_lifts_by_type[BASIC_LIFT_ID])
|
||||
if(possible_match.specific_lift_id != id)
|
||||
continue
|
||||
|
||||
return possible_match
|
||||
|
||||
return null
|
||||
|
||||
/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/Initialize(mapload, ndir, built)
|
||||
. = ..()
|
||||
// Kind of a cop-out
|
||||
AddElement(/datum/element/contextual_screentip_bare_hands, lmb_text = "Call Elevator")
|
||||
|
||||
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/button/elevator, 28)
|
||||
@@ -0,0 +1,361 @@
|
||||
/**
|
||||
* # Elevator control panel
|
||||
*
|
||||
* A wallmounted simple machine that controls elevators,
|
||||
* allowing users to enter a UI to move it up or down
|
||||
*
|
||||
* These can be placed in two methods:
|
||||
* - You can place the control panel on the same turf as a lift. It will move up and down with the lift
|
||||
* - You can place the control panel to the side of a lift, NOT attached to the lift. It will remain in position
|
||||
* I don't recommend using both methods on the same elevator, as it might result in some jank, but it's functional.
|
||||
*/
|
||||
/obj/machinery/elevator_control_panel
|
||||
name = "elevator panel"
|
||||
// Fire alarm reference.
|
||||
desc = "<i>\"In case of emergency, please use the stairs.\"</i> Thus, always use the stairs."
|
||||
density = FALSE
|
||||
|
||||
icon = 'icons/obj/airlock_machines.dmi'
|
||||
icon_state = "airlock_control_standby"
|
||||
base_icon_state = "airlock_control"
|
||||
|
||||
power_channel = AREA_USAGE_ENVIRON
|
||||
// Indestructible until someone wants to make these constructible, with all the chaos that implies
|
||||
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
|
||||
|
||||
/// Were we instantiated at mapload? Used to determine when we should link / throw errors
|
||||
var/maploaded = FALSE
|
||||
|
||||
/// A weakref to the lift_master datum we control
|
||||
var/datum/weakref/lift_weakref
|
||||
/// What specific_lift_id do we link with?
|
||||
var/linked_elevator_id
|
||||
|
||||
/// A list of all possible destinations this elevator can travel.
|
||||
/// Assoc list of "Floor name" to "z level of destination".
|
||||
/// By default the floor names will auto-generate ("Floor 1", "Floor 2", etc).
|
||||
var/list/linked_elevator_destination
|
||||
/// If you want to override what each floor is named as, you can do so with this list.
|
||||
/// Make this an assoc list of "z level you want to rename" to "desired name".
|
||||
/// So, if you want the z-level 2 destination to be named "Cargo", you would do list("2" = "Cargo").
|
||||
var/list/preset_destination_names
|
||||
|
||||
/// What z-level did we move to last? Used for showing the user in the UI which direction we're moving.
|
||||
var/last_move_target
|
||||
/// TimerID to our door reset timer, made by emergency opening doors
|
||||
var/door_reset_timerid
|
||||
|
||||
/obj/machinery/elevator_control_panel/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
var/static/list/tool_behaviors = list(
|
||||
TOOL_MULTITOOL = list(SCREENTIP_CONTEXT_LMB = "Reset Panel"),
|
||||
)
|
||||
AddElement(/datum/element/contextual_screentip_tools, tool_behaviors)
|
||||
AddElement(/datum/element/contextual_screentip_bare_hands, lmb_text = "Send Elevator")
|
||||
|
||||
// Machinery returns lateload by default via parent,
|
||||
// this is just here for redundancy's sake.
|
||||
. = INITIALIZE_HINT_LATELOAD
|
||||
|
||||
maploaded = mapload
|
||||
// Maploaded panels link in LateInitialize...
|
||||
if(mapload)
|
||||
return
|
||||
|
||||
// And non-mapload panels link in Initialize
|
||||
var/datum/lift_master/lift = get_associated_lift()
|
||||
if(!lift)
|
||||
return
|
||||
|
||||
lift_weakref = WEAKREF(lift)
|
||||
populate_destinations_list(lift)
|
||||
|
||||
/obj/machinery/elevator_control_panel/LateInitialize()
|
||||
. = ..()
|
||||
// If we weren't maploaded, we probably already linked (or tried to link) in Initialize().
|
||||
if(!maploaded)
|
||||
return
|
||||
|
||||
// This is exclusively for linking in mapload, just to ensure all elevator parts are created,
|
||||
// and also so we can throw mapping errors to let people know if they messed up setup.
|
||||
var/datum/lift_master/lift = get_associated_lift()
|
||||
if(!lift)
|
||||
log_mapping("Elevator control panel at [AREACOORD(src)] found no associated lift to link with, this may be a mapping error.")
|
||||
return
|
||||
|
||||
lift_weakref = WEAKREF(lift)
|
||||
populate_destinations_list(lift)
|
||||
|
||||
/obj/machinery/elevator_control_panel/emag_act(mob/user, obj/item/card/emag/emag_card)
|
||||
if(obj_flags & EMAGGED)
|
||||
return
|
||||
|
||||
var/datum/lift_master/lift = lift_weakref?.resolve()
|
||||
if(!lift)
|
||||
return
|
||||
|
||||
for(var/obj/structure/industrial_lift/lift_platform as anything in lift.lift_platforms)
|
||||
lift_platform.violent_landing = TRUE
|
||||
lift_platform.warns_on_down_movement = FALSE
|
||||
lift_platform.elevator_vertical_speed = initial(lift_platform.elevator_vertical_speed) * 0.5
|
||||
|
||||
playsound(src, SFX_SPARKS, 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
|
||||
balloon_alert(user, "safeties overridden")
|
||||
obj_flags |= EMAGGED
|
||||
|
||||
/obj/machinery/elevator_control_panel/multitool_act(mob/living/user)
|
||||
var/datum/lift_master/lift = lift_weakref?.resolve()
|
||||
if(!lift)
|
||||
return
|
||||
|
||||
balloon_alert(user, "resetting panel...")
|
||||
playsound(src, 'sound/machines/locktoggle.ogg', 50, TRUE)
|
||||
if(!do_after(user, 6 SECONDS, src))
|
||||
balloon_alert(user, "interrupted!")
|
||||
return TRUE
|
||||
|
||||
if(QDELETED(lift) || !length(lift.lift_platforms))
|
||||
return
|
||||
|
||||
// If we were emagged, reset us
|
||||
if(obj_flags & EMAGGED)
|
||||
for(var/obj/structure/industrial_lift/lift_platform as anything in lift.lift_platforms)
|
||||
lift_platform.violent_landing = initial(lift_platform.violent_landing)
|
||||
lift_platform.warns_on_down_movement = initial(lift_platform.warns_on_down_movement)
|
||||
lift_platform.elevator_vertical_speed = initial(lift_platform.elevator_vertical_speed)
|
||||
|
||||
obj_flags &= ~EMAGGED
|
||||
|
||||
// If we had doors open, stop the timer and reset them
|
||||
if(door_reset_timerid)
|
||||
deltimer(door_reset_timerid)
|
||||
reset_doors()
|
||||
|
||||
// Be vague about whether something was accomplished or not
|
||||
balloon_alert(user, "panel reset")
|
||||
playsound(src, 'sound/machines/locktoggle.ogg', 50, TRUE)
|
||||
|
||||
return TRUE
|
||||
|
||||
/// Find the elevator associated with our lift button.
|
||||
/obj/machinery/elevator_control_panel/proc/get_associated_lift()
|
||||
for(var/datum/lift_master/possible_match as anything in GLOB.active_lifts_by_type[BASIC_LIFT_ID])
|
||||
if(possible_match.specific_lift_id != linked_elevator_id)
|
||||
continue
|
||||
|
||||
return possible_match
|
||||
|
||||
return null
|
||||
|
||||
/// Goes through and populates the linked_elevator_destination list with all possible destinations the lift can go.
|
||||
/obj/machinery/elevator_control_panel/proc/populate_destinations_list(datum/lift_master/linked_lift)
|
||||
// This list will track all the raw z-levels which we found that we can travel to
|
||||
var/list/raw_destinations = list()
|
||||
|
||||
// Get a list of all the starting locs our elevator starts at
|
||||
var/list/starting_locs = list()
|
||||
for(var/obj/structure/industrial_lift/lift_piece as anything in linked_lift.lift_platforms)
|
||||
starting_locs |= lift_piece.locs
|
||||
// The raw destination list will start with all the z's we start at
|
||||
raw_destinations |= lift_piece.z
|
||||
|
||||
// Get all destinations below us
|
||||
add_destinations_in_a_direction_recursively(starting_locs, DOWN, raw_destinations)
|
||||
// Get all destinations above us
|
||||
add_destinations_in_a_direction_recursively(starting_locs, UP, raw_destinations)
|
||||
|
||||
linked_elevator_destination = list()
|
||||
for(var/z_level in raw_destinations)
|
||||
// Check if this z-level has a preset destination associated.
|
||||
var/preset_name = preset_destination_names?[num2text(z_level)]
|
||||
// If we don't have a preset name, use Floor z-1 for the title.
|
||||
// z - 1 is used because the station z-level is 2, and goes up.
|
||||
linked_elevator_destination["[z_level]"] = preset_name || "Floor [z_level - 1]"
|
||||
|
||||
// Reverse the destination list.
|
||||
// By this point the list will go from bottom floor to top floor,
|
||||
// which is unintuitive when passed to the UI to show to users.
|
||||
// This way we have the top floors at the top, and the bottom floors the bottom.
|
||||
reverse_range(linked_elevator_destination)
|
||||
update_static_data_for_all_viewers()
|
||||
|
||||
/**
|
||||
* Recursively adds destinations to the list of linked_elevator_destination
|
||||
* until it fails to find a valid stopping point in the passed direction.
|
||||
*/
|
||||
/obj/machinery/elevator_control_panel/proc/add_destinations_in_a_direction_recursively(list/turfs_to_check, direction, list/destinations)
|
||||
// Only vertical elevators are supported - use trams for horizontal ones.
|
||||
if(direction != UP && direction != DOWN)
|
||||
CRASH("[type] was given an invalid direction in add_destinations_in_a_direction_recursively!")
|
||||
|
||||
var/list/turf/checked_turfs = list()
|
||||
// Go through every turf passed in our list of turfs to check.
|
||||
for(var/turf/place in turfs_to_check)
|
||||
// If the place we're checking isn't openspace, then we can't go downwards
|
||||
if(direction == DOWN && !istype(place, /turf/open/openspace))
|
||||
return
|
||||
|
||||
// Check the turf at the next level (either above or below the place we're checking)
|
||||
var/turf/next_level = get_step_multiz(place, direction)
|
||||
// No turf = at the edge of a map vertically
|
||||
if(!next_level)
|
||||
return
|
||||
// If the next level above us has a roof, we can't move up
|
||||
if(direction == UP && !istype(next_level, /turf/open/openspace))
|
||||
return
|
||||
|
||||
// Otherwise, we can feasibly move our direction with this turf
|
||||
checked_turfs += next_level
|
||||
|
||||
// If we somehow found no turfs, BUT made it this far, something definitely went wrong.
|
||||
if(!length(checked_turfs))
|
||||
CRASH("[type] found no turfs in add_destinations_in_a_direction_recursively!")
|
||||
|
||||
// Add the Zs of all the found turfs as possible destinations
|
||||
for(var/turf/found as anything in checked_turfs)
|
||||
// We check all turfs we found in case of multi-z lift memes.
|
||||
destinations |= found.z
|
||||
|
||||
// And recursively call the proc with all the turfs we found on the next level
|
||||
add_destinations_in_a_direction_recursively(checked_turfs, direction, destinations)
|
||||
|
||||
/obj/machinery/elevator_control_panel/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "ElevatorPanel", name)
|
||||
ui.open()
|
||||
|
||||
/obj/machinery/elevator_control_panel/ui_status(mob/user)
|
||||
// We moved up a z-level, probably via the lift itself, so don't preserve the UI.
|
||||
if(user.z != z)
|
||||
return UI_CLOSE
|
||||
|
||||
// Our lift is gone entirely - look, but don't touch.
|
||||
if(!lift_weakref?.resolve())
|
||||
return UI_UPDATE
|
||||
|
||||
// We're non-functional - don't do anything.
|
||||
if(!check_panel())
|
||||
return UI_DISABLED
|
||||
|
||||
// Otherwise, just check default state (is the user conscious and close, etc).
|
||||
return ..()
|
||||
|
||||
/obj/machinery/elevator_control_panel/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["emergency_level"] = capitalize(SSsecurity_level.get_current_level_as_text())
|
||||
data["is_emergency"] = SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED
|
||||
data["doors_open"] = !!door_reset_timerid
|
||||
|
||||
var/datum/lift_master/lift = lift_weakref?.resolve()
|
||||
if(lift)
|
||||
data["lift_exists"] = TRUE
|
||||
data["currently_moving"] = lift.controls_locked == LIFT_PLATFORM_LOCKED
|
||||
data["currently_moving_to_floor"] = last_move_target
|
||||
data["current_floor"] = lift.lift_platforms[1].z
|
||||
|
||||
else
|
||||
data["lift_exists"] = FALSE
|
||||
data["currently_moving"] = FALSE
|
||||
data["current_floor"] = 0 // 0 shows up as "Floor -1" in the UI, which is fine for what it is
|
||||
|
||||
return data
|
||||
|
||||
/obj/machinery/elevator_control_panel/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["all_floor_data"] = list()
|
||||
for(var/destination in linked_elevator_destination)
|
||||
data["all_floor_data"] += list(list(
|
||||
"name" = linked_elevator_destination[destination],
|
||||
"z_level" = text2num(destination),
|
||||
))
|
||||
|
||||
return data
|
||||
|
||||
/obj/machinery/elevator_control_panel/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
if(!check_panel())
|
||||
return TRUE // We shouldn't be usable right now, update UI
|
||||
|
||||
switch(action)
|
||||
if("move_lift")
|
||||
if(!allowed(usr))
|
||||
balloon_alert(usr, "access denied!")
|
||||
return
|
||||
|
||||
var/desired_z = params["z"]
|
||||
// num2text here is required as the z is stored as strings in the list, but passed here as a number.
|
||||
if(!(num2text(desired_z) in linked_elevator_destination))
|
||||
return TRUE // Something is inaccurate, update UI
|
||||
|
||||
var/datum/lift_master/lift = lift_weakref?.resolve()
|
||||
if(!lift || lift.controls_locked == LIFT_PLATFORM_LOCKED)
|
||||
return TRUE // We shouldn't be moving anything, update UI
|
||||
|
||||
INVOKE_ASYNC(lift, /datum/lift_master.proc/move_to_zlevel, desired_z, CALLBACK(src, .proc/check_panel), usr)
|
||||
last_move_target = desired_z
|
||||
return TRUE // Succcessfully initiated a move. Regardless of whether it actually works, update the UI
|
||||
|
||||
if("emergency_door")
|
||||
var/datum/lift_master/lift = lift_weakref?.resolve()
|
||||
if(!lift)
|
||||
return TRUE // Something is wrong, update UI
|
||||
|
||||
// The emergency door button is only available at red alert or higher.
|
||||
// This is so people don't keep it in emergency mode 100% of the time.
|
||||
if(SSsecurity_level.get_current_level_as_number() < SEC_LEVEL_RED)
|
||||
return TRUE // The security level might have been lowered since last update, so update UI
|
||||
|
||||
// Open all lift doors, it's an emergency dang it!
|
||||
lift.update_lift_doors(action = OPEN_DOORS)
|
||||
door_reset_timerid = addtimer(CALLBACK(src, .proc/reset_doors), 3 MINUTES, TIMER_UNIQUE|TIMER_STOPPABLE)
|
||||
return TRUE // We opened up all the doors, update the UI so the emergency button is replaced correctly
|
||||
|
||||
if("reset_doors")
|
||||
if(!door_reset_timerid)
|
||||
return TRUE // All the doors were shut since last update, so update UI
|
||||
|
||||
deltimer(door_reset_timerid)
|
||||
reset_doors()
|
||||
return TRUE // We closed all the doors, update the UI so the door button is replaced correctly
|
||||
|
||||
/// Callback for move_to_zlevel to ensure the lift can continue to move.
|
||||
/obj/machinery/elevator_control_panel/proc/check_panel()
|
||||
if(QDELETED(src))
|
||||
return FALSE
|
||||
if(machine_stat & (NOPOWER|BROKEN))
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/// Helper proc to go through all of our desetinations and reset all elevator doors,
|
||||
/// closing doors on z-levels the lift is away from, and opening doors on the z the lift is
|
||||
/obj/machinery/elevator_control_panel/proc/reset_doors()
|
||||
var/datum/lift_master/lift = lift_weakref?.resolve()
|
||||
if(!lift)
|
||||
return
|
||||
|
||||
var/list/zs_we_are_present_on = lift.get_zs_we_are_on()
|
||||
var/list/zs_we_are_absent = list()
|
||||
|
||||
for(var/z_level in linked_elevator_destination)
|
||||
z_level = text2num(z_level) // Stored as strings.
|
||||
if(z_level in zs_we_are_present_on)
|
||||
continue
|
||||
|
||||
zs_we_are_absent |= z_level
|
||||
|
||||
// Open all the doors on the zs we should be open on,
|
||||
// and close all doors we aren't on. Simple enough.
|
||||
lift.update_lift_doors(zs_we_are_present_on, action = OPEN_DOORS)
|
||||
lift.update_lift_doors(zs_we_are_absent, action = CLOSE_DOORS)
|
||||
|
||||
door_reset_timerid = null
|
||||
|
||||
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/elevator_control_panel, 30)
|
||||
@@ -51,10 +51,26 @@ GLOBAL_LIST_EMPTY(lifts)
|
||||
///if TRUE, this platform will late initialize and then expand to become a multitile object across all other linked platforms on this z level
|
||||
var/create_multitile_platform = FALSE
|
||||
|
||||
/// Does our elevator warn people (with visual effects) when moving down?
|
||||
var/warns_on_down_movement = FALSE
|
||||
/// if TRUE, we will gib anyone we land on top of. if FALSE, we will just apply damage with a serious wound penalty.
|
||||
var/violent_landing = TRUE
|
||||
/// How long does it take for the elevator to move vertically?
|
||||
var/elevator_vertical_speed = 2 SECONDS
|
||||
|
||||
/// We use a radial to travel primarily, instead of a button / ui
|
||||
var/radial_travel = TRUE
|
||||
/// A lazylist of REFs to all mobs which have a radial open currently
|
||||
var/list/current_operators
|
||||
|
||||
/obj/structure/industrial_lift/Initialize(mapload)
|
||||
. = ..()
|
||||
GLOB.lifts.Add(src)
|
||||
|
||||
// Yes if it's VV'd it won't be accurate but it probably shouldn't ever be
|
||||
if(radial_travel)
|
||||
AddElement(/datum/element/contextual_screentip_bare_hands, lmb_text = "Send Elevator")
|
||||
|
||||
set_movement_registrations()
|
||||
|
||||
//since lift_master datums find all connected platforms when an industrial lift first creates it and then
|
||||
@@ -273,30 +289,41 @@ GLOBAL_LIST_EMPTY(lifts)
|
||||
for(var/turf/dest_turf as anything in entering_locs)
|
||||
SEND_SIGNAL(dest_turf, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move)
|
||||
|
||||
if(istype(dest_turf, /turf/closed/wall))
|
||||
var/turf/closed/wall/C = dest_turf
|
||||
do_sparks(2, FALSE, C)
|
||||
C.dismantle_wall(devastated = TRUE)
|
||||
for(var/mob/M in urange(8, src))
|
||||
shake_camera(M, 2, 3)
|
||||
playsound(C, 'sound/effects/meteorimpact.ogg', 100, TRUE)
|
||||
if(iswallturf(dest_turf))
|
||||
var/turf/closed/wall/hit_wall = dest_turf
|
||||
do_sparks(2, FALSE, hit_wall)
|
||||
hit_wall.dismantle_wall(devastated = TRUE)
|
||||
for(var/mob/nearby_witness in urange(8, src))
|
||||
shake_camera(nearby_witness, 2, 3)
|
||||
playsound(hit_wall, 'sound/effects/meteorimpact.ogg', 100, TRUE)
|
||||
|
||||
for(var/mob/living/crushed in dest_turf.contents)
|
||||
to_chat(crushed, span_userdanger("You are crushed by [src]!"))
|
||||
crushed.gib(FALSE,FALSE,FALSE)//the nicest kind of gibbing, keeping everything intact.
|
||||
if(violent_landing)
|
||||
// Violent landing = gibbed. But the nicest kind of gibbing, keeping everything intact.
|
||||
crushed.gib(FALSE, FALSE, FALSE)
|
||||
else
|
||||
// Less violent landing simply crushes every bone in your body.
|
||||
crushed.Paralyze(30 SECONDS, ignore_canstun = TRUE)
|
||||
crushed.apply_damage(30, BRUTE, BODY_ZONE_CHEST, wound_bonus = 30)
|
||||
crushed.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = 25)
|
||||
crushed.apply_damage(15, BRUTE, BODY_ZONE_L_LEG, wound_bonus = 15)
|
||||
crushed.apply_damage(15, BRUTE, BODY_ZONE_R_LEG, wound_bonus = 15)
|
||||
crushed.apply_damage(15, BRUTE, BODY_ZONE_L_ARM, wound_bonus = 15)
|
||||
crushed.apply_damage(15, BRUTE, BODY_ZONE_R_ARM, wound_bonus = 15)
|
||||
|
||||
else if(going == UP)
|
||||
for(var/turf/dest_turf as anything in entering_locs)
|
||||
///handles any special interactions objects could have with the lift/tram, handled on the item itself
|
||||
SEND_SIGNAL(dest_turf, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move)
|
||||
|
||||
if(istype(dest_turf, /turf/closed/wall))
|
||||
var/turf/closed/wall/C = dest_turf
|
||||
do_sparks(2, FALSE, C)
|
||||
C.dismantle_wall(devastated = TRUE)
|
||||
if(iswallturf(dest_turf))
|
||||
var/turf/closed/wall/hit_wall = dest_turf
|
||||
do_sparks(2, FALSE, hit_wall)
|
||||
hit_wall.dismantle_wall(devastated = TRUE)
|
||||
for(var/mob/client_mob in SSspatial_grid.orthogonal_range_search(src, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS, 8))
|
||||
shake_camera(client_mob, 2, 3)
|
||||
playsound(C, 'sound/effects/meteorimpact.ogg', 100, TRUE)
|
||||
playsound(hit_wall, 'sound/effects/meteorimpact.ogg', 100, TRUE)
|
||||
|
||||
else
|
||||
///potentially finds a spot to throw the victim at for daring to be hit by a tram. is null if we havent found anything to throw
|
||||
@@ -507,47 +534,93 @@ GLOBAL_LIST_EMPTY(lifts)
|
||||
|
||||
return TRUE
|
||||
|
||||
/obj/structure/industrial_lift/proc/use(mob/living/user)
|
||||
if(!isliving(user) || !in_range(src, user) || user.combat_mode)
|
||||
/// Callback / general proc to check if the lift is usable by the passed mob.
|
||||
/obj/structure/industrial_lift/proc/can_open_lift_radial(mob/living/user, starting_position)
|
||||
// Gotta be a living mob
|
||||
if(!isliving(user))
|
||||
return FALSE
|
||||
// Gotta be awake and aware
|
||||
if(user.incapacitated())
|
||||
return FALSE
|
||||
// Maintain the god given right to fight an elevator
|
||||
if(user.combat_mode)
|
||||
return FALSE
|
||||
// Gotta be by the lift
|
||||
if(!user.Adjacent(src))
|
||||
return FALSE
|
||||
// If the lift moves while the radial is open close that shit
|
||||
if(starting_position != loc)
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/// Opens the radial for the lift, allowing the user to move it around.
|
||||
/obj/structure/industrial_lift/proc/open_lift_radial(mob/living/user)
|
||||
var/starting_position = loc
|
||||
if(!can_open_lift_radial(user, starting_position))
|
||||
return
|
||||
// One radial per person
|
||||
for(var/obj/structure/industrial_lift/other_platform as anything in lift_master_datum.lift_platforms)
|
||||
if(REF(user) in other_platform.current_operators)
|
||||
return
|
||||
|
||||
|
||||
var/list/possible_directions = list()
|
||||
if(lift_master_datum.Check_lift_move(UP))
|
||||
var/static/image/up_arrow
|
||||
if(!up_arrow)
|
||||
up_arrow = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH)
|
||||
|
||||
possible_directions["Up"] = up_arrow
|
||||
|
||||
if(lift_master_datum.Check_lift_move(DOWN))
|
||||
var/static/image/down_arrow
|
||||
if(!down_arrow)
|
||||
down_arrow = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH)
|
||||
|
||||
possible_directions["Down"] = down_arrow
|
||||
|
||||
add_fingerprint(user)
|
||||
if(!length(possible_directions))
|
||||
balloon_alert(user, "elevator out of service!")
|
||||
return
|
||||
|
||||
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(lift_master_datum.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/check_menu, user, src.loc), require_near = TRUE, tooltips = TRUE)
|
||||
if(!isliving(user) || !in_range(src, user) || user.combat_mode)
|
||||
LAZYADD(current_operators, REF(user))
|
||||
var/result = show_radial_menu(
|
||||
user = user,
|
||||
anchor = src,
|
||||
choices = possible_directions,
|
||||
custom_check = CALLBACK(src, .proc/can_open_lift_radial, user, starting_position),
|
||||
require_near = TRUE,
|
||||
tooltips = TRUE,
|
||||
)
|
||||
|
||||
LAZYREMOVE(current_operators, REF(user))
|
||||
if(!can_open_lift_radial(user, starting_position))
|
||||
return //nice try
|
||||
if(!isnull(result) && result != "Cancel" && lift_master_datum.controls_locked)
|
||||
// Only show this message if they actually wanted to move
|
||||
balloon_alert(user, "elevator controls locked!")
|
||||
return
|
||||
switch(result)
|
||||
if("Up")
|
||||
// We have to make sure that they don't do illegal actions by not having their radial menu refresh from someone else moving the lift.
|
||||
if(!lift_master_datum.Check_lift_move(UP))
|
||||
to_chat(user, span_warning("[src] doesn't seem to able to move up!"))
|
||||
add_fingerprint(user)
|
||||
// We have to make sure that they don't do illegal actions
|
||||
// by not having their radial menu refresh from someone else moving the lift.
|
||||
if(!lift_master_datum.simple_move_wrapper(UP, elevator_vertical_speed, user))
|
||||
return
|
||||
lift_master_datum.MoveLift(UP, user)
|
||||
show_fluff_message(TRUE, user)
|
||||
use(user)
|
||||
|
||||
show_fluff_message(UP, user)
|
||||
open_lift_radial(user)
|
||||
|
||||
if("Down")
|
||||
if(!lift_master_datum.Check_lift_move(DOWN))
|
||||
to_chat(user, span_warning("[src] doesn't seem to able to move down!"))
|
||||
add_fingerprint(user)
|
||||
if(!lift_master_datum.simple_move_wrapper(DOWN, elevator_vertical_speed, user))
|
||||
return
|
||||
lift_master_datum.MoveLift(DOWN, user)
|
||||
show_fluff_message(FALSE, user)
|
||||
use(user)
|
||||
|
||||
show_fluff_message(DOWN, user)
|
||||
open_lift_radial(user)
|
||||
|
||||
if("Cancel")
|
||||
return
|
||||
add_fingerprint(user)
|
||||
|
||||
/**
|
||||
* Proc to ensure that the radial menu closes when it should.
|
||||
@@ -567,45 +640,76 @@ GLOBAL_LIST_EMPTY(lifts)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
use(user)
|
||||
if(!radial_travel)
|
||||
return ..()
|
||||
|
||||
return open_lift_radial(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(isAdminGhostAI(user))
|
||||
use(user)
|
||||
if(!radial_travel)
|
||||
return
|
||||
if(!isAdminGhostAI(user))
|
||||
return
|
||||
|
||||
return open_lift_radial(user)
|
||||
|
||||
/obj/structure/industrial_lift/attack_paw(mob/user, list/modifiers)
|
||||
return use(user)
|
||||
if(!radial_travel)
|
||||
return ..()
|
||||
|
||||
/obj/structure/industrial_lift/attackby(obj/item/W, mob/user, params)
|
||||
return use(user)
|
||||
return open_lift_radial(user)
|
||||
|
||||
/obj/structure/industrial_lift/attack_robot(mob/living/silicon/robot/R)
|
||||
if(R.Adjacent(src))
|
||||
return use(R)
|
||||
/obj/structure/industrial_lift/attackby(obj/item/attacking_item, mob/user, params)
|
||||
if(!radial_travel)
|
||||
return ..()
|
||||
|
||||
return open_lift_radial(user)
|
||||
|
||||
/obj/structure/industrial_lift/attack_robot(mob/living/user)
|
||||
if(!radial_travel)
|
||||
return ..()
|
||||
|
||||
return open_lift_radial(user)
|
||||
|
||||
/**
|
||||
* Shows a message indicating that the lift has moved up or down.
|
||||
* Arguments:
|
||||
* * going_up - Boolean on whether or not we're going up, to adjust the message appropriately.
|
||||
* * direction - What direction are we going
|
||||
* * user - The mob that caused the lift to move, for the visible message.
|
||||
*/
|
||||
/obj/structure/industrial_lift/proc/show_fluff_message(going_up, mob/user)
|
||||
if(going_up)
|
||||
/obj/structure/industrial_lift/proc/show_fluff_message(direction, mob/user)
|
||||
if(direction == UP)
|
||||
user.visible_message(span_notice("[user] moves the lift upwards."), span_notice("You move the lift upwards."))
|
||||
else
|
||||
|
||||
if(direction == DOWN)
|
||||
user.visible_message(span_notice("[user] moves the lift downwards."), span_notice("You move the lift downwards."))
|
||||
|
||||
// A subtype intended for "public use"
|
||||
/obj/structure/industrial_lift/public
|
||||
icon = 'icons/turf/floors.dmi'
|
||||
icon_state = "rockvault"
|
||||
base_icon_state = null
|
||||
smoothing_flags = NONE
|
||||
smoothing_groups = null
|
||||
canSmoothWith = null
|
||||
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
|
||||
warns_on_down_movement = TRUE
|
||||
violent_landing = FALSE
|
||||
elevator_vertical_speed = 3 SECONDS
|
||||
radial_travel = FALSE
|
||||
|
||||
/obj/structure/industrial_lift/debug
|
||||
name = "transport platform"
|
||||
desc = "A lightweight platform. It moves in any direction, except up and down."
|
||||
color = "#5286b9ff"
|
||||
lift_id = DEBUG_LIFT_ID
|
||||
radial_travel = TRUE
|
||||
|
||||
/obj/structure/industrial_lift/debug/use(mob/user)
|
||||
/obj/structure/industrial_lift/debug/open_lift_radial(mob/user)
|
||||
if (!in_range(src, user))
|
||||
return
|
||||
//NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST
|
||||
@@ -626,29 +730,29 @@ GLOBAL_LIST_EMPTY(lifts)
|
||||
|
||||
switch(result)
|
||||
if("NORTH")
|
||||
lift_master_datum.MoveLiftHorizontal(NORTH, z)
|
||||
use(user)
|
||||
lift_master_datum.move_lift_horizontally(NORTH, z)
|
||||
open_lift_radial(user)
|
||||
if("NORTHEAST")
|
||||
lift_master_datum.MoveLiftHorizontal(NORTHEAST, z)
|
||||
use(user)
|
||||
lift_master_datum.move_lift_horizontally(NORTHEAST, z)
|
||||
open_lift_radial(user)
|
||||
if("EAST")
|
||||
lift_master_datum.MoveLiftHorizontal(EAST, z)
|
||||
use(user)
|
||||
lift_master_datum.move_lift_horizontally(EAST, z)
|
||||
open_lift_radial(user)
|
||||
if("SOUTHEAST")
|
||||
lift_master_datum.MoveLiftHorizontal(SOUTHEAST, z)
|
||||
use(user)
|
||||
lift_master_datum.move_lift_horizontally(SOUTHEAST, z)
|
||||
open_lift_radial(user)
|
||||
if("SOUTH")
|
||||
lift_master_datum.MoveLiftHorizontal(SOUTH, z)
|
||||
use(user)
|
||||
lift_master_datum.move_lift_horizontally(SOUTH, z)
|
||||
open_lift_radial(user)
|
||||
if("SOUTHWEST")
|
||||
lift_master_datum.MoveLiftHorizontal(SOUTHWEST, z)
|
||||
use(user)
|
||||
lift_master_datum.move_lift_horizontally(SOUTHWEST, z)
|
||||
open_lift_radial(user)
|
||||
if("WEST")
|
||||
lift_master_datum.MoveLiftHorizontal(WEST, z)
|
||||
use(user)
|
||||
lift_master_datum.move_lift_horizontally(WEST, z)
|
||||
open_lift_radial(user)
|
||||
if("NORTHWEST")
|
||||
lift_master_datum.MoveLiftHorizontal(NORTHWEST, z)
|
||||
use(user)
|
||||
lift_master_datum.move_lift_horizontally(NORTHWEST, z)
|
||||
open_lift_radial(user)
|
||||
if("Cancel")
|
||||
return
|
||||
|
||||
@@ -668,6 +772,7 @@ GLOBAL_LIST_EMPTY(lifts)
|
||||
|
||||
lift_id = TRAM_LIFT_ID
|
||||
lift_master_type = /datum/lift_master/tram
|
||||
radial_travel = FALSE
|
||||
|
||||
/// Set by the tram control console in late initialize
|
||||
var/travelling = FALSE
|
||||
@@ -699,9 +804,6 @@ GLOBAL_LIST_EMPTY(lifts)
|
||||
src.travelling = travelling
|
||||
SEND_SIGNAL(src, COMSIG_TRAM_SET_TRAVELLING, travelling)
|
||||
|
||||
/obj/structure/industrial_lift/tram/use(mob/user) //dont click the floor dingus we use computers now
|
||||
return
|
||||
|
||||
/obj/structure/industrial_lift/tram/set_currently_z_moving()
|
||||
return FALSE //trams can never z fall and shouldnt waste any processing time trying to do so
|
||||
|
||||
|
||||
@@ -31,8 +31,6 @@ GLOBAL_LIST_EMPTY(active_lifts_by_type)
|
||||
///overridable ID string to link control units to this specific lift_master datum. created by placing a lift id landmark object
|
||||
///somewhere on the tram, if its anywhere on the tram we'll find it in init and set this to whatever it specifies
|
||||
var/specific_lift_id
|
||||
///what directions we're allowed to move
|
||||
var/allowed_travel_directions = ALL
|
||||
|
||||
///if true, the lift cannot be manually moved.
|
||||
var/controls_locked = FALSE
|
||||
@@ -239,6 +237,42 @@ GLOBAL_LIST_EMPTY(active_lifts_by_type)
|
||||
|
||||
return winner
|
||||
|
||||
/// Returns a lift platform on the z-level which is vertically closest to the passed target_z
|
||||
/datum/lift_master/proc/return_closest_platform_to_z(target_z)
|
||||
var/obj/structure/industrial_lift/found_platform
|
||||
for(var/obj/structure/industrial_lift/lift as anything in lift_platforms)
|
||||
// Already at the same Z-level, we can stop
|
||||
if(lift.z == target_z)
|
||||
found_platform = lift
|
||||
break
|
||||
|
||||
// Set up an initial lift to compare to
|
||||
if(!found_platform)
|
||||
found_platform = lift
|
||||
continue
|
||||
|
||||
// Same level, we can go with the one we currently have
|
||||
if(lift.z == found_platform.z)
|
||||
continue
|
||||
|
||||
// If the difference between the current found platform and the target
|
||||
// if less than the distance between the next lift and the target,
|
||||
// our current platform is closer to the target than the next one, so we can skip it
|
||||
if(abs(found_platform.z - target_z) < abs(lift.z - target_z))
|
||||
continue
|
||||
|
||||
// The difference is smaller for this lift, so it's closer
|
||||
found_platform = lift
|
||||
|
||||
return found_platform
|
||||
|
||||
/// Returns a list of all the z-levels our lift is currently on.
|
||||
/datum/lift_master/proc/get_zs_we_are_on()
|
||||
var/list/zs_we_are_present_on = list()
|
||||
for(var/obj/structure/industrial_lift/lift as anything in lift_platforms)
|
||||
zs_we_are_present_on |= lift.z
|
||||
return zs_we_are_present_on
|
||||
|
||||
///returns all industrial_lifts associated with this tram on the given z level or given atoms z level
|
||||
/datum/lift_master/proc/get_platforms_on_level(atom/atom_reference_OR_z_level_number)
|
||||
var/z = atom_reference_OR_z_level_number
|
||||
@@ -259,13 +293,12 @@ GLOBAL_LIST_EMPTY(active_lifts_by_type)
|
||||
/**
|
||||
* 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(LIFT_PLATFORM_LOCKED)
|
||||
/datum/lift_master/proc/move_lift_vertically(going, mob/user)
|
||||
//lift_platforms are sorted in order of lowest z to highest z, so going upwards we need to move them in reverse order to not collide
|
||||
if(going == UP)
|
||||
var/obj/structure/industrial_lift/platform_to_move
|
||||
@@ -280,14 +313,208 @@ GLOBAL_LIST_EMPTY(active_lifts_by_type)
|
||||
else if(going == DOWN)
|
||||
for(var/obj/structure/industrial_lift/lift_platform as anything in lift_platforms)
|
||||
lift_platform.travel(going)
|
||||
|
||||
/**
|
||||
* Moves the lift after a passed delay.
|
||||
*
|
||||
* This is a more "user friendly" or "realistic" lift move.
|
||||
* It includes things like:
|
||||
* - Allowing lift "travel time"
|
||||
* - Shutting elevator safety doors
|
||||
* - Sound effects while moving
|
||||
* - Safety warnings for anyone below the lift (while it's moving downwards)
|
||||
*
|
||||
* Arguments:
|
||||
* duration - required, how long do we wait to move the lift?
|
||||
* door_duration - optional, how long should we wait to open the doors after arriving? If null, we won't open or close doors
|
||||
* direction - which direction are we moving the lift?
|
||||
* user - optional, who is moving the lift?
|
||||
*/
|
||||
/datum/lift_master/proc/move_after_delay(lift_move_duration, door_duration, direction, mob/user)
|
||||
if(!isnum(lift_move_duration))
|
||||
CRASH("[type] move_after_delay called with invalid duration ([lift_move_duration]).")
|
||||
if(lift_move_duration <= 0 SECONDS)
|
||||
move_lift_vertically(direction, user)
|
||||
return
|
||||
|
||||
// Get the lowest or highest lift according to which direction we're moving
|
||||
var/obj/structure/industrial_lift/prime_lift = return_closest_platform_to_z(direction == UP ? world.maxz : 0)
|
||||
|
||||
// If anyone changes the hydraulic sound effect I sure hope they update this variable...
|
||||
var/hydraulic_sfx_duration = 2 SECONDS
|
||||
// ...because we use the duration of the sound effect to make it last for roughly the duration of the lift travel
|
||||
playsound(prime_lift, 'sound/mecha/hydraulic.ogg', 25, vary = TRUE, frequency = clamp(hydraulic_sfx_duration / lift_move_duration, 0.33, 3))
|
||||
|
||||
// Move the lift after a timer
|
||||
addtimer(CALLBACK(src, .proc/move_lift_vertically, direction, user), lift_move_duration, TIMER_UNIQUE)
|
||||
// Open doors after the set duration if supplied
|
||||
if(isnum(door_duration))
|
||||
addtimer(CALLBACK(src, .proc/open_lift_doors_callback), door_duration, TIMER_UNIQUE)
|
||||
|
||||
// Here on we only care about lifts going DOWN
|
||||
if(direction != DOWN)
|
||||
return
|
||||
|
||||
// Okay we're going down, let's try to display some warnings to people below
|
||||
var/list/turf/lift_locs = list()
|
||||
for(var/obj/structure/industrial_lift/going_to_move as anything in lift_platforms)
|
||||
// This lift has no warnings so we don't even need to worry about it
|
||||
if(!going_to_move.warns_on_down_movement)
|
||||
continue
|
||||
// Collect all the turfs our lift is found at
|
||||
lift_locs |= going_to_move.locs
|
||||
|
||||
for(var/turf/moving in lift_locs)
|
||||
// Find what's below the turf that's moving
|
||||
var/turf/below_us = get_step_multiz(moving, DOWN)
|
||||
// Hold up the turf below us is also in our locs list. Multi-z lift? Don't show a warning
|
||||
if(below_us in lift_locs)
|
||||
continue
|
||||
// Display the warning for until we land
|
||||
new /obj/effect/temp_visual/telegraphing/lift_travel(below_us, lift_move_duration)
|
||||
|
||||
/**
|
||||
* Simple wrapper for checking if we can move 1 zlevel, and if we can, do said move.
|
||||
* Locks controls, closes all doors, then moves the lift and re-opens the doors afterwards.
|
||||
*
|
||||
* Arguments:
|
||||
* direction - which direction are we moving?
|
||||
* lift_move_duration - how long does the move take? can be 0 or null for instant move.
|
||||
* door_duration - how long does it take for the doors to open after a move?
|
||||
* user - optional, who moved it?
|
||||
*/
|
||||
/datum/lift_master/proc/simple_move_wrapper(direction, lift_move_duration, mob/user)
|
||||
if(!Check_lift_move(direction))
|
||||
return FALSE
|
||||
|
||||
// Lock controls, to prevent moving-while-moving memes
|
||||
set_controls(LIFT_PLATFORM_LOCKED)
|
||||
// Close all lift doors
|
||||
update_lift_doors(action = CLOSE_DOORS)
|
||||
|
||||
if(isnull(lift_move_duration) || lift_move_duration <= 0 SECONDS)
|
||||
// Do an instant move
|
||||
move_lift_vertically(direction, user)
|
||||
// Open doors on the zs we arrive at
|
||||
update_lift_doors(get_zs_we_are_on(), action = OPEN_DOORS)
|
||||
// And unlock the controls after
|
||||
set_controls(LIFT_PLATFORM_UNLOCKED)
|
||||
return TRUE
|
||||
|
||||
// Do a delayed move
|
||||
move_after_delay(
|
||||
lift_move_duration = lift_move_duration,
|
||||
door_duration = lift_move_duration * 1.5,
|
||||
direction = direction,
|
||||
user = user,
|
||||
)
|
||||
|
||||
addtimer(CALLBACK(src, .proc/set_controls, LIFT_PLATFORM_UNLOCKED), lift_move_duration * 1.5)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Moves the lift to the passed z-level.
|
||||
*
|
||||
* Checks for validity of the move: Are we moving to the same z-level, can we actually move to that z-level?
|
||||
* Does NOT check if the lift controls are currently locked.
|
||||
*
|
||||
* Moves to the passed z-level by calling move_after_delay repeatedly until the passed z-level is reached.
|
||||
* This proc sleeps as it moves.
|
||||
*
|
||||
* Arguments:
|
||||
* target_z - required, the Z we want to move to
|
||||
* loop_callback - optional, an additional callback invoked during the l oop that allows the move to cancel.
|
||||
* user - optional, who started the move
|
||||
*/
|
||||
/datum/lift_master/proc/move_to_zlevel(target_z, datum/callback/loop_callback, mob/user)
|
||||
if(!isnum(target_z) || target_z <= 0)
|
||||
CRASH("[type] move_to_zlevel was passed an invalid target_z ([target_z]).")
|
||||
|
||||
var/obj/structure/industrial_lift/prime_lift = return_closest_platform_to_z(target_z)
|
||||
var/lift_z = prime_lift.z
|
||||
// We're already at the desired z-level!
|
||||
if(target_z == lift_z)
|
||||
return FALSE
|
||||
|
||||
// The amount of z levels between the our and target_z
|
||||
var/z_difference = abs(target_z - lift_z)
|
||||
// Direction (up/down) needed to go to reach target_z
|
||||
var/direction = lift_z < target_z ? UP : DOWN
|
||||
|
||||
// We can't go that way anymore, or possibly ever
|
||||
if(!Check_lift_move(direction))
|
||||
return FALSE
|
||||
|
||||
// Okay we're ready to start moving now.
|
||||
set_controls(LIFT_PLATFORM_LOCKED)
|
||||
var/travel_speed = prime_lift.elevator_vertical_speed
|
||||
|
||||
// Close all lift doors
|
||||
update_lift_doors(action = CLOSE_DOORS)
|
||||
// Approach the desired z-level one step at a time
|
||||
for(var/i in 1 to z_difference)
|
||||
if(!Check_lift_move(direction))
|
||||
break
|
||||
if(loop_callback && !loop_callback.Invoke())
|
||||
break
|
||||
// move_after_delay will set up a timer and cause us to move after a time
|
||||
move_after_delay(
|
||||
lift_move_duration = travel_speed,
|
||||
direction = direction,
|
||||
user = user,
|
||||
)
|
||||
// and we don't want to send another request until the timer's done
|
||||
stoplag(travel_speed + 0.1 SECONDS)
|
||||
if(QDELETED(src) || QDELETED(prime_lift))
|
||||
return
|
||||
|
||||
addtimer(CALLBACK(src, .proc/open_lift_doors_callback), 2 SECONDS)
|
||||
set_controls(LIFT_PLATFORM_UNLOCKED)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Updates all blast doors and shutters that share an ID with our lift.
|
||||
*
|
||||
* Arguments:
|
||||
* on_z_level - optional, only open doors on this z-level or list of z-levels
|
||||
* action - how do we update the doors? OPEN_DOORS to make them open, CLOSE_DOORS to make them shut
|
||||
*/
|
||||
/datum/lift_master/proc/update_lift_doors(on_z_level, action)
|
||||
|
||||
if(!isnull(on_z_level) && !islist(on_z_level))
|
||||
on_z_level = list(on_z_level)
|
||||
|
||||
var/played_ding = FALSE
|
||||
for(var/obj/machinery/door/poddoor/elevator_door in GLOB.machines)
|
||||
if(elevator_door.id != specific_lift_id)
|
||||
continue
|
||||
if(on_z_level && !(elevator_door.z in on_z_level))
|
||||
continue
|
||||
|
||||
switch(action)
|
||||
if(OPEN_DOORS)
|
||||
INVOKE_ASYNC(elevator_door, /obj/machinery/door/poddoor.proc/open)
|
||||
|
||||
if(CLOSE_DOORS)
|
||||
INVOKE_ASYNC(elevator_door, /obj/machinery/door/poddoor.proc/close)
|
||||
|
||||
else
|
||||
stack_trace("Elevator lift update_lift_doors called with an improper action ([action]).")
|
||||
|
||||
if(!played_ding)
|
||||
playsound(elevator_door, 'sound/machines/ding.ogg', 50, TRUE)
|
||||
played_ding = TRUE
|
||||
|
||||
/// Helper used in callbacks to open all the doors our lift is on
|
||||
/datum/lift_master/proc/open_lift_doors_callback()
|
||||
update_lift_doors(get_zs_we_are_on(), action = OPEN_DOORS)
|
||||
|
||||
/**
|
||||
* 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)
|
||||
/datum/lift_master/proc/move_lift_horizontally(going)
|
||||
set_controls(LIFT_PLATFORM_LOCKED)
|
||||
|
||||
if(multitile_platform)
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
if(tram_part.controls_locked || tram_part.travelling) // someone else started already
|
||||
return FALSE
|
||||
tram_part.tram_travel(to_where)
|
||||
visible_message("The tram has been called to [to_where.name]")
|
||||
say("The tram has been called to [to_where.name].")
|
||||
return TRUE
|
||||
|
||||
/obj/item/circuit_component/tram_controls
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
var/start_time = TICK_USAGE
|
||||
travel_distance--
|
||||
|
||||
MoveLiftHorizontal(travel_direction)
|
||||
move_lift_horizontally(travel_direction)
|
||||
|
||||
var/duration = TICK_USAGE_TO_MS(start_time)
|
||||
if(slowed_down)
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
/obj/item/circuit_component/assembly_input/unregister_shell(atom/movable/shell)
|
||||
UnregisterSignal(shell, list(COMSIG_ASSEMBLY_PULSED, COMSIG_ITEM_ATTACK_SELF))
|
||||
|
||||
/obj/item/circuit_component/assembly_input/proc/on_pulsed()
|
||||
/obj/item/circuit_component/assembly_input/proc/on_pulsed(datum/source, mob/pulser)
|
||||
SIGNAL_HANDLER
|
||||
signal.set_output(COMPONENT_SIGNAL)
|
||||
|
||||
|
||||
@@ -3153,6 +3153,8 @@
|
||||
#include "code\modules\hydroponics\grown\weeds\nettle.dm"
|
||||
#include "code\modules\hydroponics\grown\weeds\starthistle.dm"
|
||||
#include "code\modules\industrial_lift\crossing_signal.dm"
|
||||
#include "code\modules\industrial_lift\elevator_button.dm"
|
||||
#include "code\modules\industrial_lift\elevator_panel.dm"
|
||||
#include "code\modules\industrial_lift\industrial_lift.dm"
|
||||
#include "code\modules\industrial_lift\lift_master.dm"
|
||||
#include "code\modules\industrial_lift\tram_controls.dm"
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import { clamp } from 'common/math';
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Blink, Button, Dimmer, Icon, Section, Stack } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
type FloorData = {
|
||||
name: string;
|
||||
z_level: number;
|
||||
};
|
||||
|
||||
type ElevatorPanelData = {
|
||||
// What Z the lift is on
|
||||
current_floor: number;
|
||||
// Station emergency level - "Red", "Green", etc
|
||||
emergency_level: string;
|
||||
// State: Is the station in red or delta alert?
|
||||
is_emergency: BooleanLike;
|
||||
// State: Are the doors emergency opened?
|
||||
doors_open: BooleanLike;
|
||||
// State: Does the lift exist?
|
||||
lift_exists: BooleanLike;
|
||||
// State: Is the lift moving?
|
||||
currently_moving: BooleanLike;
|
||||
// What floor are we currently moving to?
|
||||
currently_moving_to_floor: number | null;
|
||||
// A list of all floors we can move around to
|
||||
all_floor_data: FloorData[];
|
||||
};
|
||||
|
||||
export const ElevatorPanel = (props, context) => {
|
||||
const { data, act } = useBackend<ElevatorPanelData>(context);
|
||||
|
||||
const {
|
||||
current_floor,
|
||||
emergency_level,
|
||||
is_emergency,
|
||||
doors_open,
|
||||
lift_exists,
|
||||
currently_moving,
|
||||
all_floor_data,
|
||||
} = data;
|
||||
|
||||
/*
|
||||
* We want to grow our UI if we have a lot of floors to display,
|
||||
* while also leaving some whitespace - just so it's not too cluttered.
|
||||
*
|
||||
* 400 is chosen for the min, to prevent it from being too tiny.
|
||||
*
|
||||
* 600 is chosen for the max, as it fits 10 floors pretty comfortably.
|
||||
* If you seriously need more than 10 floors for an elevator in this game,
|
||||
* you should reconsider your map's layout. It's not worth it.
|
||||
*/
|
||||
const calculatedHeight = clamp(all_floor_data.length * 90, 400, 600);
|
||||
|
||||
return (
|
||||
<Window width={200} height={calculatedHeight} theme="retro">
|
||||
<Window.Content>
|
||||
{!lift_exists && <NoLiftDimmer />}
|
||||
<Stack height="100%" vertical>
|
||||
<Stack.Item>
|
||||
<Section title="Floor" align="center">
|
||||
<FloorPanel />
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section fill align="center">
|
||||
{!!currently_moving && <MovingDimmer />}
|
||||
<Stack vertical width="90%">
|
||||
{all_floor_data.map((floor, index) => (
|
||||
<Stack.Item key={index}>
|
||||
<Button
|
||||
style={{
|
||||
'font-weight': 'bold',
|
||||
'font-size': '14px',
|
||||
}}
|
||||
fluid
|
||||
ellipsis
|
||||
textAlign="left"
|
||||
icon="circle"
|
||||
disabled={floor.z_level === current_floor}
|
||||
onClick={() => act('move_lift', { z: floor.z_level })}>
|
||||
{floor.name}
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Section>
|
||||
{doors_open ? (
|
||||
<Button
|
||||
width="65%"
|
||||
icon="door-closed"
|
||||
tooltip={
|
||||
'Closes all elevator doors, except \
|
||||
those on the level of the elevator.'
|
||||
}
|
||||
onClick={() => act('reset_doors')}>
|
||||
Reset Doors
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
width="65%"
|
||||
icon="door-open"
|
||||
disabled={!is_emergency}
|
||||
color={'bad'}
|
||||
tooltip={
|
||||
is_emergency
|
||||
? 'In case of emergency, opens all lift doors.'
|
||||
: `The station is only at ${emergency_level} alert.`
|
||||
}
|
||||
onClick={() => act('emergency_door')}>
|
||||
Emergency
|
||||
</Button>
|
||||
)}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const NoLiftDimmer = () => {
|
||||
return (
|
||||
<Dimmer>
|
||||
<Stack vertical align="center">
|
||||
<Stack.Item>
|
||||
<Icon size={8} name="exclamation" />
|
||||
</Stack.Item>
|
||||
<Stack.Item fontSize="16px">No elevator connected.</Stack.Item>
|
||||
</Stack>
|
||||
</Dimmer>
|
||||
);
|
||||
};
|
||||
|
||||
const MovingDimmer = () => {
|
||||
return (
|
||||
<Dimmer>
|
||||
<Stack vertical align="center">
|
||||
<Stack.Item>
|
||||
<Icon size={8} name="spinner" spin />
|
||||
</Stack.Item>
|
||||
<Stack.Item fontSize="16px">Moving...</Stack.Item>
|
||||
</Stack>
|
||||
</Dimmer>
|
||||
);
|
||||
};
|
||||
|
||||
const FloorPanel = (props, context) => {
|
||||
const { data } = useBackend<ElevatorPanelData>(context);
|
||||
const { current_floor, currently_moving, currently_moving_to_floor } = data;
|
||||
|
||||
return (
|
||||
<Stack width="50%" backgroundColor="black">
|
||||
<Stack.Item ml={2} mr={1} mt={1} mb={1}>
|
||||
<Stack vertical align="center">
|
||||
<Stack.Item>
|
||||
<ArrowIcon
|
||||
icon="arrow-up"
|
||||
is_moving={
|
||||
currently_moving &&
|
||||
currently_moving_to_floor &&
|
||||
currently_moving_to_floor > current_floor
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<ArrowIcon
|
||||
icon="arrow-down"
|
||||
is_moving={
|
||||
currently_moving &&
|
||||
currently_moving_to_floor &&
|
||||
currently_moving_to_floor < current_floor
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item mt={1}>
|
||||
<Box
|
||||
textColor="white"
|
||||
style={{
|
||||
'font-family': 'Monospace',
|
||||
'font-size': '50px',
|
||||
'font-weight': 'bold',
|
||||
}}>
|
||||
{current_floor - 1}
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const ArrowIcon = (props) => {
|
||||
return props.is_moving ? (
|
||||
<Blink time={500} interval={500}>
|
||||
<Icon name={props.icon} color={'green'} size={2} />
|
||||
</Blink>
|
||||
) : (
|
||||
<Icon name={props.icon} color={'grey'} size={2} />
|
||||
);
|
||||
};
|
||||
@@ -28,7 +28,7 @@
|
||||
'../components/Button.scss',
|
||||
$with: (
|
||||
'color-default': #e8e4c9,
|
||||
'color-disabled': #363636,
|
||||
'color-disabled': #505046,
|
||||
'color-selected': #9d0808,
|
||||
'color-caution': #be6209,
|
||||
'color-danger': #9a9d00
|
||||
@@ -55,6 +55,17 @@
|
||||
outline: base.em(1px) solid #161613;
|
||||
}
|
||||
|
||||
.Button--disabled {
|
||||
color: #c5c5c2;
|
||||
font-family: monospace;
|
||||
&:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
&:focus {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.Layout__content {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user