Ports advanced shield generators from Baystation

- Creates new advanced shield generators, designed to replace old hull and bubble shield generators.
- Upgrades the floor mounted and handheld shield diffusers.
- Makes underfloor shield diffusers actually constructable.
- Handheld diffusers orderable via uplink.
- Removes supply packs and research datums  for the old generators, but leaves their code in place for maps that still use them.
- Integrates with the meteor and electrical storm events.
- Integrates with mob AI (they know how to attack it)
This commit is contained in:
Leshana
2020-04-03 00:36:46 -04:00
parent a91150750f
commit e2bd546f61
18 changed files with 1349 additions and 125 deletions

49
code/__defines/shields.dm Normal file
View File

@@ -0,0 +1,49 @@
#define SHIELD_DAMTYPE_PHYSICAL 1 // Physical damage - bullets, meteors, various hand objects - aka. "brute" damtype.
#define SHIELD_DAMTYPE_EM 2 // Electromagnetic damage - Ion weaponry, stun beams, ...
#define SHIELD_DAMTYPE_HEAT 3 // Heat damage - Lasers, fire
// TODO - Thats power not energy you silly goose! I think we mean 45 kilojoules
#define ENERGY_PER_HP (45 KILOWATTS)// Base amount energy that will be deducted from the generator's internal reserve per 1 HP of damage taken
#define ENERGY_UPKEEP_PER_TILE (4 KILOWATTS) // Base upkeep per tile protected. Multiplied by various enabled shield modes. Without them the field does literally nothing.
#define ENERGY_UPKEEP_IDLE 45 // Base upkeep when idle; modified by other factors.
// This shield model is slightly inspired by Sins of a Solar Empire series. In short, shields are designed to analyze what hits them, and adapt themselves against that type of damage.
// This means shields will become increasingly effective against things like emitters - as they will adapt to heat damage, however they will be vulnerable to brute and EM damage.
// In a theoretical assault scenario, it is best to combine all damage types, so mitigation can't build up. The value is capped to prevent full scale invulnerability.
#define MAX_MITIGATION_BASE 40 // % Base maximal reachable mitigation.
#define MAX_MITIGATION_RESEARCH 10 // % Added to MAX_MITIGATION_BASE when generator is built using more advanced components. This value is added for each "tier" of used component, ie. basic one has 1, the best one has 5. Actual maximum should be 90% in this case (with best components). Make sure you won't get above 100%!
#define MITIGATION_HIT_GAIN 5 // Mitigation gain per hit of respective damage type.
#define MITIGATION_HIT_LOSS 4 // Mitigation loss per hit. If we get hit once by EM damage type, EM mitigation will grow, while Physical and Heat mitigation values drop.
#define MITIGATION_LOSS_PASSIVE 0.5 // Mitigation of all damage types will drop by this every tick, up to 0.
// Shield modes allow you to calibrate the field to fit specific needs. It is, for example, possible to create a field that will block airflow, but let people pass.
// Relevant mode bitflags (maximal of 16 flags due to current BYOND limitations)
#define MODEFLAG_HYPERKINETIC 1
#define MODEFLAG_PHOTONIC 2
#define MODEFLAG_NONHUMANS 4
#define MODEFLAG_HUMANOIDS 8
#define MODEFLAG_ANORGANIC 16
#define MODEFLAG_ATMOSPHERIC 32
#define MODEFLAG_HULL 64
#define MODEFLAG_BYPASS 128
#define MODEFLAG_OVERCHARGE 256
#define MODEFLAG_MODULATE 512
#define MODEFLAG_MULTIZ 1024
#define MODEFLAG_EM 2048
// Return codes for shield hits.
#define SHIELD_ABSORBED 1 // The shield has completely absorbed the hit
#define SHIELD_BREACHED_MINOR 2 // The hit was absorbed, but a small gap will be created in the field (1-3 tiles)
#define SHIELD_BREACHED_MAJOR 3 // Same as above, with 2-5 tile gap
#define SHIELD_BREACHED_CRITICAL 4 // Same as above, with 4-8 tile gap
#define SHIELD_BREACHED_FAILURE 5 // Same as above, with 8-16 tile gap. Occurs when the hit exhausts all remaining shield energy.
#define SHIELD_OFF 0 // The shield is offline
#define SHIELD_DISCHARGING 1 // The shield is shutting down and discharging.
#define SHIELD_RUNNING 2 // The shield is running
#define SHIELD_IDLE 3 // The shield is being kept at an idle state
#define SHIELD_SPINNING_UP 4 // Going from idle to running
#define SHIELD_SHUTDOWN_DISPERSION_RATE (10000 KILOWATTS) // The rate at which shield energy disperses when shutdown is initiated.

View File

