mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-08-21 20:11:04 +01:00
Ship combat rework (#22566)
Makes ship combat significantly more destructive (and a bit deadlier), especially with non-explosive projectiles. **Additions:** - A shell that penetrates a solid (dense) structure will cause spalling. This spall does 2-30 damage, depending on range and has a high embed chance. These embedded chunks are large enough to be ripped out by hand. (But you probably shouldn't). The pilot's suit has 30 ballistic armor, so a prepared bridge crew can survive a lot of spall. - Adds a gun that shoots any ship weapon that admins can spawn. Useful for testing, or for adminbus where they don't want to have to spawn a whole overmap ship. - Made several structures destructible, vending machines, computer frames, grilles (these were just bugged) and watertanks (and their children). These structures could just block infinite shots and were common enough to serve as effective armor for the horizon. These now spawn shrapnel (of their material if it's set), in addition to sheets of steel when destroyed. - Added support for negative maim chance. This allows chosen projectiles to be prevented from decapitating. No more accidentally gibbing the bridge crew's heads. (Unless they get hit with something really really big). **Balance:** - All non-explosive anti ship rounds got significant buffs. Typically 2-3x Anti-Material numbers. - Armour piercing anti-ship rounds got massively increased penetration stats. Typically 4-8 from 1-2. - Doors now get destroyed in less hits, if they are hit by anti-material projectiles. Hits to destroy -= Antimaterial / 2 - Francisca frag shells now shoot less fragments but deal more damage. 394 projectiles for every shot was a bit laggy, now it's only 280. **Shields:** Shields now take more power. 13-14 megawatts for a 10 strength shield. Just inside the ship's generation capacity with both the INDRA and Supermatter running. Instead of deleting any projectile they are hit by, shields now have different failure modes. - Firstly, a piercing projectile has it's penetration reduced by the shield strength. Eg A shot that would go through 10 walls, only penetrates 4 after going through a 6 strength shield. - Secondly, a projectile has it's damage multiplied by 1 - shield strength / 10, then has shield strength subtracted from it's damage. Eg. A damage 100 projectile, hitting a 5 strength shield, has it's damaged reduced to 50, (100 * 0.5), then to 45. (50-5) - Thirdly, an explosive projectile (that does not penetrate per the first step) does not explode, but instead deals it's full damage to the shield. Hits to the shield now also damage the field itself, not just the shield tile. As such, a very big hit on the shield can bring the whole thing down, requiring an engineer to reset it. Shields can now be upgraded with research components. Additionally, shield generators can now support more than one capacitor. The roundstart capacitor on the Horizon is only good enough to support a 6 strength shield. The upgrades increase efficiency, strength loss over time and charge speed. **Overmap Targetting** Previously, shells would spawn within 20 tiles of their target and aim directly forwards. This was bugged for shots with burst, which would instead all aim inwards and mostly miss. This is the reason the Grauwolf (in addition to the explosion bug below) and Fransisca felt so useless, only one of every shell in a burst would hit. Shots now spawn at the map edge, based on the orientation of the projectile and ship. Diagonals are now accounted for. Shots are then aimed directly at the target selected on the console, with 3 degrees of dispersion. **Bugfixes:** - Fixes lots of explosions that didn't pass a turf to the explosions subsystem. This caused the explosion not to happen if the thing hit got qdeled before the subsystem could get the turf to spawn the explosion on. - Embedded shrapnel no longer gets the initial projectile name, this prevents it getting named shrapnelshrapnel. - Fixes a bunch of projectile piercing bugs, mostly with grilles and windows. Shots now go past window frames without hitting them. - Fixes shields not having a click delay when attacked. --------- Signed-off-by: FenodyreeAv <fenodyree.av@gmail.com> Co-authored-by: Matt Atlas <mattiathebest2000@hotmail.it>
This commit is contained in:
co-authored by
Matt Atlas
parent
fb3189f542
commit
cf585ef505
@@ -1429,14 +1429,15 @@
|
||||
blood_color = null
|
||||
return 1
|
||||
|
||||
/mob/living/carbon/human/get_visible_implants(var/class = 0)
|
||||
/mob/living/carbon/human/get_visible_implants(var/class = 0) //Default class 0, class 1 is tiny objects, so class 0 will show everything.
|
||||
|
||||
var/list/visible_implants = list()
|
||||
for(var/obj/item/organ/external/organ in src.organs)
|
||||
for(var/obj/item/O in organ.implants)
|
||||
if(!istype(O,/obj/item/implant) && (O.w_class > class) && !istype(O,/obj/item/material/shard/shrapnel))
|
||||
visible_implants += O
|
||||
|
||||
if(istype(O,/obj/item/material/shard/shrapnel) && (O.w_class > class + 2)) //If the shrapnel is larger than class 2 (small), it is visible.
|
||||
visible_implants += O
|
||||
return(visible_implants)
|
||||
|
||||
/mob/living/carbon/human/embedded_needs_process()
|
||||
|
||||
@@ -541,7 +541,7 @@
|
||||
sleep(10)
|
||||
playsound(src, 'sound/items/countdown.ogg', 125, 1)
|
||||
sleep(20)
|
||||
explosion(src, -1, 1, 5)
|
||||
explosion(get_turf(src), -1, 1, 5)
|
||||
src.gib()
|
||||
|
||||
/mob/living/carbon/human/proc/hivenet()
|
||||
|
||||
@@ -177,4 +177,4 @@
|
||||
/obj/projectile/beam/tesla/plasmageist/on_hit(atom/target, blocked, def_zone)
|
||||
. = ..()
|
||||
if(isliving(target))
|
||||
explosion(target, -1, 0, 2)
|
||||
explosion(get_turf(target), -1, 0, 2)
|
||||
|
||||
@@ -254,7 +254,7 @@
|
||||
return
|
||||
|
||||
/mob/living/simple_animal/hostile/carp/bloater/proc/explode()
|
||||
explosion(src, -1, 1, 2)
|
||||
explosion(get_turf(src), -1, 1, 2)
|
||||
src.gib()
|
||||
|
||||
/mob/living/simple_animal/hostile/carp/old
|
||||
|
||||
@@ -594,13 +594,13 @@
|
||||
if(W.w_class >= w_class && (dam_flags & DAMAGE_FLAG_EDGE))
|
||||
edge_eligible = TRUE
|
||||
|
||||
if(!blunt_eligible && edge_eligible && (brute >= max_damage / (DROPLIMB_THRESHOLD_EDGE + maim_bonus_to_add)))
|
||||
if(!blunt_eligible && edge_eligible && (brute >= max_damage / max((DROPLIMB_THRESHOLD_EDGE + maim_bonus_to_add), 0.1)))
|
||||
droplimb(0, DROPLIMB_EDGE)
|
||||
else if(burn >= max_damage / ((dam_flags & DAMAGE_FLAG_LASER ? DROPLIMB_THRESHOLD_DESTROY_PROJECTILE : DROPLIMB_THRESHOLD_DESTROY) + maim_bonus_to_add))
|
||||
else if(burn >= max_damage / max((dam_flags & DAMAGE_FLAG_LASER ? DROPLIMB_THRESHOLD_DESTROY_PROJECTILE : DROPLIMB_THRESHOLD_DESTROY) + maim_bonus_to_add, 0.1))
|
||||
droplimb(0, DROPLIMB_BURN)
|
||||
else if(blunt_eligible && brute >= max_damage / ((dam_flags & DAMAGE_FLAG_BULLET ? DROPLIMB_THRESHOLD_DESTROY_PROJECTILE : DROPLIMB_THRESHOLD_DESTROY) + maim_bonus_to_add))
|
||||
else if(blunt_eligible && brute >= max_damage / max((dam_flags & DAMAGE_FLAG_BULLET ? DROPLIMB_THRESHOLD_DESTROY_PROJECTILE : DROPLIMB_THRESHOLD_DESTROY) + maim_bonus_to_add, 0.1))
|
||||
droplimb(0, DROPLIMB_BLUNT)
|
||||
else if(brute >= max_damage / (DROPLIMB_THRESHOLD_TEAROFF + maim_bonus_to_add))
|
||||
else if(brute >= max_damage / max((DROPLIMB_THRESHOLD_TEAROFF + maim_bonus_to_add), 0.1))
|
||||
droplimb(0, DROPLIMB_EDGE)
|
||||
|
||||
/obj/item/organ/external/heal_damage(brute, burn, internal = 0, robo_repair = 0)
|
||||
|
||||
@@ -211,7 +211,7 @@
|
||||
P.dir = S.dir
|
||||
P.icon_state = overmap_icon_state
|
||||
P.speed = get_speed()
|
||||
P.entry_target = entry_point
|
||||
P.submap_target = entry_point
|
||||
forceMove(P)
|
||||
log_and_message_admins("A projectile ([name]) has entered the Overmap! (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[P.x];Y=[P.y];Z=[P.z]'>JMP</a>)")
|
||||
return TRUE
|
||||
@@ -227,11 +227,87 @@
|
||||
anti_materiel_potential = 3
|
||||
impact_sounds = list(BULLET_IMPACT_MEAT = SOUNDS_BULLET_MEAT, BULLET_IMPACT_METAL = SOUNDS_BULLET_METAL)
|
||||
accuracy = 100
|
||||
projectile_piercing = PASSMOB|PASSDOORS|PASSGLASS|PASSCLOSEDTURF|PASSWINDOW|PASSMACHINE|PASSBLOB|PASSFLAPS|PASSVEHICLE //It's a ship weapon let it try to penetrate everything.
|
||||
projectile_piercing = PASSGLASS|PASSGRILLE|PASSBLOB|PASSMOB|PASSCLOSEDTURF|PASSMACHINE|PASSSTRUCTURE|PASSFLAPS|PASSDOORS|PASSVEHICLE|PASSWINDOW|PASSSHIELD //It's a ship weapon let it try to penetrate everything.
|
||||
pierce_decay_damage = 0.95 //Ship weapon projectiles don't lose much damage on pierce by default, but this can be set per projectile.
|
||||
///This is passed to explosion(), it is stored here for when projectiles hit shields and need to damage the shield as if they had exploded.
|
||||
var/list/explosion_strength = list(0, 0, 0)
|
||||
///The last thing this projectile pierced. Used for spalling effects in on_hit()
|
||||
var/atom/last_thing_pierced = null
|
||||
var/obj/item/ship_ammunition/ammo
|
||||
var/primed = FALSE
|
||||
var/hit_target = FALSE //First target we hit. Used to report if a hit was successful.
|
||||
///First target we hit. Used to report if a hit was successful.
|
||||
var/hit_target = FALSE
|
||||
|
||||
/obj/projectile/ship_ammo/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) //When the projectile moves from a dense tile to a non-dense tile.
|
||||
. = ..()
|
||||
if(last_thing_pierced)
|
||||
var/turf/entered_turf = isturf(loc) ? loc : get_turf(loc)
|
||||
if(!(entered_turf.check_density(FALSE, TRUE))) //Checks for mobs so we don't spawn spalling inside the poor bridge crew, getting hit by the shell is bad enough.
|
||||
handle_spalling(entered_turf, movement_dir, last_thing_pierced)
|
||||
last_thing_pierced = null
|
||||
|
||||
/**
|
||||
* This creates a cone of shrapnel whenever a projectile pierces something dense and then crosses into a non-dense turf.
|
||||
* This means that it won't spawn all the shrapnel inside the next thing it penetrates, to simulate spalling.
|
||||
* All of of the projectile stats are set custom here, in future it might be good to set them dynmically based on the material of the thing pierced.
|
||||
* Too many things don't have a material set, so that will have to wait.
|
||||
*/
|
||||
/obj/projectile/ship_ammo/proc/handle_spalling(atom/source, shrapnel_dir, thing_pierced)
|
||||
set waitfor = FALSE
|
||||
|
||||
src.visible_message(SPAN_DANGER("Huge chunks of shrapnel spray out from \the [thing_pierced] as \the [src] punches through!"))
|
||||
|
||||
var/turf/O = get_turf(src)
|
||||
var/list/target_turfs = list()
|
||||
target_turfs += get_step(O, shrapnel_dir)
|
||||
target_turfs += get_step(O, turn(shrapnel_dir, -45))
|
||||
target_turfs += get_step(O, turn(shrapnel_dir, 45))
|
||||
target_turfs += get_step(get_step(O, turn(shrapnel_dir, -45)), shrapnel_dir)
|
||||
target_turfs += get_step(get_step(O, turn(shrapnel_dir, 45)), shrapnel_dir)
|
||||
|
||||
for(var/turf/T in target_turfs)
|
||||
var/obj/projectile/bullet/pellet/fragment/spall/P
|
||||
if(istype(thing_pierced, /obj/structure/window))
|
||||
P = new /obj/projectile/bullet/pellet/fragment/spall/glass(O)
|
||||
else if(istype(thing_pierced, /obj/structure/grille) || istype(thing_pierced, /obj/structure/window_frame))
|
||||
P = new /obj/projectile/bullet/pellet/fragment/spall/metalrod(O)
|
||||
else if(istype(thing_pierced, /obj/structure/machinery/door/airlock))
|
||||
var/obj/structure/machinery/door/airlock/D = thing_pierced
|
||||
if(D.window_material && D.window_material == SSmaterials.get_material_by_name(MATERIAL_GLASS))
|
||||
P = new /obj/projectile/bullet/pellet/fragment/spall/glass(O)
|
||||
else
|
||||
P = new /obj/projectile/bullet/pellet/fragment/spall(O)
|
||||
else
|
||||
P = new /obj/projectile/bullet/pellet/fragment/spall(O)
|
||||
P.preparePixelProjectile(T, src)
|
||||
P.firer = src
|
||||
P.fired_from = thing_pierced
|
||||
P.fire()
|
||||
return TRUE
|
||||
|
||||
/obj/projectile/bullet/pellet/fragment/spall
|
||||
damage = 2 //Between 30 and 3 damage, depending on how close you are. Pilot suits are 30 ballistic armour, so they should survive a lot of spall.
|
||||
pellets = 15
|
||||
armor_penetration = 10
|
||||
range_step = 1
|
||||
range = 15
|
||||
embed_chance = 80 //Make the people hit pull big chunks of shrapnel out by hand.
|
||||
maim_rate = -1 //We don't want this to decapitate people in the unlucky event they get hit by the shell (or shell explosion) directly, then more shrapnel afterwards.
|
||||
anti_materiel_potential = 2 //Enough to break windows.
|
||||
name = "spall"
|
||||
shrapnel_type = /obj/item/material/shard/shrapnel/large
|
||||
|
||||
/obj/projectile/bullet/pellet/fragment/spall/glass
|
||||
damage = 1
|
||||
armor_penetration = 5
|
||||
name = "glass"
|
||||
shrapnel_type = /obj/item/material/shard
|
||||
|
||||
/obj/projectile/bullet/pellet/fragment/spall/metalrod
|
||||
damage = 3
|
||||
armor_penetration = 15
|
||||
name = "metal rod"
|
||||
shrapnel_type = /obj/item/stack/rods
|
||||
|
||||
/obj/projectile/ship_ammo/Destroy()
|
||||
ammo = null
|
||||
@@ -261,6 +337,14 @@
|
||||
)
|
||||
if(ammo && ammo.origin)
|
||||
ammo.origin.signal_hit(hit_data)
|
||||
|
||||
if(istype(target, /turf/simulated/wall) || istype(target, /obj/structure/machinery/door)) //Stores the last thing we pierced for spalling purposes.
|
||||
last_thing_pierced = target
|
||||
else if (istype(target, /obj/structure)) //If it is a structure on a dense turf, that uses health,
|
||||
if (target.should_use_health)
|
||||
var/turf/T = get_turf(target)
|
||||
if (T.check_density(FALSE, TRUE))
|
||||
last_thing_pierced = target
|
||||
return ..()
|
||||
|
||||
/obj/projectile/ship_ammo/proc/on_translate(var/turf/entry_turf, var/target_turf) //This proc is called when the projectile enters a new ship's overmap zlevel.
|
||||
@@ -273,6 +357,5 @@
|
||||
pellet.ammo.origin = ammo.origin
|
||||
pellet.ammo.impact_type = ammo.impact_type
|
||||
pellet.dir = dir
|
||||
var/turf/front_turf = get_step(pellet, pellet.dir)
|
||||
pellet.preparePixelProjectile(target_turf, front_turf)
|
||||
pellet.preparePixelProjectile(target_turf, pellet, deviation = 3) //Deviation can be tuned. These spawn at the map edge so small deviations matter a lot.
|
||||
pellet.fire()
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
var/obj/item/ship_ammunition/ammunition
|
||||
/// The target is the actual overmap object we're hitting.
|
||||
var/atom/target
|
||||
/// The entry target is where the projectile itself is going to spawn in world.
|
||||
var/obj/entry_target
|
||||
/// The submap target is passed by the targetting console, it is the landmark selected on that console. This is the point on the submap (eg. The Horizon's Bridge) that the projectile will aim for when it enters.
|
||||
var/obj/submap_target
|
||||
var/range = OVERMAP_PROJECTILE_RANGE_MEDIUM
|
||||
var/current_range_counter = 0
|
||||
// A projectile with 0 speed does not move. Note that this is the 'lag' variable on walk_towards! Lower speed is better.
|
||||
@@ -65,81 +65,132 @@
|
||||
/obj/effect/overmap/projectile/Destroy()
|
||||
ammunition = null
|
||||
target = null
|
||||
entry_target = null
|
||||
walk(src, 0)
|
||||
submap_target = null
|
||||
return ..()
|
||||
|
||||
///Gets the turf the projectile should enter on. If it's diagnonal it spawns in the corner, if it's orthogonal it spawns on the map edge relative to the target landmark.
|
||||
/obj/effect/overmap/projectile/proc/get_entry_turf(atom/target_landmark, direction)
|
||||
var/turf/target = locate(target_landmark.x, target_landmark.y, target_landmark.z)
|
||||
if(!target_landmark || !target)
|
||||
return 0
|
||||
|
||||
switch(direction)
|
||||
if(NORTH|WEST)
|
||||
target = get_ranged_target_turf(locate(1, world.maxy, target.z), direction, max(8, 2 * ammunition.burst)) //We offset the target turf from the edge by 8 (one screen), or twice the burst size. Bursts get their positions randomized up to twice the burst size.
|
||||
if(NORTH|EAST)
|
||||
target = get_ranged_target_turf(locate(world.maxx, world.maxy, target.z), direction, max(8, 2 * ammunition.burst))
|
||||
if(SOUTH|WEST)
|
||||
target = get_ranged_target_turf(locate(1, 1, target.z), direction, max(8, 2 * ammunition.burst))
|
||||
if(SOUTH|EAST)
|
||||
target = get_ranged_target_turf(locate(world.maxx, 1, target.z), direction, max(8, 2 * ammunition.burst))
|
||||
if(NORTH)
|
||||
target = get_ranged_target_turf(locate(target.x, world.maxy, target.z), direction, max(8, 2 * ammunition.burst))
|
||||
if(SOUTH)
|
||||
target = get_ranged_target_turf(locate(target.x, 1, target.z), direction, max(8, 2 * ammunition.burst))
|
||||
if(EAST)
|
||||
target = get_ranged_target_turf(locate(world.maxx, target.y, target.z), direction, max(8, 2 * ammunition.burst))
|
||||
if(WEST)
|
||||
target = get_ranged_target_turf(locate(1, target.y, target.z), direction, max(8, 2 * ammunition.burst))
|
||||
return target
|
||||
|
||||
/obj/effect/overmap/projectile/proc/prepare_for_entry()
|
||||
moving = FALSE
|
||||
entering = FALSE
|
||||
walk(src, 0)
|
||||
|
||||
///Checks if we can hit the thing we just bumped into. If we can, do the initial setup for the projectile entering the submab and continue.
|
||||
/obj/effect/overmap/projectile/proc/check_entry()
|
||||
. = FALSE
|
||||
if(!ammunition)
|
||||
return
|
||||
var/turf/T = get_turf(src)
|
||||
for(var/obj/effect/overmap/A in T)
|
||||
if(ammunition && A == ammunition.origin)
|
||||
if(ammunition && A == ammunition.origin) //Don't collide with the thing that fired us.
|
||||
continue
|
||||
if(istype(A, /obj/effect/overmap/visitable))
|
||||
var/obj/effect/overmap/visitable/V = A
|
||||
if((V.check_ownership(entry_target)) || (V == target)) //Target spotted!
|
||||
if((V.check_ownership(submap_target)) || (V == target)) //If the visitable is owned by the target landmark, or is the target itself, we can hit it.
|
||||
var/turf/target_turf = get_turf(submap_target)
|
||||
var/obj/projectile/ship_ammo/widowmaker = new ammunition.original_projectile.type
|
||||
prepare_for_entry()
|
||||
widowmaker.ammo = ammunition
|
||||
qdel(ammunition.original_projectile) //No longer needed.
|
||||
ammunition.original_projectile = widowmaker
|
||||
widowmaker.primed = TRUE
|
||||
|
||||
if(istype(V, /obj/effect/overmap/visitable/sector/exoplanet) && (ammunition.overmap_behaviour & SHIP_AMMO_CAN_HIT_PLANETS))
|
||||
. = TRUE
|
||||
//Manually stopping because this proc needs to sleep for a bit.
|
||||
prepare_for_entry()
|
||||
var/obj/projectile/ship_ammo/widowmaker = new ammunition.original_projectile.type
|
||||
widowmaker.ammo = ammunition
|
||||
qdel(ammunition.original_projectile) //No longer needed.
|
||||
var/turf/laze = get_turf(entry_target)
|
||||
ammunition.original_projectile = widowmaker
|
||||
playsound(laze, 'sound/weapons/gunshot/ship_weapons/orbital_travel.ogg', 60)
|
||||
laze.visible_message(SPAN_DANGER("<font size=6>A bright star is getting closer from the sky...!</font>"))
|
||||
sleep(11 SECONDS) //Let the sound play!
|
||||
widowmaker.primed = TRUE
|
||||
widowmaker.forceMove(entry_target)
|
||||
widowmaker.on_hit(laze, is_landmark_hit = TRUE)
|
||||
log_and_message_admins("A projectile ([name]) has entered a z-level at [entry_target.name]! (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[widowmaker.x];Y=[widowmaker.y];Z=[widowmaker.z]'>JMP</a>)")
|
||||
say_dead_direct("A projectile ([name]) has entered a z-level at [entry_target.name]!")
|
||||
qdel(widowmaker)
|
||||
qdel(src)
|
||||
else if(istype(V, /obj/effect/overmap/visitable) && (ammunition.overmap_behaviour & SHIP_AMMO_CAN_HIT_VISITABLES))
|
||||
. = TRUE
|
||||
if(istype(V, /obj/effect/overmap/visitable/ship))
|
||||
var/obj/effect/overmap/visitable/ship/VS = V
|
||||
if(istype(ammunition.origin, /obj/effect/overmap/visitable/ship))
|
||||
var/naval_heading = SSatlas.headings_to_naval["[VS.dir]"]["[ammunition.heading]"]
|
||||
var/corrected_heading = SSatlas.naval_to_dir["[VS.fore_dir]"][naval_heading]
|
||||
ammunition.heading = corrected_heading
|
||||
else //if it's not a ship it doesn't have a fore direction, so we need to autocorrect
|
||||
ammunition.heading = entry_target.dir
|
||||
prepare_for_entry()
|
||||
var/obj/projectile/ship_ammo/widowmaker = new ammunition.original_projectile.type
|
||||
widowmaker.ammo = ammunition
|
||||
qdel(ammunition.original_projectile) //No longer needed.
|
||||
ammunition.original_projectile = widowmaker
|
||||
widowmaker.primed = TRUE
|
||||
var/turf/entry_turf_initial = get_ranged_target_turf(entry_target, REVERSE_DIR(entry_target.dir), 20)
|
||||
var/entry_dir_choice = (dir & NORTH) || (dir & SOUTH) ? list(EAST, WEST) : list(NORTH, SOUTH)
|
||||
var/turf/entry_turf = get_ranged_target_turf(entry_turf_initial, entry_dir_choice, 5)
|
||||
widowmaker.forceMove(entry_turf)
|
||||
widowmaker.dir = ammunition.heading
|
||||
var/turf/target_turf = get_step(widowmaker, widowmaker.dir)
|
||||
widowmaker.on_translate(entry_turf, target_turf)
|
||||
log_and_message_admins("A projectile ([widowmaker.name]) has entered a z-level at [entry_target.name], with direction [dir2text(widowmaker.dir)]! (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[widowmaker.x];Y=[widowmaker.y];Z=[widowmaker.z]'>JMP</a>)")
|
||||
say_dead_direct("A projectile ([widowmaker.name]) has entered a z-level at [entry_target.name], with direction [dir2text(widowmaker.dir)]!")
|
||||
widowmaker.preparePixelProjectile(target_turf, entry_turf)
|
||||
widowmaker.fired_from = src
|
||||
widowmaker.fire()
|
||||
qdel(src)
|
||||
if(istype(A, /obj/effect/overmap/event))
|
||||
return check_entry_exoplanet(widowmaker, target_turf, target_turf)
|
||||
else if(istype(V, /obj/effect/overmap/visitable/ship) && (ammunition.overmap_behaviour & SHIP_AMMO_CAN_HIT_VISITABLES))
|
||||
return check_entry_ship(widowmaker, target_turf, V)
|
||||
else if(ammunition.overmap_behaviour & SHIP_AMMO_CAN_HIT_VISITABLES)
|
||||
return check_entry_visitable(widowmaker, target_turf)
|
||||
|
||||
if(istype(A, /obj/effect/overmap/event)) //Check hazards last, in case a ship is hiding in a hazard.
|
||||
var/obj/effect/overmap/event/EV = A
|
||||
if(EV.can_be_destroyed && (ammunition.overmap_behaviour & SHIP_AMMO_CAN_HIT_HAZARDS))
|
||||
. = TRUE
|
||||
qdel(EV)
|
||||
qdel(src)
|
||||
|
||||
///Handle bombardment of planets. Spawn the shot in a random open tile near the target and fire the projectile at it. If there is no open tile, call on_hit directly. This allows non-explosive weapons to strafe targets.
|
||||
/obj/effect/overmap/projectile/proc/check_entry_exoplanet(obj/projectile/ship_ammo/widowmaker, turf/entry_turf, turf/laze)
|
||||
. = TRUE
|
||||
playsound(laze, 'sound/weapons/gunshot/ship_weapons/orbital_travel.ogg', 60)
|
||||
laze.visible_message(SPAN_DANGER("<font size=6>A bright star is getting closer from the sky...!</font>"))
|
||||
sleep(11 SECONDS) //Let the sound play!
|
||||
var/turf/nearest_valid_turf
|
||||
|
||||
if (!(nearest_valid_turf = get_random_turf_in_range(entry_turf, 1, 5, TRUE, FALSE))) //Get a random empty turf between 1 and 5 tiles away from the target, if this fails call on_hit directly on the target turf.
|
||||
widowmaker.forceMove(submap_target)
|
||||
widowmaker.on_hit(laze, is_landmark_hit = TRUE)
|
||||
log_and_message_admins("A ([widowmaker.name]) landed at ([entry_turf.x], [entry_turf.y], [entry_turf.z]) aimed at [submap_target.name]! (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[widowmaker.x];Y=[widowmaker.y];Z=[widowmaker.z]'>JMP</a>)")
|
||||
say_dead_direct("A ([widowmaker.name]) landed at ([entry_turf.x], [entry_turf.y], [entry_turf.z]) aimed at [submap_target.name]!")
|
||||
qdel(widowmaker)
|
||||
qdel(src)
|
||||
|
||||
else //If there's an empty turf, spawn our projectile there and aim it at the target.
|
||||
widowmaker.on_translate(entry_turf, nearest_valid_turf)
|
||||
log_and_message_admins("A ([widowmaker.name]) landed at ([entry_turf.x], [entry_turf.y], [entry_turf.z]) aimed at [submap_target.name]! (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[widowmaker.x];Y=[widowmaker.y];Z=[widowmaker.z]'>JMP</a>)")
|
||||
say_dead_direct("A ([widowmaker.name]) landed at ([entry_turf.x], [entry_turf.y], [entry_turf.z]) aimed at [submap_target.name]!")
|
||||
widowmaker.preparePixelProjectile(nearest_valid_turf, entry_turf)
|
||||
widowmaker.fired_from = src
|
||||
widowmaker.fire()
|
||||
qdel(src)
|
||||
|
||||
//Handle hitting ships. This requires translating the projectile's direction to account for the relative rotation of the ship. The projectile then spawns at the edge of the map, aimed at the target landmark.
|
||||
/obj/effect/overmap/projectile/proc/check_entry_ship(obj/projectile/ship_ammo/widowmaker, turf/target_turf, obj/effect/overmap/visitable/ship/VS)
|
||||
var/shot_direction = src.dir
|
||||
var/naval_heading = SSatlas.headings_to_naval["[VS.dir]"]["[shot_direction]"]
|
||||
var/corrected_heading = SSatlas.naval_to_dir["[VS.fore_dir]"][naval_heading]
|
||||
shot_direction = corrected_heading
|
||||
|
||||
var/turf/entry_turf = get_entry_turf(submap_target, REVERSE_DIR(shot_direction))
|
||||
widowmaker.forceMove(entry_turf)
|
||||
widowmaker.dir = shot_direction
|
||||
widowmaker.on_translate(entry_turf, target_turf)
|
||||
log_and_message_admins("A ([widowmaker.name]) arrived [naval_heading] of \the [target] at ([entry_turf.x], [entry_turf.y], [entry_turf.z]) aimed at [submap_target.name], with direction [dir2text(shot_direction)]! (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[submap_target.x];Y=[submap_target.y];Z=[submap_target.z]'>JMP</a>)")
|
||||
say_dead_direct("A ([widowmaker.name]) arrived [naval_heading] of \the [target] at ([entry_turf.x], [entry_turf.y], [entry_turf.z]) aimed at [submap_target.name], with direction [dir2text(shot_direction)]! (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[submap_target.x];Y=[submap_target.y];Z=[submap_target.z]'>JMP</a>)")
|
||||
widowmaker.preparePixelProjectile(target_turf, entry_turf)
|
||||
widowmaker.fired_from = src
|
||||
widowmaker.fire()
|
||||
qdel(src)
|
||||
return TRUE
|
||||
|
||||
///Handle hitting visitables that can't spin, such as asteroids and stations. Otherwise identical to hitting a ship.
|
||||
/obj/effect/overmap/projectile/proc/check_entry_visitable(obj/projectile/ship_ammo/widowmaker, turf/target_turf)
|
||||
var/shot_direction = src.dir
|
||||
var/turf/entry_turf = get_entry_turf(submap_target, REVERSE_DIR(shot_direction))
|
||||
widowmaker.forceMove(entry_turf)
|
||||
widowmaker.dir = shot_direction
|
||||
widowmaker.on_translate(entry_turf, target_turf)
|
||||
log_and_message_admins("A ([widowmaker.name]) flew in at ([entry_turf.x], [entry_turf.y], [entry_turf.z]) aimed at [submap_target.name], with direction [dir2text(shot_direction)]! (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[submap_target.x];Y=[submap_target.y];Z=[submap_target.z]'>JMP</a>)")
|
||||
say_dead_direct("A ([widowmaker.name]) flew in at ([entry_turf.x], [entry_turf.y], [entry_turf.z]) aimed at [submap_target.name], with direction [dir2text(shot_direction)]! (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[submap_target.x];Y=[submap_target.y];Z=[submap_target.z]'>JMP</a>)")
|
||||
widowmaker.preparePixelProjectile(target_turf, entry_turf)
|
||||
widowmaker.fired_from = src
|
||||
widowmaker.fire()
|
||||
qdel(src)
|
||||
return TRUE
|
||||
|
||||
/obj/effect/overmap/projectile/proc/move_to()
|
||||
if(isnull(target) || !speed)
|
||||
walk(src, 0)
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
icon = 'icons/obj/guns/ship/ship_ammo_autocannon.dmi'
|
||||
icon_state = "autocannon_frag"
|
||||
overmap_icon_state = "cannon_salvo"
|
||||
impact_type = SHIP_AMMO_IMPACT_HE
|
||||
impact_type = SHIP_AMMO_IMPACT_FRAG
|
||||
ammunition_flags = SHIP_AMMO_FLAG_INFLAMMABLE|SHIP_AMMO_FLAG_VERY_HEAVY
|
||||
caliber = SHIP_CALIBER_60MM
|
||||
burst = 8
|
||||
@@ -59,7 +59,8 @@
|
||||
icon_state = "small"
|
||||
damage = 100
|
||||
armor_penetration = 60
|
||||
penetrating = 3
|
||||
penetrating = 8
|
||||
anti_materiel_potential = 6
|
||||
|
||||
/obj/projectile/ship_ammo/autocannon/he
|
||||
name = "60mm HE shell"
|
||||
@@ -67,6 +68,7 @@
|
||||
damage = 80
|
||||
armor_penetration = 30
|
||||
penetrating = 0 // Explodes on the hull.
|
||||
explosion_strength = list(0, 2, 3)
|
||||
|
||||
/obj/projectile/ship_ammo/autocannon/frag
|
||||
name = "60mm fragmentation shell"
|
||||
@@ -77,8 +79,8 @@
|
||||
|
||||
/obj/projectile/ship_ammo/autocannon/he/on_hit(atom/target, blocked, def_zone, is_landmark_hit)
|
||||
. = ..()
|
||||
explosion(target, 0, 2, 3)
|
||||
explosion(get_turf(target), explosion_strength[1], explosion_strength[2], explosion_strength[3])
|
||||
|
||||
/obj/projectile/ship_ammo/autocannon/frag/on_hit(atom/target, blocked, def_zone, is_landmark_hit)
|
||||
fragem(target, 70, 70, 1, 2, 15, 5, TRUE)
|
||||
..()
|
||||
fragem(src, 70, 70, 1, 2, 12, 4, TRUE, spread_range = 5) //Targets 5 tiles around the impact site and shoots a projectile at them.
|
||||
. = ..()
|
||||
|
||||
@@ -27,14 +27,16 @@
|
||||
damage = 10000
|
||||
armor_penetration = 1000
|
||||
penetrating = 1
|
||||
explosion_strength = list(2, 5, 7)
|
||||
|
||||
/obj/projectile/ship_ammo/blaster/on_hit(atom/target, blocked, def_zone, is_landmark_hit)
|
||||
. = ..()
|
||||
var/turf/epicenter = get_turf(target)
|
||||
if(ismob(target))
|
||||
var/mob/M = target
|
||||
M.visible_message(SPAN_DANGER("<font size=5>\The [src] disintegrates [M]'s chest and blasts them into pieces!</font>"))
|
||||
if(isturf(target) || isobj(target))
|
||||
explosion(target, 2, 5, 7)
|
||||
explosion(epicenter, explosion_strength[1], explosion_strength[2], explosion_strength[3])
|
||||
|
||||
/obj/structure/machinery/ammunition_loader/blaster
|
||||
name = "mining blaster loader"
|
||||
|
||||
@@ -119,10 +119,11 @@
|
||||
damage = 150
|
||||
armor_penetration = 75
|
||||
penetrating = 0
|
||||
explosion_strength = list(1, 3, 6)
|
||||
|
||||
/obj/projectile/ship_ammo/bruiser/he/on_hit(atom/target, blocked, def_zone, is_landmark_hit)
|
||||
. = ..()
|
||||
explosion(target, 1, 3, 6)
|
||||
explosion(get_turf(target), explosion_strength[1], explosion_strength[2], explosion_strength[3])
|
||||
|
||||
/obj/projectile/ship_ammo/bruiser/real/he
|
||||
name = "178mm shell"
|
||||
@@ -130,6 +131,7 @@
|
||||
damage = 350
|
||||
armor_penetration = 125
|
||||
penetrating = 0
|
||||
explosion_strength = list(3, 6, 8)
|
||||
|
||||
/obj/projectile/ship_ammo/bruiser/real/ap
|
||||
name = "178mm shell"
|
||||
@@ -137,6 +139,7 @@
|
||||
damage = 250
|
||||
armor_penetration = 250
|
||||
penetrating = 2
|
||||
explosion_strength = list(0, 2, 4)
|
||||
|
||||
/obj/projectile/ship_ammo/bruiser/real/canister
|
||||
damage = 40
|
||||
@@ -157,10 +160,11 @@
|
||||
|
||||
/obj/projectile/ship_ammo/bruiser/real/on_hit(atom/target, blocked, def_zone, is_landmark_hit)
|
||||
. = ..()
|
||||
var/turf/epicenter = get_turf(target)
|
||||
if(ammo.impact_type == SHIP_AMMO_IMPACT_HE)
|
||||
explosion(target, 3, 6, 8)
|
||||
explosion(epicenter, explosion_strength[1], explosion_strength[2], explosion_strength[3])
|
||||
if(ammo.impact_type == SHIP_AMMO_IMPACT_AP)
|
||||
explosion(target, 0, 2, 4)
|
||||
explosion(epicenter, explosion_strength[1], explosion_strength[2], explosion_strength[3])
|
||||
|
||||
/obj/projectile/ship_ammo/bruiser/real/beehive/on_hit(atom/target, blocked, def_zone, is_landmark_hit)
|
||||
. = ..()
|
||||
|
||||
@@ -27,14 +27,16 @@
|
||||
damage = 10000
|
||||
armor_penetration = 1000
|
||||
penetrating = 50
|
||||
explosion_strength = list(1, 5, 3)
|
||||
|
||||
/obj/projectile/ship_ammo/coilgun/on_hit(atom/target, blocked, def_zone, is_landmark_hit)
|
||||
. = ..()
|
||||
var/turf/epicenter = get_turf(target)
|
||||
if(ismob(target))
|
||||
var/mob/M = target
|
||||
M.visible_message(SPAN_DANGER("<font size=5>\The [src] blows [M]'s chest apart and punches straight through!</font>"))
|
||||
if(isturf(target) || isobj(target))
|
||||
explosion(target, 1, 5, 3)
|
||||
explosion(epicenter, explosion_strength[1], explosion_strength[2], explosion_strength[3])
|
||||
|
||||
/obj/structure/machinery/ammunition_loader/sol
|
||||
icon_state = "ammo_loader_sol"
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
name_override = "40mm FRAG burst"
|
||||
desc = "A box of fragmentation shells for use in a Francisca rotary gun."
|
||||
icon_state = "box_inc"
|
||||
impact_type = SHIP_AMMO_IMPACT_HE
|
||||
impact_type = SHIP_AMMO_IMPACT_FRAG
|
||||
projectile_type_override = /obj/projectile/ship_ammo/francisca/frag
|
||||
|
||||
/obj/projectile/ship_ammo/francisca
|
||||
@@ -50,20 +50,24 @@
|
||||
icon_state = "small"
|
||||
damage = 50
|
||||
armor_penetration = 50
|
||||
penetrating = 2
|
||||
penetrating = 3
|
||||
anti_materiel_potential = 10
|
||||
|
||||
/obj/projectile/ship_ammo/francisca/ap
|
||||
name = "40mm AP shell"
|
||||
damage = 30
|
||||
armor_penetration = 100
|
||||
penetrating = 4
|
||||
penetrating = 8
|
||||
anti_materiel_potential = 8
|
||||
|
||||
/obj/projectile/ship_ammo/francisca/frag
|
||||
name = "40mm FRAG shell"
|
||||
icon_state = "small"
|
||||
damage = 30
|
||||
armor_penetration = 50
|
||||
penetrating = 1
|
||||
anti_materiel_potential = 6
|
||||
|
||||
/obj/projectile/ship_ammo/francisca/frag/on_hit(atom/target, blocked, def_zone, is_landmark_hit)
|
||||
fragem(src, 70, 70, 1, 2, 10, 4, TRUE)
|
||||
fragem(src, 70, 70, 1, 2, 12, 4, TRUE, spread_range = 5) //Targets 5 tiles around the impact site and shoots a projectile at them.
|
||||
. = ..()
|
||||
|
||||
@@ -36,16 +36,21 @@
|
||||
damage = 100
|
||||
armor_penetration = 50
|
||||
penetrating = 0
|
||||
anti_materiel_potential = 6
|
||||
explosion_strength = list(0, 2, 4)
|
||||
|
||||
/obj/projectile/ship_ammo/grauwolf/on_hit(atom/target, blocked, def_zone, is_landmark_hit)
|
||||
. = ..()
|
||||
var/turf/epicenter = get_turf(target)
|
||||
if(ammo && ammo.impact_type == SHIP_AMMO_IMPACT_HE)
|
||||
explosion(target, 0, 2, 4)
|
||||
explosion(epicenter, explosion_strength[1], explosion_strength[2], explosion_strength[3])
|
||||
else
|
||||
explosion(target, 0, 1, 2)
|
||||
explosion(epicenter, explosion_strength[1], explosion_strength[2], explosion_strength[3])
|
||||
|
||||
/obj/projectile/ship_ammo/grauwolf/ap
|
||||
name = "armor-piercing flak"
|
||||
damage = 50
|
||||
armor_penetration = 50
|
||||
penetrating = 2
|
||||
penetrating = 8
|
||||
anti_materiel_potential = 8
|
||||
explosion_strength = list(0, 1, 2)
|
||||
|
||||
@@ -26,14 +26,16 @@
|
||||
damage = 10000
|
||||
armor_penetration = 1000
|
||||
penetrating = 1
|
||||
explosion_strength = list(3, 6, 8)
|
||||
|
||||
/obj/projectile/ship_ammo/lammergeier/on_hit(atom/target, blocked, def_zone, is_landmark_hit)
|
||||
. = ..()
|
||||
var/turf/epicenter = get_turf(target)
|
||||
if(ismob(target))
|
||||
var/mob/M = target
|
||||
M.visible_message(SPAN_DANGER("<font size=5>\The [src] blows [M]'s chest apart and punches straight through!</font>"))
|
||||
if(isturf(target) || isobj(target))
|
||||
explosion(target, 3, 6, 8)
|
||||
explosion(epicenter, explosion_strength[1], explosion_strength[2], explosion_strength[3])
|
||||
|
||||
/obj/structure/machinery/ammunition_loader/lammergeier
|
||||
name = "typhoon cannon loader"
|
||||
|
||||
@@ -159,10 +159,10 @@
|
||||
M.visible_message(SPAN_DANGER("<font size=6>[M] evaporates as they are engulfed by the beam!</font>"))
|
||||
M.dust()
|
||||
return
|
||||
explosion(target, 6, 6, 6)
|
||||
explosion(get_turf(target), 6, 6, 6)
|
||||
else
|
||||
target.visible_message(SPAN_DANGER("<font size=6>A giant, purple laser descends from the sky!</font>"))
|
||||
explosion(target, 30, 30, 30)
|
||||
explosion(get_turf(target), 30, 30, 30)
|
||||
|
||||
/obj/structure/machinery/zat_lever
|
||||
name = "activation lever"
|
||||
|
||||
@@ -28,11 +28,13 @@
|
||||
damage = 10000
|
||||
armor_penetration = 1000
|
||||
penetrating = 1
|
||||
explosion_strength = list(2, 3, 4)
|
||||
|
||||
/obj/projectile/ship_ammo/coilgun/light/on_hit(atom/target, blocked, def_zone, is_landmark_hit)
|
||||
. = ..()
|
||||
var/turf/epicenter = get_turf(target)
|
||||
if(ismob(target))
|
||||
var/mob/M = target
|
||||
M.visible_message(SPAN_DANGER("<font size=5>\The [src] blows [M]'s chest apart and punches straight through!</font>"))
|
||||
if(isturf(target) || isobj(target))
|
||||
explosion(target, 2, 3, 4)
|
||||
explosion(epicenter, explosion_strength[1], explosion_strength[2], explosion_strength[3])
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
/obj/projectile/ship_ammo/longbow/on_hit(atom/target, blocked, def_zone, is_landmark_hit)
|
||||
. = ..()
|
||||
var/turf/epicenter = get_turf(target)
|
||||
if(ismob(target))
|
||||
var/mob/M = target
|
||||
M.visible_message(SPAN_DANGER("<font size=5>\The [src] blows [M] apart and punches straight through!</font>"))
|
||||
@@ -39,13 +40,13 @@
|
||||
qdel(target)
|
||||
penetrated = TRUE
|
||||
else
|
||||
explosion(target, 4, 8, 12)
|
||||
explosion(epicenter, 4, 8, 12)
|
||||
qdel(src)
|
||||
if(SHIP_AMMO_IMPACT_HE)
|
||||
explosion(target, 6, 8, 10)
|
||||
explosion(epicenter, 6, 8, 10)
|
||||
if(SHIP_AMMO_IMPACT_BUNKERBUSTER)
|
||||
target.visible_message(SPAN_DANGER("<font size=5>\The [src] punches straight through \the [target]!</font>"))
|
||||
explosion(target, 1, 2, 4)
|
||||
explosion(epicenter, 1, 2, 4)
|
||||
target.ex_act(1)
|
||||
if(!QDELING(target) && target.density)
|
||||
qdel(target)
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
damage = 40
|
||||
armor_penetration = 40
|
||||
penetrating = 0
|
||||
explosion_strength = list(0, 2, 3)
|
||||
|
||||
|
||||
/obj/projectile/ship_ammo/nephilim/ap
|
||||
@@ -60,4 +61,4 @@
|
||||
|
||||
/obj/projectile/ship_ammo/nephilim/he/on_hit(atom/target, blocked, def_zone, is_landmark_hit)
|
||||
. = ..()
|
||||
explosion(target, 0, 2, 3)
|
||||
explosion(get_turf(target), explosion_strength[1], explosion_strength[2], explosion_strength[3])
|
||||
|
||||
@@ -60,6 +60,8 @@
|
||||
var/obj/projectile/energy/floramut/gene/projectile = .
|
||||
projectile.gene = gene
|
||||
|
||||
//*ADMIN SPAWN ONLY*/
|
||||
|
||||
/obj/item/gun/energy/meteorgun
|
||||
name = "meteor gun"
|
||||
desc = "For the love of god, make sure you're aiming this the right way!"
|
||||
@@ -88,6 +90,105 @@
|
||||
slot_flags = SLOT_BELT
|
||||
can_turret = FALSE
|
||||
|
||||
/obj/item/gun/energy/admin_ship_weapon
|
||||
name = "ship artillery testing gun"
|
||||
desc = "If you are holding this, admin-help, this fires ship-weapon bullets for testing purposes."
|
||||
icon = 'icons/obj/guns/meteor_gun.dmi'
|
||||
icon_state = "meteor_gun"
|
||||
item_state = "meteor_gun"
|
||||
slot_flags = SLOT_BELT
|
||||
fire_sound='sound/weapons/gunshot/ship_weapons/flak_fire.ogg'
|
||||
projectile_type = /obj/projectile/ship_ammo/autocannon
|
||||
sel_mode = 1
|
||||
accuracy = 1
|
||||
max_shots = 1000
|
||||
self_recharge = TRUE
|
||||
recharge_time = 5
|
||||
charge_meter = 0
|
||||
can_switch_modes = FALSE
|
||||
|
||||
firemodes = list(
|
||||
// Autocannon
|
||||
list(mode_name="autocannon", burst=1, projectile_type=/obj/projectile/ship_ammo/autocannon),
|
||||
list(mode_name="autocannon frag", burst=1, projectile_type=/obj/projectile/ship_ammo/autocannon/frag),
|
||||
list(mode_name="autocannon he", burst=1, projectile_type=/obj/projectile/ship_ammo/autocannon/he),
|
||||
// Blaster
|
||||
list(mode_name="blaster", burst=1, projectile_type=/obj/projectile/ship_ammo/blaster),
|
||||
// Bruiser
|
||||
list(mode_name="bruiser canister", burst=1, projectile_type=/obj/projectile/ship_ammo/bruiser),
|
||||
list(mode_name="bruiser flechette", burst=1, projectile_type=/obj/projectile/ship_ammo/bruiser/flechette),
|
||||
list(mode_name="bruiser he", burst=1, projectile_type=/obj/projectile/ship_ammo/bruiser/he),
|
||||
// Coilgun
|
||||
list(mode_name="coilgun", burst=1, projectile_type=/obj/projectile/ship_ammo/coilgun),
|
||||
list(mode_name="light coilgun", burst=1, projectile_type=/obj/projectile/ship_ammo/coilgun/light),
|
||||
// Francisca
|
||||
list(mode_name="francisca fmj", burst=1, projectile_type=/obj/projectile/ship_ammo/francisca),
|
||||
list(mode_name="francisca ap", burst=1, projectile_type=/obj/projectile/ship_ammo/francisca/ap),
|
||||
list(mode_name="francisca frag", burst=1, projectile_type=/obj/projectile/ship_ammo/francisca/frag),
|
||||
// Grauwolf
|
||||
list(mode_name="grauwolf he", burst=1, projectile_type=/obj/projectile/ship_ammo/grauwolf),
|
||||
list(mode_name="grauwolf ap", burst=1, projectile_type=/obj/projectile/ship_ammo/grauwolf/ap),
|
||||
// Lammergeier
|
||||
list(mode_name="lammergeier", burst=1, projectile_type=/obj/projectile/ship_ammo/lammergeier),
|
||||
// Leviathan
|
||||
list(mode_name="leviathan", burst=1, projectile_type=/obj/projectile/ship_ammo/leviathan),
|
||||
// Longbow
|
||||
list(mode_name="longbow ap", burst=1, projectile_type=/obj/projectile/ship_ammo/longbow),
|
||||
list(mode_name="longbow he", burst=1, projectile_type=/obj/projectile/ship_ammo/longbow),
|
||||
list(mode_name="longbow bunkerbuster", burst=1, projectile_type=/obj/projectile/ship_ammo/longbow),
|
||||
// Nephilim
|
||||
list(mode_name="nephilim he", burst=1, projectile_type=/obj/projectile/ship_ammo/nephilim),
|
||||
list(mode_name="nephilim ap", burst=1, projectile_type=/obj/projectile/ship_ammo/nephilim/ap),
|
||||
)
|
||||
|
||||
/obj/item/gun/energy/admin_ship_weapon/consume_next_projectile() //Longbow ammo has warhead data that can't simply be set in the firemodes.
|
||||
var/obj/projectile/ship_ammo/projectile = ..()
|
||||
if(!projectile)
|
||||
return projectile
|
||||
|
||||
var/datum/firemode/current_mode = firemodes[sel_mode]
|
||||
var/obj/item/ship_ammunition/loaded_ammo
|
||||
switch(current_mode?.name)
|
||||
if("longbow ap")
|
||||
loaded_ammo = new /obj/item/ship_ammunition/longbow/preset_ap()
|
||||
projectile.penetrating = 1
|
||||
if("longbow he")
|
||||
loaded_ammo = new /obj/item/ship_ammunition/longbow/preset_he()
|
||||
if("longbow bunkerbuster")
|
||||
loaded_ammo = new /obj/item/ship_ammunition/longbow/preset_bb()
|
||||
projectile.penetrating = 3
|
||||
|
||||
if(!loaded_ammo)
|
||||
return projectile
|
||||
|
||||
loaded_ammo.forceMove(projectile)
|
||||
projectile.ammo = loaded_ammo
|
||||
projectile.name = loaded_ammo.name
|
||||
projectile.desc = loaded_ammo.desc
|
||||
return projectile
|
||||
|
||||
/obj/item/gun/energy/admin_ship_weapon/attack_self(mob/user) //So admins can shoot this at the ship from out of sight.
|
||||
toggle_scope(2.0, user)
|
||||
|
||||
/obj/item/gun/energy/admin_ship_weapon/crew
|
||||
name = "horizon artillery testing gun"
|
||||
desc = "If you are holding this, admin-help, this fires the horizon's guns"
|
||||
|
||||
firemodes = list(
|
||||
// Francisca
|
||||
list(mode_name="francisca fmj", burst=1, projectile_type=/obj/projectile/ship_ammo/francisca),
|
||||
list(mode_name="francisca ap", burst=1, projectile_type=/obj/projectile/ship_ammo/francisca/ap),
|
||||
list(mode_name="francisca frag", burst=1, projectile_type=/obj/projectile/ship_ammo/francisca/frag),
|
||||
// Grauwolf
|
||||
list(mode_name="grauwolf he", burst=1, projectile_type=/obj/projectile/ship_ammo/grauwolf),
|
||||
list(mode_name="grauwolf ap", burst=1, projectile_type=/obj/projectile/ship_ammo/grauwolf/ap),
|
||||
// Longbow
|
||||
list(mode_name="longbow ap", burst=1, projectile_type=/obj/projectile/ship_ammo/longbow),
|
||||
list(mode_name="longbow he", burst=1, projectile_type=/obj/projectile/ship_ammo/longbow),
|
||||
list(mode_name="longbow bunkerbuster", burst=1, projectile_type=/obj/projectile/ship_ammo/longbow),
|
||||
)
|
||||
|
||||
//*END OF ADMIN SPAWN ONLY*//
|
||||
|
||||
/obj/item/gun/energy/mindflayer
|
||||
name = "mind flayer"
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
var/pierce_chance = 0
|
||||
/// 0-1 multiplier, the projectile's damage is modified by multiplying this after each pierce.
|
||||
var/pierce_decay_damage = 0.7
|
||||
///Used to determine whether or not to apply embed chances on hit.
|
||||
///Used to determine whether or not to apply embed chances on hit, also used in ship weapons for spalling.
|
||||
var/last_hit_pierced = FALSE
|
||||
/// If objects are below this layer, we pass through them.
|
||||
var/hit_threshhold = PROJECTILE_HIT_THRESHHOLD_LAYER
|
||||
@@ -1220,8 +1220,8 @@
|
||||
var/obj/item/SP = new shrapnel_type(organ)
|
||||
SP.edge = TRUE
|
||||
SP.sharp = TRUE
|
||||
SP.name = (name != "shrapnel") ? "[initial(name)] shrapnel" : "shrapnel"
|
||||
SP.desc += " It looks like it was fired from [fired_from]."
|
||||
SP.name = (name != "shrapnel") ? "[name] shrapnel" : "shrapnel"
|
||||
SP.desc += " It looks like it came from from \a [fired_from]."
|
||||
SP.forceMove(organ)
|
||||
organ.embed(SP)
|
||||
return SP
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
M.emitter_blasts_taken += 1
|
||||
else if(prob(33))
|
||||
M.emitter_blasts_taken += 1
|
||||
explosion(target, -1, 0, 2)
|
||||
explosion(get_turf(target), -1, 0, 2)
|
||||
. = ..()
|
||||
|
||||
/obj/projectile/beam/thermaldrill
|
||||
|
||||
@@ -481,7 +481,7 @@
|
||||
|
||||
/obj/projectile/bullet/gauss/highex/on_hit(atom/target, blocked, def_zone)
|
||||
. = ..()
|
||||
explosion(target, -1, 0, 2)
|
||||
explosion(get_turf(target), -1, 0, 2)
|
||||
if(ismovable(target))
|
||||
var/atom/movable/T = target
|
||||
var/throwdir = get_dir(firer,target)
|
||||
@@ -502,7 +502,7 @@
|
||||
armor_penetration = 5
|
||||
|
||||
/obj/projectile/bullet/cannonball/explosive/on_hit(atom/target, blocked, def_zone)
|
||||
explosion(target, -1, 1, 2)
|
||||
explosion(get_turf(target), -1, 1, 2)
|
||||
. = ..()
|
||||
|
||||
/obj/projectile/bullet/nuke
|
||||
@@ -518,7 +518,7 @@
|
||||
if(ishuman(mob))
|
||||
mob.apply_damage(250, DAMAGE_RADIATION, damage_flags = DAMAGE_FLAG_DISPERSED)
|
||||
new /obj/effect/temp_visual/nuke(target.loc)
|
||||
explosion(target,2,5,9)
|
||||
explosion(get_turf(target),2,5,9)
|
||||
. = ..()
|
||||
|
||||
/obj/projectile/bullet/shard
|
||||
@@ -542,7 +542,7 @@
|
||||
var/heavy_impact_range = 1
|
||||
|
||||
/obj/projectile/bullet/recoilless_rifle/on_hit(atom/target, blocked, def_zone)
|
||||
explosion(target, -1, heavy_impact_range, 2)
|
||||
explosion(get_turf(target), -1, heavy_impact_range, 2)
|
||||
. = ..()
|
||||
|
||||
/obj/projectile/bullet/peac
|
||||
@@ -562,7 +562,7 @@
|
||||
/obj/projectile/bullet/peac/on_hit(atom/target, blocked, def_zone)
|
||||
. = ..()
|
||||
|
||||
explosion(target, devastation_range, heavy_impact_range, light_impact_range)
|
||||
explosion(get_turf(target), devastation_range, heavy_impact_range, light_impact_range)
|
||||
|
||||
/obj/projectile/bullet/peac/he
|
||||
name = "high-explosive missile"
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
if(ismob(target))
|
||||
var/mob/M = target
|
||||
M.gib()
|
||||
explosion(target, -1, 0, 5)
|
||||
explosion(get_turf(target), -1, 0, 5)
|
||||
. = ..()
|
||||
|
||||
/obj/projectile/energy/bfg/New()
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
edge = TRUE
|
||||
|
||||
/obj/projectile/bullet/gyro/on_hit(atom/target, blocked, def_zone)
|
||||
explosion(target, -1, 0, 2)
|
||||
explosion(get_turf(target), -1, 0, 2)
|
||||
. = ..()
|
||||
|
||||
/obj/projectile/bullet/gyro/law
|
||||
@@ -87,7 +87,7 @@
|
||||
/obj/projectile/bullet/gyro/law/on_hit(atom/target, blocked, def_zone)
|
||||
. = ..()
|
||||
|
||||
explosion(target, -1, 0, 2)
|
||||
explosion(get_turf(target), -1, 0, 2)
|
||||
var/obj/T = target
|
||||
var/throwdir = get_dir(firer,target)
|
||||
T.throw_at(get_edge_target_turf(target, throwdir),3,3)
|
||||
@@ -215,7 +215,7 @@
|
||||
edge = TRUE
|
||||
|
||||
/obj/projectile/bullet/trod/on_hit(atom/target, blocked, def_zone)
|
||||
explosion(target, 0, 0, 4)
|
||||
explosion(get_turf(target), 0, 0, 4)
|
||||
. = ..()
|
||||
|
||||
/obj/projectile/chameleon
|
||||
@@ -235,7 +235,7 @@
|
||||
penetrating = 1
|
||||
|
||||
/obj/projectile/bullet/cannon/on_hit(atom/target, blocked, def_zone)
|
||||
explosion(target, 1, 2, 3, 3)
|
||||
explosion(get_turf(target), 1, 2, 3, 3)
|
||||
. = ..()
|
||||
|
||||
//magic
|
||||
@@ -256,7 +256,7 @@
|
||||
damage_type = DAMAGE_BURN
|
||||
|
||||
/obj/projectile/magic/fireball/on_hit(atom/target, blocked, def_zone)
|
||||
explosion(target, 0, 0, 4)
|
||||
explosion(get_turf(target), 0, 0, 4)
|
||||
. = ..()
|
||||
|
||||
/obj/projectile/magic/teleport //literaly bluespace crystal code, because i am lazy and it seems to work
|
||||
@@ -301,7 +301,7 @@
|
||||
|
||||
/obj/item/missile/throw_impact(atom/hit_atom)
|
||||
if(primed)
|
||||
explosion(hit_atom, 0, 1, 2, 4)
|
||||
explosion(get_turf(hit_atom), 0, 1, 2, 4)
|
||||
qdel(src)
|
||||
else
|
||||
..()
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
if(target != firer)
|
||||
var/mob/M = target
|
||||
M.gib()
|
||||
explosion(target, 5, 5, 5)
|
||||
explosion(get_turf(target), 5, 5, 5)
|
||||
. = ..()
|
||||
|
||||
// /obj/projectile/hollow_purple/after_move()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
var/capacity = 1000
|
||||
var/can_tamper = TRUE
|
||||
var/is_leaking = FALSE
|
||||
maxhealth = OBJECT_HEALTH_HIGH
|
||||
|
||||
/obj/structure/reagent_dispensers/mechanics_hints(mob/user, distance, is_adjacent)
|
||||
. += ..()
|
||||
@@ -97,6 +98,15 @@
|
||||
var/splash_amount = min(amount_per_transfer_from_this,60) //Hard limit of 60 per process
|
||||
reagents.trans_to_turf(get_turf(src),splash_amount)
|
||||
|
||||
/obj/structure/reagent_dispensers/on_death(damage, damage_flags, damage_type, armor_penetration, obj/weapon)
|
||||
if(!should_use_health)
|
||||
return FALSE
|
||||
if(reagents.total_volume > 0)
|
||||
var/splash_area = max(1, round(sqrt(reagents.total_volume / 60.0))) //Splash roughly 60u on every turf.
|
||||
reagents.splash_area(get_turf(src), splash_area, reagents.total_volume)
|
||||
visible_message(SPAN_WARNING("As \the [src] is destroyed, it spills [reagents.get_primary_reagent_name()] everywhere!"))
|
||||
dismantle()
|
||||
|
||||
//Fire extinguisher tank
|
||||
|
||||
/obj/structure/reagent_dispensers/extinguisher
|
||||
@@ -255,6 +265,8 @@
|
||||
amount_per_transfer_from_this = 45
|
||||
can_tamper = FALSE
|
||||
reagents_to_add = list(/singleton/reagent/capsaicin/condensed = 1000)
|
||||
maxhealth = OBJECT_HEALTH_VERY_LOW //Made of glass.
|
||||
material = MATERIAL_GLASS
|
||||
|
||||
/obj/structure/reagent_dispensers/virusfood
|
||||
name = "virus food dispenser"
|
||||
@@ -265,6 +277,8 @@
|
||||
density = 0
|
||||
can_tamper = FALSE
|
||||
reagents_to_add = list(/singleton/reagent/nutriment/virusfood = 1000)
|
||||
maxhealth = OBJECT_HEALTH_VERY_LOW //Made of glass.
|
||||
material = MATERIAL_GLASS
|
||||
|
||||
/obj/structure/reagent_dispensers/acid
|
||||
name = "sulphuric acid dispenser"
|
||||
@@ -275,6 +289,8 @@
|
||||
density = 0
|
||||
can_tamper = FALSE
|
||||
reagents_to_add = list(/singleton/reagent/acid = 1000)
|
||||
maxhealth = OBJECT_HEALTH_VERY_LOW //Made of glass.
|
||||
material = MATERIAL_GLASS
|
||||
|
||||
/obj/structure/reagent_dispensers/peppertank/luminol
|
||||
name = "luminol dispenser"
|
||||
@@ -282,6 +298,8 @@
|
||||
icon_state = "luminoltank"
|
||||
amount_per_transfer_from_this = 50
|
||||
reagents_to_add = list(/singleton/reagent/luminol = 1000)
|
||||
maxhealth = OBJECT_HEALTH_VERY_LOW //Made of glass.
|
||||
material = MATERIAL_GLASS
|
||||
|
||||
/obj/structure/reagent_dispensers/peppertank/spacecleaner
|
||||
name = "cleaner dispenser"
|
||||
@@ -289,6 +307,8 @@
|
||||
icon_state = "cleanertank"
|
||||
amount_per_transfer_from_this = 250
|
||||
reagents_to_add = list(/singleton/reagent/spacecleaner = 1000)
|
||||
maxhealth = OBJECT_HEALTH_VERY_LOW //Made of glass.
|
||||
material = MATERIAL_GLASS
|
||||
|
||||
//Water Cooler
|
||||
|
||||
@@ -305,6 +325,8 @@
|
||||
reagents_to_add = list(/singleton/reagent/water = 500)
|
||||
var/cups = 12
|
||||
var/cup_type = /obj/item/reagent_containers/food/drinks/sillycup
|
||||
maxhealth = OBJECT_HEALTH_LOW //Made of plastic.
|
||||
material = MATERIAL_PLASTIC
|
||||
|
||||
/obj/structure/reagent_dispensers/water_cooler/attack_hand(var/mob/user)
|
||||
if(cups > 0)
|
||||
@@ -348,6 +370,8 @@
|
||||
desc = "An empty keg."
|
||||
icon_state = "keg"
|
||||
amount_per_transfer_from_this = 10
|
||||
maxhealth = OBJECT_HEALTH_LOW //Made of wood
|
||||
material = MATERIAL_WOOD
|
||||
|
||||
/obj/structure/reagent_dispensers/keg/attackby(obj/item/attacking_item, mob/user)
|
||||
if (istype(attacking_item, /obj/item/stack/rods))
|
||||
@@ -479,6 +503,8 @@
|
||||
icon_state = "chemical_barrel"
|
||||
amount_per_transfer_from_this = 300
|
||||
reagents_to_add = list(/singleton/reagent/radioactive_waste = 1000)
|
||||
maxhealth = OBJECT_HEALTH_VERY_HIGH //Made of plasteel, because it's dangerous to break.
|
||||
material = MATERIAL_PLASTEEL
|
||||
|
||||
/obj/structure/reagent_dispensers/antagonist_hints(mob/user, distance, is_adjacent)
|
||||
. += ..()
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
/// The actual strength of the field.
|
||||
var/field_strength = 0
|
||||
/// Current strengthening rate of a single field.
|
||||
var/strengthen_rate = 0.2
|
||||
/// Maximum rate by which an energy field can be strengthened.
|
||||
var/max_strengthen_rate = 0.5
|
||||
/// The percentage of the shield strength that needs to be replaced each second
|
||||
var/dissipation_rate = 0.030
|
||||
var/strengthen_rate = 0.1
|
||||
/// Maximum rate by which an energy field can be strengthened. This is set by shield components in RefreshComponents, the initial rate with standard components is 0.5 (2 Manipulators, Rating 1: give 0.1 each)
|
||||
var/max_strengthen_rate = 0.3
|
||||
/// The percentage of the shield strength that needs to be replaced each second. This is modified by shield components in RefreshComponents, the initial rate with standard components is 0.030. (2 Micro Lasers, Rating 1: subtract 0.003 each)
|
||||
var/dissipation_rate = 0.036
|
||||
/// An energy field will dissipate by at least this rate in renwicks per field tile (otherwise field would never dissipate completely as dissipation is a percentage)
|
||||
var/min_dissipation = 0.01
|
||||
/// Our target field strength.
|
||||
@@ -18,8 +18,8 @@
|
||||
var/max_field_strength = 10
|
||||
/// The time passed since the last "fail", AKA losing charge faster than you can replenish it.
|
||||
var/time_since_fail = 100
|
||||
/// How many renwicks per watt.
|
||||
var/energy_conversion_rate = 0.00006
|
||||
/// How many renwicks per watt. This is modified by shield components in RefreshComponents, the initial rate with standard components is 0.00005 (2 Capacitors, Rating 1: give 0.00001 each)
|
||||
var/energy_conversion_rate = 0.00003
|
||||
/// If the field is strong, then the energy field objects will turn dense.
|
||||
var/strong_field = FALSE
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
* @assumed_charge: the charge that is given to this energy field. You have to get the required energy first, if you want a balanced field.
|
||||
*/
|
||||
/datum/energy_field/proc/handle_strength(assumed_charge = 0)
|
||||
assumed_charge = max(assumed_charge, 0)
|
||||
if(length(field))
|
||||
time_since_fail++
|
||||
//the amount of renwicks that the generator can add this tick, over the entire field
|
||||
@@ -40,7 +41,7 @@
|
||||
var/renwick_increase_per_field = total_renwick_increase / length(field) //per field tile
|
||||
var/renwick_upkeep_per_field = max(field_strength * dissipation_rate, min_dissipation)
|
||||
var/amount_to_strengthen = renwick_increase_per_field - renwick_upkeep_per_field
|
||||
field_strength = min(field_strength + amount_to_strengthen, max_field_strength)
|
||||
field_strength = max(min(field_strength + amount_to_strengthen, max_field_strength), 0)
|
||||
|
||||
if(field_strength < 1)
|
||||
if(strong_field)
|
||||
@@ -61,7 +62,7 @@
|
||||
var/required_energy = 0
|
||||
if(length(field))
|
||||
var/renwick_upkeep_per_field = max(field_strength * dissipation_rate, min_dissipation)
|
||||
var/target_renwick_increase = min(target_field_strength - field_strength, strengthen_rate) + renwick_upkeep_per_field //per field tile
|
||||
var/target_renwick_increase = max(min(target_field_strength - field_strength, strengthen_rate) + renwick_upkeep_per_field, 0) //per field tile
|
||||
required_energy = length(field) * target_renwick_increase / energy_conversion_rate
|
||||
return required_energy
|
||||
|
||||
@@ -100,8 +101,8 @@
|
||||
|
||||
data["average_field"] = round(field_strength, 0.01)
|
||||
data["progress_field"] = (target_field_strength ? round(100 * field_strength / target_field_strength, 0.1) : "NA")
|
||||
data["power_take"] = round(length(field) * max(field_strength * dissipation_rate, min_dissipation) / energy_conversion_rate)
|
||||
data["shield_power"] = round(length(field) * min(strengthen_rate, target_field_strength - field_strength) / energy_conversion_rate)
|
||||
data["power_take"] = round(length(field) * max(field_strength * dissipation_rate, min_dissipation) / energy_conversion_rate / 1000) //Divide by 100 to convert to kW
|
||||
data["shield_power"] = round(max(length(field) * min(strengthen_rate, target_field_strength - field_strength) / energy_conversion_rate / 1000, 0)) //Divide by 100 to convert to kW
|
||||
data["strengthen_rate"] = (strengthen_rate * 10)
|
||||
data["max_strengthen_rate"] = (max_strengthen_rate * 10)
|
||||
data["target_field_strength"] = target_field_strength
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
var/ticks_recovering = 10
|
||||
/// If strength goes is 1 or above, this is set to TRUE, this is to prevent flickering and animate being called constantly
|
||||
var/is_strong = FALSE
|
||||
pass_flags_self = PASSSHIELD //Currently only ship weapons have this flag.
|
||||
|
||||
atmos_canpass = CANPASS_ALWAYS
|
||||
|
||||
@@ -42,6 +43,7 @@
|
||||
energy_field.remove_individual_field(src)
|
||||
|
||||
/obj/effect/energy_field/attackby(obj/item/attacking_item, mob/user)
|
||||
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
|
||||
user.do_attack_animation(src, attacking_item)
|
||||
if(attacking_item.force < 10)
|
||||
user.visible_message(SPAN_WARNING("[user] harmlessly attacks \the [src] with \the [attacking_item]."),
|
||||
@@ -65,17 +67,76 @@
|
||||
damage_field(0.5 + severity)
|
||||
|
||||
/obj/effect/energy_field/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
|
||||
. = ..()
|
||||
|
||||
var/initial_damage = hitting_projectile.get_structure_damage()
|
||||
/**
|
||||
* The shield has three possible interactions with a projectile:
|
||||
*
|
||||
* If the projectile is piercing (penetrating >= 1 and pierce_flags = PASSSHIELD), the shield will decrease what it can subsequently penetrate.
|
||||
* The projectile will have it's pierces increased by half the shield's strength. If a projectile's pierces is greater than it's penetrating, it stops.
|
||||
*
|
||||
* Eg. A penetrating 10 shot hits a strength 10 shield. It will subsequently go through 4 additional walls (10/2 = 5, +1 for the shield tile), instead of the 10 it would originally.
|
||||
*/
|
||||
if(piercing_hit)
|
||||
hitting_projectile.pierces += round((energy_field.field_strength / 2))
|
||||
if(hitting_projectile.pierces < hitting_projectile.penetrating)
|
||||
visible_message(SPAN_WARNING("\The [src] flickers and fails as it is penetrated by the \the [hitting_projectile]."))
|
||||
damage_field(initial_damage / 20) //The shield takes less damage if it is penetrated like this.
|
||||
return BULLET_ACT_FORCE_PIERCE
|
||||
|
||||
/**
|
||||
* A projectile without pass_flags = PASSSHIELD will have its damage reduced by the shield, if its damage is reduced to zero it is blocked.
|
||||
* A strong projectile hitting a weak shield will penetrate it. A strength 10 field will always block a projectile.
|
||||
* The projectile's damage is multiplied by 1 minus the shield's strength divided by 10, then the shield's strength is subtracted from it's damage.
|
||||
*
|
||||
* Eg. A 50 damage projectile hits a 5 strength shield. (damage multiplier: 1 - 5/10 = 0.5), (shot damage: = 50 * 0.5 = 25), (final damage: 25 - 5 = 20). Penetrates.
|
||||
* Therefore the shot would continue on, but only deal 20 damage when it hits.
|
||||
*
|
||||
* Eg. A 50 damage projectile hits a 9 strength shield. (damage multiplier: 1 - 9/10 = 0.1), (shot damage: = 50 * 0.1 = 5), (final damage: 5 - 9 = -4). Blocked.
|
||||
*/
|
||||
if(energy_field.field_strength < 10) //A strength 10 field will always block a projectile. Without modifications to the engines, one or more upgraded SMES units, or upgrades from research the Horizon cannot generate a field of strength 10 that covers the whole ship.
|
||||
hitting_projectile.damage *= 1 - max(0, energy_field.field_strength / 10)
|
||||
hitting_projectile.damage -= energy_field.field_strength
|
||||
if(hitting_projectile.damage >= 0)
|
||||
damage_field(initial_damage / 10)
|
||||
visible_message(SPAN_WARNING("\The [src] flashes and depletes the [hitting_projectile]'s energy, but doesn't fully block it."))
|
||||
return BULLET_ACT_FORCE_PIERCE
|
||||
else
|
||||
visible_message(SPAN_WARNING("\The [src] shimmers and absorbs \the [hitting_projectile]."))
|
||||
return BULLET_ACT_BLOCK
|
||||
|
||||
/**
|
||||
* An explosive projectile that fails to penetrate will be deleted before it can explode.
|
||||
* All explosive ship weapons also have high damage, so this only really matters on a strength 10 shield.
|
||||
* If the projectile is explosive, it deals whatever damage that explosion would have done to the shield.
|
||||
*/
|
||||
if(istype(hitting_projectile, /obj/projectile/ship_ammo))
|
||||
var/obj/projectile/ship_ammo/explosive_projectile = hitting_projectile
|
||||
if(explosive_projectile.explosion_strength[3] || explosive_projectile.explosion_strength[2] || explosive_projectile.explosion_strength[1])
|
||||
for(var/obj/effect/energy_field/shield_tile in energy_field.field)
|
||||
var/distance = get_dist(src, shield_tile)
|
||||
if(distance <= explosive_projectile.explosion_strength[1])
|
||||
shield_tile.damage_field(3)
|
||||
else if(distance <= explosive_projectile.explosion_strength[2])
|
||||
shield_tile.damage_field(2)
|
||||
else if(distance <= explosive_projectile.explosion_strength[3])
|
||||
shield_tile.damage_field(1)
|
||||
// explosive_projectile.explosion_strength = list(0, 0, 0) // Set the explosion strength to 0.
|
||||
visible_message(SPAN_WARNING("\The [src] shimmers and absorbs \the [hitting_projectile]."))
|
||||
return BULLET_ACT_BLOCK
|
||||
|
||||
damage_field(initial_damage / 10)
|
||||
|
||||
. = ..() //If we get down here fall back on normal piercing calculations, for normal projectiles hitting shields.
|
||||
if(. != BULLET_ACT_HIT)
|
||||
return .
|
||||
|
||||
damage_field(hitting_projectile.get_structure_damage() / 10)
|
||||
|
||||
/obj/effect/energy_field/proc/damage_field(var/severity)
|
||||
if(!severity)
|
||||
return
|
||||
|
||||
damage += severity
|
||||
energy_field.field_strength = max(energy_field.field_strength - (severity / length(energy_field.field)), 0)
|
||||
|
||||
if(!(datum_flags & DF_ISPROCESSING))
|
||||
// Start processing ONLY when we're damaged. Through processing, we're going to slowly climb back up to field strength.
|
||||
|
||||
@@ -14,13 +14,33 @@
|
||||
var/stored_charge = 0
|
||||
var/last_stored_charge = 0
|
||||
var/time_since_fail = 100
|
||||
var/max_charge = 2e7 //20 MJ
|
||||
var/max_charge_rate = 9000000 //9 MW
|
||||
///How much energy the capacitor can store, value is in Joules, displayed in the UI as MJ. This value is calculated in RefreshParts() based on the components used, the value listed here is what base components provides, changing the value only here will not do anything.
|
||||
var/max_charge = 20 MEGA //200 MJ
|
||||
///How much energy the capacitor can absorb per second, value is in Watts, displayed in the UI as MW. This value is calculated in RefreshParts() based on the components used, the value listed here is what base components provides, changing the value only here will not do anything.
|
||||
var/max_charge_rate = 9 MEGA WATTS //9 MW
|
||||
var/locked = FALSE
|
||||
|
||||
var/charge_rate = 100000 //100 kW
|
||||
var/charge_rate = 100 KILO WATTS //100 kW
|
||||
var/obj/structure/machinery/shield_gen/owned_gen
|
||||
|
||||
component_types = list(
|
||||
/obj/item/circuitboard/shield_cap,
|
||||
/obj/item/stock_parts/capacitor = 4,
|
||||
/obj/item/stock_parts/micro_laser = 1,
|
||||
/obj/item/stock_parts/subspace/filter = 1,
|
||||
/obj/item/stock_parts/subspace/treatment = 1,
|
||||
/obj/item/stock_parts/subspace/analyzer = 1,
|
||||
/obj/item/stock_parts/console_screen = 1,
|
||||
/obj/item/stack/cable_coil = 5
|
||||
)
|
||||
|
||||
/obj/structure/machinery/shield_capacitor/upgrade_hints(mob/user, distance, is_adjacent)
|
||||
. += ..()
|
||||
. += "Upgraded <b>capacitors</b> will increase capacity."
|
||||
. += SPAN_NOTICE(" - The current capacity is <b>[max_charge / 1e6]</b> MJ")
|
||||
. += "Upgraded <b>microlasers</b> will increase the charging rate."
|
||||
. += SPAN_NOTICE(" - The current maximum charge rate is <b>[max_charge_rate / 1e6]</b> MW")
|
||||
|
||||
/obj/structure/machinery/shield_capacitor/Initialize()
|
||||
..()
|
||||
return INITIALIZE_HINT_LATELOAD
|
||||
@@ -29,8 +49,9 @@
|
||||
. = ..()
|
||||
for(var/obj/structure/machinery/shield_gen/possible_gen in range(1, src))
|
||||
if(get_dir(src, possible_gen) == dir)
|
||||
possible_gen.owned_capacitor = src
|
||||
break
|
||||
if(possible_gen.attach_capacitor(src))
|
||||
owned_gen = possible_gen
|
||||
break
|
||||
|
||||
/obj/structure/machinery/shield_capacitor/emag_act(var/remaining_charges, var/mob/user)
|
||||
if(prob(75))
|
||||
@@ -40,8 +61,26 @@
|
||||
updateDialog()
|
||||
spark(src, 5, GLOB.alldirs)
|
||||
|
||||
/obj/structure/machinery/shield_capacitor/RefreshParts()
|
||||
..()
|
||||
max_charge = 0
|
||||
max_charge_rate = 6 MEGA WATTS //9 MW after base components. 15 MW with full upgrades.
|
||||
|
||||
for(var/obj/item/stock_parts/P in component_parts)
|
||||
if(iscapacitor(P))
|
||||
max_charge += P.rating * 5 MEGA
|
||||
else if(ismicrolaser(P))
|
||||
max_charge_rate += P.rating * 3 MEGA WATTS
|
||||
|
||||
/obj/structure/machinery/shield_capacitor/attackby(obj/item/attacking_item, mob/user)
|
||||
|
||||
if(default_part_replacement(user, attacking_item))
|
||||
return TRUE
|
||||
else if(default_deconstruction_screwdriver(user, attacking_item))
|
||||
return TRUE
|
||||
else if(default_deconstruction_crowbar(user, attacking_item))
|
||||
return TRUE
|
||||
|
||||
if(istype(attacking_item, /obj/item/card/id))
|
||||
if(allowed(user))
|
||||
locked = !locked
|
||||
@@ -55,14 +94,14 @@
|
||||
|
||||
if(anchored)
|
||||
for(var/obj/structure/machinery/shield_gen/gen in range(1, src))
|
||||
if(get_dir(src, gen) == src.dir && !gen.owned_capacitor)
|
||||
owned_gen = gen
|
||||
owned_gen.owned_capacitor = src
|
||||
owned_gen.updateDialog()
|
||||
if(get_dir(src, gen) == src.dir)
|
||||
if(gen.attach_capacitor(src))
|
||||
owned_gen = gen
|
||||
break
|
||||
else
|
||||
if(owned_gen && owned_gen.owned_capacitor == src)
|
||||
owned_gen.owned_capacitor = null
|
||||
owned_gen = null
|
||||
if(owned_gen)
|
||||
owned_gen.detach_capacitor(src)
|
||||
owned_gen = null
|
||||
else
|
||||
..()
|
||||
|
||||
@@ -83,10 +122,10 @@
|
||||
data["locked"] = locked
|
||||
data["active"] = active
|
||||
data["time_since_fail"] = time_since_fail
|
||||
data["charge_rate"] = charge_rate
|
||||
data["stored_charge"] = stored_charge
|
||||
data["max_charge"] = max_charge
|
||||
data["max_charge_rate"] = max_charge_rate
|
||||
data["charge_rate"] = charge_rate / 1000 //UI expects kW
|
||||
data["stored_charge"] = round(stored_charge / 1e6, 0.1) //UI expects MJ
|
||||
data["max_charge"] = max_charge / 1e6 //UI expects MJ
|
||||
data["max_charge_rate"] = max_charge_rate / 1000 //UI expects kW
|
||||
return data
|
||||
|
||||
/obj/structure/machinery/shield_capacitor/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
@@ -101,7 +140,8 @@
|
||||
active = !active
|
||||
. = TRUE
|
||||
if("charge_rate")
|
||||
charge_rate = between(10000, params["charge_rate"], max_charge_rate)
|
||||
// UI sends kW; convert to W and clamp.
|
||||
charge_rate = between(0 KILO WATTS, round(params["charge_rate"]) * 1000, max_charge_rate)
|
||||
. = TRUE
|
||||
|
||||
/obj/structure/machinery/shield_capacitor/process()
|
||||
|
||||
@@ -25,23 +25,52 @@
|
||||
var/field_radius = 3
|
||||
/// Maximum field radius.
|
||||
var/max_field_radius = 100
|
||||
/// The shield capacitor attached to this shield generator.
|
||||
var/obj/structure/machinery/shield_capacitor/owned_capacitor
|
||||
/// The shield capacitors attached to this shield generator.
|
||||
var/list/owned_capacitors = list()
|
||||
/// If this shield generator supports multi-z.
|
||||
var/multiz = TRUE
|
||||
///How much the manipulator rating increases the maximum field strengthen rate. This increases how fast the shield can strengthen, allowing it to recover faster.
|
||||
var/manipulator_bonus = 0.1 //Maximum recovery rate with base components is 0.5 renwicks per second, with maxed out manipulators it's 0.9 renwicks per second.
|
||||
///How much the capacitor rating increases the field conversion rate. This increases the efficiency of converting power into shield strength, decreasing all shield power costs.
|
||||
var/capacitor_bonus = 0.000005
|
||||
///How much the micro laser rating decreased the field dissipation rate. This reduces the amount of shield strength lost per tick, reducing the shield upkeep.
|
||||
var/micro_laser_bonus = 0.002
|
||||
|
||||
component_types = list(
|
||||
/obj/item/circuitboard/shield_gen,
|
||||
/obj/item/stock_parts/manipulator = 2,
|
||||
/obj/item/stock_parts/micro_laser = 2,
|
||||
/obj/item/stock_parts/capacitor = 2,
|
||||
/obj/item/stock_parts/subspace/transmitter = 1,
|
||||
/obj/item/stock_parts/subspace/crystal = 1,
|
||||
/obj/item/stock_parts/subspace/amplifier = 1,
|
||||
/obj/item/stock_parts/console_screen = 1,
|
||||
/obj/item/stack/cable_coil = 5
|
||||
)
|
||||
|
||||
/obj/structure/machinery/shield_gen/Initialize()
|
||||
for(var/obj/structure/machinery/shield_capacitor/possible_cap in range(1, src))
|
||||
if(get_dir(possible_cap, src) == possible_cap.dir)
|
||||
owned_capacitor = possible_cap
|
||||
break
|
||||
owned_capacitors = list()
|
||||
for(var/obj/structure/machinery/shield_capacitor/possible_cap in range(1, src)) //Attach nearby capacitors
|
||||
attach_capacitor(possible_cap)
|
||||
energy_field = new(src, get_shielded_turfs())
|
||||
. = ..()
|
||||
|
||||
/obj/structure/machinery/shield_gen/Destroy()
|
||||
owned_capacitor = null
|
||||
for(var/obj/structure/machinery/shield_capacitor/cap in owned_capacitors) //Detach any owned capacitors
|
||||
detach_capacitor(cap)
|
||||
owned_capacitors = list()
|
||||
return ..()
|
||||
|
||||
/obj/structure/machinery/shield_gen/upgrade_hints(mob/user, distance, is_adjacent)
|
||||
. += ..()
|
||||
. += "Upgraded <b>manipulators</b> will increase the maximum field strengthen rate."
|
||||
. += SPAN_NOTICE("\t- The current maximum field strengthen rate is <b>[energy_field.max_strengthen_rate]</b> renwicks per second.")
|
||||
. += "Upgraded <b>capacitors</b> will increase the field conversion rate."
|
||||
. += SPAN_NOTICE("\t- The current field conversion efficiency increase is: <b>[(initial(energy_field.energy_conversion_rate) + (2 * capacitor_bonus)) / energy_field.energy_conversion_rate * 100]%</b>")
|
||||
. += "Upgraded <b>micro lasers</b> will decrease the energy field dispersion rate."
|
||||
. += SPAN_NOTICE("\t- The current field dissipation rate decrease is: <b>[(initial(energy_field.dissipation_rate) - (2 * micro_laser_bonus)) / energy_field.dissipation_rate * 100]%</b>")
|
||||
|
||||
|
||||
/obj/structure/machinery/shield_gen/emag_act(var/remaining_charges, var/mob/user)
|
||||
if(prob(75))
|
||||
locked = !locked
|
||||
@@ -52,6 +81,13 @@
|
||||
spark(src, 5, GLOB.alldirs)
|
||||
|
||||
/obj/structure/machinery/shield_gen/attackby(obj/item/attacking_item, mob/user)
|
||||
if(default_part_replacement(user, attacking_item))
|
||||
return TRUE
|
||||
else if(default_deconstruction_screwdriver(user, attacking_item))
|
||||
return TRUE
|
||||
else if(default_deconstruction_crowbar(user, attacking_item))
|
||||
return TRUE
|
||||
|
||||
if(istype(attacking_item, /obj/item/card/id))
|
||||
if(allowed(user))
|
||||
locked = !locked
|
||||
@@ -66,24 +102,41 @@
|
||||
if(active)
|
||||
toggle()
|
||||
if(anchored)
|
||||
// Attach nearby capacitors wrenching down the shield
|
||||
owned_capacitors = list()
|
||||
for(var/obj/structure/machinery/shield_capacitor/cap in range(1, src))
|
||||
if(cap.owned_gen)
|
||||
continue
|
||||
if(get_dir(cap, src) == cap.dir && cap.anchored)
|
||||
owned_capacitor = cap
|
||||
owned_capacitor.owned_gen = src
|
||||
break
|
||||
attach_capacitor(cap)
|
||||
else
|
||||
if(owned_capacitor && owned_capacitor.owned_gen == src)
|
||||
owned_capacitor.owned_gen = null
|
||||
owned_capacitor = null
|
||||
// Detach any owned capacitors when unwrenching
|
||||
for(var/obj/structure/machinery/shield_capacitor/cap in owned_capacitors)
|
||||
detach_capacitor(cap)
|
||||
owned_capacitors = list()
|
||||
else
|
||||
..()
|
||||
|
||||
/obj/structure/machinery/shield_gen/RefreshParts()
|
||||
..()
|
||||
var/max_strengthen_rate_increase = 0
|
||||
var/energy_conversion_rate_increase = 0
|
||||
var/dissipation_rate_decrease = 0
|
||||
|
||||
for(var/obj/item/stock_parts/P in component_parts)
|
||||
if(ismanipulator(P))
|
||||
max_strengthen_rate_increase += P.rating
|
||||
else if(iscapacitor(P))
|
||||
energy_conversion_rate_increase += P.rating
|
||||
else if(ismicrolaser(P))
|
||||
dissipation_rate_decrease += P.rating
|
||||
|
||||
if(energy_field)
|
||||
energy_field.max_strengthen_rate = initial(energy_field.max_strengthen_rate) + (max_strengthen_rate_increase * manipulator_bonus)
|
||||
energy_field.energy_conversion_rate = initial(energy_field.energy_conversion_rate) + (energy_conversion_rate_increase * capacitor_bonus)
|
||||
energy_field.dissipation_rate = max(0, initial(energy_field.dissipation_rate) - (dissipation_rate_decrease * micro_laser_bonus))
|
||||
energy_field.strengthen_rate = min(energy_field.strengthen_rate, energy_field.max_strengthen_rate) ///If the strengthen rate got decreased and the current strengthen rate is now above the max, reduce it to the max.
|
||||
|
||||
/obj/structure/machinery/shield_gen/attack_ai(mob/user)
|
||||
if(!ai_can_interact(user))
|
||||
return
|
||||
return attack_hand(user)
|
||||
|
||||
/obj/structure/machinery/shield_gen/attack_hand(mob/user)
|
||||
if(stat & BROKEN)
|
||||
@@ -98,22 +151,38 @@
|
||||
to_chat(user, SPAN_WARNING("The device needs to be bolted to the ground first."))
|
||||
return
|
||||
else
|
||||
if(owned_capacitor)
|
||||
if(!((owned_capacitor in range(1, src)) && get_dir(owned_capacitor, src) == owned_capacitor.dir && owned_capacitor.anchored))
|
||||
if(owned_capacitor.owned_gen == src)
|
||||
owned_capacitor.owned_gen = null
|
||||
owned_capacitor = null
|
||||
if(!owned_capacitor)
|
||||
for(var/obj/structure/machinery/shield_capacitor/cap in owned_capacitors) //Make sure the capacitors weren't blown up
|
||||
if(!(cap in range(1, src)) || get_dir(cap, src) != cap.dir || !cap.anchored)
|
||||
if(cap && cap.owned_gen == src)
|
||||
cap.owned_gen = null
|
||||
owned_capacitors -= cap
|
||||
if(!owned_capacitors || owned_capacitors.len == 0) // Try to attach any valid adjacent capacitors
|
||||
for(var/obj/structure/machinery/shield_capacitor/cap in range(1, src))
|
||||
if(cap.owned_gen)
|
||||
continue
|
||||
if(get_dir(cap, src) == cap.dir && cap.anchored)
|
||||
owned_capacitor = cap
|
||||
owned_capacitor.owned_gen = src
|
||||
updateDialog()
|
||||
break
|
||||
attach_capacitor(cap)
|
||||
return ui_interact(user)
|
||||
|
||||
/obj/structure/machinery/shield_gen/proc/attach_capacitor(obj/structure/machinery/shield_capacitor/cap)
|
||||
if(!cap || cap.owned_gen || !cap.anchored)
|
||||
return FALSE
|
||||
if(!(cap in range(1, src)) || get_dir(cap, src) != cap.dir)
|
||||
return FALSE
|
||||
if(!owned_capacitors)
|
||||
owned_capacitors = list()
|
||||
owned_capacitors += cap
|
||||
cap.owned_gen = src
|
||||
updateDialog()
|
||||
return TRUE
|
||||
|
||||
/obj/structure/machinery/shield_gen/proc/detach_capacitor(obj/structure/machinery/shield_capacitor/cap)
|
||||
if(!cap)
|
||||
return FALSE
|
||||
if(owned_capacitors && (cap in owned_capacitors))
|
||||
owned_capacitors -= cap
|
||||
if(cap.owned_gen == src)
|
||||
cap.owned_gen = null
|
||||
updateDialog()
|
||||
return TRUE
|
||||
|
||||
/obj/structure/machinery/shield_gen/process()
|
||||
if(active)
|
||||
if(!anchored)
|
||||
@@ -128,15 +197,41 @@
|
||||
energy_field.handle_strength(assumed_charge)
|
||||
|
||||
/**
|
||||
* Called whenever the field needs to take charge from the capacitor.
|
||||
* Called whenever the field needs to take charge from attached capacitors.
|
||||
*/
|
||||
/obj/structure/machinery/shield_gen/proc/assume_charge(required_energy)
|
||||
if(!owned_capacitor || !owned_capacitor.active)
|
||||
if(!owned_capacitors || owned_capacitors.len == 0)
|
||||
return 0
|
||||
var/assumed_charge = min(owned_capacitor.stored_charge, required_energy)
|
||||
assumed_charge = max(assumed_charge, 0)
|
||||
owned_capacitor.stored_charge -= assumed_charge
|
||||
return assumed_charge
|
||||
var/list/active_capacitors = list()
|
||||
var/available_charge = 0
|
||||
for(var/obj/structure/machinery/shield_capacitor/cap in owned_capacitors) //Loop through capacitors that can provide charge
|
||||
if(!cap || !cap.active || cap.stored_charge <= 0)
|
||||
continue
|
||||
active_capacitors += cap
|
||||
available_charge += cap.stored_charge
|
||||
|
||||
if(!active_capacitors.len || available_charge <= 0) //If we have no capacitors, or they're not charged stop
|
||||
return 0
|
||||
|
||||
var/assumed_charge = min(available_charge, required_energy)
|
||||
if(assumed_charge >= available_charge)
|
||||
for(var/obj/structure/machinery/shield_capacitor/cap in active_capacitors)
|
||||
cap.stored_charge = 0
|
||||
return assumed_charge
|
||||
|
||||
var/remaining = assumed_charge
|
||||
for(var/i = 1, i <= active_capacitors.len, i++)
|
||||
var/obj/structure/machinery/shield_capacitor/cap = active_capacitors[i]
|
||||
var/took = (i == active_capacitors.len) ? remaining : round(assumed_charge * (cap.stored_charge / available_charge))
|
||||
took = min(took, cap.stored_charge, remaining)
|
||||
if(took <= 0)
|
||||
continue
|
||||
cap.stored_charge -= took
|
||||
remaining -= took
|
||||
if(remaining <= 0)
|
||||
break
|
||||
|
||||
return assumed_charge - remaining
|
||||
|
||||
/obj/structure/machinery/shield_gen/ex_act(var/severity)
|
||||
if(active)
|
||||
@@ -145,12 +240,18 @@
|
||||
|
||||
/obj/structure/machinery/shield_gen/proc/toggle()
|
||||
if(!active)
|
||||
if(!owned_capacitor)
|
||||
if(!owned_capacitors || owned_capacitors.len == 0)
|
||||
balloon_alert_to_viewers("no capacitor")
|
||||
return
|
||||
else if(!owned_capacitor?.active)
|
||||
balloon_alert_to_viewers("capacitor offline")
|
||||
return
|
||||
else
|
||||
var/has_active = FALSE
|
||||
for(var/obj/structure/machinery/shield_capacitor/cap in owned_capacitors)
|
||||
if(cap && cap.active)
|
||||
has_active = TRUE
|
||||
break
|
||||
if(!has_active)
|
||||
balloon_alert_to_viewers("capacitors offline")
|
||||
return
|
||||
active = !active
|
||||
update_icon()
|
||||
if(active)
|
||||
@@ -213,7 +314,8 @@
|
||||
/obj/structure/machinery/shield_gen/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["owned_capacitor"] = !!owned_capacitor
|
||||
data["owned_capacitor"] = length(owned_capacitors)
|
||||
data["owned_capacitor_count"] = length(owned_capacitors)
|
||||
data["active"] = active
|
||||
data["time_since_fail"] = energy_field ? energy_field.time_since_fail : 0
|
||||
data["multiz"] = multiz
|
||||
|
||||
@@ -4,6 +4,18 @@
|
||||
/obj/structure/machinery/shield_gen/external
|
||||
name = "hull shield generator"
|
||||
|
||||
component_types = list(
|
||||
/obj/item/circuitboard/shield_gen_ex,
|
||||
/obj/item/stock_parts/manipulator = 2,
|
||||
/obj/item/stock_parts/micro_laser = 2,
|
||||
/obj/item/stock_parts/capacitor = 2,
|
||||
/obj/item/stock_parts/subspace/transmitter = 1,
|
||||
/obj/item/stock_parts/subspace/crystal = 1,
|
||||
/obj/item/stock_parts/subspace/amplifier = 1,
|
||||
/obj/item/stock_parts/console_screen = 1,
|
||||
/obj/item/stack/cable_coil = 5
|
||||
)
|
||||
|
||||
//Search for space turfs within range that are adjacent to a simulated turf.
|
||||
/obj/structure/machinery/shield_gen/external/get_shielded_turfs()
|
||||
var/list/out = list()
|
||||
|
||||
Reference in New Issue
Block a user