Engineering spriting (detailing & port), portable shield balance, bugfixes (#21862)

EXTREMELY MINOR MISCELLANEOUS CHANGES TEEHEE

changes:
- imageadd: "Adds +bolted, +welded, +lights overlays to several large
machines for easier visual identification of whether or not clicking on
that emitter will kill you or not, etc."
- imageadd: "Modifies the base of the Shield Wall Generator sprite to
better match that of the Emergency Shield Projector (and so they can use
the same overlays mentioned above)."
- imageadd: "Tweaks the Emitter's directional sprites slightly so that
the base doesn't seem to change shape quite so drastically when
rotated."
- imageadd: "Ports very pretty space heater sprites made by Wallemations
and vinylspiders from /tg/."
  - imageadd: "Adds unique sprites for Old and Heavy-Duty power cells."
- balance: "Increases power draw and range of Emergency Shield
Projectors."
  - balance: "Increases power draw of Shield Wall Generators."
- balance: "Greatly reduces time for debugger to fix APCs corrupted by
electrical storms."
- balance: "Increases multi-z shield capacitor reserve charge and
maximum throughput, so that high-strength shields can actually be run
(at high power draw)."
- balance: "Tweaks the INDRA power output formula to scale better with
increasing temperature."
- balance: "Slight adjustments to space heater efficiency formula with a
friendly handwave to entropy."
- balance: "Old power cells have a 5% chance to be defective and have
slightly lower maxcharge than standard cells."
  - qol: "Adds extended examine hints to engineering objects."
- qol: "Extends list of object types which INDRA core field will
interact peacefully with."
- qol: "Improved space heater UX (chat msgs changed to balloon alerts,
audible feedback, etc.)"
  - code_imp: "Updates many code comments to use DMdocs."
- code_imp: "Consolidates various shield generation machine sprites in
scattered across multiple files into a single shield generator machines
sprite file."
  - bugfix: "Fixes Emergency Shield Projector hard del issue."
- bugfix: "Emergency Shield Projector now shuts down as intended if the
area's APC gets completely drained."
- bugfix: "Firing the gyrotron into the INDRA while no reactants are
present will no longer generate heat."
  - bugfix: "Fixes INDRA runtime."
- bugfix: "Fixes Power Monitoring app not displaying grid sensors when
run from a modular computer held in-hand."
  
  Bolted/welded down Emitter:
<img width="130" height="101" alt="Screenshot 2026-02-13 123842"
src="https://github.com/user-attachments/assets/8357b5ea-77e6-46f7-b4a0-724aaa141cef"
/>

Modified shieldwall sprites (with old ones for comparison)
<img width="667" height="103" alt="Screenshot 2026-02-13 111422"
src="https://github.com/user-attachments/assets/2b1e849e-f955-49cb-b094-0bef098a754f"
/>

Shields actually useful for damage control now (though they'll blow
through APC charge if you go as crazy as this):
<img width="804" height="891" alt="Screenshot 2026-02-13 124245"
src="https://github.com/user-attachments/assets/39268b42-5abe-4a3b-ba8a-a18bdc63873e"
/>

### Asset Licenses
The following assets that **have not** been created by myself are
included in this PR:

