Files
Aurora.3/code/modules/shieldgen/emergency_shield.dm
T
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

413 lines
12 KiB
Plaintext

/obj/machinery/shieldgen
name = "emergency shield projector"
desc = "Used to seal minor hull breaches."
icon = 'icons/obj/machinery/shielding.dmi'
icon_state = "shieldoff"
density = TRUE
opacity = FALSE
anchored = FALSE
req_access = list(ACCESS_ENGINE)
maxhealth = 100
var/active = FALSE
/// Malfunction causes parts of the shield to slowly dissipate
var/malfunction = FALSE
var/list/deployed_shields = list()
var/list/regenerating = list()
/// Range at which it can project shields.
var/range = 4
panel_open = FALSE
var/locked = FALSE
/// Periodically recheck if we need to rebuild a shield.
var/check_delay = 30
use_power = POWER_USE_IDLE
idle_power_usage = 250 WATTS
/// Gets replaced by the # of shield objs its trying to maintain.
active_power_usage = 1000 WATTS
/obj/machinery/shieldgen/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "This machine's primary function is to project energy shields calibrated for atmospheric containment; these shields keep the air in and the vacuum out."
. += "If activated while within <b>[range]</b> tiles of a 'space' turf, an 'open space' turf, or an otherwise 'airless' turf, it will automatically project shields there, making it extremely useful for containing hull breaches."
. += "It will also project energy shields onto any engineering tape within range that has been toggled as a 'shield marker' using a multitool."
. += "ALT-click the [src] to lock or unlock it (if you have the appropriate ID access)."
/obj/machinery/shieldgen/Destroy()
collapse_shields()
return ..()
/obj/machinery/shieldgen/proc/shields_up()
if(src.active) return FALSE //If it's already turned on, how did this get called?
src.active = TRUE
update_icon()
create_shields()
var/shield_power_usage
for(var/obj/machinery/shield/shield_tile in deployed_shields)
shield_power_usage += shield_tile.shield_sustain_power
change_power_consumption(shield_power_usage, POWER_USE_ACTIVE)
update_use_power(POWER_USE_ACTIVE)
return TRUE
/obj/machinery/shieldgen/proc/shields_down()
if(!src.active) return FALSE //If it's already off, how did this get called?
src.active = FALSE
update_icon()
collapse_shields()
update_use_power(POWER_USE_IDLE)
/obj/machinery/shieldgen/proc/create_shields()
for(var/T in RANGE_TURFS(range, src))
var/turf/target_tile = T
if(locate(/obj/machinery/shield) in target_tile)
continue
var/obj/item/tape/engineering/E = locate() in target_tile
if(E?.shield_marker)
deploy_shield(target_tile)
else if(istype(target_tile,/turf/space) || istype(target_tile,/turf/simulated/open) || istype(target_tile,/turf/simulated/floor/exoplanet/asteroid/ash) || istype(target_tile,/turf/simulated/floor/airless))
if(malfunction && prob(33) || !malfunction)
deploy_shield(target_tile)
/obj/machinery/shieldgen/proc/deploy_shield(var/turf/T)
var/obj/machinery/shield/shield_tile = new /obj/machinery/shield(T)
src.deployed_shields += shield_tile
use_power_oneoff(shield_tile.shield_generate_power)
/obj/machinery/shieldgen/proc/collapse_shields()
for(var/obj/machinery/shield/shield_tile in deployed_shields)
src.deployed_shields -= shield_tile
if(!QDELETED(shield_tile))
qdel(shield_tile)
update_use_power(POWER_USE_IDLE)
/obj/machinery/shieldgen/power_change()
..()
if(!active) return
if(stat & NOPOWER)
src.shields_down()
update_icon()
/obj/machinery/shieldgen/process()
if((stat & (BROKEN|NOPOWER)) || !anchored)
if(active)
visible_message(SPAN_WARNING("\The [src] shuts down."), SPAN_NOTICE("You hear a heavy droning fade out."))
shields_down()
check_delay = initial(check_delay)
if(malfunction)
if(deployed_shields.len && prob(5))
var/obj/machinery/shield/random_failing_shield = pick(deployed_shields)
deployed_shields -= random_failing_shield
if(!QDELETED(random_failing_shield))
qdel(random_failing_shield)
else
if(check_delay <= 0)
if(shields_up())
var/new_power_usage = 0
for(var/obj/machinery/shield/shield_tile in deployed_shields)
new_power_usage += shield_tile.shield_sustain_power
if(new_power_usage != active_power_usage)
change_power_consumption(new_power_usage, POWER_USE_ACTIVE)
use_power_oneoff(0)
check_delay = initial(check_delay)
else
check_delay--
/obj/machinery/shieldgen/proc/checkhp()
if(health <= 30)
src.malfunction = TRUE
if(health <= 0)
spawn(0)
explosion(get_turf(src.loc), 0, 0, 1, 0, 0, 0)
qdel(src)
update_icon()
return
/obj/machinery/shieldgen/ex_act(severity)
switch(severity)
if(1.0)
src.health -= 75
src.checkhp()
if(2.0)
src.health -= 30
if (prob(15))
src.malfunction = 1
src.checkhp()
if(3.0)
src.health -= 10
src.checkhp()
return
/obj/machinery/shieldgen/emp_act(severity)
. = ..()
switch(severity)
if(EMP_HEAVY)
src.health /= 2 //cut health in half
malfunction = TRUE
locked = pick(0,1)
if(EMP_LIGHT)
if(prob(50))
src.health *= 0.3 //chop off a third of the health
malfunction = TRUE
checkhp()
/obj/machinery/shieldgen/attack_hand(mob/user)
if(locked)
balloon_alert(user, "locked")
return
if(panel_open)
balloon_alert(user, "panel still open")
return
if(stat & BROKEN)
balloon_alert(user, "broken")
return
if(stat & NOPOWER)
balloon_alert(user, "no power")
return
if(!anchored)
balloon_alert(user, "unanchored")
return
if(active)
user.visible_message(SPAN_NOTICE("[icon2html(src, viewers(get_turf(src)))] [user] deactivates the shield generator."), \
SPAN_NOTICE("[icon2html(src, viewers(get_turf(src)))] You deactivate the shield generator."), \
"You hear heavy droning fade out.")
shields_down()
else
user.visible_message(SPAN_NOTICE("[icon2html(src, viewers(get_turf(src)))] [user] activate the shield generator."), \
SPAN_NOTICE("[icon2html(src, viewers(get_turf(src)))] You activate the shield generator."), \
"You hear heavy droning.")
shields_up()
return
/obj/machinery/shieldgen/emag_act(var/remaining_charges, var/mob/user)
if(!malfunction)
malfunction = TRUE
update_icon()
return TRUE
/obj/machinery/shieldgen/attackby(obj/item/attacking_item, mob/user)
if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER)
update_icon()
attacking_item.play_tool_sound(get_turf(src), 50)
if(panel_open)
balloon_alert(user, "panel closed")
panel_open = FALSE
else
balloon_alert(user, "panel open")
panel_open = TRUE
else if(attacking_item.tool_behaviour == TOOL_CABLECOIL && malfunction && panel_open)
var/obj/item/stack/cable_coil/coil = attacking_item
to_chat(user, SPAN_NOTICE("You begin to replace the wires."))
//if(do_after(user, min(60, round( ((maxhealth/health)*10)+(malfunction*10) ))) //Take longer to repair heavier damage
if(attacking_item.use_tool(src, user, 30, volume = 50))
if (coil.use(1))
health = initial(health)
malfunction = FALSE
to_chat(user, SPAN_NOTICE("You repair the [src]!"))
update_icon()
else if(attacking_item.tool_behaviour == TOOL_WRENCH)
if(locked)
playsound(src, 'sound/machines/terminal/terminal_error.ogg', 25, FALSE)
balloon_alert(user, "locked")
return
if(attacking_item.use_tool(src, user, 1 SECONDS, volume = 50))
anchored = !anchored
add_fingerprint(user)
if(!anchored && active)
shields_down()
var/others_msg = anchored ? "<b>[user]</b> secures the external reinforcing bolts to the floor." : "<b>[user]</b> unsecures the external reinforcing bolts."
var/self_msg = anchored ? "You secure the external reinforcing bolts to the floor." : "You unsecure the external reinforcing bolts."
user.visible_message(others_msg, SPAN_NOTICE(self_msg), SPAN_NOTICE("You hear a ratcheting noise."))
update_icon()
return
else
..()
/obj/machinery/shieldgen/AltClick(mob/user)
if(Adjacent(user))
add_fingerprint(user)
if(allowed(user))
locked = !locked
if(locked)
playsound(src, 'sound/machines/terminal/terminal_button03.ogg', 35, FALSE)
else
playsound(src, 'sound/machines/terminal/terminal_button01.ogg', 35, FALSE)
balloon_alert(user, locked ? "locked" : "unlocked")
else
playsound(src, 'sound/machines/terminal/terminal_error.ogg', 25, FALSE)
balloon_alert(user, "access denied")
/obj/machinery/shieldgen/update_icon()
ClearOverlays()
if(anchored)
AddOverlays("+bolts")
if(active && !(stat & NOPOWER))
src.icon_state = malfunction ? "shieldonbr":"shieldon"
else
src.icon_state = malfunction ? "shieldoffbr":"shieldoff"
/obj/machinery/shield
name = "emergency energy shield"
desc = "An energy shield used to contain hull breaches."
icon = 'icons/effects/effects.dmi'
icon_state = "shield-old"
density = TRUE
opacity = FALSE
anchored = TRUE
unacidable = TRUE
atmos_canpass = CANPASS_NEVER
maxhealth = 75
/// How much power we use when first creating the shield
var/shield_generate_power = 75 KILO WATTS
/// How much power we use when just being sustained.
var/shield_sustain_power = 30 KILO WATTS
/obj/machinery/shield/malfai
name = "emergency forcefield"
desc = "A forcefield which seems to be projected by the station's emergency atmosphere containment field."
health = 100
/obj/machinery/shield/malfai/New()
..()
desc = "A forcefield which seems to be projected by the [station_name(TRUE)]'s emergency atmosphere containment field."
/obj/machinery/shield/malfai/process()
health -= 0.5 // Slowly lose integrity over time
check_failure()
/obj/machinery/shield/proc/check_failure()
var/health_percentage = (health / initial(health)) * 100
switch(health_percentage)
if(-INFINITY to 25)
if(alpha != 150)
animate(src, 1 SECOND, alpha = 150)
if(26 to 50)
if(alpha != 175)
animate(src, 1 SECOND, alpha = 175)
if(51 to 75)
if(alpha != 210)
animate(src, 1 SECOND, alpha = 210)
if(76 to 90)
if(alpha != 230)
animate(src, 1 SECOND, alpha = 230)
if(91 to INFINITY)
if(alpha != initial(alpha))
animate(src, 1 SECOND, alpha = initial(alpha))
if(health <= 0)
visible_message(SPAN_NOTICE("\The [src] dissipates!"))
qdel(src)
return
/obj/machinery/shield/New()
src.set_dir(pick(1,2,3,4))
..()
update_nearby_tiles(need_rebuild=1)
/obj/machinery/shield/Destroy()
opacity = FALSE
density = FALSE
update_nearby_tiles()
return ..()
/obj/machinery/shield/CanPass(atom/movable/mover, turf/target, height, air_group)
if(mover?.movement_type & PHASING)
return TRUE
if(!height || air_group)
return FALSE
else
return ..()
/obj/machinery/shield/attackby(obj/item/attacking_item, mob/user)
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
user.do_attack_animation(src, attacking_item)
//Calculate damage
var/aforce = attacking_item.force
if(attacking_item.damtype == DAMAGE_BRUTE || attacking_item.damtype == DAMAGE_BURN)
health -= aforce
//Play a fitting sound
playsound(src.loc, 'sound/effects/EMPulse.ogg', 75, 1)
check_failure()
..()
/obj/machinery/shield/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
health -= hitting_projectile.get_structure_damage()
check_failure()
opacity = TRUE
addtimer(CALLBACK(src, PROC_REF(update_opacity), FALSE), 2 SECONDS)
/obj/machinery/shield/proc/update_opacity(var/new_opacity)
if(src)
opacity = new_opacity
/obj/machinery/shield/ex_act(severity)
switch(severity)
if(1.0)
if (prob(75))
qdel(src)
if(2.0)
if (prob(50))
qdel(src)
if(3.0)
if (prob(25))
qdel(src)
return
/obj/machinery/shield/emp_act(severity)
. = ..()
switch(severity)
if(EMP_HEAVY)
qdel(src)
if(EMP_LIGHT)
if(prob(50))
qdel(src)
/obj/machinery/shield/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
//Let everyone know we've been hit!
visible_message(SPAN_NOTICE("<B>\[src] was hit by [hitting_atom].</B>"))
//Super realistic, resource-intensive, real-time damage calculations.
var/tforce = 0
if(ismob(hitting_atom))
tforce = 40
else if(isobj(hitting_atom))
var/obj/O = hitting_atom
tforce = O.throwforce
src.health -= tforce
//This seemed to be the best sound for hitting a force field.
playsound(src.loc, 'sound/effects/EMPulse.ogg', 100, 1)
check_failure()
//The shield becomes dense to absorb the blow.. purely asthetic.
opacity = TRUE
addtimer(CALLBACK(src, PROC_REF(update_opacity), FALSE), 2 SECONDS)
..()
return