From c84ab67fd30d88cd2bb819fbbc0e1655e93f4658 Mon Sep 17 00:00:00 2001 From: Neerti Date: Thu, 13 Apr 2017 00:36:13 -0400 Subject: [PATCH] Changes Shields See PR for details --- code/__defines/machinery.dm | 4 + code/_helpers/unsorted.dm | 13 ++ code/game/gamemodes/meteor/meteors.dm | 97 ++++++++---- code/game/machinery/doors/airlock.dm | 2 +- code/modules/shieldgen/energy_field.dm | 146 ++++++++++++++---- code/modules/shieldgen/handheld_defuser.dm | 49 ++++++ code/modules/shieldgen/shield_capacitor.dm | 22 +-- code/modules/shieldgen/shield_diffuser.dm | 74 +++++++++ code/modules/shieldgen/shield_gen.dm | 126 ++++++++++----- code/modules/shieldgen/shield_gen_external.dm | 10 +- .../xenoarcheaology/effects/forcefield.dm | 4 +- icons/obj/machines/shielding.dmi | Bin 2427 -> 5862 bytes maps/example/example-1.dmm | 32 ++-- polaris.dme | 2 + 14 files changed, 444 insertions(+), 137 deletions(-) create mode 100644 code/modules/shieldgen/handheld_defuser.dm create mode 100644 code/modules/shieldgen/shield_diffuser.dm diff --git a/code/__defines/machinery.dm b/code/__defines/machinery.dm index 6f8bec025b..cae68c3a23 100644 --- a/code/__defines/machinery.dm +++ b/code/__defines/machinery.dm @@ -3,6 +3,10 @@ var/global/defer_powernet_rebuild = 0 // True if net rebuild will be called #define CELLRATE 0.002 // Multiplier for watts per tick <> cell storage (e.g., 0.02 means if there is a load of 1000 watts, 20 units will be taken from a cell per second) // It's a conversion constant. power_used*CELLRATE = charge_provided, or charge_used/CELLRATE = power_provided +#define KILOWATTS *1000 +#define MEGAWATTS *1000000 +#define GIGAWATTS *1000000000 + // Doors! #define DOOR_CRUSH_DAMAGE 20 #define ALIEN_SELECT_AFK_BUFFER 1 // How many minutes that a person can be AFK before not being allowed to be an alien. diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm index d244916ec2..4aa05ac244 100644 --- a/code/_helpers/unsorted.dm +++ b/code/_helpers/unsorted.dm @@ -1299,3 +1299,16 @@ var/mob/dview/dview_mob = new tY = max(1, min(world.maxy, origin.y + (text2num(tY) - (world.view + 1)))) return locate(tX, tY, tZ) +// Displays something as commonly used (non-submultiples) SI units. +/proc/format_SI(var/number, var/symbol) + switch(round(abs(number))) + if(0 to 1000-1) + return "[number] [symbol]" + if(1e3 to 1e6-1) + return "[round(number / 1000, 0.1)] k[symbol]" // kilo + if(1e6 to 1e9-1) + return "[round(number / 1e6, 0.1)] m[symbol]" // mega + if(1e9 to 1e12-1) // Probably not needed but why not be complete? + return "[round(number / 1e9, 0.1)] g[symbol]" // giga + if(1e12 to 1e15-1) + return "[round(number / 1e12, 0.1)] t[symbol]" // tera diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm index bd056f7c69..ad8b89ec80 100644 --- a/code/game/gamemodes/meteor/meteors.dm +++ b/code/game/gamemodes/meteor/meteors.dm @@ -80,6 +80,10 @@ var/turf/T = locate(endx, endy, Z) return T +// Override for special behavior when getting hit by meteors, and only meteors. Return one if the meteor hasn't been 'stopped'. +/atom/proc/handle_meteor_impact(var/obj/effect/meteor/meteor) + return TRUE + /////////////////////// //The meteor effect ////////////////////// @@ -101,6 +105,11 @@ var/meteordrop = /obj/item/weapon/ore/iron var/dropamt = 2 + // How much damage it does to walls, using take_damage(). + // Normal walls will die to 150 or more, where as reinforced walls need 800 to penetrate. Durasteel walls need 1200 damage to go through. + // Multiply this and the hits var to get a rough idea of how penetrating a meteor is. + var/wall_power = 100 + /obj/effect/meteor/New() ..() z_original = z @@ -132,8 +141,11 @@ /obj/effect/meteor/Bump(atom/A) if(A) - ram_turf(get_turf(A)) - get_hit() + if(A.handle_meteor_impact(src)) // Used for special behaviour when getting hit specifically by a meteor, like a shield. + ram_turf(get_turf(A)) + get_hit() + else + die(0) /obj/effect/meteor/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) return istype(mover, /obj/effect/meteor) ? 1 : ..() @@ -146,7 +158,11 @@ //then, ram the turf if it still exists if(T) - T.ex_act(hitpwr) + if(istype(T, /turf/simulated/wall)) + var/turf/simulated/wall/W = T + W.take_damage(wall_power) // Stronger walls can halt asteroids. + else + T.ex_act(hitpwr) // Floors and other things lack fancy health. //process getting 'hit' by colliding with a dense object @@ -154,9 +170,12 @@ /obj/effect/meteor/proc/get_hit() hits-- if(hits <= 0) - make_debris() - meteor_effect() - qdel(src) + die(1) + +/obj/effect/meteor/proc/die(var/explode = 1) + make_debris() + meteor_effect(explode) + qdel(src) /obj/effect/meteor/ex_act() return @@ -172,21 +191,24 @@ var/obj/item/O = new meteordrop(get_turf(src)) O.throw_at(dest, 5, 10) -/obj/effect/meteor/proc/meteor_effect() +/obj/effect/meteor/proc/shake_players() + for(var/mob/M in player_list) + var/turf/T = get_turf(M) + if(!T || T.z != src.z) + continue + var/dist = get_dist(M.loc, src.loc) + shake_camera(M, dist > 20 ? 3 : 5, dist > 20 ? 1 : 3) + +/obj/effect/meteor/proc/meteor_effect(var/explode) if(heavy) - for(var/mob/M in player_list) - var/turf/T = get_turf(M) - if(!T || T.z != src.z) - continue - var/dist = get_dist(M.loc, src.loc) - shake_camera(M, dist > 20 ? 3 : 5, dist > 20 ? 1 : 3) + shake_players() /////////////////////// //Meteor types /////////////////////// -//Dust +// Dust breaks windows and hurts normal walls, generally more of an annoyance than a danger unless two happen to hit the same spot. /obj/effect/meteor/dust name = "space dust" icon_state = "dust" @@ -194,63 +216,74 @@ hits = 1 hitpwr = 3 meteordrop = /obj/item/weapon/ore/glass + wall_power = 50 -//Medium-sized +// Medium-sized meteors aren't very special and can be stopped easily by r-walls. /obj/effect/meteor/medium name = "meteor" dropamt = 3 + wall_power = 200 -/obj/effect/meteor/medium/meteor_effect() +/obj/effect/meteor/medium/meteor_effect(var/explode) ..() - explosion(src.loc, 0, 1, 2, 3, 0) + if(explode) + explosion(src.loc, 0, 1, 2, 3, 0) -//Large-sized +// Large-sized meteors generally pack the most punch, but are more concentrated towards the epicenter. /obj/effect/meteor/big name = "large meteor" icon_state = "large" - hits = 6 + hits = 8 heavy = 1 dropamt = 4 + wall_power = 400 -/obj/effect/meteor/big/meteor_effect() +/obj/effect/meteor/big/meteor_effect(var/explode) ..() - explosion(src.loc, 1, 2, 3, 4, 0) + if(explode) + explosion(src.loc, devastation_range = 2, heavy_impact_range = 4, light_impact_range = 6, flash_range = 12, adminlog = 0) -//Flaming meteor +// 'Flaming' meteors do less overall damage but are spread out more due to a larger but weaker explosion at the end. /obj/effect/meteor/flaming name = "flaming meteor" icon_state = "flaming" hits = 5 heavy = 1 meteordrop = /obj/item/weapon/ore/phoron + wall_power = 100 -/obj/effect/meteor/flaming/meteor_effect() +/obj/effect/meteor/flaming/meteor_effect(var/explode) ..() - explosion(src.loc, 1, 2, 3, 4, 0, 0, 5) + if(explode) + explosion(src.loc, devastation_range = 1, heavy_impact_range = 2, light_impact_range = 8, flash_range = 16, adminlog = 0) -//Radiation meteor +// Irradiated meteors do less physical damage but project a ten-tile ranged pulse of radiation upon exploding. /obj/effect/meteor/irradiated name = "glowing meteor" icon_state = "glowing" heavy = 1 meteordrop = /obj/item/weapon/ore/uranium + wall_power = 75 -/obj/effect/meteor/irradiated/meteor_effect() +/obj/effect/meteor/irradiated/meteor_effect(var/explode) ..() - explosion(src.loc, 0, 0, 4, 3, 0) + if(explode) + explosion(src.loc, devastation_range = 0, heavy_impact_range = 0, light_impact_range = 4, flash_range = 6, adminlog = 0) new /obj/effect/decal/cleanable/greenglow(get_turf(src)) - for(var/mob/living/L in view(5, src)) + for(var/mob/living/L in view(10, src)) L.apply_effect(40, IRRADIATE) +// This meteor fries toasters. /obj/effect/meteor/emp name = "conducting meteor" icon_state = "glowing_blue" desc = "Hide your floppies!" meteordrop = /obj/item/weapon/ore/osmium dropamt = 3 + wall_power = 80 -/obj/effect/meteor/emp/meteor_effect() +/obj/effect/meteor/emp/meteor_effect(var/explode) ..() // Best case scenario: Comparable to a low-yield EMP grenade. // Worst case scenario: Comparable to a standard yield EMP grenade. @@ -265,10 +298,12 @@ hitpwr = 1 heavy = 1 meteordrop = /obj/item/weapon/ore/phoron + wall_power = 150 -/obj/effect/meteor/tunguska/meteor_effect() +/obj/effect/meteor/tunguska/meteor_effect(var/explode) ..() - explosion(src.loc, 5, 10, 15, 20, 0) + if(explode) + explosion(src.loc, 5, 10, 15, 20, 0) /obj/effect/meteor/tunguska/Bump() ..() diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 97dd9f5fe0..6aeecfc8e5 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -944,7 +944,7 @@ About the new airlock wires panel: healthcheck() /obj/effect/energy_field/airlock_crush(var/crush_damage) - Stress(crush_damage) + adjust_strength(crush_damage) /obj/structure/closet/airlock_crush(var/crush_damage) ..() diff --git a/code/modules/shieldgen/energy_field.dm b/code/modules/shieldgen/energy_field.dm index 04b471182b..59f2f02ab6 100644 --- a/code/modules/shieldgen/energy_field.dm +++ b/code/modules/shieldgen/energy_field.dm @@ -1,61 +1,100 @@ //---------- actual energy field +// Each field object has a strength var (mensured in "Renwicks"). +// Melee weapons do 5% of their normal (force var) damage, so a harmbaton would do 0.75 Renwick. +// Projectiles do 5% of their structural damage, so a normal laser would do 2 Renwick damage. +// For meteors, one Renwick is about equal to one layer of r-wall. +// Meteors will be completely halted by the shield if the shield survives the impact. +// Explosions do 4 Renwick of damage per severity, at a max of 12. + /obj/effect/energy_field - name = "energy field" + name = "energy shield field" desc = "Impenetrable field of energy, capable of blocking anything as long as it's active." icon = 'icons/obj/machines/shielding.dmi' - icon_state = "shieldsparkles" + icon_state = "shield" + alpha = 100 anchored = 1 layer = 4.1 //just above mobs density = 0 - invisibility = 101 - var/strength = 0 + var/obj/machinery/shield_gen/my_gen = null + var/strength = 0 // in Renwicks var/ticks_recovering = 10 + var/max_strength = 10 -/obj/effect/energy_field/New() - ..() +/obj/effect/energy_field/New(var/newloc, var/new_gen) + ..(newloc) + my_gen = new_gen update_nearby_tiles() /obj/effect/energy_field/Destroy() update_nearby_tiles() + my_gen.field.Remove(src) + my_gen = null + var/turf/current_loc = get_turf(src) + spawn(1) // Updates neightbors after we're gone. + for(var/direction in cardinal) + var/turf/T = get_step(current_loc, direction) + if(T) + for(var/obj/effect/energy_field/F in T) + F.update_icon() ..() /obj/effect/energy_field/ex_act(var/severity) - Stress(0.5 + severity) + adjust_strength(-(4 - severity) * 4) /obj/effect/energy_field/bullet_act(var/obj/item/projectile/Proj) - Stress(Proj.get_structure_damage() / 10) + adjust_strength(-Proj.get_structure_damage() / 10) -/obj/effect/energy_field/proc/Stress(var/severity) - strength -= severity +/obj/effect/energy_field/attackby(obj/item/W, mob/user) + if(W.force) + adjust_strength(-W.force / 20) + user.do_attack_animation(src) + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) + ..() - //if we take too much damage, drop out - the generator will bring us back up if we have enough power - ticks_recovering = min(ticks_recovering + 2, 10) - if(strength < 1) - invisibility = 101 - density = 0 - ticks_recovering = 10 - strength = 0 - else if(strength >= 1) - invisibility = 0 - density = 1 +/obj/effect/energy_field/attack_hand(var/mob/living/user) + impact_effect(3) // Harmless, but still produces the 'impact' effect. + ..() -/obj/effect/energy_field/proc/Strengthen(var/severity) - strength += severity - if (strength < 0) - strength = 0 +/obj/effect/energy_field/Bumped(atom/A) + ..(A) + impact_effect(2) - //if we take too much damage, drop out - the generator will bring us back up if we have enough power +/obj/effect/energy_field/handle_meteor_impact(var/obj/effect/meteor/meteor) + var/penetrated = TRUE + adjust_strength(-max((meteor.wall_power * meteor.hits) / 800, 0)) // One renwick (strength var) equals one r-wall for the purposes of meteor-stopping. + sleep(1) + if(density) // Check if we're still up. + penetrated = FALSE + explosion(meteor.loc, 0, 0, 0, 0, 0, 0, 0) // For the sound effect. + + // Returning FALSE will kill the meteor. + return penetrated // If the shield's still around, the meteor was successfully stopped, otherwise keep going and plow into the station. + +/obj/effect/energy_field/proc/adjust_strength(amount, impact = 1) var/old_density = density - if(strength >= 1) - invisibility = 0 - density = 1 - else if(strength < 1) - invisibility = 101 - density = 0 - - if (density != old_density) + strength = between(0, strength + amount, max_strength) + + //maptext = "[round(strength, 0.1)]/[max_strength]" + + //if we take too much damage, drop out - the generator will bring us back up if we have enough power + if(amount < 0) // Taking damage. + if(impact) + impact_effect(round(abs(amount * 2))) + + ticks_recovering = min(ticks_recovering + 2, 10) + if(strength < 1) // We broke + density = 0 + ticks_recovering = 10 + strength = 0 + + else if(amount > 0) // Healing damage. + if(strength >= 1) + density = 1 + + if(density != old_density) + update_icon() update_nearby_tiles() /obj/effect/energy_field/CanPass(atom/movable/mover, turf/target, height=1.5, air_group = 0) @@ -66,3 +105,44 @@ //return (!density || !height || air_group) return !density + +/obj/effect/energy_field/update_icon(var/update_neightbors = 0) + overlays.Cut() + var/list/adjacent_shields_dir = list() + for(var/direction in cardinal) + var/turf/T = get_step(src, direction) + if(T) // Incase we somehow stepped off the map. + for(var/obj/effect/energy_field/F in T) + if(update_neightbors) + F.update_icon(0) + adjacent_shields_dir |= direction + break + // Icon_state and Glow + if(density) + icon_state = "shield" + set_light(3, 3, "#66FFFF") + else + icon_state = "shield_broken" + set_light(3, 5, "#FF9900") + + // Edge overlays + for(var/found_dir in adjacent_shields_dir) + overlays += image(src.icon, src, icon_state = "shield_edge", dir = found_dir) + + +// Small visual effect, makes the shield tiles brighten up by becoming more opaque for a moment, and spreads to nearby shields. +/obj/effect/energy_field/proc/impact_effect(var/i, var/list/affected_shields = list()) + i = between(1, i, 10) + alpha = 200 + animate(src, alpha = initial(alpha), time = 1 SECOND) + affected_shields |= src + i-- + if(i) + spawn(2) + for(var/direction in cardinal) + var/turf/T = get_step(src, direction) + if(T) // Incase we somehow stepped off the map. + for(var/obj/effect/energy_field/F in T) + if(!(F in affected_shields)) + F.impact_effect(i, affected_shields) // Spread the effect to them. + diff --git a/code/modules/shieldgen/handheld_defuser.dm b/code/modules/shieldgen/handheld_defuser.dm new file mode 100644 index 0000000000..a2909fa7e6 --- /dev/null +++ b/code/modules/shieldgen/handheld_defuser.dm @@ -0,0 +1,49 @@ +/obj/item/weapon/shield_diffuser + name = "portable shield diffuser" + 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." + icon = 'icons/obj/machines/shielding.dmi' + icon_state = "hdiffuser_off" + var/obj/item/weapon/cell/device/cell + var/enabled = 0 + +/obj/item/weapon/shield_diffuser/update_icon() + if(enabled) + icon_state = "hdiffuser_on" + else + icon_state = "hdiffuser_off" + +/obj/item/weapon/shield_diffuser/New() + cell = new(src) + ..() + +/obj/item/weapon/shield_diffuser/Destroy() + qdel(cell) + cell = null + if(enabled) + processing_objects.Remove(src) + . = ..() + +/obj/item/weapon/shield_diffuser/process() + if(!enabled) + return + + for(var/direction in cardinal) + var/turf/simulated/shielded_tile = get_step(get_turf(src), direction) + for(var/obj/effect/energy_field/S in shielded_tile) + if(istype(S) && cell.checked_use(10 KILOWATTS * CELLRATE)) + qdel(S) + +/obj/item/weapon/shield_diffuser/attack_self() + enabled = !enabled + update_icon() + if(enabled) + processing_objects.Add(src) + else + processing_objects.Remove(src) + to_chat(usr, "You turn \the [src] [enabled ? "on" : "off"].") + +/obj/item/weapon/shield_diffuser/examine() + . = ..() + to_chat(usr, "The charge meter reads [cell ? cell.percent() : 0]%") + to_chat(usr, "It is [enabled ? "enabled" : "disabled"].") \ No newline at end of file diff --git a/code/modules/shieldgen/shield_capacitor.dm b/code/modules/shieldgen/shield_capacitor.dm index 45d7e311dd..53ddcec06e 100644 --- a/code/modules/shieldgen/shield_capacitor.dm +++ b/code/modules/shieldgen/shield_capacitor.dm @@ -19,14 +19,6 @@ var/charge_rate = 100000 //100 kW var/obj/machinery/shield_gen/owned_gen -/obj/machinery/shield_capacitor/New() - spawn(10) - for(var/obj/machinery/shield_gen/possible_gen in range(1, src)) - if(get_dir(src, possible_gen) == src.dir) - possible_gen.owned_capacitor = src - break - ..() - /obj/machinery/shield_capacitor/emag_act(var/remaining_charges, var/mob/user) if(prob(75)) src.locked = !src.locked @@ -54,13 +46,13 @@ if(anchored) spawn(0) for(var/obj/machinery/shield_gen/gen in range(1, src)) - if(get_dir(src, gen) == src.dir && !gen.owned_capacitor) + if(get_dir(src, gen) == src.dir) owned_gen = gen - owned_gen.owned_capacitor = src + owned_gen.capacitors |= src owned_gen.updateDialog() else - if(owned_gen && owned_gen.owned_capacitor == src) - owned_gen.owned_capacitor = null + if(owned_gen && src in owned_gen.capacitors) + owned_gen.capacitors -= src owned_gen = null else ..() @@ -82,16 +74,16 @@ else t += "This capacitor is: [active ? "Online" : "Offline" ] [active ? "\[Deactivate\]" : "\[Activate\]"]
" t += "Capacitor Status: [time_since_fail > 2 ? "OK." : "Discharging!"]
" - t += "Stored Energy: [round(stored_charge/1000, 0.1)] kJ ([100 * round(stored_charge/max_charge, 0.1)]%)
" + t += "Stored Energy: [format_SI(stored_charge, "J")] ([100 * round(stored_charge/max_charge, 0.01)]%)
" t += "Charge Rate: \ \[----\] \ \[---\] \ \[--\] \ - \[-\][charge_rate] W \ + \[-\][format_SI(charge_rate, "W")]\ \[+\] \ \[++\] \ \[+++\] \ - \[+++\]
" + \[++++\]
" t += "
" t += "Refresh " t += "Close
" diff --git a/code/modules/shieldgen/shield_diffuser.dm b/code/modules/shieldgen/shield_diffuser.dm new file mode 100644 index 0000000000..1d2edb43cb --- /dev/null +++ b/code/modules/shieldgen/shield_diffuser.dm @@ -0,0 +1,74 @@ +/obj/machinery/shield_diffuser + name = "shield diffuser" + desc = "A small underfloor device specifically designed to disrupt energy barriers." + 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_state = "fdiffuser_on" + use_power = 2 + idle_power_usage = 100 + active_power_usage = 2000 + anchored = 1 + density = 0 + level = 1 + var/alarm = 0 + var/enabled = 1 + +/obj/machinery/shield_diffuser/New() + ..() + var/turf/T = get_turf(src) + hide(!T.is_plating()) + +//If underfloor, hide the cable +/obj/machinery/shield_diffuser/hide(var/i) + if(istype(loc, /turf)) + invisibility = i ? 101 : 0 + update_icon() + +/obj/machinery/shield_diffuser/hides_under_flooring() + return 1 + +/obj/machinery/shield_diffuser/process() + if(alarm) + alarm-- + if(!alarm) + update_icon() + return + + if(!enabled) + return + for(var/direction in cardinal) + var/turf/simulated/shielded_tile = get_step(get_turf(src), direction) + for(var/obj/effect/energy_field/S in shielded_tile) + qdel(S) + +/obj/machinery/shield_diffuser/update_icon() + if(alarm) + icon_state = "fdiffuser_emergency" + return + if((stat & (NOPOWER | BROKEN)) || !enabled) + icon_state = "fdiffuser_off" + else + icon_state = "fdiffuser_on" + +/obj/machinery/shield_diffuser/attack_hand() + if(alarm) + to_chat(usr, "You press an override button on \the [src], re-enabling it.") + alarm = 0 + update_icon() + return + enabled = !enabled + use_power = enabled + 1 + update_icon() + to_chat(usr, "You turn \the [src] [enabled ? "on" : "off"].") + +/obj/machinery/shield_diffuser/proc/meteor_alarm(var/duration) + if(!duration) + return + alarm = round(max(alarm, duration)) + update_icon() + +/obj/machinery/shield_diffuser/examine(var/mob/user) + . = ..() + to_chat(user, "It is [enabled ? "enabled" : "disabled"].") + if(alarm) + to_chat(user, "A red LED labeled \"Proximity Alarm\" is blinking on the control panel.") \ No newline at end of file diff --git a/code/modules/shieldgen/shield_gen.dm b/code/modules/shieldgen/shield_gen.dm index 90e32d6492..1b50407be5 100644 --- a/code/modules/shieldgen/shield_gen.dm +++ b/code/modules/shieldgen/shield_gen.dm @@ -1,10 +1,3 @@ -//renwicks: fictional unit to describe shield strength -//a small meteor hit will deduct 1 renwick of strength from that shield tile -//light explosion range will do 1 renwick's damage -//medium explosion range will do 2 renwick's damage -//heavy explosion range will do 3 renwick's damage -//explosion damage is cumulative. if a tile is in range of light, medium and heavy damage, it will take a hit from all three - /obj/machinery/shield_gen name = "bubble shield generator" desc = "Machine that generates an impenetrable field of energy when activated." @@ -13,7 +6,7 @@ var/active = 0 var/field_radius = 3 var/max_field_radius = 100 - var/list/field + var/list/field = list() density = 1 var/locked = 0 var/average_field_strength = 0 @@ -23,20 +16,25 @@ var/min_dissipation = 0.01 //will dissipate by at least this rate in renwicks per field tile (otherwise field would never dissipate completely as dissipation is a percentage) var/powered = 0 var/check_powered = 1 - var/obj/machinery/shield_capacitor/owned_capacitor + var/list/capacitors = list() var/target_field_strength = 10 var/max_field_strength = 10 var/time_since_fail = 100 var/energy_conversion_rate = 0.0002 //how many renwicks per watt? + var/z_range = 0 // How far 'up and or down' to extend the shield to, in z-levels. Only works on MultiZ supported z-levels. use_power = 0 //doesn't use APC power /obj/machinery/shield_gen/New() - spawn(10) - for(var/obj/machinery/shield_capacitor/possible_cap in range(1, src)) - if(get_dir(possible_cap, src) == possible_cap.dir) - owned_capacitor = possible_cap - break - field = new/list() + spawn(1 SECOND) + if(anchored) + for(var/obj/machinery/shield_capacitor/cap in range(1, src)) + if(!cap.anchored) + continue + if(cap.owned_gen) + continue + if(get_dir(cap, src) == cap.dir) + capacitors |= cap + cap.owned_gen = src ..() /obj/machinery/shield_gen/Destroy() @@ -44,7 +42,7 @@ field.Remove(D) D.loc = null ..() - + /obj/machinery/shield_gen/emag_act(var/remaining_charges, var/mob/user) if(prob(75)) src.locked = !src.locked @@ -76,14 +74,14 @@ if(cap.owned_gen) continue if(get_dir(cap, src) == cap.dir && src.anchored) - owned_capacitor = cap - owned_capacitor.owned_gen = src + // owned_capacitor = cap + capacitors |= cap + cap.owned_gen = src updateDialog() - break + // break else - if(owned_capacitor && owned_capacitor.owned_gen == src) - owned_capacitor.owned_gen = null - owned_capacitor = null + for(var/obj/machinery/shield_capacitor/capacitor in capacitors) + capacitor.owned_gen = null else ..() @@ -105,7 +103,13 @@ if(locked) t += "Swipe your ID card to begin." else - t += "[owned_capacitor ? "Charge capacitor connected." : "Unable to locate charge capacitor!"]
" + t += "[capacitors.len ? "Charge capacitor(s) connected." : "Unable to locate charge capacitor!"]
" + var/i = 0 + for(var/obj/machinery/shield_capacitor/capacitor in capacitors) + i++ + t += "Capacitor #[i]: [capacitor.active ? "Online." : "Offline."] \ + Charge: [round(capacitor.stored_charge/1000, 0.1)] kJ ([100 * round(capacitor.stored_charge/capacitor.max_charge, 0.01)]%) \ + Status: [capacitor.time_since_fail > 2 ? "OK." : "Discharging!"]
" t += "This generator is: [active ? "Online" : "Offline" ] [active ? "\[Deactivate\]" : "\[Activate\]"]
" t += "Field Status: [time_since_fail > 2 ? "Stable" : "Unstable"]
" t += "Coverage Radius (restart required): \ @@ -116,12 +120,17 @@ + \ ++ \ +++
" + if(HasAbove(src.z) || HasBelow(src.z)) // Won't show up on maps lacking MultiZ support. + t += "Vertical Shielding (restart required): \ + - \ + [z_range] Vertical Range \ + +
" t += "Overall Field Strength: [round(average_field_strength, 0.01)] Renwick ([target_field_strength ? round(100 * average_field_strength / target_field_strength, 0.1) : "NA"]%)
" - t += "Upkeep Power: [round(field.len * max(average_field_strength * dissipation_rate, min_dissipation) / energy_conversion_rate)] W
" + t += "Upkeep Power: [format_SI(round(field.len * max(average_field_strength * dissipation_rate, min_dissipation) / energy_conversion_rate), "W")]
" t += "Charge Rate: -- \ [strengthen_rate] Renwick/s \ ++
" - t += "Shield Generation Power: [round(field.len * min(strengthen_rate, target_field_strength - average_field_strength) / energy_conversion_rate)] W
" + t += "Shield Generation Power: [format_SI(round(field.len * min(strengthen_rate, target_field_strength - average_field_strength) / energy_conversion_rate), "W")]
" t += "Maximum Field Strength: \ \[min\] \ -- \ @@ -148,13 +157,28 @@ var/renwick_upkeep_per_field = max(average_field_strength * dissipation_rate, min_dissipation) //figure out how much energy we need to draw from the capacitor - if(active && owned_capacitor && owned_capacitor.active) + if(active && capacitors.len) + // Get a list of active capacitors to drain from. + var/list/active_capacitors = list() + for(var/obj/machinery/shield_capacitor/capacitor in capacitors) // Some capacitors might be off. Exclude them. + if(capacitor.active && capacitor.stored_charge > 0) + active_capacitors |= capacitor + var/target_renwick_increase = min(target_field_strength - average_field_strength, strengthen_rate) + renwick_upkeep_per_field //per field tile var/required_energy = field.len * target_renwick_increase / energy_conversion_rate - var/assumed_charge = min(owned_capacitor.stored_charge, required_energy) + + // Gets the charge for all capacitors + var/sum_charge = 0 + for(var/obj/machinery/shield_capacitor/capacitor in active_capacitors) + sum_charge += capacitor.stored_charge + + var/assumed_charge = min(sum_charge, required_energy) total_renwick_increase = assumed_charge * energy_conversion_rate - owned_capacitor.stored_charge -= assumed_charge + + for(var/obj/machinery/shield_capacitor/capacitor in active_capacitors) + capacitor.stored_charge -= max(assumed_charge / active_capacitors.len, 0) // Drain from all active capacitors evenly. + else renwick_upkeep_per_field = max(renwick_upkeep_per_field, 0.5) @@ -162,12 +186,13 @@ average_field_strength = 0 //recalculate the average field strength for(var/obj/effect/energy_field/E in field) + E.max_strength = target_field_strength var/amount_to_strengthen = renwick_increase_per_field - renwick_upkeep_per_field if(E.ticks_recovering > 0 && amount_to_strengthen > 0) - E.Strengthen( min(amount_to_strengthen / 10, 0.1) ) + E.adjust_strength( min(amount_to_strengthen / 10, 0.1), 0 ) E.ticks_recovering -= 1 else - E.Strengthen(amount_to_strengthen) + E.adjust_strength(amount_to_strengthen, 0) average_field_strength += E.strength @@ -194,6 +219,8 @@ strengthen_rate = between(0, strengthen_rate + text2num(href_list["strengthen_rate"]), max_strengthen_rate) else if( href_list["target_field_strength"] ) target_field_strength = between(1, target_field_strength + text2num(href_list["target_field_strength"]), max_field_strength) + else if( href_list["z_range"] ) + z_range = between(0, z_range + text2num(href_list["z_range"]), 10) // Max is extending ten z-levels up and down. Probably too big of a number but it shouldn't matter. updateDialog() @@ -213,16 +240,19 @@ if(T in covered_turfs) covered_turfs.Remove(T) for(var/turf/O in covered_turfs) - var/obj/effect/energy_field/E = new(O) + var/obj/effect/energy_field/E = new(O, src) field.Add(E) covered_turfs = null for(var/mob/M in view(5,src)) M << "\icon[src] You hear heavy droning start up." + for(var/obj/effect/energy_field/E in field) // Update the icons here to ensure all the shields have been made already. + E.update_icon() else for(var/obj/effect/energy_field/D in field) field.Remove(D) - D.loc = null + //D.loc = null + qdel(D) for(var/mob/M in view(5,src)) M << "\icon[src] You hear heavy droning fade out." @@ -236,12 +266,38 @@ else icon_state = "generator0" -//TODO MAKE THIS MULTIZ COMPATIBLE //grab the border tiles in a circle around this machine /obj/machinery/shield_gen/proc/get_shielded_turfs() var/list/out = list() - var/turf/gen_turf = get_turf(src) + var/turf/T = get_turf(src) + if (!T) + return + + out += get_shielded_turfs_on_z_level(T) + + if(z_range) + var/i = z_range + while(HasAbove(T.z) && i) + T = GetAbove(T) + i-- + if(istype(T)) + out += get_shielded_turfs_on_z_level(T) + + T = get_turf(src) + i = z_range + + while(HasBelow(T.z) && i) + T = GetBelow(T) + i-- + if(istype(T)) + out += get_shielded_turfs_on_z_level(T) + + return out + +/obj/machinery/shield_gen/proc/get_shielded_turfs_on_z_level(var/turf/gen_turf) + var/list/out = list() + if (!gen_turf) return @@ -260,4 +316,4 @@ T = locate(gen_turf.x + field_radius, gen_turf.y + y_offset, gen_turf.z) if (T) out += T - return out + return out \ No newline at end of file diff --git a/code/modules/shieldgen/shield_gen_external.dm b/code/modules/shieldgen/shield_gen_external.dm index 182339d64e..8086d0b7e3 100644 --- a/code/modules/shieldgen/shield_gen_external.dm +++ b/code/modules/shieldgen/shield_gen_external.dm @@ -7,15 +7,13 @@ /obj/machinery/shield_gen/external/New() ..() -//NOT MULTIZ COMPATIBLE //Search for space turfs within range that are adjacent to a simulated turf. -/obj/machinery/shield_gen/external/get_shielded_turfs() +/obj/machinery/shield_gen/external/get_shielded_turfs_on_z_level(var/turf/gen_turf) var/list/out = list() - - var/turf/gen_turf = get_turf(src) + if (!gen_turf) return - + var/turf/T for (var/x_offset = -field_radius; x_offset <= field_radius; x_offset++) for (var/y_offset = -field_radius; y_offset <= field_radius; y_offset++) @@ -24,4 +22,4 @@ //check neighbors of T if (locate(/turf/simulated/) in orange(1, T)) out += T - return out + return out \ No newline at end of file diff --git a/code/modules/xenoarcheaology/effects/forcefield.dm b/code/modules/xenoarcheaology/effects/forcefield.dm index 6a48620b40..028eff3a75 100644 --- a/code/modules/xenoarcheaology/effects/forcefield.dm +++ b/code/modules/xenoarcheaology/effects/forcefield.dm @@ -30,9 +30,9 @@ ..() for(var/obj/effect/energy_field/E in created_field) if(E.strength < 1) - E.Strengthen(0.15) + E.adjust_strength(0.15, 0) else if(E.strength < 5) - E.Strengthen(0.25) + E.adjust_strength(0.25, 0) /datum/artifact_effect/forcefield/UpdateMove() if(created_field.len && holder) diff --git a/icons/obj/machines/shielding.dmi b/icons/obj/machines/shielding.dmi index 58dd98641bc84cce5694e643c9645881dfe14678..42087073d3ba86825917c1f6559a9b520cff8d60 100644 GIT binary patch delta 5849 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