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 58dd98641b..42087073d3 100644 Binary files a/icons/obj/machines/shielding.dmi and b/icons/obj/machines/shielding.dmi differ diff --git a/maps/example/example-1.dmm b/maps/example/example-1.dmm index 26c7e4bd46..d8ac29b9f0 100644 --- a/maps/example/example-1.dmm +++ b/maps/example/example-1.dmm @@ -31,12 +31,16 @@ "E" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/effect/floor_decal/corner/white/diagonal,/turf/simulated/floor/tiled/dark,/area/aisat) "F" = (/obj/effect/landmark{name = "JoinLate"},/obj/effect/floor_decal/corner/white/diagonal,/turf/simulated/floor/tiled/dark,/area/aisat) "G" = (/obj/effect/landmark/start,/turf/simulated/floor/tiled/dark,/area/aisat) -"H" = (/obj/structure/stairs/south,/turf/simulated/floor/tiled/dark,/area/aisat) -"I" = (/obj/effect/floor_decal/sign/a,/turf/simulated/floor/tiled/dark,/area/aisat) -"J" = (/obj/machinery/light,/obj/effect/floor_decal/corner/white/diagonal,/turf/simulated/floor/tiled/dark,/area/aisat) -"K" = (/obj/structure/cable/yellow,/obj/structure/cable/yellow{d1 = 16; d2 = 0; icon_state = "16-0"},/turf/simulated/floor/plating,/area/aisat) -"L" = (/turf/simulated/floor/tiled/red,/area/aisat) -"M" = (/obj/turbolift_map_holder/example{dir = 1; icon = 'icons/obj/turbolift_preview_5x5.dmi'},/turf/simulated/floor/tiled/red,/area/aisat) +"H" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/effect/floor_decal/corner/white/diagonal,/obj/machinery/shield_gen,/turf/simulated/floor/tiled/dark,/area/aisat) +"I" = (/obj/structure/stairs/south,/turf/simulated/floor/tiled/dark,/area/aisat) +"J" = (/obj/effect/floor_decal/sign/a,/turf/simulated/floor/tiled/dark,/area/aisat) +"K" = (/obj/machinery/light,/obj/effect/floor_decal/corner/white/diagonal,/turf/simulated/floor/tiled/dark,/area/aisat) +"L" = (/obj/machinery/light,/obj/machinery/shield_capacitor{anchored = 1; dir = 4; icon_state = "capacitor"; tag = "icon-capacitor (EAST)"},/obj/structure/cable/yellow{d2 = 4; icon_state = "0-4"},/turf/simulated/floor/plating,/area/aisat) +"M" = (/obj/machinery/shield_gen/external{anchored = 1},/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/turf/simulated/floor/plating,/area/aisat) +"N" = (/obj/machinery/shield_capacitor{anchored = 1; dir = 8; icon_state = "capacitor"; tag = "icon-capacitor (WEST)"},/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/obj/structure/cable/yellow{d2 = 8; icon_state = "0-8"},/turf/simulated/floor/plating,/area/aisat) +"O" = (/obj/structure/cable/yellow,/obj/structure/cable/yellow{d1 = 16; d2 = 0; icon_state = "16-0"},/obj/structure/cable/yellow{d1 = 1; d2 = 8; icon_state = "1-8"},/turf/simulated/floor/plating,/area/aisat) +"P" = (/turf/simulated/floor/tiled/red,/area/aisat) +"Q" = (/obj/turbolift_map_holder/example{dir = 1; icon = 'icons/obj/turbolift_preview_5x5.dmi'},/turf/simulated/floor/tiled/red,/area/aisat) (1,1,1) = {" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa @@ -58,16 +62,16 @@ aaaaaaaabuhhhhhvhhhhhhbaaaaaaaaa aaaaaaaabwnnnnxyznnnnAbaaaaaaaaa aaaaaaaabhhhhhBCDhhhhEbaaaaaaaaa aaaaaaaabhhFFFhnhhhhhEbaaaaaaaaa -aaaaaaaabhhFFFhGhhhhhEbaaaaaaaaa +aaaaaaaabhhFFFhGhhhhhHbaaaaaaaaa aaaaaaaabnbFFFhnhhhhhEbaaaaaaaaa -aaaaaaaabHbhhhhIhhhhhEbaaaaaaaaa -aaaaaaaabbbhJhhnhhJhhKbaaaaaaaaa +aaaaaaaabIbhhhhJhhhhhEbaaaaaaaaa +aaaaaaaabbbhKhhnhhLMNObaaaaaaaaa aaaaaaaabbbbbbnnnbbbbbbaaaaaaaaa -aaaaaaaaaaaabLLLLLbaaaaaaaaaaaaa -aaaaaaaaaaaabLLLLLbaaaaaaaaaaaaa -aaaaaaaaaaaabLLLLLbaaaaaaaaaaaaa -aaaaaaaaaaaabLLLLLbaaaaaaaaaaaaa -aaaaaaaaaaaabMLLLLbaaaaaaaaaaaaa +aaaaaaaaaaaabPPPPPbaaaaaaaaaaaaa +aaaaaaaaaaaabPPPPPbaaaaaaaaaaaaa +aaaaaaaaaaaabPPPPPbaaaaaaaaaaaaa +aaaaaaaaaaaabPPPPPbaaaaaaaaaaaaa +aaaaaaaaaaaabQPPPPbaaaaaaaaaaaaa aaaaaaaaaaaabbbbbbbaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa diff --git a/polaris.dme b/polaris.dme index 1f6c125707..f691f962e0 100644 --- a/polaris.dme +++ b/polaris.dme @@ -2008,8 +2008,10 @@ #include "code\modules\security levels\security levels.dm" #include "code\modules\shieldgen\emergency_shield.dm" #include "code\modules\shieldgen\energy_field.dm" +#include "code\modules\shieldgen\handheld_defuser.dm" #include "code\modules\shieldgen\sheldwallgen.dm" #include "code\modules\shieldgen\shield_capacitor.dm" +#include "code\modules\shieldgen\shield_diffuser.dm" #include "code\modules\shieldgen\shield_gen.dm" #include "code\modules\shieldgen\shield_gen_external.dm" #include "code\modules\shuttles\antagonist.dm"