Files
Aurora.3/code/game/objects/structures/railing.dm
T
Matt Atlas d5931c5ffe Adds an universal object health system. (#21266)
https://www.youtube.com/watch?v=ZpUYjpKg9KY

As per title. The goal is to add health to most objects in the game and
thus allow them to be dynamically destructible, while also avoiding
shitcode like each structure having its own snowflake health update
proc.

---------

Signed-off-by: Matt Atlas <mattiathebest2000@hotmail.it>
Co-authored-by: Matt Atlas <liermattia@gmail.com>
Co-authored-by: SleepyGemmy <99297919+SleepyGemmy@users.noreply.github.com>
2026-04-16 23:22:08 +00:00

452 lines
14 KiB
Plaintext

/obj/structure/railing
name = "railing"
desc = "A simple bar railing designed to protect against careless trespass."
icon = 'icons/obj/structure/blocker/railing_basic.dmi'
icon_state = "railing0-1"
density = TRUE
climbable = TRUE
layer = OBJ_LAYER
anchored = FALSE
pass_flags_self = LETPASSTHROW|PASSSTRUCTURE|PASSRAILING
atom_flags = ATOM_FLAG_CHECKS_BORDER
obj_flags = OBJ_FLAG_ROTATABLE|OBJ_FLAG_MOVES_UNSUPPORTED
build_amt = 2
maxhealth = OBJECT_HEALTH_EXTREMELY_LOW
var/broken = FALSE
var/neighbor_status = 0
/// If the object shouldn't inherit a material, set this to True.
var/non_material_object = FALSE
var/can_wrench = TRUE
var/can_screwdriver = TRUE
can_astar_pass = CANASTARPASS_ALWAYS_PROC
/obj/structure/railing/get_damage_condition_hints(mob/user, distance, is_adjacent)
if (health < maxhealth)
switch(health / maxhealth)
if (0.0 to 0.5)
. = SPAN_WARNING("It looks severely damaged!")
if (0.25 to 0.5)
. = SPAN_WARNING("It looks damaged!")
if (0.5 to 1.0)
. = SPAN_NOTICE("It has a few scrapes and dents.")
/obj/structure/railing/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
if (anchored)
. += "It could be [density ? "opened" : "closed"] to passage with a wrench."
/obj/structure/railing/assembly_hints(mob/user, distance, is_adjacent)
. += ..()
if (health < maxhealth)
. += "It could be repaired with a few choice <b>welds</b>."
. += "It [anchored ? "is" : "could be"] anchored to the floor with a row of <b>screws</b>."
if (!anchored)
. += "It is held together by a couple of <b>bolts</b>."
/obj/structure/railing/mapped
color = COLOR_GUNMETAL
anchored = TRUE
/obj/structure/railing/mapped/Initialize()
. = ..()
color = COLOR_GUNMETAL // They're not painted!
/obj/structure/railing/mapped/no_density
density = FALSE
/obj/structure/railing/mapped/no_density/Initialize()
. = ..()
update_icon()
/obj/structure/railing/mapped/no_density/low
icon_state = "railing0-0"
/obj/structure/railing/New(var/newloc, var/material_key = DEFAULT_WALL_MATERIAL)
material = material_key // Converted to datum in initialize().
..(newloc)
/obj/structure/railing/process()
if(!material || !material.radioactivity)
return
for(var/mob/living/L in range(1,src))
L.apply_damage(round(material.radioactivity / 20), DAMAGE_RADIATION)
/obj/structure/railing/Initialize()
. = ..()
if(!non_material_object)
if(!isnull(material) && !istype(material))
material = SSmaterials.get_material_by_name(material)
if(!istype(material))
return INITIALIZE_HINT_QDEL
name = "[material.display_name] [initial(name)]"
desc = "A simple [material.display_name] railing designed to protect against careless trespass."
set_maxhealth(round(material.integrity / 5))
set_health(maxhealth)
color = material.icon_colour
if(material.products_need_process())
START_PROCESSING(SSprocessing, src)
if(anchored)
update_icon(FALSE)
/obj/structure/railing/Destroy()
anchored = FALSE
broken = TRUE
for(var/thing in range(1, src))
var/turf/T = thing
for(var/obj/structure/railing/R in T.contents)
R.update_icon()
return ..()
/obj/structure/railing/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover,/obj/projectile))
return TRUE
if(!istype(mover) || mover.pass_flags & PASSRAILING)
return TRUE
if(mover.throwing)
return TRUE
if(get_dir(loc, target) == dir)
return !density
return TRUE
/obj/structure/railing/CanAStarPass(to_dir, datum/can_pass_info/pass_info)
if(to_dir == dir)
return !density
return TRUE
/obj/structure/railing/on_death(damage, damage_flags, damage_type, armor_penetration, obj/weapon)
visible_message(SPAN_WARNING("\The [src] [material.destruction_desc]!"))
playsound(get_turf(src), 'sound/effects/grillehit.ogg', 50, TRUE)
material.place_shard(get_turf(src))
qdel(src)
/obj/structure/railing/proc/NeighborsCheck(var/UpdateNeighbors = 1)
neighbor_status = 0
var/Rturn = turn(src.dir, -90)
var/Lturn = turn(src.dir, 90)
for(var/obj/structure/railing/R in get_turf(src))
if((R.dir == Lturn) && R.anchored)
neighbor_status |= 32 // neighbor is making a left turn on the same tile
if(UpdateNeighbors)
R.update_icon(0)
if((R.dir == Rturn) && R.anchored)
neighbor_status |= 2 // neighbor is making a right turn on the same tile
if(UpdateNeighbors)
R.update_icon(0)
for(var/obj/structure/railing/R in get_step(src, Lturn))
if((R.dir == src.dir) && R.anchored)
neighbor_status |= 16 // neighbor is in our left, in the same line
if(UpdateNeighbors)
R.update_icon(0)
for(var/obj/structure/railing/R in get_step(src, Rturn))
if((R.dir == src.dir) && R.anchored)
neighbor_status |= 1 // neighbor is in our right, in the same line
if (UpdateNeighbors)
R.update_icon(0)
for(var/obj/structure/railing/R in get_step(src, (Lturn + src.dir)))
if((R.dir == Rturn) && R.anchored)
neighbor_status |= 64 // neighbor is in our diagonal left, making a longer turn
if (UpdateNeighbors)
R.update_icon(0)
for(var/obj/structure/railing/R in get_step(src, (Rturn + src.dir)))
if((R.dir == Lturn) && R.anchored)
neighbor_status |= 4 // neighbor is in our diagonal right, making a longer turn
if (UpdateNeighbors)
R.update_icon(0)
/obj/structure/railing/update_icon(var/update_neighbors = TRUE)
NeighborsCheck(update_neighbors)
CutOverlays()
if(dir == SOUTH)
layer = ABOVE_HUMAN_LAYER
else
layer = initial(layer)
if(!neighbor_status || !anchored)
icon_state = "railing0-[density]"
else
icon_state = "railing1-[density]"
if(neighbor_status & 32)
AddOverlays(image(icon, "corneroverlay[density]"))
if((neighbor_status & 16) || !(neighbor_status & 32) || (neighbor_status & 64))
AddOverlays(image(icon, "frontoverlay_l[density]"))
if(!(neighbor_status & 2) || (neighbor_status & 1) || (neighbor_status & 4))
AddOverlays(image(icon, "frontoverlay_r[density]"))
if(neighbor_status & 4)
var/pix_offset_x = 0
var/pix_offset_y = 0
switch(dir)
if(NORTH)
pix_offset_x = 32
if(SOUTH)
pix_offset_x = -32
if(EAST)
pix_offset_y = -32
if(WEST)
pix_offset_y = 32
AddOverlays(image(icon, "mcorneroverlay[density]", pixel_x = pix_offset_x, pixel_y = pix_offset_y))
if(!(neighbor_status & 16) && !(neighbor_status & 64) && !(neighbor_status & 32)) // Left endcap, we have no connections in left
AddOverlays(image(icon, "frontend_l[density]"))
if(!(neighbor_status & 1) && !(neighbor_status & 4) && !(neighbor_status & 2)) // Right endcap, we have no connections in right
AddOverlays(image(icon, "frontend_r[density]"))
/obj/structure/railing/verb/flip() // This will help push railing to remote places, such as open space turfs
set name = "Flip Railing"
set category = "Object"
set src in oview(1)
if(usr.incapacitated())
return FALSE
if(anchored)
to_chat(usr, SPAN_WARNING("It is fastened to the floor and cannot be flipped."))
return FALSE
if(turf_is_crowded(TRUE))
to_chat(usr, SPAN_WARNING("You can't flip \the [src] - something is in the way."))
return FALSE
forceMove(get_step(src, src.dir))
set_dir(turn(dir, 180))
update_icon()
/obj/structure/railing/CheckExit(var/atom/movable/O, var/turf/target)
if(istype(O) && CanPass(O, target))
return TRUE
if(get_dir(O.loc, target) == dir)
if(!density)
return TRUE
return FALSE
return TRUE
/obj/structure/railing/attackby(obj/item/attacking_item, mob/user)
// Handle harm intent grabbing/tabling.
if(istype(attacking_item, /obj/item/grab) && user.Adjacent(src))
var/obj/item/grab/G = attacking_item
if(ishuman(G.affecting))
var/obj/occupied = turf_is_crowded(TRUE)
if(occupied)
to_chat(user, SPAN_WARNING("There's \a [occupied] in the way!"))
return
if(G.state >= GRAB_NECK)
if(user.a_intent == I_HURT)
visible_message(SPAN_DANGER("[G.assailant] slams [G.affecting]'s face against \the [src]!"))
playsound(get_turf(src), 'sound/effects/grillehit.ogg', 50, TRUE)
if(prob(30))
G.affecting.Weaken(5)
G.affecting.apply_damage(15, DAMAGE_BRUTE, BP_HEAD)
else
G.affecting.forceMove(get_step(src, get_dir(user, src)))
G.affecting.Weaken(5)
visible_message(SPAN_WARNING("[G.assailant] throws \the [G.affecting] over \the [src]!"))
qdel(G)
else
to_chat(user, SPAN_WARNING("You need a better grip to do that!"))
return
// Dismantle
if(attacking_item.tool_behaviour == TOOL_WRENCH)
if(!can_wrench)
to_chat(user, SPAN_WARNING("This [src] cannot be adjusted."))
return
if(!anchored)
user.visible_message(SPAN_NOTICE("\The [user] starts dismantling \the [src]..."), SPAN_NOTICE("You start dismantling \the [src]..."))
if(attacking_item.use_tool(src, user, 20, volume = 50))
if(anchored)
return
user.visible_message(SPAN_NOTICE("\The [user] dismantles \the [src]."), SPAN_NOTICE("You dismantle \the [src]."))
dismantle()
qdel(src)
return
// Wrench Open
else
attacking_item.play_tool_sound(get_turf(src), 50)
if(density)
user.visible_message(SPAN_NOTICE("\The [user] wrenches \the [src] open."), SPAN_NOTICE("You wrench \the [src] open."))
density = FALSE
else
user.visible_message(SPAN_NOTICE("\The [user] wrenches \the [src] closed."), SPAN_NOTICE("You wrench \the [src] closed."))
density = TRUE
update_icon()
return
// Repair
if(attacking_item.tool_behaviour == TOOL_WELDER)
var/obj/item/weldingtool/F = attacking_item
if(F.isOn())
if(health >= maxhealth)
to_chat(user, SPAN_WARNING("\The [src] does not need repairs."))
return
attacking_item.play_tool_sound(get_turf(src), 50)
if(do_after(user, 20, src))
if(health >= maxhealth)
return
user.visible_message(SPAN_NOTICE("\The [user] repairs some damage to \the [src]."), SPAN_NOTICE("You repair some damage to \the [src]."))
add_health(maxhealth * 0.5)
return
// Install
if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER)
if(!can_screwdriver)
to_chat(user, SPAN_WARNING("This [src] cannot be adjusted."))
return
if(!density)
to_chat(user, SPAN_NOTICE("You need to wrench \the [src] from back into place first."))
return
user.visible_message(anchored ? SPAN_NOTICE("\The [user] begins unscrewing \the [src].") : SPAN_NOTICE("\The [user] begins fastening \the [src].") )
attacking_item.play_tool_sound(get_turf(src), 75)
if(do_after(user, 10, src) && density)
to_chat(user, (anchored ? SPAN_NOTICE("You have unfastened \the [src] from the floor.") : SPAN_NOTICE("You have fastened \the [src] to the floor.")))
anchored = !anchored
update_icon()
return
if(attacking_item.force && (attacking_item.damtype == DAMAGE_BURN || attacking_item.damtype == DAMAGE_BRUTE))
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
visible_message(SPAN_WARNING("\The [src] has been [LAZYLEN(attacking_item.attack_verb) ? pick(attacking_item.attack_verb) : "attacked"] with \the [attacking_item] by \the [user]!"))
add_damage(attacking_item.force, attacking_item.damage_flags(), attacking_item.damtype, attacking_item.armor_penetration, attacking_item)
return
. = ..()
/obj/structure/railing/ex_act(severity)
qdel(src)
/obj/structure/railing/can_climb(var/mob/living/user, post_climb_check=0)
. = ..()
if(!.)
return
var/turf/destination_turf = get_destination_turf(user)
if(destination_turf.density)
to_chat(user, SPAN_DANGER("You can't climb into \the [destination_turf]."))
return FALSE
if(!Adjacent(destination_turf))
to_chat(user, SPAN_DANGER("You can't climb there, the way is blocked."))
return FALSE
for(var/atom/A in destination_turf.contents - src)
if(A.density && !(A.atom_flags & ATOM_FLAG_CHECKS_BORDER))
to_chat(user, SPAN_DANGER("You can't climb there, the way is blocked."))
return FALSE
return TRUE
/obj/structure/railing/do_climb(mob/living/user)
user.visible_message(SPAN_WARNING("\The [user] starts climbing over \the [src]!"))
LAZYADD(climbers, user)
if(!do_after(user, 2 SECONDS))
LAZYREMOVE(climbers, user)
return
if(!can_climb(user, post_climb_check = TRUE))
LAZYREMOVE(climbers, user)
return
var/turf/destination_turf = get_destination_turf(user)
user.forceMove(destination_turf)
user.visible_message(SPAN_WARNING("\The [user] climbs over \the [src]!"))
LAZYREMOVE(climbers, user)
if(!anchored || material.is_brittle())
add_damage(maxhealth) // Fatboy
/obj/structure/railing/proc/get_destination_turf(var/mob/user)
. = get_turf(src) // by default, we pop into the turf the railing's on
if(get_turf(user) == . || !(get_dir(src, user) & dir)) // if the user's inside our turf or behind us, go in front of us
. = get_step(src, dir)
//fence
/obj/structure/railing/fence
name = "fence"
color = "#824B28"
anchored = TRUE
/obj/structure/railing/fence/Initialize()
. = ..()
color = "#824B28"
/obj/structure/railing/fence/New(var/newloc, var/material_key = MATERIAL_WOOD)
material = material_key
..(newloc)
/obj/structure/railing/retractable
name = "\improper retractable railing"
icon = 'icons/obj/doors/retractable_railing.dmi'
icon_state = "railing1"
anchored = TRUE
maxhealth = OBJECT_HEALTH_MEDIUM
non_material_object = TRUE
can_wrench = FALSE
can_screwdriver = FALSE
var/closed_layer = ABOVE_DOOR_LAYER
var/open_layer = OPEN_DOOR_LAYER
var/icon_state_open = "railing0"
var/icon_state_opening = "railingc0"
var/icon_state_closed = "railing1"
var/icon_state_closing = "railingc1"
var/operating
/obj/structure/railing/retractable/Initialize()
. = ..()
if(dir == SOUTH)
closed_layer = ABOVE_HUMAN_LAYER
if(density)//Allows preset-open to work
layer = closed_layer
set_opacity(initial(opacity))
/obj/structure/railing/retractable/update_icon()
if(density)
icon_state = icon_state_closed
else
icon_state = icon_state_open
/obj/structure/railing/retractable/proc/toggle_state()
if(operating)
return
operating = TRUE
flick(density ? icon_state_opening : icon_state_closing, src)
playsound(get_turf(src), 'sound/machines/retractable_railing_openclose.ogg', 20)
if(density)
icon_state = icon_state_open
layer = open_layer
else
icon_state = icon_state_closed
layer = closed_layer
addtimer(CALLBACK(src, PROC_REF(finish_toggling)), 1.2 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_NO_HASH_WAIT)
return TRUE
/obj/structure/railing/retractable/proc/finish_toggling()
density = !density
operating = FALSE
/obj/structure/railing/retractable/NeighborsCheck()
return
/obj/structure/railing/retractable/flip()
return
/obj/structure/railing/retractable/open
density = FALSE