@@ -35,48 +35,6 @@
containertype = /obj/structure/closet/crate/engineering containertype = /obj/structure/closet/crate/engineering
containername = "Superconducting Transmission Coil crate" containername = "Superconducting Transmission Coil crate"
/datum/supply_pack/eng/shield_capacitor
name = "Shield Capacitor"
contains = list(/obj/machinery/shield_capacitor)
cost = 20
containertype = /obj/structure/closet/crate/engineering
containername = "shield capacitor crate"
/datum/supply_pack/eng/shield_capacitor/advanced
name = "Advanced Shield Capacitor"
contains = list(/obj/machinery/shield_capacitor/advanced)
cost = 30
containertype = /obj/structure/closet/crate/engineering
containername = "advanced shield capacitor crate"
/datum/supply_pack/eng/bubble_shield
name = "Bubble Shield Generator"
contains = list(/obj/machinery/shield_gen)
cost = 40
containertype = /obj/structure/closet/crate/engineering
containername = "shield bubble generator crate"
/datum/supply_pack/eng/bubble_shield/advanced
name = "Advanced Bubble Shield Generator"
contains = list(/obj/machinery/shield_gen/advanced)
cost = 60
containertype = /obj/structure/closet/crate/engineering
containername = "advanced bubble shield generator crate"
/datum/supply_pack/eng/hull_shield
name = "Hull Shield Generator"
contains = list(/obj/machinery/shield_gen/external)
cost = 80
containertype = /obj/structure/closet/crate/engineering
containername = "shield hull generator crate"
/datum/supply_pack/eng/hull_shield/advanced
name = "Advanced Hull Shield Generator"
contains = list(/obj/machinery/shield_gen/external/advanced)
cost = 120
containertype = /obj/structure/closet/crate/engineering
containername = "advanced hull shield generator crate"
/datum/supply_pack/eng/electrical /datum/supply_pack/eng/electrical
name = "Electrical maintenance crate" name = "Electrical maintenance crate"
contains = list( contains = list(
@@ -173,29 +131,19 @@
containername = "Particle Accelerator crate" containername = "Particle Accelerator crate"
access = access_ce access = access_ce
/datum/supply_pack/eng/shield_gen /datum/supply_pack/eng/shield_generator
contains = list(/obj/item/weapon/circuitboard/shield_gen) name = "Shield Generator Construction Kit"
name = "Bubble shield generator circuitry" contains = list(
cost = 30 /obj/item/weapon/circuitboard/shield_generator,
containertype = /obj/structure/closet/crate/secure/engineering /obj/item/weapon/stock_parts/capacitor,
containername = "bubble shield generator circuitry crate" /obj/item/weapon/stock_parts/micro_laser,
access = access_ce /obj/item/weapon/smes_coil,
/obj/item/weapon/stock_parts/console_screen,
/datum/supply_pack/eng/shield_gen_ex /obj/item/weapon/stock_parts/subspace/amplifier
contains = list(/obj/item/weapon/circuitboard/shield_gen_ex) )
name = "Hull shield generator circuitry" cost = 80
cost = 30 containertype = /obj/structure/closet/crate/engineering
containertype = /obj/structure/closet/crate/secure/engineering containername = "shield generator construction kit crate"
containername = "hull shield generator circuitry crate"
access = access_ce
/datum/supply_pack/eng/shield_cap
contains = list(/obj/item/weapon/circuitboard/shield_cap)
name = "Bubble shield capacitor circuitry"
cost = 30
containertype = /obj/structure/closet/crate/secure/engineering
containername = "shield capacitor circuitry crate"
access = access_ce
/datum/supply_pack/eng/smbig /datum/supply_pack/eng/smbig
name = "Supermatter Core" name = "Supermatter Core"

View File

@@ -53,6 +53,12 @@
name = "Black Ammunition Duffle Bag" name = "Black Ammunition Duffle Bag"
path = /obj/item/weapon/storage/backpack/dufflebag/syndie/ammo path = /obj/item/weapon/storage/backpack/dufflebag/syndie/ammo
/datum/uplink_item/item/tools/shield_diffuser
name = "Handheld Shield Diffuser"
desc = "A small device used to disrupt energy barriers, and allow passage through them."
item_cost = 16
path = /obj/item/weapon/shield_diffuser
/datum/uplink_item/item/tools/space_suit /datum/uplink_item/item/tools/space_suit
name = "Space Suit" name = "Space Suit"
item_cost = 15 item_cost = 15

View File

@@ -0,0 +1,46 @@
/datum/wires/shield_generator
holder_type = /obj/machinery/power/shield_generator
wire_count = 5
var/const/SHIELDGEN_WIRE_POWER = 1 // Cut to disable power input into the generator. Pulse does nothing. Mend to restore.
var/const/SHIELDGEN_WIRE_HACK = 2 // Pulse to hack the generator, enabling hacked modes. Cut to unhack. Mend does nothing.
var/const/SHIELDGEN_WIRE_CONTROL = 4 // Cut to lock most shield controls. Mend to unlock them. Pulse does nothing.
var/const/SHIELDGEN_WIRE_AICONTROL = 8 // Cut to disable AI control. Mend to restore.
var/const/SHIELDGEN_WIRE_NOTHING = 16 // A blank wire that doesn't have any specific function
/datum/wires/shield_generator/CanUse(var/mob/living/L)
var/obj/machinery/power/shield_generator/S = holder
if(S.panel_open)
return 1
return 0
/datum/wires/shield_generator/GetInteractWindow()
var/obj/machinery/power/shield_generator/S = holder
. += ..()
. += show_hint(0x1, S.mode_changes_locked, "The orange light is on.", "The orange light is off.")
. += show_hint(0x2, S.ai_control_disabled, "The blue light is off.", "The blue light is blinking.")
. += show_hint(0x4, S.hacked, "The violet light is pulsing.", "The violet light is steady.")
. += show_hint(0x8, S.input_cut, "The red light is off.", "The red light is on.")
/datum/wires/shield_generator/UpdateCut(index, mended)
var/obj/machinery/power/shield_generator/S = holder
switch(index)
if(SHIELDGEN_WIRE_POWER)
S.input_cut = !mended
if(SHIELDGEN_WIRE_HACK)
if(!mended)
S.hacked = 0
if(S.check_flag(MODEFLAG_BYPASS))
S.toggle_flag(MODEFLAG_BYPASS)
if(S.check_flag(MODEFLAG_OVERCHARGE))
S.toggle_flag(MODEFLAG_OVERCHARGE)
if(SHIELDGEN_WIRE_CONTROL)
S.mode_changes_locked = !mended
if(SHIELDGEN_WIRE_AICONTROL)
S.ai_control_disabled = !mended
/datum/wires/shield_generator/UpdatePulsed(var/index)
var/obj/machinery/power/shield_generator/S = holder
switch(index)
if(SHIELDGEN_WIRE_HACK)
S.hacked = 1

View File

@@ -183,6 +183,8 @@
var/turf/simulated/wall/W = T var/turf/simulated/wall/W = T
W.take_damage(wall_power) // Stronger walls can halt asteroids. W.take_damage(wall_power) // Stronger walls can halt asteroids.
/obj/effect/meteor/proc/get_shield_damage()
return max(((max(hits, 2)) * (heavy + 1) * rand(6, 12)) / hitpwr , 0)
//process getting 'hit' by colliding with a dense object //process getting 'hit' by colliding with a dense object
//or randomly when ramming turfs //or randomly when ramming turfs
@@ -307,6 +309,9 @@
// Worst case scenario: Comparable to a standard yield EMP grenade. // Worst case scenario: Comparable to a standard yield EMP grenade.
empulse(src, rand(1, 3), rand(2, 4), rand(3, 7), rand(5, 10)) empulse(src, rand(1, 3), rand(2, 4), rand(3, 7), rand(5, 10))
/obj/effect/meteor/emp/get_shield_damage()
return ..() * rand(2,4)
//Station buster Tunguska //Station buster Tunguska
/obj/effect/meteor/tunguska /obj/effect/meteor/tunguska
name = "tunguska meteor" name = "tunguska meteor"

View File

@@ -2,6 +2,37 @@
#error T_BOARD macro is not defined but we need it! #error T_BOARD macro is not defined but we need it!
#endif #endif
//
// New shield generator
//
/obj/item/weapon/circuitboard/shield_generator
name = T_BOARD("advanced shield generator")
board_type = new /datum/frame/frame_types/machine
build_path = /obj/machinery/power/shield_generator
origin_tech = list(TECH_MAGNET = 3, TECH_POWER = 4, TECH_BLUESPACE = 2, TECH_ENGINEERING = 3)
req_components = list(
/obj/item/weapon/stock_parts/capacitor = 1,
/obj/item/weapon/stock_parts/micro_laser = 1,
/obj/item/weapon/smes_coil = 1,
/obj/item/weapon/stock_parts/console_screen = 1,
/obj/item/weapon/stock_parts/subspace/amplifier = 1,
/obj/item/stack/cable_coil = 5)
/obj/item/weapon/circuitboard/shield_diffuser
name = T_BOARD("shield diffuser")
board_type = new /datum/frame/frame_types/machine
build_path = /obj/machinery/shield_diffuser
origin_tech = list(TECH_MAGNET = 4, TECH_POWER = 2, TECH_ILLEGAL = 1)
req_components = list(
/obj/item/weapon/stock_parts/capacitor = 1,
/obj/item/weapon/stock_parts/micro_laser = 1,
/obj/item/weapon/stock_parts/console_screen = 1)
//
// Legacy shield generators
//
/obj/item/weapon/circuitboard/shield_gen_ex /obj/item/weapon/circuitboard/shield_gen_ex
name = T_BOARD("hull shield generator") name = T_BOARD("hull shield generator")
board_type = new /datum/frame/frame_types/machine board_type = new /datum/frame/frame_types/machine

View File

@@ -285,6 +285,12 @@
ai_log("destroy_surroundings() : Attacking hull shield.", AI_LOG_INFO) ai_log("destroy_surroundings() : Attacking hull shield.", AI_LOG_INFO)
return melee_attack(shield) return melee_attack(shield)
// Kill energy shields in the way.
for(var/obj/effect/shield/S in problem_turf)
if(S.density) // Don't attack shields that are already down.
ai_log("destroy_surroundings() : Attacking energy shield.", AI_LOG_INFO)
return melee_attack(S)
// Kill common obstacle in the way like tables. // Kill common obstacle in the way like tables.
var/obj/structure/obstacle = locate(/obj/structure, problem_turf) var/obj/structure/obstacle = locate(/obj/structure, problem_turf)
if(istype(obstacle, /obj/structure/window) || istype(obstacle, /obj/structure/closet) || istype(obstacle, /obj/structure/table) || istype(obstacle, /obj/structure/grille)) if(istype(obstacle, /obj/structure/window) || istype(obstacle, /obj/structure/closet) || istype(obstacle, /obj/structure/table) || istype(obstacle, /obj/structure/grille))

View File

@@ -35,8 +35,16 @@
/datum/event/electrical_storm/tick() /datum/event/electrical_storm/tick()
..() ..()
// See if shields can stop it first (It would be nice to port baystation's cooler shield gens perhaps) // See if shields can stop it first
// TODO - We need a better shield generator system to handle this properly. var/list/shields = list()
for(var/obj/machinery/power/shield_generator/G in global.machines)
if((G.z in affecting_z) && G.running && G.check_flag(MODEFLAG_EM))
shields += G
if(shields.len)
var/obj/machinery/power/shield_generator/shield_gen = pick(shields)
//Minor breaches aren't enough to let through frying amounts of power
if(shield_gen.deal_shield_damage(30 * severity, SHIELD_DAMTYPE_EM) <= SHIELD_BREACHED_MINOR)
return
if(!valid_apcs.len) if(!valid_apcs.len)
log_debug("No valid APCs found for electrical storm event ship=[victim]!") log_debug("No valid APCs found for electrical storm event ship=[victim]!")
return return

View File

@@ -2,7 +2,8 @@
startWhen = 30 // About one minute early warning startWhen = 30 // About one minute early warning
endWhen = 60 // Adjusted automatically in tick() endWhen = 60 // Adjusted automatically in tick()
has_skybox_image = TRUE has_skybox_image = TRUE
var/next_meteor = 6 var/alarmWhen = 30
var/next_meteor = 40
var/waves = 1 var/waves = 1
var/start_side var/start_side
var/next_meteor_lower = 10 var/next_meteor_lower = 10
@@ -32,6 +33,12 @@
command_announcement.Announce("\The [location_name()] is now in a meteor shower.", "Meteor Alert") command_announcement.Announce("\The [location_name()] is now in a meteor shower.", "Meteor Alert")
/datum/event/meteor_wave/tick() /datum/event/meteor_wave/tick()
// Begin sending the alarm signals to shield diffusers so the field is already regenerated (if it exists) by the time actual meteors start flying around.
if(activeFor >= alarmWhen)
for(var/obj/machinery/shield_diffuser/SD in global.machines)
if(SD.z in affecting_z)
SD.meteor_alarm(10)
if(waves && activeFor >= next_meteor) if(waves && activeFor >= next_meteor)
send_wave() send_wave()
@@ -105,6 +112,7 @@
next_meteor_lower = 5 next_meteor_lower = 5
next_meteor_upper = 10 next_meteor_upper = 10
next_meteor = 0 next_meteor = 0
alarmWhen = 0
/datum/event/meteor_wave/overmap/tick() /datum/event/meteor_wave/overmap/tick()
if(victim && !victim.is_still()) // Meteors mostly fly in your face if(victim && !victim.is_still()) // Meteors mostly fly in your face

View File

@@ -570,36 +570,6 @@ CIRCUITS BELOW
build_path = /obj/item/weapon/circuitboard/telecomms/exonet_node build_path = /obj/item/weapon/circuitboard/telecomms/exonet_node
sort_string = "PAAAH" sort_string = "PAAAH"
/datum/design/circuit/shield
req_tech = list(TECH_BLUESPACE = 4, TECH_PHORON = 3)
materials = list("glass" = 2000, "gold" = 1000)
/datum/design/circuit/shield/AssembleDesignName()
name = "Shield generator circuit design ([name])"
/datum/design/circuit/shield/AssembleDesignDesc()
if(!desc)
desc = "Allows for the construction of \a [name] shield generator."
/datum/design/circuit/shield/bubble
name = "bubble"
id = "shield_gen"
build_path = /obj/item/weapon/circuitboard/shield_gen
sort_string = "VAAAZ" // Duplicate string, really need to redo this whole thing
/datum/design/circuit/shield/hull
name = "hull"
id = "shield_gen_ex"
build_path = /obj/item/weapon/circuitboard/shield_gen_ex
sort_string = "VAAAB"
/datum/design/circuit/shield/capacitor
name = "capacitor"
desc = "Allows for the construction of a shield capacitor circuit board."
id = "shield_cap"
req_tech = list(TECH_MAGNET = 3, TECH_POWER = 4)
build_path = /obj/item/weapon/circuitboard/shield_cap
sort_string = "VAAAC"
/datum/design/circuit/ntnet_relay /datum/design/circuit/ntnet_relay
name = "NTNet Quantum Relay" name = "NTNet Quantum Relay"
id = "ntnet_relay" id = "ntnet_relay"
@@ -621,6 +591,20 @@ CIRCUITS BELOW
build_path = /obj/item/weapon/circuitboard/microwave/advanced build_path = /obj/item/weapon/circuitboard/microwave/advanced
sort_string = "MAAAC" sort_string = "MAAAC"
/datum/design/circuit/shield_generator
name = "shield generator"
id = "shield_generator"
req_tech = list(TECH_MAGNET = 3, TECH_POWER = 4, TECH_BLUESPACE = 2, TECH_ENGINEERING = 3)
build_path = /obj/item/weapon/circuitboard/shield_generator
sort_string = "OAAAA"
/datum/design/circuit/shield_diffuser
name = "shield diffuser"
id = "shield_diffuser"
req_tech = list(TECH_MAGNET = 4, TECH_POWER = 2, TECH_ENGINEERING = 5)
build_path = /obj/item/weapon/circuitboard/shield_diffuser
sort_string = "OAAAB"
/datum/design/circuit/pointdefense /datum/design/circuit/pointdefense
name = "point defense battery" name = "point defense battery"
id = "pointdefense" id = "pointdefense"
@@ -634,12 +618,3 @@ CIRCUITS BELOW
req_tech = list(TECH_DATA = 4, TECH_ENGINEERING = 3, TECH_COMBAT = 2) req_tech = list(TECH_DATA = 4, TECH_ENGINEERING = 3, TECH_COMBAT = 2)
build_path = /obj/item/weapon/circuitboard/pointdefense_control build_path = /obj/item/weapon/circuitboard/pointdefense_control
sort_string = "OAABB" sort_string = "OAABB"
/* I have no idea how this was even running before, but it doesn't seem to be necessary.
///////////////////////////////////
/////////Shield Generators/////////
///////////////////////////////////
/datum/design/circuit/shield
req_tech = list(TECH_BLUESPACE = 4, TECH_PHORON = 3)
materials = list("$glass" = 2000, "sacid" = 20, "$phoron" = 10000, "$diamond" = 5000, "$gold" = 10000)
*/

View File

@@ -0,0 +1,338 @@
//
// This is the shield effect object for the supercool shield gens.
//
/obj/effect/shield
name = "energy shield"
desc = "An impenetrable field of energy, capable of blocking anything as long as it's active."
icon = 'icons/obj/machines/shielding.dmi'
icon_state = "shield_normal"
anchored = 1
plane = MOB_PLANE
layer = ABOVE_MOB_LAYER
density = 1
invisibility = 0
var/obj/machinery/power/shield_generator/gen = null // Owning generator
var/disabled_for = 0
var/diffused_for = 0
can_atmos_pass = ATMOS_PASS_YES
/obj/effect/shield/update_icon()
if(gen && gen.check_flag(MODEFLAG_PHOTONIC) && !disabled_for && !diffused_for)
set_opacity(1)
else
set_opacity(0)
if(gen && gen.check_flag(MODEFLAG_OVERCHARGE))
icon_state = "shield_overcharged"
else
icon_state = "shield_normal"
if(density)
set_light(3, 3, "#66FFFF")
else
set_light(0)
// Prevents singularities and pretty much everything else from moving the field segments away.
// The only thing that is allowed to move us is the Destroy() proc.
/obj/effect/shield/forceMove()
if(QDELING(src))
return ..()
return 0
/obj/effect/shield/Destroy()
if(can_atmos_pass != ATMOS_PASS_YES)
update_nearby_tiles()
. = ..()
if(gen)
if(src in gen.field_segments)
gen.field_segments -= src
if(src in gen.damaged_segments)
gen.damaged_segments -= src
gen = null
// Temporarily collapses this shield segment.
/obj/effect/shield/proc/fail(var/duration)
if(duration <= 0)
return
if(gen)
gen.damaged_segments |= src
disabled_for += duration
set_density(0)
set_invisibility(INVISIBILITY_MAXIMUM)
update_nearby_tiles()
update_icon()
update_explosion_resistance()
// Regenerates this shield segment.
/obj/effect/shield/proc/regenerate()
if(!gen)
return
disabled_for = max(0, disabled_for - 1)
diffused_for = max(0, diffused_for - 1)
if(!disabled_for && !diffused_for)
set_density(1)
set_invisibility(0)
update_nearby_tiles()
update_icon()
update_explosion_resistance()
gen.damaged_segments -= src
/obj/effect/shield/proc/diffuse(var/duration)
// The shield is trying to counter diffusers. Cause lasting stress on the shield.
if(gen.check_flag(MODEFLAG_BYPASS) && !disabled_for)
take_damage(duration * rand(8, 12), SHIELD_DAMTYPE_EM)
return
diffused_for = max(duration, 0)
gen.damaged_segments |= src
set_density(0)
set_invisibility(INVISIBILITY_MAXIMUM)
update_nearby_tiles()
update_icon()
update_explosion_resistance()
/obj/effect/shield/attack_generic(var/source, var/damage, var/emote)
take_damage(damage, SHIELD_DAMTYPE_PHYSICAL)
if(gen.check_flag(MODEFLAG_OVERCHARGE) && istype(source, /mob/living/))
overcharge_shock(source)
..(source, damage, emote)
// Fails shield segments in specific range. Range of 1 affects the shielded turf only.
/obj/effect/shield/proc/fail_adjacent_segments(var/range, var/hitby = null)
if(hitby)
visible_message("<span class='danger'>\The [src] flashes a bit as \the [hitby] collides with it, eventually fading out in a rain of sparks!</span>")
else
visible_message("<span class='danger'>\The [src] flashes a bit as it eventually fades out in a rain of sparks!</span>")
fail(range * 2)
for(var/obj/effect/shield/S in range(range, src))
// Don't affect shields owned by other shield generators
if(S.gen != src.gen)
continue
// The closer we are to impact site, the longer it takes for shield to come back up.
S.fail(-(-range + get_dist(src, S)) * 2)
// Small visual effect, makes the shield tiles brighten up by becoming more opaque for a moment, and spreads to nearby shields.
/obj/effect/shield/proc/flash_adjacent_segments(var/range)
range = between(1, range, 10) // Sanity check
for(var/obj/effect/shield/S in range(range, src))
// Don't affect shields owned by other shield generators
if(S.gen != src.gen || S == src)
continue
// Note: Range is a non-exact aproximation of the spread effect. If it doesn't look good
// we'll need to switch to actually walking along the shields to get exact number of steps away.
addtimer(CALLBACK(S, .proc/impact_flash), get_dist(src, S) * 2)
impact_flash()
// Small visual effect, makes the shield tiles brighten up by becoming more opaque for a moment
/obj/effect/shield/proc/impact_flash()
alpha = 100
animate(src, alpha = initial(alpha), time = 1 SECOND)
// Just for fun
/obj/effect/shield/attack_hand(var/user)
flash_adjacent_segments(3)
/obj/effect/shield/take_damage(var/damage, var/damtype, var/hitby)
if(!gen)
qdel(src)
return
if(!damtype)
crash_with("CANARY: shield.take_damage() callled without damtype.")
if(!damage)
return
damage = round(damage)
new /obj/effect/temp_visual/shield_impact_effect(get_turf(src))
switch(gen.deal_shield_damage(damage, damtype))
if(SHIELD_ABSORBED)
flash_adjacent_segments(round(damage/10)) // Nice visual effect only.
return
if(SHIELD_BREACHED_MINOR)
fail_adjacent_segments(rand(1, 3), hitby)
return
if(SHIELD_BREACHED_MAJOR)
fail_adjacent_segments(rand(2, 5), hitby)
return
if(SHIELD_BREACHED_CRITICAL)
fail_adjacent_segments(rand(4, 8), hitby)
return
if(SHIELD_BREACHED_FAILURE)
fail_adjacent_segments(rand(8, 16), hitby)
return
// As we have various shield modes, this handles whether specific things can pass or not.
/obj/effect/shield/CanPass(var/atom/movable/mover, var/turf/target)
// Somehow we don't have a generator. This shouldn't happen. Delete the shield.
if(!gen)
qdel(src)
return 1
if(disabled_for || diffused_for)
return 1
if(mover)
return mover.can_pass_shield(gen)
return 1
/obj/effect/shield/proc/set_can_atmos_pass(var/new_value)
if(new_value == can_atmos_pass)
return
can_atmos_pass = new_value
update_nearby_tiles()
// EMP. It may seem weak but keep in mind that multiple shield segments are likely to be affected.
/obj/effect/shield/emp_act(var/severity)
if(!disabled_for)
take_damage(rand(30,60) / severity, SHIELD_DAMTYPE_EM)
// Explosions
/obj/effect/shield/ex_act(var/severity)
if(!disabled_for)
take_damage(rand(10,15) / severity, SHIELD_DAMTYPE_PHYSICAL)
// Fire
/obj/effect/shield/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
if(!disabled_for)
take_damage(rand(5,10), SHIELD_DAMTYPE_HEAT)
// Projectiles
/obj/effect/shield/bullet_act(var/obj/item/projectile/proj)
if(proj.damage_type == BURN)
take_damage(proj.get_structure_damage(), SHIELD_DAMTYPE_HEAT)
else if (proj.damage_type == BRUTE)
take_damage(proj.get_structure_damage(), SHIELD_DAMTYPE_PHYSICAL)
else
take_damage(proj.get_structure_damage(), SHIELD_DAMTYPE_EM)
// Attacks with hand tools. Blocked by Hyperkinetic flag.
/obj/effect/shield/attackby(var/obj/item/weapon/I as obj, var/mob/user as mob)
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
user.do_attack_animation(src)
if(gen.check_flag(MODEFLAG_HYPERKINETIC))
user.visible_message("<span class='danger'>\The [user] hits \the [src] with \the [I]!</span>")
if(I.damtype == BURN)
take_damage(I.force, SHIELD_DAMTYPE_HEAT)
else if (I.damtype == BRUTE)
take_damage(I.force, SHIELD_DAMTYPE_PHYSICAL)
else
take_damage(I.force, SHIELD_DAMTYPE_EM)
else
user.visible_message("<span class='danger'>\The [user] tries to attack \the [src] with \the [I], but it passes through!</span>")
// Special treatment for meteors because they would otherwise penetrate right through the shield.
/obj/effect/shield/Bumped(var/atom/movable/mover)
if(!gen)
qdel(src)
return 0
mover.shield_impact(src)
return ..()
// Meteors call this instad of Bumped for some reason
/obj/effect/shield/handle_meteor_impact(var/obj/effect/meteor/meteor)
meteor.shield_impact(src)
return !QDELETED(meteor) // If it was stopped it will have been deleted
/obj/effect/shield/proc/overcharge_shock(var/mob/living/M)
M.adjustFireLoss(rand(20, 40))
M.Weaken(5)
to_chat(M, "<span class='danger'>As you come into contact with \the [src] a surge of energy paralyses you!</span>")
take_damage(10, SHIELD_DAMTYPE_EM)
// Called when a flag is toggled. Can be used to add on-toggle behavior, such as visual changes.
/obj/effect/shield/proc/flags_updated()
if(!gen)
qdel(src)
return
// Update airflow - If atmospheric we block air as long as we're enabled (density works for this)
set_can_atmos_pass(gen.check_flag(MODEFLAG_ATMOSPHERIC) ? ATMOS_PASS_DENSITY : ATMOS_PASS_YES)
update_icon()
update_explosion_resistance()
/obj/effect/shield/proc/update_explosion_resistance()
if(gen && gen.check_flag(MODEFLAG_HYPERKINETIC))
explosion_resistance = INFINITY
else
explosion_resistance = 0
//
// Visual effect of shield taking impact
//
/obj/effect/temp_visual/shield_impact_effect
name = "shield impact"
icon = 'icons/obj/machines/shielding.dmi'
icon_state = "shield_impact"
plane = MOB_PLANE
layer = ABOVE_MOB_LAYER
duration = 2 SECONDS
randomdir = FALSE
//
// Shield collision checks below
//
// Called only if shield is active/not destroyed etc.
/atom/movable/proc/can_pass_shield(var/obj/machinery/power/shield_generator/gen)
return 1
// Other mobs
/mob/living/can_pass_shield(var/obj/machinery/power/shield_generator/gen)
return !gen.check_flag(MODEFLAG_NONHUMANS)
// Human mobs
/mob/living/carbon/human/can_pass_shield(var/obj/machinery/power/shield_generator/gen)
if(isSynthetic())
return !gen.check_flag(MODEFLAG_ANORGANIC)
return !gen.check_flag(MODEFLAG_HUMANOIDS)
// Silicon mobs
/mob/living/silicon/can_pass_shield(var/obj/machinery/power/shield_generator/gen)
return !gen.check_flag(MODEFLAG_ANORGANIC)
// Generic objects. Also applies to bullets and meteors.
/obj/can_pass_shield(var/obj/machinery/power/shield_generator/gen)
return !gen.check_flag(MODEFLAG_HYPERKINETIC)
// Beams
/obj/item/projectile/beam/can_pass_shield(var/obj/machinery/power/shield_generator/gen)
return !gen.check_flag(MODEFLAG_PHOTONIC)
// Shield on-impact logic here. This is called only if the object is actually blocked by the field (can_pass_shield applies first)
/atom/movable/proc/shield_impact(var/obj/effect/shield/S)
return
/mob/living/shield_impact(var/obj/effect/shield/S)
if(!S.gen.check_flag(MODEFLAG_OVERCHARGE))
return
S.overcharge_shock(src)
/obj/effect/meteor/shield_impact(var/obj/effect/shield/S)
if(!S.gen.check_flag(MODEFLAG_HYPERKINETIC))
return
S.take_damage(get_shield_damage(), SHIELD_DAMTYPE_PHYSICAL, src)
visible_message("<span class='danger'>\The [src] breaks into dust!</span>")
make_debris()
qdel(src)

View File

@@ -1,20 +1,20 @@
/obj/item/weapon/shield_diffuser /obj/item/weapon/shield_diffuser
name = "portable shield diffuser" name = "portable shield diffuser"
desc = "A small handheld device designed to disrupt energy barriers" desc = "A small handheld device designed to disrupt energy barriers."
description_info = "This device disrupts shields on directly adjacent tiles (in a + shaped pattern), in a similar way the floor mounted variant does. It is, however, portable and run by an internal battery. Can be recharged with a regular recharger." description_info = "This device disrupts shields on directly adjacent tiles (in a + shaped pattern), in a similar way the floor mounted variant does. It is, however, portable and run by an internal battery. Can be recharged with a regular recharger."
icon = 'icons/obj/machines/shielding.dmi' icon = 'icons/obj/machines/shielding.dmi'
icon_state = "hdiffuser_off" icon_state = "hdiffuser_off"
origin_tech = list(TECH_MAGNET = 5, TECH_POWER = 5, TECH_ILLEGAL = 2)
var/obj/item/weapon/cell/device/cell var/obj/item/weapon/cell/device/cell
var/enabled = 0 var/enabled = 0
/obj/item/weapon/shield_diffuser/New() /obj/item/weapon/shield_diffuser/Initialize()
. = ..()
cell = new(src) cell = new(src)
..()
/obj/item/weapon/shield_diffuser/Destroy() /obj/item/weapon/shield_diffuser/Destroy()
qdel(cell) QDEL_NULL(cell)
cell = null
if(enabled) if(enabled)
STOP_PROCESSING(SSobj, src) STOP_PROCESSING(SSobj, src)
. = ..() . = ..()
@@ -24,10 +24,15 @@
/obj/item/weapon/shield_diffuser/process() /obj/item/weapon/shield_diffuser/process()
if(!enabled) if(!enabled)
return return PROCESS_KILL
for(var/direction in cardinal) for(var/direction in cardinal)
var/turf/simulated/shielded_tile = get_step(get_turf(src), direction) var/turf/simulated/shielded_tile = get_step(get_turf(src), direction)
for(var/obj/effect/shield/S in shielded_tile)
// 10kJ per pulse, but gap in the shield lasts for longer than regular diffusers.
if(istype(S) && !S.diffused_for && !S.disabled_for && cell.checked_use(10 KILOWATTS * CELLRATE))
S.diffuse(20)
// Legacy shield support
for(var/obj/effect/energy_field/S in shielded_tile) for(var/obj/effect/energy_field/S in shielded_tile)
if(istype(S) && cell.checked_use(10 KILOWATTS * CELLRATE)) if(istype(S) && cell.checked_use(10 KILOWATTS * CELLRATE))
qdel(S) qdel(S)
@@ -38,16 +43,16 @@
else else
icon_state = "hdiffuser_off" icon_state = "hdiffuser_off"
/obj/item/weapon/shield_diffuser/attack_self() /obj/item/weapon/shield_diffuser/attack_self(mob/user)
enabled = !enabled enabled = !enabled
update_icon() update_icon()
if(enabled) if(enabled)
START_PROCESSING(SSobj, src) START_PROCESSING(SSobj, src)
else else
STOP_PROCESSING(SSobj, src) STOP_PROCESSING(SSobj, src)
to_chat(usr, "You turn \the [src] [enabled ? "on" : "off"].") to_chat(user, "You turn \the [src] [enabled ? "on" : "off"].")
/obj/item/weapon/shield_diffuser/examine() /obj/item/weapon/shield_diffuser/examine(mob/user)
. = ..() . = ..()
to_chat(usr, "The charge meter reads [cell ? cell.percent() : 0]%") to_chat(user, "The charge meter reads [cell ? cell.percent() : 0]%")
to_chat(usr, "It is [enabled ? "enabled" : "disabled"].") to_chat(user, "It is [enabled ? "enabled" : "disabled"].")

View File

@@ -4,6 +4,7 @@
description_info = "This device disrupts shields on directly adjacent tiles (in a + shaped pattern). They are commonly installed around exterior airlocks to prevent shields from blocking EVA access." description_info = "This device disrupts shields on directly adjacent tiles (in a + shaped pattern). They are commonly installed around exterior airlocks to prevent shields from blocking EVA access."
icon = 'icons/obj/machines/shielding.dmi' icon = 'icons/obj/machines/shielding.dmi'
icon_state = "fdiffuser_on" icon_state = "fdiffuser_on"
circuit = /obj/item/weapon/circuitboard/shield_diffuser
use_power = USE_POWER_ACTIVE use_power = USE_POWER_ACTIVE
idle_power_usage = 25 // Previously 100. idle_power_usage = 25 // Previously 100.
active_power_usage = 500 // Previously 2000 active_power_usage = 500 // Previously 2000
@@ -13,12 +14,17 @@
var/alarm = 0 var/alarm = 0
var/enabled = 1 var/enabled = 1
/obj/machinery/shield_diffuser/New() /obj/machinery/shield_diffuser/Initialize()
..() . = ..()
// TODO - Remove this bit once machines are converted to Initialize
if(ispath(circuit))
circuit = new circuit(src)
default_apply_parts()
var/turf/T = get_turf(src) var/turf/T = get_turf(src)
hide(!T.is_plating()) hide(!T.is_plating())
//If underfloor, hide the cable //If underfloor, hide the cable^H^H diffuser
/obj/machinery/shield_diffuser/hide(var/i) /obj/machinery/shield_diffuser/hide(var/i)
if(istype(loc, /turf)) if(istype(loc, /turf))
invisibility = i ? 101 : 0 invisibility = i ? 101 : 0
@@ -38,6 +44,9 @@
return return
for(var/direction in cardinal) for(var/direction in cardinal)
var/turf/simulated/shielded_tile = get_step(get_turf(src), direction) var/turf/simulated/shielded_tile = get_step(get_turf(src), direction)
for(var/obj/effect/shield/S in shielded_tile)
S.diffuse(5)
// Legacy shield support
for(var/obj/effect/energy_field/S in shielded_tile) for(var/obj/effect/energy_field/S in shielded_tile)
qdel(S) qdel(S)
@@ -50,16 +59,27 @@
else else
icon_state = "fdiffuser_on" icon_state = "fdiffuser_on"
/obj/machinery/shield_diffuser/attack_hand() /obj/machinery/shield_diffuser/attack_hand(mob/user as mob)
if((. = ..()))
return
if(alarm) if(alarm)
to_chat(usr, "You press an override button on \the [src], re-enabling it.") to_chat(user, "You press an override button on \the [src], re-enabling it.")
alarm = 0 alarm = 0
update_icon() update_icon()
return return
enabled = !enabled enabled = !enabled
update_use_power(enabled ? USE_POWER_ACTIVE : USE_POWER_IDLE) update_use_power(enabled ? USE_POWER_ACTIVE : USE_POWER_IDLE)
update_icon() update_icon()
to_chat(usr, "You turn \the [src] [enabled ? "on" : "off"].") to_chat(user, "You turn \the [src] [enabled ? "on" : "off"].")
/obj/machinery/shield_diffuser/attackby(var/obj/item/W, var/mob/user)
if(default_deconstruction_screwdriver(user, W))
return
if(default_deconstruction_crowbar(user, W))
return
if(default_part_replacement(user, W))
return
return ..()
/obj/machinery/shield_diffuser/proc/meteor_alarm(var/duration) /obj/machinery/shield_diffuser/proc/meteor_alarm(var/duration)
if(!duration) if(!duration)
@@ -71,4 +91,4 @@
. = ..() . = ..()
to_chat(user, "It is [enabled ? "enabled" : "disabled"].") to_chat(user, "It is [enabled ? "enabled" : "disabled"].")
if(alarm) if(alarm)
to_chat(user, "A red LED labeled \"Proximity Alarm\" is blinking on the control panel.") to_chat(user, "A red LED labeled \"Proximity Alarm\" is blinking on the control panel.")

View File

@@ -0,0 +1,533 @@
//
// This generator is for the supercool big shields intended for ships that do nice stuff with overmaps.
//
/obj/machinery/power/shield_generator
name = "advanced shield generator"
desc = "A heavy-duty shield generator and capacitor, capable of generating energy shields at large distances."
icon = 'icons/obj/machines/shielding.dmi'
icon_state = "generator0"
circuit = /obj/item/weapon/circuitboard/shield_generator
density = 1
var/datum/wires/shield_generator/wires = null
var/list/field_segments = list() // List of all shield segments owned by this generator.
var/list/damaged_segments = list() // List of shield segments that have failed and are currently regenerating.
var/shield_modes = 0 // Enabled shield mode flags
var/mitigation_em = 0 // Current EM mitigation
var/mitigation_physical = 0 // Current Physical mitigation
var/mitigation_heat = 0 // Current Burn mitigation
var/mitigation_max = 0 // Maximal mitigation reachable with this generator. Set by RefreshParts()
var/max_energy = 0 // Maximal stored energy. In joules. Depends on the type of used SMES coil when constructing this generator.
var/current_energy = 0 // Current stored energy.
var/field_radius = 1 // Current field radius.
var/target_radius = 1 // Desired field radius.
var/running = SHIELD_OFF // Whether the generator is enabled or not.
var/input_cap = 1 MEGAWATTS // Currently set input limit. Set to 0 to disable limits altogether. The shield will try to input this value per tick at most
var/upkeep_power_usage = 0 // Upkeep power usage last tick.
var/upkeep_multiplier = 1 // Multiplier of upkeep values.
var/power_usage = 0 // Total power usage last tick.
var/overloaded = 0 // Whether the field has overloaded and shut down to regenerate.
var/hacked = 0 // Whether the generator has been hacked by cutting the safety wire.
var/offline_for = 0 // The generator will be inoperable for this duration in ticks.
var/input_cut = 0 // Whether the input wire is cut.
var/mode_changes_locked = 0 // Whether the control wire is cut, locking out changes.
var/ai_control_disabled = 0 // Whether the AI control is disabled.
var/list/mode_list = null // A list of shield_mode datums.
var/full_shield_strength = 0 // The amount of power shields need to be at full operating strength.
var/initial_shield_modes = MODEFLAG_HYPERKINETIC|MODEFLAG_EM|MODEFLAG_ATMOSPHERIC|MODEFLAG_HUMANOIDS
var/idle_multiplier = 1 // Trades off cost vs. spin-up time from idle to running
var/idle_valid_values = list(1, 2, 5, 10)
var/spinup_delay = 20
var/spinup_counter = 0
/obj/machinery/power/shield_generator/update_icon()
if(running)
icon_state = "generator1"
set_light(1, 2, "#66FFFF")
else
icon_state = "generator0"
set_light(0)
/obj/machinery/power/shield_generator/Initialize()
. = ..()
if(!wires)
wires = new(src)
// TODO - Remove this bit once machines are converted to Initialize
if(ispath(circuit))
circuit = new circuit(src)
default_apply_parts()
connect_to_network()
mode_list = list()
for(var/st in subtypesof(/datum/shield_mode))
var/datum/shield_mode/SM = new st()
mode_list.Add(SM)
toggle_flag(initial_shield_modes)
/obj/machinery/power/shield_generator/Destroy()
shutdown_field()
field_segments = null
damaged_segments = null
mode_list = null
. = ..()
/obj/machinery/power/shield_generator/RefreshParts()
max_energy = 0
full_shield_strength = 0
for(var/obj/item/weapon/smes_coil/S in component_parts)
full_shield_strength += (S.ChargeCapacity * 5)
max_energy = full_shield_strength * 20
current_energy = between(0, current_energy, max_energy)
mitigation_max = MAX_MITIGATION_BASE + MAX_MITIGATION_RESEARCH * total_component_rating_of_type(/obj/item/weapon/stock_parts/capacitor)
mitigation_em = between(0, mitigation_em, mitigation_max)
mitigation_physical = between(0, mitigation_physical, mitigation_max)
mitigation_heat = between(0, mitigation_heat, mitigation_max)
..()
// Shuts down the shield, removing all shield segments and unlocking generator settings.
/obj/machinery/power/shield_generator/proc/shutdown_field()
for(var/obj/effect/shield/S in field_segments)
qdel(S)
running = SHIELD_OFF
current_energy = 0
mitigation_em = 0
mitigation_physical = 0
mitigation_heat = 0
update_icon()
// Generates the field objects. Deletes existing field, if applicable.
/obj/machinery/power/shield_generator/proc/regenerate_field()
if(field_segments.len)
for(var/obj/effect/shield/S in field_segments)
qdel(S)
// The generator is not turned on, so don't generate any new tiles.
if(!running)
return
var/list/shielded_turfs
if(check_flag(MODEFLAG_HULL))
shielded_turfs = fieldtype_hull()
else
shielded_turfs = fieldtype_square()
for(var/turf/T in shielded_turfs)
var/obj/effect/shield/S = new(T)
S.gen = src
S.flags_updated()
field_segments |= S
update_icon()
// Recalculates and updates the upkeep multiplier
/obj/machinery/power/shield_generator/proc/update_upkeep_multiplier()
var/new_upkeep = 1.0
for(var/datum/shield_mode/SM in mode_list)
if(check_flag(SM.mode_flag))
new_upkeep *= SM.multiplier
upkeep_multiplier = new_upkeep
/obj/machinery/power/shield_generator/process()
upkeep_power_usage = 0
power_usage = 0
if(offline_for)
offline_for = max(0, offline_for - 1)
// We're turned off.
if(running == SHIELD_OFF)
return
if(target_radius != field_radius && running != SHIELD_RUNNING) // Do not recalculate the field while it's running; that's extremely laggy.
field_radius += (target_radius > field_radius) ? 1 : -1
// We are shutting down, therefore our stored energy disperses faster than usual.
else if(running == SHIELD_DISCHARGING)
current_energy -= SHIELD_SHUTDOWN_DISPERSION_RATE
else if(running == SHIELD_SPINNING_UP)
spinup_counter--
if(spinup_counter <= 0)
running = SHIELD_RUNNING
regenerate_field()
mitigation_em = between(0, mitigation_em - MITIGATION_LOSS_PASSIVE, mitigation_max)
mitigation_heat = between(0, mitigation_heat - MITIGATION_LOSS_PASSIVE, mitigation_max)
mitigation_physical = between(0, mitigation_physical - MITIGATION_LOSS_PASSIVE, mitigation_max)
if(running == SHIELD_RUNNING)
upkeep_power_usage = round((field_segments.len - damaged_segments.len) * ENERGY_UPKEEP_PER_TILE * upkeep_multiplier)
else if(running > SHIELD_RUNNING)
upkeep_power_usage = round(ENERGY_UPKEEP_IDLE * idle_multiplier * (field_radius * 8) * upkeep_multiplier) // Approximates number of turfs.
if(powernet && (running >= SHIELD_RUNNING) && !input_cut)
var/energy_buffer = 0
energy_buffer = draw_power(min(upkeep_power_usage, input_cap))
power_usage += round(energy_buffer)
if(energy_buffer < upkeep_power_usage)
current_energy -= round(upkeep_power_usage - energy_buffer) // If we don't have enough energy from the grid, take it from the internal battery instead.
// Now try to recharge our internal energy.
var/energy_to_demand
if(input_cap)
energy_to_demand = between(0, max_energy - current_energy, input_cap - energy_buffer)
else
energy_to_demand = max(0, max_energy - current_energy)
energy_buffer = draw_power(energy_to_demand)
power_usage += energy_buffer
current_energy += round(energy_buffer)
else
current_energy -= round(upkeep_power_usage) // We are shutting down, or we lack external power connection. Use energy from internal source instead.
if(current_energy <= 0)
energy_failure()
if(!overloaded)
for(var/obj/effect/shield/S in damaged_segments)
S.regenerate()
else if (field_integrity() > 25)
overloaded = 0
/obj/machinery/power/shield_generator/attackby(obj/item/O as obj, mob/user as mob)
if(panel_open && (O?.is_multitool() || O?.is_wirecutter()))
wires.Interact(user)
return TRUE
if(default_deconstruction_screwdriver(user, O))
return
if(O?.is_crowbar() || O?.is_wrench() || istype(O, /obj/item/weapon/storage/part_replacer))
if(offline_for)
to_chat(user, "<span class='warning'>Wait until \the [src] cools down from emergency shutdown first!</span>")
return
if(running)
to_chat(user, "<span class='notice'>Turn off \the [src] first!</span>")
return
if(default_deconstruction_crowbar(user, O))
return
if(default_part_replacement(user, O))
return
if(default_unfasten_wrench(user, O, 40))
return
return ..()
/obj/machinery/power/shield_generator/proc/energy_failure()
if(running == SHIELD_DISCHARGING)
shutdown_field()
else
current_energy = 0
overloaded = 1
for(var/obj/effect/shield/S in field_segments)
S.fail(1)
/obj/machinery/power/shield_generator/proc/set_idle(var/new_state)
if(new_state)
if(running == SHIELD_IDLE)
return
running = SHIELD_IDLE
for(var/obj/effect/shield/S in field_segments)
qdel(S)
else
if(running != SHIELD_IDLE)
return
running = SHIELD_SPINNING_UP
spinup_counter = round(spinup_delay / idle_multiplier)
update_icon()
/obj/machinery/power/shield_generator/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
var/data[0]
data["running"] = running
data["modes"] = get_flag_descriptions()
data["overloaded"] = overloaded
data["mitigation_max"] = mitigation_max
data["mitigation_physical"] = round(mitigation_physical, 0.1)
data["mitigation_em"] = round(mitigation_em, 0.1)
data["mitigation_heat"] = round(mitigation_heat, 0.1)
data["field_integrity"] = field_integrity()
data["max_energy"] = round(max_energy / 1000000, 0.1)
data["current_energy"] = round(current_energy / 1000000, 0.1)
data["percentage_energy"] = round(data["current_energy"] / data["max_energy"] * 100)
data["total_segments"] = field_segments ? field_segments.len : 0
data["functional_segments"] = damaged_segments ? data["total_segments"] - damaged_segments.len : data["total_segments"]
data["field_radius"] = field_radius
data["target_radius"] = target_radius
data["input_cap_kw"] = round(input_cap / 1000)
data["upkeep_power_usage"] = round(upkeep_power_usage / 1000, 0.1)
data["power_usage"] = round(power_usage / 1000)
data["hacked"] = hacked
data["offline_for"] = offline_for * 2
data["idle_multiplier"] = idle_multiplier
data["idle_valid_values"] = idle_valid_values
data["spinup_counter"] = spinup_counter
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "shield_generator.tmpl", src.name, 500, 800)
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
/obj/machinery/power/shield_generator/attack_hand(mob/user)
if((. = ..()))
return
if(panel_open && Adjacent(user))
wires.Interact(user)
return
if(CanUseTopic(user, global.default_state) > STATUS_CLOSE)
ui_interact(user)
return TRUE
/obj/machinery/power/shield_generator/CanUseTopic(var/mob/user)
if(issilicon(user) && !Adjacent(user) && ai_control_disabled)
return STATUS_UPDATE
if(panel_open)
return min(..(), STATUS_DISABLED)
return ..()
/obj/machinery/power/shield_generator/Topic(href, href_list, datum/topic_state/state = default_state)
if((. = ..()))
return
if(href_list["begin_shutdown"])
if(running < SHIELD_RUNNING) // Discharging or off
return
var/alert = alert(usr, "Are you sure you wish to do this? It will drain the power inside the internal storage rapidly.", "Are you sure?", "Yes", "No")
if(!CanInteract(usr, state))
return
if(running < SHIELD_RUNNING)
return
if(alert == "Yes")
set_idle(TRUE) // do this first to clear the field
running = SHIELD_DISCHARGING
return TOPIC_REFRESH
if(href_list["start_generator"])
if(offline_for)
return
set_idle(TRUE)
return TOPIC_REFRESH
if(href_list["toggle_idle"])
if(running < SHIELD_RUNNING)
return TOPIC_HANDLED
set_idle(text2num(href_list["toggle_idle"]))
return TOPIC_REFRESH
// Instantly drops the shield, but causes a cooldown before it may be started again. Also carries a risk of EMP at high charge.
if(href_list["emergency_shutdown"])
if(!running)
return TOPIC_HANDLED
var/choice = input(usr, "Are you sure that you want to initiate an emergency shield shutdown? This will instantly drop the shield, and may result in unstable release of stored electromagnetic energy. Proceed at your own risk.") in list("Yes", "No")
if((choice != "Yes") || !running)
return TOPIC_HANDLED
// If the shield would take 5 minutes to disperse and shut down using regular methods, it will take x1.5 (7 minutes and 30 seconds) of this time to cool down after emergency shutdown
offline_for = round(current_energy / (SHIELD_SHUTDOWN_DISPERSION_RATE / 1.5))
var/old_energy = current_energy
shutdown_field()
log_and_message_admins("has triggered \the [src]'s emergency shutdown!", usr)
spawn()
empulse(src, old_energy / 60000000, old_energy / 32000000, 1) // If shields are charged at 450 MJ, the EMP will be 7.5, 14.0625. 90 MJ, 1.5, 2.8125
old_energy = 0
return TOPIC_REFRESH
if(mode_changes_locked)
return TOPIC_REFRESH
if(href_list["set_range"])
var/new_range = input(usr, "Enter new field range (1-[world.maxx]). Leave blank to cancel.", "Field Radius Control", field_radius) as num
if(!new_range)
return TOPIC_HANDLED
target_radius = between(1, new_range, world.maxx)
return TOPIC_REFRESH
if(href_list["set_input_cap"])
var/new_cap = round(input(usr, "Enter new input cap (in kW). Enter 0 or nothing to disable input cap.", "Generator Power Control", round(input_cap / 1000)) as num)
if(!new_cap)
input_cap = 0
return
input_cap = max(0, new_cap) * 1000
return TOPIC_REFRESH
if(href_list["toggle_mode"])
// Toggling hacked-only modes requires the hacked var to be set to 1
if((text2num(href_list["toggle_mode"]) & (MODEFLAG_BYPASS | MODEFLAG_OVERCHARGE)) && !hacked)
return TOPIC_HANDLED
toggle_flag(text2num(href_list["toggle_mode"]))
return TOPIC_REFRESH
if(href_list["switch_idle"])
if(running == SHIELD_SPINNING_UP)
return TOPIC_REFRESH
var/new_idle = text2num(href_list["switch_idle"])
if(new_idle in idle_valid_values)
idle_multiplier = new_idle
return TOPIC_REFRESH
/obj/machinery/power/shield_generator/proc/field_integrity()
if(full_shield_strength)
return round(CLAMP01(current_energy / full_shield_strength) * 100)
return 0
// Takes specific amount of damage
/obj/machinery/power/shield_generator/proc/deal_shield_damage(var/damage, var/shield_damtype)
var/energy_to_use = damage * ENERGY_PER_HP
if(check_flag(MODEFLAG_MODULATE))
mitigation_em -= MITIGATION_HIT_LOSS
mitigation_heat -= MITIGATION_HIT_LOSS
mitigation_physical -= MITIGATION_HIT_LOSS
switch(shield_damtype)
if(SHIELD_DAMTYPE_PHYSICAL)
mitigation_physical += MITIGATION_HIT_LOSS + MITIGATION_HIT_GAIN
energy_to_use *= 1 - (mitigation_physical / 100)
if(SHIELD_DAMTYPE_EM)
mitigation_em += MITIGATION_HIT_LOSS + MITIGATION_HIT_GAIN
energy_to_use *= 1 - (mitigation_em / 100)
if(SHIELD_DAMTYPE_HEAT)
mitigation_heat += MITIGATION_HIT_LOSS + MITIGATION_HIT_GAIN
energy_to_use *= 1 - (mitigation_heat / 100)
mitigation_em = between(0, mitigation_em, mitigation_max)
mitigation_heat = between(0, mitigation_heat, mitigation_max)
mitigation_physical = between(0, mitigation_physical, mitigation_max)
current_energy -= energy_to_use
// Overload the shield, which will shut it down until we recharge above 25% again
if(current_energy < 0)
energy_failure()
return SHIELD_BREACHED_FAILURE
if(prob(10 - field_integrity()))
return SHIELD_BREACHED_CRITICAL
if(prob(20 - field_integrity()))
return SHIELD_BREACHED_MAJOR
if(prob(35 - field_integrity()))
return SHIELD_BREACHED_MINOR
return SHIELD_ABSORBED
// Checks whether specific flags are enabled
/obj/machinery/power/shield_generator/proc/check_flag(var/flag)
return (shield_modes & flag)
/obj/machinery/power/shield_generator/proc/toggle_flag(var/flag)
shield_modes ^= flag
update_upkeep_multiplier()
for(var/obj/effect/shield/S in field_segments)
S.flags_updated()
if((flag & (MODEFLAG_HULL|MODEFLAG_MULTIZ)) && (running == SHIELD_RUNNING))
regenerate_field()
if(flag & MODEFLAG_MODULATE)
mitigation_em = 0
mitigation_physical = 0
mitigation_heat = 0
/obj/machinery/power/shield_generator/proc/get_flag_descriptions()
var/list/all_flags = list()
for(var/datum/shield_mode/SM in mode_list)
if(SM.hacked_only && !hacked)
continue
all_flags.Add(list(list(
"name" = SM.mode_name,
"desc" = SM.mode_desc,
"flag" = SM.mode_flag,
"status" = check_flag(SM.mode_flag),
"hacked" = SM.hacked_only,
"multiplier" = SM.multiplier
)))
return all_flags
// These two procs determine tiles that should be shielded given the field range. They are quite CPU intensive and may trigger BYOND infinite loop checks, therefore they are set
// as background procs to prevent locking up the server. They are only called when the field is generated, or when hull mode is toggled on/off.
/obj/machinery/power/shield_generator/proc/fieldtype_square()
set background = 1
var/list/out = list()
var/list/base_turfs = get_base_turfs()
for(var/turf/gen_turf in base_turfs)
var/turf/T
for (var/x_offset = -field_radius; x_offset <= field_radius; x_offset++)
T = locate(gen_turf.x + x_offset, gen_turf.y - field_radius, gen_turf.z)
if(T)
out += T
T = locate(gen_turf.x + x_offset, gen_turf.y + field_radius, gen_turf.z)
if(T)
out += T
for (var/y_offset = -field_radius+1; y_offset < field_radius; y_offset++)
T = locate(gen_turf.x - field_radius, gen_turf.y + y_offset, gen_turf.z)
if(T)
out += T
T = locate(gen_turf.x + field_radius, gen_turf.y + y_offset, gen_turf.z)
if(T)
out += T
return out
/obj/machinery/power/shield_generator/proc/fieldtype_hull()
set background = 1
. = list()
var/list/base_turfs = get_base_turfs()
// Old code found all space turfs and added them if it had a non-space neighbor.
// This one finds all non-space turfs and adds all its non-space neighbors.
for(var/turf/gen_turf in base_turfs)
var/area/TA = null // Variable for area checking. Defining it here so memory does not have to be allocated repeatedly.
for(var/turf/T in trange(field_radius, gen_turf))
// Don't expand to space or on shuttle areas.
if(istype(T, /turf/space) || istype(T, /turf/simulated/open))
continue
// Find adjacent space/shuttle tiles and cover them. Shuttles won't be blocked if shield diffuser is mapped in and turned on.
for(var/turf/TN in orange(1, T))
TA = get_area(TN)
//if ((istype(TN, /turf/space) || (istype(TN, /turf/simulated/open) && (istype(TA, /area/space) || TA.area_flags & AREA_FLAG_EXTERNAL))))
if((istype(TN, /turf/space) || (istype(TN, /turf/simulated/open) && (istype(TA, /area/space)))))
. |= TN
continue
// Returns a list of turfs from which a field will propagate. If multi-Z mode is enabled, this will return a "column" of turfs above and below the generator.
/obj/machinery/power/shield_generator/proc/get_base_turfs()
var/list/turfs = list()
var/turf/T = get_turf(src)
if(!istype(T))
return
turfs.Add(T)
// Multi-Z mode is disabled
if(!check_flag(MODEFLAG_MULTIZ))
return turfs
while(HasAbove(T.z))
T = GetAbove(T)
if(istype(T))
turfs.Add(T)
T = get_turf(src)
while(HasBelow(T.z))
T = GetBelow(T)
if(istype(T))
turfs.Add(T)
return turfs

View File

@@ -0,0 +1,83 @@
// Definitions for shield modes. Names, descriptions and power usage multipliers can be changed here.
// Do not change the mode_flag variables without a good reason!
/datum/shield_mode
var/mode_name // User-friendly name of this mode.
var/mode_desc // A short description of what the mode does.
var/mode_flag // Mode bitflag. See defines file.
var/multiplier // Energy usage multiplier. Each enabled mode multiplies upkeep power usage by this number. Values between 1-2 are good balance-wise. Hacked modes can go up to 3-4
var/hacked_only = 0 // Set to 1 to allow usage of this shield mode only on hacked generators.
/datum/shield_mode/hyperkinetic
mode_name = "Hyperkinetic Projectiles"
mode_desc = "This mode blocks various fast moving physical objects, such as bullets, blunt weapons, meteors and other."
mode_flag = MODEFLAG_HYPERKINETIC
multiplier = 1.2
/datum/shield_mode/photonic
mode_name = "Photonic Dispersion"
mode_desc = "This mode blocks majority of light. This includes beam weaponry and most of the visible light spectrum."
mode_flag = MODEFLAG_PHOTONIC
multiplier = 1.3
/datum/shield_mode/em
mode_name = "Electro-Magnetic Shielding"
mode_desc = "This mode blocks various high-power emissions like electrical storms."
mode_flag = MODEFLAG_EM
multiplier = 1.3
/datum/shield_mode/humanoids
mode_name = "Humanoid Lifeforms"
mode_desc = "This mode blocks various humanoid lifeforms. Does not affect fully synthetic humanoids."
mode_flag = MODEFLAG_HUMANOIDS
multiplier = 1.5
/datum/shield_mode/silicon
mode_name = "Silicon Lifeforms"
mode_desc = "This mode blocks various silicon based lifeforms."
mode_flag = MODEFLAG_ANORGANIC
multiplier = 1.5
/datum/shield_mode/mobs
mode_name = "Unknown Lifeforms"
mode_desc = "This mode blocks various other non-humanoid and non-silicon lifeforms. Typical uses include blocking carps."
mode_flag = MODEFLAG_NONHUMANS
multiplier = 1.5
/datum/shield_mode/atmosphere
mode_name = "Atmospheric Containment"
mode_desc = "This mode blocks air flow and acts as atmosphere containment."
mode_flag = MODEFLAG_ATMOSPHERIC
multiplier = 1.3
/datum/shield_mode/hull
mode_name = "Hull Shielding"
mode_desc = "This mode recalibrates the field to cover surface of the installation instead of projecting a bubble shaped field."
mode_flag = MODEFLAG_HULL
multiplier = 1
/datum/shield_mode/adaptive
mode_name = "Adaptive Field Harmonics"
mode_desc = "This mode modulates the shield harmonic frequencies, allowing the field to adapt to various damage types."
mode_flag = MODEFLAG_MODULATE
multiplier = 2
/datum/shield_mode/bypass
mode_name = "Diffuser Bypass"
mode_desc = "This mode disables the built-in safeties which allows the generator to counter effect of various shield diffusers. This tends to create a very large strain on the generator. Does not work with enabled safety protocols."
mode_flag = MODEFLAG_BYPASS
multiplier = 3
hacked_only = 1
/datum/shield_mode/overcharge
mode_name = "Field Overcharge"
mode_desc = "This mode polarises the field, causing damage on contact. Does not work with enabled safety protocols."
mode_flag = MODEFLAG_OVERCHARGE
multiplier = 3
hacked_only = 1
/datum/shield_mode/multiz
mode_name = "Multi-Dimensional Field Warp"
mode_desc = "Recalibrates the field projection array to increase the vertical height of the field, allowing it's usage on multi-deck stations or ships."
mode_flag = MODEFLAG_MULTIZ
multiplier = 1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,158 @@
{{if data.offline_for}}
<h1>EMERGENCY SHUTDOWN</h1>
<b>An emergency shutdown has been initiated - generator cooling down</b><br>
<i>Please wait until the generator cools down before resuming operation. Estimated time left: {{:data.offline_for}} seconds.</i>
{{else}}
<h2>SYSTEM STATUS</h2>
<div class="item">
<div class="itemLabel">
Generator is:
</div>
<div class="itemContent">
{{if data.running == 2}}
{{if data.overloaded}}
Recovering
{{else}}
Active
{{/if}}
{{else data.running == 1}}
Shutting Down
{{else data.running == 3}}
Inactive
{{else data.running == 4}}
Spinning Up&nbsp;
{{if data.target_radius != data.field_radius}}
(Adjusting Radius)
{{else}}
({{:data.spinup_counter * 2}}s)
{{/if}}
{{else}}
Offline
{{/if}}
</div>
</div>
<div class="item">
<div class="itemLabel">
Energy Storage:
</div>
<div class="itemContent">
{{:data.current_energy}}/{{:data.max_energy}} MJ ({{:data.percentage_energy}}%)
</div>
</div>
<div class="item">
<div class="itemLabel">
Shield Integrity:
</div>
<div class="itemContent">
{{:data.field_integrity}}%
</div>
</div>
<div class="item">
<div class="itemLabel">
Mitigation:
</div>
<div class="itemContent">
{{:data.mitigation_em}}% EM / {{:data.mitigation_physical}}% PH / {{:data.mitigation_heat}}% HE / {{:data.mitigation_max}}% MAX
</div>
</div>
<div class="item">
<div class="itemLabel">
Upkeep Energy Use:
</div>
<div class="itemContent">
{{:data.upkeep_power_usage}} kW
</div>
</div>
<div class="item">
<div class="itemLabel">
Total Energy Use:
</div>
<div class="itemContent">
{{if data.input_cap_kw}}
{{:data.power_usage}} / {{:data.input_cap_kw}} kW
{{else}}
{{:data.power_usage}} kW (No Limit)
{{/if}}
</div>
</div>
<div class="item">
<div class="itemLabel">
Field Size:
</div>
<div class="itemContent">
{{:data.functional_segments}} / {{:data.total_segments}} m2 (radius {{:data.field_radius}}, target {{:data.target_radius}})
</div>
</div>
<h2>CONTROLS</h2>
<table>
<tr>
{{if (data.running >= 2)}}
<td>{{:helper.link('Turn off', null, {'begin_shutdown' : '1'})}}
{{if (data.running == 3)}}
<td>{{:helper.link('Activate', null, {'toggle_idle' : '0'})}} </td>
{{else}}
<td>{{:helper.link('Deactivate', null, {'toggle_idle' : '1'})}} </td>
{{/if}}
{{else}}
<td>{{:helper.link('Turn on', null, {'start_generator' : '1'})}}
{{/if}}
{{if data.running}}
{{if data.hacked}}
<td>{{:helper.link('EMERGENCY SHUTDOWN', null, {'emergency_shutdown' : '1'}, null, 'redButton')}}
{{/if}}
{{/if}}
<tr>
<td>{{:helper.link('Set Field Range', null, {'set_range' : '1'})}}
<td>{{:helper.link('Set Input Cap', null, {'set_input_cap' : '1'})}}
<tr>
<td>Set inactive power use intensity:</td>
<td>
{{for data.idle_valid_values}}
{{:helper.link(value, null, {'switch_idle' : value}, (value == data.idle_multiplier) ? 'selected' : (data.running == 4 ? 'disabled' : null))}}
{{/for}}
</td>
</table>
<h2>FIELD CALIBRATION</h2>
<hr>
{{for data.modes}}
<div class="item">
<div class="itemLabel">
Mode:
</div>
<div class="itemContent">
{{:value.name}}
</div>
</div>
<div class="item">
<div class="itemLabel">
Description:
</div>
<div class="itemContent">
{{:value.desc}}
</div>
</div>
<div class="item">
<div class="itemLabel">
Status:
</div>
<div class="itemContent">
{{if value.status}}
Enabled
{{else}}
Disabled
{{/if}}
{{:helper.link('Toggle', null, {'toggle_mode' : value.flag})}}
</div>
</div>
<div class="item">
<div class="itemLabel">
Multiplier:
</div>
<div class="itemContent">
{{:value.multiplier}}
</div>
</div>
<hr>
{{/for}}
{{/if}}

View File

@@ -68,6 +68,7 @@
#include "code\__defines\qdel.dm" #include "code\__defines\qdel.dm"
#include "code\__defines\research.dm" #include "code\__defines\research.dm"
#include "code\__defines\roguemining_vr.dm" #include "code\__defines\roguemining_vr.dm"
#include "code\__defines\shields.dm"
#include "code\__defines\shuttle.dm" #include "code\__defines\shuttle.dm"
#include "code\__defines\sound.dm" #include "code\__defines\sound.dm"
#include "code\__defines\species_languages.dm" #include "code\__defines\species_languages.dm"
@@ -451,6 +452,7 @@
#include "code\datums\wires\radio.dm" #include "code\datums\wires\radio.dm"
#include "code\datums\wires\robot.dm" #include "code\datums\wires\robot.dm"
#include "code\datums\wires\seedstorage.dm" #include "code\datums\wires\seedstorage.dm"
#include "code\datums\wires\shield_generator.dm"
#include "code\datums\wires\smartfridge.dm" #include "code\datums\wires\smartfridge.dm"
#include "code\datums\wires\smes.dm" #include "code\datums\wires\smes.dm"
#include "code\datums\wires\suit_storage_unit.dm" #include "code\datums\wires\suit_storage_unit.dm"
@@ -3230,12 +3232,15 @@
#include "code\modules\shieldgen\directional_shield.dm" #include "code\modules\shieldgen\directional_shield.dm"
#include "code\modules\shieldgen\emergency_shield.dm" #include "code\modules\shieldgen\emergency_shield.dm"
#include "code\modules\shieldgen\energy_field.dm" #include "code\modules\shieldgen\energy_field.dm"
#include "code\modules\shieldgen\energy_shield.dm"
#include "code\modules\shieldgen\handheld_defuser.dm" #include "code\modules\shieldgen\handheld_defuser.dm"
#include "code\modules\shieldgen\sheldwallgen.dm" #include "code\modules\shieldgen\sheldwallgen.dm"
#include "code\modules\shieldgen\shield_capacitor.dm" #include "code\modules\shieldgen\shield_capacitor.dm"
#include "code\modules\shieldgen\shield_diffuser.dm" #include "code\modules\shieldgen\shield_diffuser.dm"
#include "code\modules\shieldgen\shield_gen.dm" #include "code\modules\shieldgen\shield_gen.dm"
#include "code\modules\shieldgen\shield_gen_external.dm" #include "code\modules\shieldgen\shield_gen_external.dm"
#include "code\modules\shieldgen\shield_generator.dm"
#include "code\modules\shieldgen\shield_modes.dm"
#include "code\modules\shuttles\antagonist.dm" #include "code\modules\shuttles\antagonist.dm"
#include "code\modules\shuttles\crashes.dm" #include "code\modules\shuttles\crashes.dm"
#include "code\modules\shuttles\departmental.dm" #include "code\modules\shuttles\departmental.dm"