Ship combat rework (#22566)

Makes ship combat significantly more destructive (and a bit deadlier),
especially with non-explosive projectiles.

**Additions:**
- A shell that penetrates a solid (dense) structure will cause spalling.
This spall does 2-30 damage, depending on range and has a high embed
chance. These embedded chunks are large enough to be ripped out by hand.
(But you probably shouldn't). The pilot's suit has 30 ballistic armor,
so a prepared bridge crew can survive a lot of spall.
- Adds a gun that shoots any ship weapon that admins can spawn. Useful
for testing, or for adminbus where they don't want to have to spawn a
whole overmap ship.
- Made several structures destructible, vending machines, computer
frames, grilles (these were just bugged) and watertanks (and their
children). These structures could just block infinite shots and were
common enough to serve as effective armor for the horizon. These now
spawn shrapnel (of their material if it's set), in addition to sheets of
steel when destroyed.
- Added support for negative maim chance. This allows chosen projectiles
to be prevented from decapitating. No more accidentally gibbing the
bridge crew's heads. (Unless they get hit with something really really
big).

**Balance:**
- All non-explosive anti ship rounds got significant buffs. Typically
2-3x Anti-Material numbers.
- Armour piercing anti-ship rounds got massively increased penetration
stats. Typically 4-8 from 1-2.
- Doors now get destroyed in less hits, if they are hit by anti-material
projectiles. Hits to destroy -= Antimaterial / 2
- Francisca frag shells now shoot less fragments but deal more damage.
394 projectiles for every shot was a bit laggy, now it's only 280.

**Shields:**
Shields now take more power. 13-14 megawatts for a 10 strength shield.
Just inside the ship's generation capacity with both the INDRA and
Supermatter running.

Instead of deleting any projectile they are hit by, shields now have
different failure modes.
- Firstly, a piercing projectile has it's penetration reduced by the
shield strength. Eg A shot that would go through 10 walls, only
penetrates 4 after going through a 6 strength shield.
- Secondly, a projectile has it's damage multiplied by 1 - shield
strength / 10, then has shield strength subtracted from it's damage. Eg.
A damage 100 projectile, hitting a 5 strength shield, has it's damaged
reduced to 50, (100 * 0.5), then to 45. (50-5)
- Thirdly, an explosive projectile (that does not penetrate per the
first step) does not explode, but instead deals it's full damage to the
shield.

Hits to the shield now also damage the field itself, not just the shield
tile. As such, a very big hit on the shield can bring the whole thing
down, requiring an engineer to reset it.

Shields can now be upgraded with research components. Additionally,
shield generators can now support more than one capacitor. The
roundstart capacitor on the Horizon is only good enough to support a 6
strength shield.
The upgrades increase efficiency, strength loss over time and charge
speed.

**Overmap Targetting**
Previously, shells would spawn within 20 tiles of their target and aim
directly forwards. This was bugged for shots with burst, which would
instead all aim inwards and mostly miss. This is the reason the Grauwolf
(in addition to the explosion bug below) and Fransisca felt so useless,
only one of every shell in a burst would hit.

Shots now spawn at the map edge, based on the orientation of the
projectile and ship. Diagonals are now accounted for.
Shots are then aimed directly at the target selected on the console,
with 3 degrees of dispersion.

**Bugfixes:**
- Fixes lots of explosions that didn't pass a turf to the explosions
subsystem. This caused the explosion not to happen if the thing hit got
qdeled before the subsystem could get the turf to spawn the explosion
on.
- Embedded shrapnel no longer gets the initial projectile name, this
prevents it getting named shrapnelshrapnel.
- Fixes a bunch of projectile piercing bugs, mostly with grilles and
windows. Shots now go past window frames without hitting them.
- Fixes shields not having a click delay when attacked.

---------

Signed-off-by: FenodyreeAv <fenodyree.av@gmail.com>
Co-authored-by: Matt Atlas <mattiathebest2000@hotmail.it>
This commit is contained in:
FenodyreeAv
2026-06-03 16:52:13 +00:00
committed by GitHub
co-authored by Matt Atlas
parent fb3189f542
commit cf585ef505
49 changed files with 859 additions and 251 deletions
+12 -11
View File
@@ -5,11 +5,11 @@
/// The actual strength of the field.
var/field_strength = 0
/// Current strengthening rate of a single field.
var/strengthen_rate = 0.2
/// Maximum rate by which an energy field can be strengthened.
var/max_strengthen_rate = 0.5
/// The percentage of the shield strength that needs to be replaced each second
var/dissipation_rate = 0.030
var/strengthen_rate = 0.1
/// Maximum rate by which an energy field can be strengthened. This is set by shield components in RefreshComponents, the initial rate with standard components is 0.5 (2 Manipulators, Rating 1: give 0.1 each)
var/max_strengthen_rate = 0.3
/// The percentage of the shield strength that needs to be replaced each second. This is modified by shield components in RefreshComponents, the initial rate with standard components is 0.030. (2 Micro Lasers, Rating 1: subtract 0.003 each)
var/dissipation_rate = 0.036
/// An energy field will dissipate by at least this rate in renwicks per field tile (otherwise field would never dissipate completely as dissipation is a percentage)
var/min_dissipation = 0.01
/// Our target field strength.
@@ -18,8 +18,8 @@
var/max_field_strength = 10
/// The time passed since the last "fail", AKA losing charge faster than you can replenish it.
var/time_since_fail = 100
/// How many renwicks per watt.
var/energy_conversion_rate = 0.00006
/// How many renwicks per watt. This is modified by shield components in RefreshComponents, the initial rate with standard components is 0.00005 (2 Capacitors, Rating 1: give 0.00001 each)
var/energy_conversion_rate = 0.00003
/// If the field is strong, then the energy field objects will turn dense.
var/strong_field = FALSE
@@ -32,6 +32,7 @@
* @assumed_charge: the charge that is given to this energy field. You have to get the required energy first, if you want a balanced field.
*/
/datum/energy_field/proc/handle_strength(assumed_charge = 0)
assumed_charge = max(assumed_charge, 0)
if(length(field))
time_since_fail++
//the amount of renwicks that the generator can add this tick, over the entire field
@@ -40,7 +41,7 @@
var/renwick_increase_per_field = total_renwick_increase / length(field) //per field tile
var/renwick_upkeep_per_field = max(field_strength * dissipation_rate, min_dissipation)
var/amount_to_strengthen = renwick_increase_per_field - renwick_upkeep_per_field
field_strength = min(field_strength + amount_to_strengthen, max_field_strength)
field_strength = max(min(field_strength + amount_to_strengthen, max_field_strength), 0)
if(field_strength < 1)
if(strong_field)
@@ -61,7 +62,7 @@
var/required_energy = 0
if(length(field))
var/renwick_upkeep_per_field = max(field_strength * dissipation_rate, min_dissipation)
var/target_renwick_increase = min(target_field_strength - field_strength, strengthen_rate) + renwick_upkeep_per_field //per field tile
var/target_renwick_increase = max(min(target_field_strength - field_strength, strengthen_rate) + renwick_upkeep_per_field, 0) //per field tile
required_energy = length(field) * target_renwick_increase / energy_conversion_rate
return required_energy
@@ -100,8 +101,8 @@
data["average_field"] = round(field_strength, 0.01)
data["progress_field"] = (target_field_strength ? round(100 * field_strength / target_field_strength, 0.1) : "NA")
data["power_take"] = round(length(field) * max(field_strength * dissipation_rate, min_dissipation) / energy_conversion_rate)
data["shield_power"] = round(length(field) * min(strengthen_rate, target_field_strength - field_strength) / energy_conversion_rate)
data["power_take"] = round(length(field) * max(field_strength * dissipation_rate, min_dissipation) / energy_conversion_rate / 1000) //Divide by 100 to convert to kW
data["shield_power"] = round(max(length(field) * min(strengthen_rate, target_field_strength - field_strength) / energy_conversion_rate / 1000, 0)) //Divide by 100 to convert to kW
data["strengthen_rate"] = (strengthen_rate * 10)
data["max_strengthen_rate"] = (max_strengthen_rate * 10)
data["target_field_strength"] = target_field_strength
+64 -3
View File
@@ -18,6 +18,7 @@
var/ticks_recovering = 10
/// If strength goes is 1 or above, this is set to TRUE, this is to prevent flickering and animate being called constantly
var/is_strong = FALSE
pass_flags_self = PASSSHIELD //Currently only ship weapons have this flag.
atmos_canpass = CANPASS_ALWAYS
@@ -42,6 +43,7 @@
energy_field.remove_individual_field(src)
/obj/effect/energy_field/attackby(obj/item/attacking_item, mob/user)
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
user.do_attack_animation(src, attacking_item)
if(attacking_item.force < 10)
user.visible_message(SPAN_WARNING("[user] harmlessly attacks \the [src] with \the [attacking_item]."),
@@ -65,17 +67,76 @@
damage_field(0.5 + severity)
/obj/effect/energy_field/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
var/initial_damage = hitting_projectile.get_structure_damage()
/**
* The shield has three possible interactions with a projectile:
*
* If the projectile is piercing (penetrating >= 1 and pierce_flags = PASSSHIELD), the shield will decrease what it can subsequently penetrate.
* The projectile will have it's pierces increased by half the shield's strength. If a projectile's pierces is greater than it's penetrating, it stops.
*
* Eg. A penetrating 10 shot hits a strength 10 shield. It will subsequently go through 4 additional walls (10/2 = 5, +1 for the shield tile), instead of the 10 it would originally.
*/
if(piercing_hit)
hitting_projectile.pierces += round((energy_field.field_strength / 2))
if(hitting_projectile.pierces < hitting_projectile.penetrating)
visible_message(SPAN_WARNING("\The [src] flickers and fails as it is penetrated by the \the [hitting_projectile]."))
damage_field(initial_damage / 20) //The shield takes less damage if it is penetrated like this.
return BULLET_ACT_FORCE_PIERCE
/**
* A projectile without pass_flags = PASSSHIELD will have its damage reduced by the shield, if its damage is reduced to zero it is blocked.
* A strong projectile hitting a weak shield will penetrate it. A strength 10 field will always block a projectile.
* The projectile's damage is multiplied by 1 minus the shield's strength divided by 10, then the shield's strength is subtracted from it's damage.
*
* Eg. A 50 damage projectile hits a 5 strength shield. (damage multiplier: 1 - 5/10 = 0.5), (shot damage: = 50 * 0.5 = 25), (final damage: 25 - 5 = 20). Penetrates.
* Therefore the shot would continue on, but only deal 20 damage when it hits.
*
* Eg. A 50 damage projectile hits a 9 strength shield. (damage multiplier: 1 - 9/10 = 0.1), (shot damage: = 50 * 0.1 = 5), (final damage: 5 - 9 = -4). Blocked.
*/
if(energy_field.field_strength < 10) //A strength 10 field will always block a projectile. Without modifications to the engines, one or more upgraded SMES units, or upgrades from research the Horizon cannot generate a field of strength 10 that covers the whole ship.
hitting_projectile.damage *= 1 - max(0, energy_field.field_strength / 10)
hitting_projectile.damage -= energy_field.field_strength
if(hitting_projectile.damage >= 0)
damage_field(initial_damage / 10)
visible_message(SPAN_WARNING("\The [src] flashes and depletes the [hitting_projectile]'s energy, but doesn't fully block it."))
return BULLET_ACT_FORCE_PIERCE
else
visible_message(SPAN_WARNING("\The [src] shimmers and absorbs \the [hitting_projectile]."))
return BULLET_ACT_BLOCK
/**
* An explosive projectile that fails to penetrate will be deleted before it can explode.
* All explosive ship weapons also have high damage, so this only really matters on a strength 10 shield.
* If the projectile is explosive, it deals whatever damage that explosion would have done to the shield.
*/
if(istype(hitting_projectile, /obj/projectile/ship_ammo))
var/obj/projectile/ship_ammo/explosive_projectile = hitting_projectile
if(explosive_projectile.explosion_strength[3] || explosive_projectile.explosion_strength[2] || explosive_projectile.explosion_strength[1])
for(var/obj/effect/energy_field/shield_tile in energy_field.field)
var/distance = get_dist(src, shield_tile)
if(distance <= explosive_projectile.explosion_strength[1])
shield_tile.damage_field(3)
else if(distance <= explosive_projectile.explosion_strength[2])
shield_tile.damage_field(2)
else if(distance <= explosive_projectile.explosion_strength[3])
shield_tile.damage_field(1)
// explosive_projectile.explosion_strength = list(0, 0, 0) // Set the explosion strength to 0.
visible_message(SPAN_WARNING("\The [src] shimmers and absorbs \the [hitting_projectile]."))
return BULLET_ACT_BLOCK
damage_field(initial_damage / 10)
. = ..() //If we get down here fall back on normal piercing calculations, for normal projectiles hitting shields.
if(. != BULLET_ACT_HIT)
return .
damage_field(hitting_projectile.get_structure_damage() / 10)
/obj/effect/energy_field/proc/damage_field(var/severity)
if(!severity)
return
damage += severity
energy_field.field_strength = max(energy_field.field_strength - (severity / length(energy_field.field)), 0)
if(!(datum_flags & DF_ISPROCESSING))
// Start processing ONLY when we're damaged. Through processing, we're going to slowly climb back up to field strength.
+57 -17
View File
@@ -14,13 +14,33 @@
var/stored_charge = 0
var/last_stored_charge = 0
var/time_since_fail = 100
var/max_charge = 2e7 //20 MJ
var/max_charge_rate = 9000000 //9 MW
///How much energy the capacitor can store, value is in Joules, displayed in the UI as MJ. This value is calculated in RefreshParts() based on the components used, the value listed here is what base components provides, changing the value only here will not do anything.
var/max_charge = 20 MEGA //200 MJ
///How much energy the capacitor can absorb per second, value is in Watts, displayed in the UI as MW. This value is calculated in RefreshParts() based on the components used, the value listed here is what base components provides, changing the value only here will not do anything.
var/max_charge_rate = 9 MEGA WATTS //9 MW
var/locked = FALSE
var/charge_rate = 100000 //100 kW
var/charge_rate = 100 KILO WATTS //100 kW
var/obj/structure/machinery/shield_gen/owned_gen
component_types = list(
/obj/item/circuitboard/shield_cap,
/obj/item/stock_parts/capacitor = 4,
/obj/item/stock_parts/micro_laser = 1,
/obj/item/stock_parts/subspace/filter = 1,
/obj/item/stock_parts/subspace/treatment = 1,
/obj/item/stock_parts/subspace/analyzer = 1,
/obj/item/stock_parts/console_screen = 1,
/obj/item/stack/cable_coil = 5
)
/obj/structure/machinery/shield_capacitor/upgrade_hints(mob/user, distance, is_adjacent)
. += ..()
. += "Upgraded <b>capacitors</b> will increase capacity."
. += SPAN_NOTICE(" - The current capacity is <b>[max_charge / 1e6]</b> MJ")
. += "Upgraded <b>microlasers</b> will increase the charging rate."
. += SPAN_NOTICE(" - The current maximum charge rate is <b>[max_charge_rate / 1e6]</b> MW")
/obj/structure/machinery/shield_capacitor/Initialize()
..()
return INITIALIZE_HINT_LATELOAD
@@ -29,8 +49,9 @@
. = ..()
for(var/obj/structure/machinery/shield_gen/possible_gen in range(1, src))
if(get_dir(src, possible_gen) == dir)
possible_gen.owned_capacitor = src
break
if(possible_gen.attach_capacitor(src))
owned_gen = possible_gen
break
/obj/structure/machinery/shield_capacitor/emag_act(var/remaining_charges, var/mob/user)
if(prob(75))
@@ -40,8 +61,26 @@
updateDialog()
spark(src, 5, GLOB.alldirs)
/obj/structure/machinery/shield_capacitor/RefreshParts()
..()
max_charge = 0
max_charge_rate = 6 MEGA WATTS //9 MW after base components. 15 MW with full upgrades.
for(var/obj/item/stock_parts/P in component_parts)
if(iscapacitor(P))
max_charge += P.rating * 5 MEGA
else if(ismicrolaser(P))
max_charge_rate += P.rating * 3 MEGA WATTS
/obj/structure/machinery/shield_capacitor/attackby(obj/item/attacking_item, mob/user)
if(default_part_replacement(user, attacking_item))
return TRUE
else if(default_deconstruction_screwdriver(user, attacking_item))
return TRUE
else if(default_deconstruction_crowbar(user, attacking_item))
return TRUE
if(istype(attacking_item, /obj/item/card/id))
if(allowed(user))
locked = !locked
@@ -55,14 +94,14 @@
if(anchored)
for(var/obj/structure/machinery/shield_gen/gen in range(1, src))
if(get_dir(src, gen) == src.dir && !gen.owned_capacitor)
owned_gen = gen
owned_gen.owned_capacitor = src
owned_gen.updateDialog()
if(get_dir(src, gen) == src.dir)
if(gen.attach_capacitor(src))
owned_gen = gen
break
else
if(owned_gen && owned_gen.owned_capacitor == src)
owned_gen.owned_capacitor = null
owned_gen = null
if(owned_gen)
owned_gen.detach_capacitor(src)
owned_gen = null
else
..()
@@ -83,10 +122,10 @@
data["locked"] = locked
data["active"] = active
data["time_since_fail"] = time_since_fail
data["charge_rate"] = charge_rate
data["stored_charge"] = stored_charge
data["max_charge"] = max_charge
data["max_charge_rate"] = max_charge_rate
data["charge_rate"] = charge_rate / 1000 //UI expects kW
data["stored_charge"] = round(stored_charge / 1e6, 0.1) //UI expects MJ
data["max_charge"] = max_charge / 1e6 //UI expects MJ
data["max_charge_rate"] = max_charge_rate / 1000 //UI expects kW
return data
/obj/structure/machinery/shield_capacitor/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
@@ -101,7 +140,8 @@
active = !active
. = TRUE
if("charge_rate")
charge_rate = between(10000, params["charge_rate"], max_charge_rate)
// UI sends kW; convert to W and clamp.
charge_rate = between(0 KILO WATTS, round(params["charge_rate"]) * 1000, max_charge_rate)
. = TRUE
/obj/structure/machinery/shield_capacitor/process()
+143 -41
View File
@@ -25,23 +25,52 @@
var/field_radius = 3
/// Maximum field radius.
var/max_field_radius = 100
/// The shield capacitor attached to this shield generator.
var/obj/structure/machinery/shield_capacitor/owned_capacitor
/// The shield capacitors attached to this shield generator.
var/list/owned_capacitors = list()
/// If this shield generator supports multi-z.
var/multiz = TRUE
///How much the manipulator rating increases the maximum field strengthen rate. This increases how fast the shield can strengthen, allowing it to recover faster.
var/manipulator_bonus = 0.1 //Maximum recovery rate with base components is 0.5 renwicks per second, with maxed out manipulators it's 0.9 renwicks per second.
///How much the capacitor rating increases the field conversion rate. This increases the efficiency of converting power into shield strength, decreasing all shield power costs.
var/capacitor_bonus = 0.000005
///How much the micro laser rating decreased the field dissipation rate. This reduces the amount of shield strength lost per tick, reducing the shield upkeep.
var/micro_laser_bonus = 0.002
component_types = list(
/obj/item/circuitboard/shield_gen,
/obj/item/stock_parts/manipulator = 2,
/obj/item/stock_parts/micro_laser = 2,
/obj/item/stock_parts/capacitor = 2,
/obj/item/stock_parts/subspace/transmitter = 1,
/obj/item/stock_parts/subspace/crystal = 1,
/obj/item/stock_parts/subspace/amplifier = 1,
/obj/item/stock_parts/console_screen = 1,
/obj/item/stack/cable_coil = 5
)
/obj/structure/machinery/shield_gen/Initialize()
for(var/obj/structure/machinery/shield_capacitor/possible_cap in range(1, src))
if(get_dir(possible_cap, src) == possible_cap.dir)
owned_capacitor = possible_cap
break
owned_capacitors = list()
for(var/obj/structure/machinery/shield_capacitor/possible_cap in range(1, src)) //Attach nearby capacitors
attach_capacitor(possible_cap)
energy_field = new(src, get_shielded_turfs())
. = ..()
/obj/structure/machinery/shield_gen/Destroy()
owned_capacitor = null
for(var/obj/structure/machinery/shield_capacitor/cap in owned_capacitors) //Detach any owned capacitors
detach_capacitor(cap)
owned_capacitors = list()
return ..()
/obj/structure/machinery/shield_gen/upgrade_hints(mob/user, distance, is_adjacent)
. += ..()
. += "Upgraded <b>manipulators</b> will increase the maximum field strengthen rate."
. += SPAN_NOTICE("\t- The current maximum field strengthen rate is <b>[energy_field.max_strengthen_rate]</b> renwicks per second.")
. += "Upgraded <b>capacitors</b> will increase the field conversion rate."
. += SPAN_NOTICE("\t- The current field conversion efficiency increase is: <b>[(initial(energy_field.energy_conversion_rate) + (2 * capacitor_bonus)) / energy_field.energy_conversion_rate * 100]%</b>")
. += "Upgraded <b>micro lasers</b> will decrease the energy field dispersion rate."
. += SPAN_NOTICE("\t- The current field dissipation rate decrease is: <b>[(initial(energy_field.dissipation_rate) - (2 * micro_laser_bonus)) / energy_field.dissipation_rate * 100]%</b>")
/obj/structure/machinery/shield_gen/emag_act(var/remaining_charges, var/mob/user)
if(prob(75))
locked = !locked
@@ -52,6 +81,13 @@
spark(src, 5, GLOB.alldirs)
/obj/structure/machinery/shield_gen/attackby(obj/item/attacking_item, mob/user)
if(default_part_replacement(user, attacking_item))
return TRUE
else if(default_deconstruction_screwdriver(user, attacking_item))
return TRUE
else if(default_deconstruction_crowbar(user, attacking_item))
return TRUE
if(istype(attacking_item, /obj/item/card/id))
if(allowed(user))
locked = !locked
@@ -66,24 +102,41 @@
if(active)
toggle()
if(anchored)
// Attach nearby capacitors wrenching down the shield
owned_capacitors = list()
for(var/obj/structure/machinery/shield_capacitor/cap in range(1, src))
if(cap.owned_gen)
continue
if(get_dir(cap, src) == cap.dir && cap.anchored)
owned_capacitor = cap
owned_capacitor.owned_gen = src
break
attach_capacitor(cap)
else
if(owned_capacitor && owned_capacitor.owned_gen == src)
owned_capacitor.owned_gen = null
owned_capacitor = null
// Detach any owned capacitors when unwrenching
for(var/obj/structure/machinery/shield_capacitor/cap in owned_capacitors)
detach_capacitor(cap)
owned_capacitors = list()
else
..()
/obj/structure/machinery/shield_gen/RefreshParts()
..()
var/max_strengthen_rate_increase = 0
var/energy_conversion_rate_increase = 0
var/dissipation_rate_decrease = 0
for(var/obj/item/stock_parts/P in component_parts)
if(ismanipulator(P))
max_strengthen_rate_increase += P.rating
else if(iscapacitor(P))
energy_conversion_rate_increase += P.rating
else if(ismicrolaser(P))
dissipation_rate_decrease += P.rating
if(energy_field)
energy_field.max_strengthen_rate = initial(energy_field.max_strengthen_rate) + (max_strengthen_rate_increase * manipulator_bonus)
energy_field.energy_conversion_rate = initial(energy_field.energy_conversion_rate) + (energy_conversion_rate_increase * capacitor_bonus)
energy_field.dissipation_rate = max(0, initial(energy_field.dissipation_rate) - (dissipation_rate_decrease * micro_laser_bonus))
energy_field.strengthen_rate = min(energy_field.strengthen_rate, energy_field.max_strengthen_rate) ///If the strengthen rate got decreased and the current strengthen rate is now above the max, reduce it to the max.
/obj/structure/machinery/shield_gen/attack_ai(mob/user)
if(!ai_can_interact(user))
return
return attack_hand(user)
/obj/structure/machinery/shield_gen/attack_hand(mob/user)
if(stat & BROKEN)
@@ -98,22 +151,38 @@
to_chat(user, SPAN_WARNING("The device needs to be bolted to the ground first."))
return
else
if(owned_capacitor)
if(!((owned_capacitor in range(1, src)) && get_dir(owned_capacitor, src) == owned_capacitor.dir && owned_capacitor.anchored))
if(owned_capacitor.owned_gen == src)
owned_capacitor.owned_gen = null
owned_capacitor = null
if(!owned_capacitor)
for(var/obj/structure/machinery/shield_capacitor/cap in owned_capacitors) //Make sure the capacitors weren't blown up
if(!(cap in range(1, src)) || get_dir(cap, src) != cap.dir || !cap.anchored)
if(cap && cap.owned_gen == src)
cap.owned_gen = null
owned_capacitors -= cap
if(!owned_capacitors || owned_capacitors.len == 0) // Try to attach any valid adjacent capacitors
for(var/obj/structure/machinery/shield_capacitor/cap in range(1, src))
if(cap.owned_gen)
continue
if(get_dir(cap, src) == cap.dir && cap.anchored)
owned_capacitor = cap
owned_capacitor.owned_gen = src
updateDialog()
break
attach_capacitor(cap)
return ui_interact(user)
/obj/structure/machinery/shield_gen/proc/attach_capacitor(obj/structure/machinery/shield_capacitor/cap)
if(!cap || cap.owned_gen || !cap.anchored)
return FALSE
if(!(cap in range(1, src)) || get_dir(cap, src) != cap.dir)
return FALSE
if(!owned_capacitors)
owned_capacitors = list()
owned_capacitors += cap
cap.owned_gen = src
updateDialog()
return TRUE
/obj/structure/machinery/shield_gen/proc/detach_capacitor(obj/structure/machinery/shield_capacitor/cap)
if(!cap)
return FALSE
if(owned_capacitors && (cap in owned_capacitors))
owned_capacitors -= cap
if(cap.owned_gen == src)
cap.owned_gen = null
updateDialog()
return TRUE
/obj/structure/machinery/shield_gen/process()
if(active)
if(!anchored)
@@ -128,15 +197,41 @@
energy_field.handle_strength(assumed_charge)
/**
* Called whenever the field needs to take charge from the capacitor.
* Called whenever the field needs to take charge from attached capacitors.
*/
/obj/structure/machinery/shield_gen/proc/assume_charge(required_energy)
if(!owned_capacitor || !owned_capacitor.active)
if(!owned_capacitors || owned_capacitors.len == 0)
return 0
var/assumed_charge = min(owned_capacitor.stored_charge, required_energy)
assumed_charge = max(assumed_charge, 0)
owned_capacitor.stored_charge -= assumed_charge
return assumed_charge
var/list/active_capacitors = list()
var/available_charge = 0
for(var/obj/structure/machinery/shield_capacitor/cap in owned_capacitors) //Loop through capacitors that can provide charge
if(!cap || !cap.active || cap.stored_charge <= 0)
continue
active_capacitors += cap
available_charge += cap.stored_charge
if(!active_capacitors.len || available_charge <= 0) //If we have no capacitors, or they're not charged stop
return 0
var/assumed_charge = min(available_charge, required_energy)
if(assumed_charge >= available_charge)
for(var/obj/structure/machinery/shield_capacitor/cap in active_capacitors)
cap.stored_charge = 0
return assumed_charge
var/remaining = assumed_charge
for(var/i = 1, i <= active_capacitors.len, i++)
var/obj/structure/machinery/shield_capacitor/cap = active_capacitors[i]
var/took = (i == active_capacitors.len) ? remaining : round(assumed_charge * (cap.stored_charge / available_charge))
took = min(took, cap.stored_charge, remaining)
if(took <= 0)
continue
cap.stored_charge -= took
remaining -= took
if(remaining <= 0)
break
return assumed_charge - remaining
/obj/structure/machinery/shield_gen/ex_act(var/severity)
if(active)
@@ -145,12 +240,18 @@
/obj/structure/machinery/shield_gen/proc/toggle()
if(!active)
if(!owned_capacitor)
if(!owned_capacitors || owned_capacitors.len == 0)
balloon_alert_to_viewers("no capacitor")
return
else if(!owned_capacitor?.active)
balloon_alert_to_viewers("capacitor offline")
return
else
var/has_active = FALSE
for(var/obj/structure/machinery/shield_capacitor/cap in owned_capacitors)
if(cap && cap.active)
has_active = TRUE
break
if(!has_active)
balloon_alert_to_viewers("capacitors offline")
return
active = !active
update_icon()
if(active)
@@ -213,7 +314,8 @@
/obj/structure/machinery/shield_gen/ui_data(mob/user)
var/list/data = list()
data["owned_capacitor"] = !!owned_capacitor
data["owned_capacitor"] = length(owned_capacitors)
data["owned_capacitor_count"] = length(owned_capacitors)
data["active"] = active
data["time_since_fail"] = energy_field ? energy_field.time_since_fail : 0
data["multiz"] = multiz
@@ -4,6 +4,18 @@
/obj/structure/machinery/shield_gen/external
name = "hull shield generator"
component_types = list(
/obj/item/circuitboard/shield_gen_ex,
/obj/item/stock_parts/manipulator = 2,
/obj/item/stock_parts/micro_laser = 2,
/obj/item/stock_parts/capacitor = 2,
/obj/item/stock_parts/subspace/transmitter = 1,
/obj/item/stock_parts/subspace/crystal = 1,
/obj/item/stock_parts/subspace/amplifier = 1,
/obj/item/stock_parts/console_screen = 1,
/obj/item/stack/cable_coil = 5
)
//Search for space turfs within range that are adjacent to a simulated turf.
/obj/structure/machinery/shield_gen/external/get_shielded_turfs()
var/list/out = list()