mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-28 15:47:15 +01:00
Stairs update (#94146)
## About The Pull Request 1. Stairs are now on the floor plane, meaning they don't have AO and blend in with turfs 2. Stairs now blend with adjacent stairs, forming a contiguous sprite 3. There is now a visual indicator that stairs will take you up in the form of an arrow - The arrow appears when within three tiles and only appears if the stairs can actually take you up. https://github.com/user-attachments/assets/d941a312-9470-4d2a-95f0-a1834adf0212 ## Why It's Good For The Game 1. The common method of making stairs (stair objects on stair turfs) looks ugly due to AO 2. Stair turfs have these sprites but not the objects themselves 3. Given stairs now blend in better, I figured there should be a proper indicator that stairs exist. Helps with fake stair confusion ## Changelog 🆑 Melbert qol: Stairs now have an indicator that stepping beyond them will take you upwards. You can disable it in accessibility settings if desired. image: Stairs blend in and together better. /🆑
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
#define STAIR_TERMINATOR_NO 1
|
||||
#define STAIR_TERMINATOR_YES 2
|
||||
|
||||
/// Range within which stair indicators will appear for approaching mobs
|
||||
#define STAIR_INDICATOR_RANGE 3
|
||||
|
||||
// dir determines the direction of travel to go upwards
|
||||
// stairs require /turf/open/openspace as the tile above them to work, unless your stairs have 'force_open_above' set to TRUE
|
||||
// multiple stair objects can be chained together; the Z level transition will happen on the final stair object in the chain
|
||||
@@ -10,12 +13,22 @@
|
||||
name = "stairs"
|
||||
icon = 'icons/obj/stairs.dmi'
|
||||
icon_state = "stairs"
|
||||
base_icon_state = "stairs"
|
||||
anchored = TRUE
|
||||
move_resist = INFINITY
|
||||
plane = FLOOR_PLANE
|
||||
layer = ABOVE_OPEN_TURF_LAYER
|
||||
|
||||
var/force_open_above = FALSE // replaces the turf above this stair obj with /turf/open/openspace
|
||||
var/terminator_mode = STAIR_TERMINATOR_AUTOMATIC
|
||||
var/turf/listeningTo
|
||||
/// If TRUE replaces the turf above this stair obj with /turf/open/openspace
|
||||
var/force_open_above = FALSE
|
||||
/// Determines if this stair is the last in a "chain" of stairs, ie next step is upstairs
|
||||
VAR_FINAL/terminator_mode = STAIR_TERMINATOR_AUTOMATIC
|
||||
/// Upstairs turf. Is observed for changes if force_open_above is TRUE (to re-open if necessary)
|
||||
VAR_FINAL/turf/directly_above
|
||||
/// If TRUE, we have left/middle/right sprites.
|
||||
var/has_merged_sprites = TRUE
|
||||
/// Lazyassoc list of weakef to mob viewing stair indicators to their images
|
||||
VAR_PRIVATE/list/mob_to_image
|
||||
|
||||
/obj/structure/stairs/north
|
||||
dir = NORTH
|
||||
@@ -31,49 +44,94 @@
|
||||
|
||||
/obj/structure/stairs/wood
|
||||
icon_state = "stairs_wood"
|
||||
has_merged_sprites = FALSE
|
||||
|
||||
/obj/structure/stairs/stone
|
||||
icon_state = "stairs_stone"
|
||||
has_merged_sprites = FALSE
|
||||
|
||||
/obj/structure/stairs/material
|
||||
icon_state = "stairs_material"
|
||||
material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
|
||||
has_merged_sprites = FALSE
|
||||
|
||||
/obj/structure/stairs/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
GLOB.stairs += src
|
||||
if(force_open_above)
|
||||
force_open_above()
|
||||
build_signal_listener()
|
||||
update_surrounding()
|
||||
|
||||
var/static/list/loc_connections = list(
|
||||
COMSIG_ATOM_EXIT = PROC_REF(on_exit),
|
||||
var/static/list/exit_connections = list(
|
||||
COMSIG_ATOM_EXIT = PROC_REF(on_exit_stairs),
|
||||
)
|
||||
|
||||
AddElement(/datum/element/connect_loc, loc_connections)
|
||||
AddElement(/datum/element/connect_loc, exit_connections)
|
||||
|
||||
var/static/list/range_connections = list(
|
||||
COMSIG_ATOM_ENTERED = PROC_REF(on_enter_range),
|
||||
COMSIG_ATOM_EXITED = PROC_REF(on_exit_range),
|
||||
)
|
||||
AddComponent(/datum/component/connect_range, tracked = src, connections = range_connections, range = STAIR_INDICATOR_RANGE)
|
||||
|
||||
return ..()
|
||||
|
||||
/obj/structure/stairs/Destroy()
|
||||
listeningTo = null
|
||||
if(directly_above)
|
||||
UnregisterSignal(directly_above, COMSIG_TURF_MULTIZ_NEW)
|
||||
directly_above = null
|
||||
for(var/climber_ref in mob_to_image)
|
||||
clear_climber_image(climber_ref, instant = TRUE)
|
||||
GLOB.stairs -= src
|
||||
return ..()
|
||||
|
||||
/obj/structure/stairs/Move() //Look this should never happen but...
|
||||
/obj/structure/stairs/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) //Look this should never happen but...
|
||||
. = ..()
|
||||
if(force_open_above)
|
||||
build_signal_listener()
|
||||
update_surrounding()
|
||||
|
||||
/// Updates the sprite and the sprites of neighboring stairs to reflect merged sprites
|
||||
/obj/structure/stairs/proc/update_surrounding()
|
||||
update_appearance()
|
||||
for(var/i in GLOB.cardinals)
|
||||
var/turf/T = get_step(get_turf(src), i)
|
||||
var/obj/structure/stairs/S = locate() in T
|
||||
if(S)
|
||||
S.update_appearance()
|
||||
if(!has_merged_sprites)
|
||||
return
|
||||
|
||||
/obj/structure/stairs/proc/on_exit(datum/source, atom/movable/leaving, direction)
|
||||
update_appearance()
|
||||
|
||||
for(var/obj/structure/stairs/stair in get_step(src, turn(dir, 90)))
|
||||
stair.update_appearance()
|
||||
|
||||
for(var/obj/structure/stairs/stair in get_step(src, turn(dir, -90)))
|
||||
stair.update_appearance()
|
||||
|
||||
/obj/structure/stairs/update_icon_state()
|
||||
. = ..()
|
||||
if(!has_merged_sprites)
|
||||
return
|
||||
|
||||
var/has_left_stairs = FALSE
|
||||
var/has_right_stairs = FALSE
|
||||
for(var/obj/structure/stairs/stair in get_step(src, turn(dir, 90)))
|
||||
if(stair.dir == dir)
|
||||
has_left_stairs = TRUE
|
||||
break
|
||||
|
||||
for(var/obj/structure/stairs/stair in get_step(src, turn(dir, -90)))
|
||||
if(stair.dir == dir)
|
||||
has_right_stairs = TRUE
|
||||
break
|
||||
|
||||
if(has_left_stairs && has_right_stairs)
|
||||
icon_state = "[base_icon_state]-m"
|
||||
else if(has_left_stairs)
|
||||
icon_state = "[base_icon_state]-r"
|
||||
else if(has_right_stairs)
|
||||
icon_state = "[base_icon_state]-l"
|
||||
else
|
||||
icon_state = base_icon_state
|
||||
|
||||
/obj/structure/stairs/proc/on_exit_stairs(datum/source, atom/movable/leaving, direction)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(leaving == src)
|
||||
@@ -85,19 +143,95 @@
|
||||
leaving.Bump(src)
|
||||
return COMPONENT_ATOM_BLOCK_EXIT
|
||||
|
||||
#define POINT_X_COMPONENT(pdir) ((pdir & EAST) ? 2 : ((pdir & WEST) ? -2 : 0))
|
||||
#define POINT_Y_COMPONENT(pdir) ((pdir & SOUTH) ? 2 : ((pdir & NORTH) ? -2 : 0))
|
||||
|
||||
/obj/structure/stairs/proc/on_enter_range(datum/source, atom/movable/entered)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!isliving(entered))
|
||||
return
|
||||
|
||||
var/mob/living/climber = entered
|
||||
var/datum/weakref/climber_ref = WEAKREF(climber)
|
||||
if(!climber.client || !climber.client.prefs.read_preference(/datum/preference/toggle/stair_indicator))
|
||||
return
|
||||
if(climber.dir == REVERSE_DIR(dir))
|
||||
return // walking away
|
||||
if(LAZYACCESS(mob_to_image, climber_ref))
|
||||
return // already see it
|
||||
if(!(climber in viewers(STAIR_INDICATOR_RANGE + 1, src)))
|
||||
return // can't see the staircase (+1 tile for some leeway)
|
||||
if(!isopenturf(get_step_multiz(src, UP)))
|
||||
return // no place to go up to
|
||||
|
||||
var/image/pointing_image = get_pointing_image()
|
||||
climber.client.images += pointing_image
|
||||
pointing_image.alpha = 0
|
||||
animate(pointing_image, pixel_x = POINT_X_COMPONENT(dir), pixel_y = POINT_Y_COMPONENT(dir), time = 0.5 SECONDS, easing = SINE_EASING|EASE_OUT, loop = -1, tag = "point_xy")
|
||||
animate(pixel_x = 0, pixel_y = 0, time = 0.5 SECONDS, easing = SINE_EASING|EASE_IN)
|
||||
animate(pointing_image, alpha = 180, time = 0.75 SECONDS, tag = "point_fadein")
|
||||
LAZYSET(mob_to_image, climber_ref, pointing_image)
|
||||
|
||||
/obj/structure/stairs/proc/on_exit_range(datum/source, atom/movable/exited)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!isliving(exited))
|
||||
return
|
||||
|
||||
var/datum/weakref/climber_ref = WEAKREF(exited)
|
||||
if(!LAZYACCESS(mob_to_image, climber_ref))
|
||||
return // not seeing anything
|
||||
if(exited in viewers(STAIR_INDICATOR_RANGE, src))
|
||||
return // still in range and can see the staircase
|
||||
|
||||
clear_climber_image(climber_ref)
|
||||
|
||||
/obj/structure/stairs/proc/clear_climber_image(datum/weakref/climber_ref, instant = FALSE)
|
||||
var/image/pointing_image = LAZYACCESS(mob_to_image, climber_ref)
|
||||
if(!pointing_image)
|
||||
LAZYREMOVE(mob_to_image, climber_ref) // just in case
|
||||
return
|
||||
if(instant)
|
||||
clear_climber_image_callback(climber_ref, pointing_image)
|
||||
return
|
||||
|
||||
animate(pointing_image, alpha = 0, time = 0.75 SECONDS, tag = "point_fadeout")
|
||||
// note: the player won't see a new indicator until the image is fully a removed, so this timer also serves as a cooldown
|
||||
addtimer(CALLBACK(src, PROC_REF(clear_climber_image_callback), climber_ref, pointing_image), 1.5 SECONDS, TIMER_UNIQUE)
|
||||
|
||||
/obj/structure/stairs/proc/clear_climber_image_callback(datum/weakref/climber_ref, image/pointing_image)
|
||||
PRIVATE_PROC(TRUE)
|
||||
var/mob/living/climber = climber_ref?.resolve()
|
||||
climber?.client?.images -= pointing_image
|
||||
LAZYREMOVE(mob_to_image, climber_ref)
|
||||
|
||||
/obj/structure/stairs/proc/get_pointing_image()
|
||||
PROTECTED_PROC(TRUE)
|
||||
var/image/point_image = image('icons/hud/screen_gen.dmi', src, "arrow_large_white_still")
|
||||
point_image.color = COLOR_DARK_MODERATE_LIME_GREEN
|
||||
point_image.appearance_flags |= KEEP_APART
|
||||
point_image.transform = matrix().Turn(dir2angle(REVERSE_DIR(dir)))
|
||||
point_image.layer = BELOW_MOB_LAYER
|
||||
SET_PLANE(point_image, GAME_PLANE, src)
|
||||
return point_image
|
||||
|
||||
#undef POINT_X_COMPONENT
|
||||
#undef POINT_Y_COMPONENT
|
||||
|
||||
/obj/structure/stairs/Cross(atom/movable/AM)
|
||||
if(isTerminator() && (get_dir(src, AM) == dir))
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/obj/structure/stairs/proc/stair_ascend(atom/movable/climber)
|
||||
var/turf/checking = get_step_multiz(get_turf(src), UP)
|
||||
var/turf/checking = get_step_multiz(src, UP)
|
||||
if(!istype(checking))
|
||||
return
|
||||
// I'm only interested in if the pass is unobstructed, not if the mob will actually make it
|
||||
if(!climber.can_z_move(UP, get_turf(src), checking, z_move_flags = ZMOVE_ALLOW_BUCKLED))
|
||||
return
|
||||
var/turf/target = get_step_multiz(get_turf(src), (dir|UP))
|
||||
var/turf/target = get_step_multiz(src, dir|UP)
|
||||
if(istype(target) && !climber.can_z_move(DOWN, target, z_move_flags = ZMOVE_FALL_FLAGS)) //Don't throw them into a tile that will just dump them back down.
|
||||
climber.zMove(target = target, z_move_flags = ZMOVE_STAIRS_FLAGS)
|
||||
/// Moves anything that's being dragged by src or anything buckled to it to the stairs turf.
|
||||
@@ -113,22 +247,22 @@
|
||||
if(var_name != NAMEOF(src, force_open_above))
|
||||
return
|
||||
if(!var_value)
|
||||
if(listeningTo)
|
||||
UnregisterSignal(listeningTo, COMSIG_TURF_MULTIZ_NEW)
|
||||
listeningTo = null
|
||||
if(directly_above)
|
||||
UnregisterSignal(directly_above, COMSIG_TURF_MULTIZ_NEW)
|
||||
directly_above = null
|
||||
else
|
||||
build_signal_listener()
|
||||
force_open_above()
|
||||
|
||||
/obj/structure/stairs/proc/build_signal_listener()
|
||||
if(listeningTo)
|
||||
UnregisterSignal(listeningTo, COMSIG_TURF_MULTIZ_NEW)
|
||||
var/turf/open/openspace/T = get_step_multiz(get_turf(src), UP)
|
||||
if(directly_above)
|
||||
UnregisterSignal(directly_above, COMSIG_TURF_MULTIZ_NEW)
|
||||
var/turf/open/openspace/T = get_step_multiz(src, UP)
|
||||
RegisterSignal(T, COMSIG_TURF_MULTIZ_NEW, PROC_REF(on_multiz_new))
|
||||
listeningTo = T
|
||||
directly_above = T
|
||||
|
||||
/obj/structure/stairs/proc/force_open_above()
|
||||
var/turf/open/openspace/T = get_step_multiz(get_turf(src), UP)
|
||||
var/turf/open/openspace/T = get_step_multiz(src, UP)
|
||||
if(T && !istype(T))
|
||||
T.ChangeTurf(/turf/open/openspace, flags = CHANGETURF_INHERIT_AIR)
|
||||
|
||||
@@ -136,7 +270,7 @@
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(dir == UP)
|
||||
var/turf/open/openspace/T = get_step_multiz(get_turf(src), UP)
|
||||
var/turf/open/openspace/T = get_step_multiz(src, UP)
|
||||
if(T && !istype(T))
|
||||
T.ChangeTurf(/turf/open/openspace, flags = CHANGETURF_INHERIT_AIR)
|
||||
|
||||
@@ -282,3 +416,5 @@
|
||||
#undef STAIR_TERMINATOR_AUTOMATIC
|
||||
#undef STAIR_TERMINATOR_NO
|
||||
#undef STAIR_TERMINATOR_YES
|
||||
|
||||
#undef STAIR_INDICATOR_RANGE
|
||||
|
||||
Reference in New Issue
Block a user