| Path | Original Author | License |
| --- | --- | --- |
| icons/obj/atmos.dmi (all "sheater*") | [Wallemations, VinylSpiders
(/tg/station #93800)](https://github.com/tgstation/tgstation/pull/93800)
| CC-BY-SA |

---------

Signed-off-by: Batrachophreno <Batrochophreno@gmail.com>
Co-authored-by: SleepyGemmy <99297919+SleepyGemmy@users.noreply.github.com>
This commit is contained in:
Batrachophreno
2026-03-11 16:39:54 +00:00
committed by GitHub
co-authored by SleepyGemmy
parent adeaaaee4a
commit 0c342878e5
27 changed files with 523 additions and 353 deletions
+2 -2
View File
@@ -114,9 +114,9 @@
/singleton/cargo_item/shieldgenerator
category = "security"
name = "Shield Generator"
name = "Shield Wall Generator"
supplier = "nanotrasen"
description = "A shield generator."
description = "A shield wall generator."
price = 550
items = list(
/obj/machinery/shieldwallgen
@@ -60,7 +60,7 @@
if(!T) // Safety check
return
for(var/obj/machinery/power/sensor/S in SSmachinery.all_sensors)
if(AreConnectedZLevels(S.z, computer.z) || (S.long_range))
if(AreConnectedZLevels(S.z, T.z) || (S.long_range))
if(S.name_tag == "#UNKN#") // Default name. Shouldn't happen!
warning("Powernet sensor with unset ID Tag! [S.x]X [S.y]Y [S.z]Z")
else
+9 -6
View File
@@ -22,8 +22,10 @@
var/tmp/list/climbers
// An override of turf/Enter() to make it so that magboots allow you to stop
// falling off the damned rock.
/**
* An override of turf/Enter() to make it so that magboots allow you to stop
* falling off the damned rock.
*/
/turf/simulated/open/Enter(mob/living/carbon/human/mover, atom/oldloc)
if (istype(mover) && isturf(oldloc))
if (mover.Check_Shoegrip(FALSE) && mover.can_fall(below, src))
@@ -74,10 +76,11 @@
return catwalk
return src
// Add a falling atom by default. Even if it's not an atom that can actually fall.
// SSfalling will check this on its own and remove if necessary. This is saner, as it
// centralizes control to SSfalling.
/**
* Add a falling atom by default. Even if it's not an atom that can actually fall.
* SSfalling will check this on its own and remove if necessary. This is saner, as it
* centralizes control to SSfalling.
*/
/turf/simulated/open/Entered(atom/movable/mover)
..()
if (is_hole)
+5 -4
View File
@@ -763,20 +763,21 @@ ABSTRACT_TYPE(/obj/machinery/power/apc)
else if (istype(attacking_item, /obj/item/debugger))
if(emagged || hacker || infected)
to_chat(user, SPAN_WARNING("There is a software error with the device. Attempting to fix..."))
if(attacking_item.use_tool(src, user, 50, volume = 50))
if(attacking_item.use_tool(src, user, 1 SECONDS, volume = 50))
to_chat(user, SPAN_NOTICE("Problem diagnosed, searching for solution..."))
if(attacking_item.use_tool(src, user, 150, volume = 50))
if(attacking_item.use_tool(src, user, 3 SECONDS, volume = 50))
to_chat(user, SPAN_NOTICE("Solution found. Applying fixes..."))
if(attacking_item.use_tool(src, user, 300, volume = 50))
if(attacking_item.use_tool(src, user, 8 SECONDS, volume = 50))
if(prob(15))
to_chat(user, SPAN_WARNING("Error while applying fixes. Please try again."))
return
to_chat(user, SPAN_NOTICE("Applied default software. Restarting APC..."))
if(attacking_item.use_tool(src, user, 50, volume = 50))
if(attacking_item.use_tool(src, user, 3 SECONDS, volume = 50))
to_chat(user, SPAN_NOTICE("APC Reset. Fixes applied."))
if(hacker)
hacker.hacked_apcs -= src
hacker = null
ClearOverlays() // Manually clear overlays first.
update_icon()
if(emagged)
emagged = FALSE
+14 -5
View File
@@ -3,7 +3,7 @@
#define FUSION_REACTANT_CAP 10000
#define FUSION_WARNING_DELAY 20
#define FUSION_BLACKBODY_MULTIPLIER 350
#define FUSION_INTEGRITY_RATE_LIMIT 0.11
#define FUSION_INTEGRITY_RATE_LIMIT 0.07
#define FUSION_TICK_MAX_TEMP_CHANGE 0.3
/obj/effect/fusion_em_field
@@ -63,7 +63,9 @@
/obj/structure/cable,
/obj/machinery/atmospherics,
/obj/machinery/air_sensor,
/obj/machinery/camera
/obj/machinery/camera,
/obj/item/tape,
/obj/machinery/shield
)
var/light_min_range = 2
@@ -104,8 +106,8 @@
var/pause_rupture = FALSE
var/power_log_base = 1.35
var/power_multiplier = 3.5
var/power_power = 2.9
var/power_multiplier = 3.7
var/power_power = 3.05
/obj/effect/fusion_em_field/proc/UpdateVisuals()
//Take the particle system and edit it
@@ -214,7 +216,10 @@
field_strength_power_multiplier = max((owned_core.field_strength ** 1.2) / 100, 1)
// Dump power to our powernet.
power_output = ((log(power_log_base, plasma_temperature) * power_multiplier) ** power_power) * field_strength_power_multiplier
if(plasma_temperature > 0)
power_output = ((log(power_log_base, plasma_temperature) * power_multiplier) ** power_power) * field_strength_power_multiplier
else
power_output = 0
output_archive_5 = output_archive_4
output_archive_4 = output_archive_3
output_archive_3 = output_archive_2
@@ -436,6 +441,10 @@
change_size(calc_size)
/obj/effect/fusion_em_field/proc/AddEnergy(a_energy, a_plasma_temperature)
// If there are no reactants, there's nothing to heat. Ignore.
if(!length(reactants))
return
// Boost gyro effects at low temperatures for faster startup
if(plasma_temperature <= 5000)
a_energy = a_energy * 32
@@ -1,7 +1,7 @@
/obj/machinery/containment_field
name = "containment field"
desc = "An energy field."
icon = 'icons/obj/machinery/field_generator.dmi'
icon = 'icons/effects/effects.dmi'
icon_state = "contain_f"
anchored = 1
density = 0
@@ -95,6 +95,13 @@
if(active && powernet && POWER_AVAIL(src))
AddOverlays(emissive_appearance(icon, "[icon_state]_lights"))
AddOverlays("[icon_state]_lights")
if(anchored)
AddOverlays("+bolts")
if(state == 2)
AddOverlays("+welding")
var/image/lights_image = image(icon, null, "+lights")
lights_image.plane = ABOVE_LIGHTING_PLANE
AddOverlays(lights_image)
/obj/machinery/power/emitter/attack_hand(mob/user)
add_fingerprint(user)
@@ -13,7 +13,7 @@ field_generator power level display
/obj/machinery/field_generator
name = "field generator"
desc = "A large thermal battery that projects a high amount of energy when powered."
icon = 'icons/obj/machinery/field_generator.dmi'
icon = 'icons/obj/machinery/shielding.dmi'
icon_state = "field_gen"
anchored = 0
density = 1
+265 -247
View File
@@ -1,3 +1,255 @@
/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 = TRUE
anchored = FALSE
req_access = list(ACCESS_ENGINE)
var/health = 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 = 60
use_power = POWER_USE_OFF
idle_power_usage = 250
/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(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_idle_power
change_power_consumption(shield_power_usage, POWER_USE_IDLE)
update_use_power(POWER_USE_IDLE)
/obj/machinery/shieldgen/proc/shields_down()
if(!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_OFF)
/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)
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)
deployed_shields -= shield_tile
if(!QDELETED(shield_tile))
qdel(shield_tile)
change_power_consumption(250, POWER_USE_IDLE)
update_use_power(POWER_USE_IDLE)
/obj/machinery/shieldgen/power_change()
..()
if(!active) return
if(stat & NOPOWER)
collapse_shields()
else
create_shields()
update_icon()
/obj/machinery/shieldgen/process()
if(stat & NOPOWER)
if(active)
visible_message(SPAN_WARNING("\The [src] shuts down due to a lack of power."), SPAN_NOTICE("You hear a heavy droning fade out."))
active = FALSE
collapse_shields()
return
if(malfunction)
if(deployed_shields.len && prob(5))
qdel(pick(deployed_shields))
else
if (check_delay <= 0)
create_shields()
var/new_power_usage = 0
for(var/obj/machinery/shield/shield_tile in deployed_shields)
new_power_usage += shield_tile.shield_idle_power
if (new_power_usage != idle_power_usage)
change_power_consumption(new_power_usage, POWER_USE_IDLE)
use_power_oneoff(0)
check_delay = 30
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)
to_chat(user, SPAN_WARNING("The machine is locked!"))
return
if(panel_open)
to_chat(user, SPAN_WARNING("The panel must be closed before operating this machine."))
return
if (src.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.")
src.shields_down()
else
if(anchored)
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.")
src.shields_up()
else
to_chat(user, SPAN_WARNING("The device must first be secured to the floor."))
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)
to_chat(user, SPAN_NOTICE("You close the panel."))
panel_open = FALSE
else
to_chat(user, SPAN_NOTICE("You open the panel and expose the wiring."))
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)
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
to_chat(user, SPAN_WARNING("Access denied."))
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."
@@ -8,9 +260,12 @@
anchored = TRUE
unacidable = TRUE
atmos_canpass = CANPASS_NEVER
var/health = 75 //The shield can only take so much beating (prevents perma-prisons)
var/shield_generate_power = 2500 //how much power we use when regenerating
var/shield_idle_power = 500 //how much power we use when just being sustained.
/// The shield can only take so much beating (prevents perma-prisons)
var/health = 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_idle_power = 30 KILO WATTS
/obj/machinery/shield/malfai
name = "emergency forcefield"
@@ -90,8 +345,12 @@
health -= hitting_projectile.get_structure_damage()
check_failure()
opacity = 1
spawn(20) if(src) opacity = FALSE
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)
@@ -117,7 +376,6 @@
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>"))
@@ -139,247 +397,7 @@
//The shield becomes dense to absorb the blow.. purely asthetic.
opacity = TRUE
spawn(20) if(src) opacity = FALSE
addtimer(CALLBACK(src, PROC_REF(update_opacity), FALSE), 2 SECONDS)
..()
return
/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)
var/health = 100
var/active = FALSE
// Malfunction causes parts of the shield to slowly dissapate
var/malfunction = FALSE
var/list/deployed_shields = list()
var/list/regenerating = list()
panel_open = FALSE
var/locked = FALSE
var/check_delay = 60 //periodically recheck if we need to rebuild a shield
use_power = POWER_USE_OFF
idle_power_usage = 0
/obj/machinery/shieldgen/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "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(active) return FALSE //If it's already turned on, how did this get called?
src.active = 1
update_icon()
create_shields()
var/shield_power_usage
for(var/obj/machinery/shield/shield_tile in deployed_shields)
shield_power_usage += shield_tile.shield_idle_power
change_power_consumption(shield_power_usage, POWER_USE_IDLE)
update_use_power(POWER_USE_IDLE)
/obj/machinery/shieldgen/proc/shields_down()
if(!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_OFF)
/obj/machinery/shieldgen/proc/create_shields()
for(var/T in RANGE_TURFS(2, 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/S = new /obj/machinery/shield(T)
deployed_shields += S
use_power_oneoff(S.shield_generate_power)
/obj/machinery/shieldgen/proc/collapse_shields()
for(var/obj/machinery/shield/shield_tile in deployed_shields)
qdel(shield_tile)
/obj/machinery/shieldgen/power_change()
..()
if(!active) return
if (stat & NOPOWER)
collapse_shields()
else
create_shields()
update_icon()
/obj/machinery/shieldgen/process()
if (!active || (stat & NOPOWER))
return
if(malfunction)
if(deployed_shields.len && prob(5))
qdel(pick(deployed_shields))
else
if (check_delay <= 0)
create_shields()
var/new_power_usage = 0
for(var/obj/machinery/shield/shield_tile in deployed_shields)
new_power_usage += shield_tile.shield_idle_power
if (new_power_usage != idle_power_usage)
change_power_consumption(new_power_usage, POWER_USE_IDLE)
use_power_oneoff(0)
check_delay = 60
else
check_delay--
/obj/machinery/shieldgen/proc/checkhp()
if(health <= 30)
src.malfunction = 1
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)
to_chat(user, SPAN_WARNING("The machine is locked!"))
return
if(panel_open)
to_chat(user, SPAN_WARNING("The panel must be closed before operating this machine."))
return
if (src.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.")
src.shields_down()
else
if(anchored)
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.")
src.shields_up()
else
to_chat(user, SPAN_WARNING("The device must first be secured to the floor."))
return
/obj/machinery/shieldgen/emag_act(var/remaining_charges, var/mob/user)
if(!malfunction)
malfunction = TRUE
update_icon()
return 1
/obj/machinery/shieldgen/attackby(obj/item/attacking_item, mob/user)
if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER)
attacking_item.play_tool_sound(get_turf(src), 50)
if(panel_open)
to_chat(user, SPAN_NOTICE("You close the panel."))
panel_open = FALSE
else
to_chat(user, SPAN_NOTICE("You open the panel and expose the wiring."))
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)
to_chat(user, "The bolts are covered, unlocking this would retract the covers.")
return
if(anchored)
attacking_item.play_tool_sound(get_turf(src), 100)
to_chat(user, SPAN_NOTICE("You unsecure the [src] from the floor!"))
if(active)
to_chat(user, SPAN_NOTICE("The [src] shuts off!"))
src.shields_down()
anchored = FALSE
else
if(istype(get_turf(src), /turf/space)) return //No wrenching these in space!
attacking_item.play_tool_sound(get_turf(src), 100)
to_chat(user, SPAN_NOTICE("You secure the [src] to the floor!"))
anchored = TRUE
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
to_chat(user, SPAN_WARNING("Access denied."))
playsound(src, 'sound/machines/terminal/terminal_error.ogg', 25, FALSE)
balloon_alert(user, "access denied!")
return
/obj/machinery/shieldgen/update_icon()
if(active && !(stat & NOPOWER))
src.icon_state = malfunction ? "shieldonbr":"shieldon"
else
src.icon_state = malfunction ? "shieldoffbr":"shieldoff"
return
+1 -1
View File
@@ -4,7 +4,7 @@
/obj/effect/energy_field
name = "energy shield"
desc = "A strong field of energy, capable of blocking anything as long as it's active."
icon = 'icons/obj/machinery/shielding.dmi'
icon = 'icons/effects/effects.dmi'
icon_state = "shield_normal"
alpha = 0
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+5 -4
View File
@@ -14,11 +14,11 @@
var/stored_charge = 0
var/last_stored_charge = 0
var/time_since_fail = 100
var/max_charge = 8e6 //8 MJ
var/max_charge_rate = 400000 //400 kW
var/max_charge = 2e8 //200 MJ
var/max_charge_rate = 9000000 //9 MW
var/locked = FALSE
var/charge_rate = 100000 //100 kW
var/charge_rate = 100000 //100 kW
var/obj/machinery/shield_gen/owned_gen
/obj/machinery/shield_capacitor/Initialize()
@@ -138,5 +138,6 @@
else
..()
/// Horizon-specific non-variant, for now.
/obj/machinery/shield_capacitor/multiz
max_charge_rate = 1250000
+27 -25
View File
@@ -3,17 +3,17 @@
#define POWER_ACTIVE 2
/obj/machinery/shieldwallgen
name = "shield generator"
name = "shield wall generator"
desc = "A portable shield generator, capable of casting a shield to another powered generator in range."
icon = 'icons/obj/stationobjs.dmi'
icon_state = "Shield_Gen"
icon = 'icons/obj/machinery/shielding.dmi'
icon_state = "shieldwalloff"
anchored = FALSE
density = TRUE
req_access = list(ACCESS_ENGINE_EQUIP)
/// Range at which it can pair with another shield wall generator (must be this many spaces BETWEEN THEM at maximum)
var/range = 9
var/power_state = FALSE
var/is_powered = FALSE
var/wrenched = FALSE
var/locked = TRUE
var/storedpower = 0
obj_flags = OBJ_FLAG_CONDUCTABLE
@@ -21,36 +21,40 @@
//There have to be at least two posts, so these are effectively doubled
///How much power is drawn from powernet. Increase this to allow the generator to sustain longer shields, at the cost of more power draw.
var/power_draw = 30 KILO WATTS
var/max_stored_power = 50 KILO WATTS
var/power_draw = 200 KILO WATTS
var/max_stored_power = 1000 KILO WATTS
/// Draws directly from power net. Does not use APC power.
use_power = POWER_USE_OFF
/obj/machinery/shieldwallgen/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "A shield wall generator can pair with another from up to <b>[range]</b> tiles away (maximum wall length of <b>[range - 1]</b>)."
. += "ALT-click the [src] to lock or unlock it (if you have the appropriate ID access)."
/obj/machinery/shieldwallgen/active
power_state = POWER_STARTING
is_powered = TRUE
wrenched = TRUE
anchored = TRUE
locked = FALSE
icon_state = "Shield_Gen +a"
icon_state = "shieldwallon"
storedpower = 9000000
/obj/machinery/shieldwallgen/update_icon()
ClearOverlays()
if(power_state >= POWER_STARTING)
icon_state = "Shield_Gen +a"
icon_state = "shieldwallon"
else
icon_state = "Shield_Gen"
icon_state = "shieldwalloff"
if(anchored)
AddOverlays("+bolts")
/obj/machinery/shieldwallgen/attack_hand(mob/user)
if(!wrenched)
if(!anchored)
to_chat(user, SPAN_WARNING("The shield generator needs to be firmly secured to the floor first."))
return TRUE
if(locked && !issilicon(user))
to_chat(user, SPAN_WARNING("The controls are locked!"))
playsound(src, 'sound/machines/terminal/terminal_error.ogg', 25, FALSE)
balloon_alert(user, "locked!")
return TRUE
if(!is_powered)
to_chat(user, SPAN_WARNING("The shield generator needs to be powered by wire underneath."))
@@ -105,7 +109,7 @@
storedpower = clamp(storedpower, 0, max_stored_power)
if(power_state == POWER_STARTING)
if(!wrenched)
if(!anchored)
power_state = POWER_INACTIVE
return
addtimer(CALLBACK(src, PROC_REF(setup_field), 1), 1)
@@ -133,7 +137,7 @@
oNSEW = REVERSE_DIR(NSEW)
for(var/dist = 0, dist <= 9, dist++) // checks out to 8 tiles away for another generator
for(var/dist = 0, dist <= range, dist++) // checks out to 8 tiles away for another generator
T = get_step(T2, NSEW)
T2 = T
steps += 1
@@ -162,15 +166,14 @@
if(power_state)
to_chat(user, SPAN_WARNING("You cannot unsecure \the [src] while it's active."))
return
wrenched = !wrenched
anchored = wrenched
attacking_item.play_tool_sound(get_turf(src), 75)
add_fingerprint(user)
var/others_msg = wrenched ? "<b>[user]</b> secures the external reinforcing bolts to the floor." : "<b>[user]</b> unsecures the external reinforcing bolts."
var/self_msg = wrenched ? "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."))
return
if(attacking_item.use_tool(src, user, 1 SECONDS, volume = 50))
anchored = !anchored
add_fingerprint(user)
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
return ..()
/obj/machinery/shieldwallgen/AltClick(mob/user)
@@ -184,7 +187,6 @@
playsound(src, 'sound/machines/terminal/terminal_button01.ogg', 35, FALSE)
balloon_alert(user, locked ? "locked" : "unlocked")
else
to_chat(user, SPAN_WARNING("Access denied."))
playsound(src, 'sound/machines/terminal/terminal_error.ogg', 25, FALSE)
balloon_alert(user, "access denied!")
return