diff --git a/code/__DEFINES/_flags.dm b/code/__DEFINES/_flags.dm
index 79d9e700376..fecfb93bb32 100644
--- a/code/__DEFINES/_flags.dm
+++ b/code/__DEFINES/_flags.dm
@@ -56,6 +56,8 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define PASSDOORHATCH (1<<15)
#define PASSTRACE (1<<16) //Used by turrets in the check_trajectory proc to target mobs hiding behind certain things (such as closets)
#define PASSRAILING (1<<17)
+/// Allows you to pass over energy shields, these also have additional handling for blocking projectiles in their on_hit().
+#define PASSSHIELD (1<<18)
//Movement Types
#define GROUND (1<<0)
diff --git a/code/__DEFINES/ship_weapons.dm b/code/__DEFINES/ship_weapons.dm
index 0d9035029bf..4e3726744aa 100644
--- a/code/__DEFINES/ship_weapons.dm
+++ b/code/__DEFINES/ship_weapons.dm
@@ -23,6 +23,7 @@
#define SHIP_AMMO_IMPACT_HE "high explosive"
#define SHIP_AMMO_IMPACT_PROBE "sensor probe"
#define SHIP_AMMO_IMPACT_FMJ "full metal jacket"
+#define SHIP_AMMO_IMPACT_FRAG "fragmentation"
#define SHIP_AMMO_IMPACT_AP "armour-piercing"
#define SHIP_AMMO_IMPACT_LASER "laser"
#define SHIP_AMMO_IMPACT_BUNKERBUSTER "bunker-buster"
diff --git a/code/__HELPERS/turfs.dm b/code/__HELPERS/turfs.dm
index f93e5bbdc31..5e7be94c355 100644
--- a/code/__HELPERS/turfs.dm
+++ b/code/__HELPERS/turfs.dm
@@ -50,6 +50,8 @@
turfs += T
if(turfs.len)
return pick(turfs)
+ else
+ return null
///Returns a turf based on text inputs, original turf and viewing client
/proc/parse_caught_click_modifiers(list/modifiers, turf/origin, client/viewing_client)
diff --git a/code/controllers/subsystems/initialization/atlas.dm b/code/controllers/subsystems/initialization/atlas.dm
index 3f0eae924bf..d14a5adab22 100644
--- a/code/controllers/subsystems/initialization/atlas.dm
+++ b/code/controllers/subsystems/initialization/atlas.dm
@@ -23,37 +23,50 @@ SUBSYSTEM_DEF(atlas)
/**
* Note that the dirs here are REVERSE because they're used for entry points, so it'd be the dir facing starboard for example.
* These are strings because otherwise the list indexes would be out of bounds. Thanks BYOND.
+ *
+ * SOUTH = 1, NORTH = 2, WEST = 4, EAST = 8,
+ * NORTH-EAST = 5, SOUTH-EAST = 6, NORTH-WEST = 9, SOUTH-WEST = 10,
*/
var/list/naval_to_dir = list(
- "1" = list(
+ "1" = list( //Ship Fore is facing North. (These are reversed, a shot travelling south would enter from the north.)
"starboard" = WEST,
"port" = EAST,
"fore" = SOUTH,
- "aft" = NORTH
+ "aft" = NORTH,
+ "aft-starboard" = NORTH|WEST,
+ "fore-starboard" = SOUTH|WEST,
+ "aft-port" = NORTH|EAST,
+ "fore-port" = SOUTH|EAST
),
- "2" = list(
+ "2" = list( //Ship Fore is facing South.
"starboard" = EAST,
"port" = WEST,
"fore" = NORTH,
- "aft" = SOUTH
+ "aft" = SOUTH,
+ "aft-starboard" = SOUTH|EAST,
+ "fore-starboard" = NORTH|EAST,
+ "aft-port" = SOUTH|WEST,
+ "fore-port" = NORTH|WEST
),
- "4" = list(
+ "4" = list( //Ship Fore is facing West.
"starboard" = NORTH,
"port" = SOUTH,
"fore" = WEST,
- "aft" = EAST
+ "aft" = EAST,
+ "aft-starboard" = EAST|NORTH,
+ "fore-starboard" = WEST|NORTH,
+ "aft-port" = EAST|SOUTH,
+ "fore-port" = WEST|SOUTH
),
- "4" = list(
- "starboard" = NORTH,
- "port" = SOUTH,
- "fore" = WEST,
- "aft" = EAST
- ),
- "8" = list(
+ "8" = list( //Ship Fore is facing East.
"starboard" = SOUTH,
"port" = NORTH,
"fore" = EAST,
- "aft" = WEST
+ "aft" = WEST,
+ "aft-starboard" = WEST|SOUTH,
+ "fore-starboard" = EAST|SOUTH,
+ "aft-port" = WEST|NORTH,
+ "fore-port" = EAST|NORTH
)
)
@@ -62,49 +75,49 @@ SUBSYSTEM_DEF(atlas)
"1" = "aft",
"2" = "fore",
"4" = "port",
- "5" = "port",
- "6" = "port",
+ "5" = "aft-port",
+ "6" = "fore-port",
"8" = "starboard",
- "9" = "starboard",
- "10" = "starboard"
+ "9" = "aft-starboard",
+ "10" = "fore-starboard"
),
"2" = list(
"1" = "fore",
"2" = "aft",
"4" = "starboard",
- "5" = "starboard",
- "6" = "starboard",
+ "5" = "fore-starboard",
+ "6" = "aft-starboard",
"8" = "port",
- "9" = "port",
- "10" = "port"
+ "9" = "fore-port",
+ "10" = "aft-port"
),
"4" = list(
"1" = "starboard",
"2" = "port",
"4" = "aft",
- "5" = "starboard",
- "6" = "port",
+ "5" = "starboard-aft",
+ "6" = "port-aft",
"8" = "fore",
- "9" = "starboard",
- "10" = "port"
+ "9" = "starboard-fore",
+ "10" = "port-fore"
),
"5" = list( //northeast
- "1" = "starboard",
- "2" = "port",
- "4" = "port",
+ "1" = "aft-starboard",
+ "2" = "fore-port",
+ "4" = "aft-port",
"5" = "aft",
"6" = "port",
- "8" = "starboard",
+ "8" = "fore-starboard",
"9" = "starboard",
"10" = "fore"
),
"6" = list( //southeast
- "1" = "starboard",
- "2" = "port",
- "4" = "starboard",
+ "1" = "fore-starboard",
+ "2" = "aft-port",
+ "4" = "aft-starboard",
"5" = "starboard",
"6" = "aft",
- "8" = "port",
+ "8" = "fore-port",
"9" = "fore",
"10" = "port"
),
@@ -112,31 +125,31 @@ SUBSYSTEM_DEF(atlas)
"1" = "port",
"2" = "starboard",
"4" = "fore",
- "5" = "port",
- "6" = "starboard",
+ "5" = "port-fore",
+ "6" = "starboard-fore",
"8" = "aft",
- "9" = "port",
- "10" = "starboard"
+ "9" = "port-aft",
+ "10" = "starboard-aft"
),
"9" = list( //northwest
- "1" = "port",
- "2" = "starboard",
- "4" = "port",
+ "1" = "aft-port",
+ "2" = "fore-starboard",
+ "4" = "fore-port",
"5" = "port",
- "6" = "fore",
- "8" = "starboard",
+ "6" = "aft-starboard",
+ "8" = "fore",
"9" = "aft",
"10" = "starboard"
),
"10" = list( //southwest
- "1" = "port",
- "2" = "starboard",
- "4" = "starboard",
+ "1" = "fore-port",
+ "2" = "aft-starboard",
+ "4" = "fore-starboard",
"5" = "fore",
"6" = "starboard",
- "8" = "port",
- "9" = "port",
- "10" = "aft"
+ "8" = "aft-port",
+ "9" = "aft",
+ "10" = "port"
)
)
diff --git a/code/game/objects/items/weapons/circuitboards/machinery/shieldgen.dm b/code/game/objects/items/weapons/circuitboards/machinery/shieldgen.dm
index 99848dc4245..cdd3219aa4a 100644
--- a/code/game/objects/items/weapons/circuitboards/machinery/shieldgen.dm
+++ b/code/game/objects/items/weapons/circuitboards/machinery/shieldgen.dm
@@ -4,7 +4,9 @@
build_path = /obj/structure/machinery/shield_gen/external
origin_tech = list(TECH_BLUESPACE = 4, TECH_PHORON = 3)
req_components = list(
- "/obj/item/stock_parts/manipulator/pico" = 2,
+ "/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,
@@ -17,7 +19,9 @@
build_path = /obj/structure/machinery/shield_gen
origin_tech = list(TECH_BLUESPACE = 4, TECH_PHORON = 3)
req_components = list(
- "/obj/item/stock_parts/manipulator/pico" = 2,
+ "/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,
@@ -30,7 +34,8 @@
build_path = /obj/structure/machinery/shield_capacitor
origin_tech = list(TECH_MAGNET = 3, TECH_POWER = 4)
req_components = list(
- "/obj/item/stock_parts/manipulator/pico" = 2,
+ "/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,
diff --git a/code/game/objects/items/weapons/grenades/fragmentation.dm b/code/game/objects/items/weapons/grenades/fragmentation.dm
index bb12f49253d..cc87cc1be8d 100644
--- a/code/game/objects/items/weapons/grenades/fragmentation.dm
+++ b/code/game/objects/items/weapons/grenades/fragmentation.dm
@@ -1,8 +1,29 @@
-/proc/fragem(var/source,var/fragx,var/fragy,var/light_dam,var/flash_dam,var/p_dam,var/p_range,var/can_cover=TRUE,var/shard_range = 50)
+/**
+ * Fragem works by shooting a projectile containing fragments at every tile in a circle around it.
+ *
+ * Source is the tile the projectiles spawn on.
+ *
+ * fragx and fragy are the upper and lower bounds of the random number of fragments produced.
+ *
+ * light_dam and flash_dam are the parameters for the explosion produced by the grenade. light_dam is the radius of minor explosive damage, flash_dam is the radius of the flash effect.
+ *
+ * p_dam is the damage of each individual fragments.
+ *
+ * p_range is the distance each projectile travels before losing a fragment. When a projectile loses all of its fragments, it is deleted.
+ *
+ * can_cover determines if a mob can lie on the source turf to absorb most of the fragments.
+ *
+ * shard_range is the maximum range of the projectiles produced by the fragem. This is separate from p_range to allow for projectiles that lose fragments over distance but have a hard maximum range. Default 50
+ *
+ * spread_range is the radius of the circle used to launch projectiles. Lower values mean less projectiles are used but if set too low gaps may appear in the spread pattern. Default 7
+ *
+ * maim_rate is the bonus for projectiles to decapitate or gib limbs. Negative values reduce this chance, positive values increase it. There is a minimum maim multiplier of 0.1, see handle_limb_gibbing for details.
+ */
+/proc/fragem(var/source,var/fragx,var/fragy,var/light_dam,var/flash_dam,var/p_dam,var/p_range,var/can_cover=TRUE,var/shard_range = 50,var/spread_range = 7,var/maim_rate)
var/turf/O = get_turf(source)
var/fragger = rand(fragx,fragy)
explosion(O, -1, -1, light_dam, flash_dam)
- var/list/target_turfs = getcircle(O, 7)
+ var/list/target_turfs = getcircle(O, spread_range)
var/fragments_per_projectile = round(fragger/target_turfs.len)
for(var/turf/T in target_turfs)
@@ -12,9 +33,10 @@
P.damage = p_dam
P.pellets = fragments_per_projectile
P.range_step = p_range
+ if (maim_rate)
+ P.maim_rate = maim_rate
P.range = shard_range
P.name = "shrapnel"
-
P.preparePixelProjectile(T, get_turf(source))
P.firer = source
P.fired_from = source
diff --git a/code/game/objects/items/weapons/material/shards.dm b/code/game/objects/items/weapons/material/shards.dm
index 2303abaf704..94d48999bd5 100644
--- a/code/game/objects/items/weapons/material/shards.dm
+++ b/code/game/objects/items/weapons/material/shards.dm
@@ -106,6 +106,11 @@
/obj/item/material/shard/shrapnel/Initialize(newloc, material_key)
. = ..(loc, MATERIAL_STEEL)
+/obj/item/material/shard/shrapnel/large/Initialize(newloc, material_key) //Shrapnel large enough to be removed by hand. Used by ship weapons.
+ . = ..(loc, MATERIAL_STEEL)
+ icon_state = "large"
+ w_class = WEIGHT_CLASS_NORMAL
+
/obj/item/material/shard/shrapnel/flechette/Initialize(newloc, material_key)
. = ..(loc, MATERIAL_TITANIUM)
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index fb2705f1b10..05ee754b08e 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -95,9 +95,13 @@
else
dismantle_material = get_material()
if(should_use_health && health <= 0)
- build_amt /= rand(2, 4) //if the structure is destroyed by damage, it will yield less materials
- for(var/i = 1 to build_amt)
- dismantle_material.place_sheet(loc)
+ build_amt /= rand(2, 4) //if the structure is destroyed by damage, it will yield less materials.
+ build_amt = max(1, min(build_amt, 5)) //Bound between 5 and 1, as shards don't stack into sheets.
+ for(var/i = 1 to build_amt)
+ dismantle_material.place_shard(loc)
+ else
+ for(var/i = 1 to build_amt)
+ dismantle_material.place_sheet(loc)
qdel(src)
/obj/structure/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
diff --git a/code/game/objects/structures/_machinery.dm b/code/game/objects/structures/_machinery.dm
index f08c063abce..54099d1d51b 100644
--- a/code/game/objects/structures/_machinery.dm
+++ b/code/game/objects/structures/_machinery.dm
@@ -250,6 +250,8 @@ Class Procs:
for(var/i = 1 to 2)
if(prob(50))
metal_to_spawn++
+ else
+ new /obj/item/material/shard(current_turf, DEFAULT_WALL_MATERIAL)
if(metal_to_spawn)
new /obj/item/stack/material/steel(get_turf(src), metal_to_spawn)
if(!should_use_health)
diff --git a/code/game/objects/structures/full_window_frame.dm b/code/game/objects/structures/full_window_frame.dm
index dbe0d2a3ee4..9e4e414d438 100644
--- a/code/game/objects/structures/full_window_frame.dm
+++ b/code/game/objects/structures/full_window_frame.dm
@@ -6,6 +6,7 @@
maxhealth = OBJECT_HEALTH_MEDIUM
color = COLOR_GRAY20
build_amt = 4
+ pass_flags_self = PASSRAILING
layer = WINDOW_FRAME_LAYER
anchored = TRUE
density = TRUE
@@ -59,6 +60,11 @@
return TRUE
return FALSE
+/obj/structure/window_frame/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
+ . = ..()
+ if(. != BULLET_ACT_HIT)
+ return .
+
/obj/structure/window_frame/attackby(obj/item/attacking_item, mob/user)
if((attacking_item.tool_behaviour == TOOL_SCREWDRIVER) && (istype(loc, /turf/simulated) || anchored))
if(has_glass_installed)
diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm
index f02cea9d467..65a82cc687e 100644
--- a/code/game/objects/structures/grille.dm
+++ b/code/game/objects/structures/grille.dm
@@ -159,6 +159,9 @@
var/damage = hitting_projectile.get_structure_damage()
var/passthrough = 0
+ if(piercing_hit)
+ return BULLET_ACT_FORCE_PIERCE //Shots that would have penetrated anyway don't get the damage reduction or chance to not penetrate.
+
if(!damage)
return BULLET_ACT_BLOCK
@@ -181,7 +184,7 @@
passthrough = 1
if(passthrough)
- . = BULLET_ACT_HIT
+ . = BULLET_ACT_FORCE_PIERCE
damage = between(0, (damage - hitting_projectile.damage)*(hitting_projectile.damage_type == DAMAGE_BRUTE? 0.4 : 1), 10) //if the bullet passes through then the grille avoids most of the damage
add_damage(damage * 0.2)
diff --git a/code/game/objects/structures/machinery/computer/buildandrepair.dm b/code/game/objects/structures/machinery/computer/buildandrepair.dm
index 6a394a3ac97..9a863b95b79 100644
--- a/code/game/objects/structures/machinery/computer/buildandrepair.dm
+++ b/code/game/objects/structures/machinery/computer/buildandrepair.dm
@@ -8,6 +8,8 @@
icon_state = "0"
build_amt = 5
var/state = 0
+ maxhealth = OBJECT_HEALTH_MEDIUM
+
var/obj/item/circuitboard/circuit = null
/obj/structure/computerframe/attackby(obj/item/attacking_item, mob/user)
diff --git a/code/game/objects/structures/machinery/doors/door.dm b/code/game/objects/structures/machinery/doors/door.dm
index 74a8e74cbfc..1de42cda807 100644
--- a/code/game/objects/structures/machinery/doors/door.dm
+++ b/code/game/objects/structures/machinery/doors/door.dm
@@ -256,7 +256,7 @@
// Emitter Blasts - these will eventually completely destroy the door, given enough time.
if (damage > 90)
- destroy_hits--
+ destroy_hits -= (1 * hitting_projectile.anti_materiel_potential)
if (destroy_hits <= 0)
visible_message(SPAN_DANGER("\The [src.name] disintegrates!"))
switch (hitting_projectile.damage_type)
diff --git a/code/game/objects/structures/machinery/vending/_vending.dm b/code/game/objects/structures/machinery/vending/_vending.dm
index a91013fcf10..5c6517d5783 100644
--- a/code/game/objects/structures/machinery/vending/_vending.dm
+++ b/code/game/objects/structures/machinery/vending/_vending.dm
@@ -63,6 +63,7 @@
density = 1
clicksound = SFX_BUTTON
manufacturer = "idris"
+ maxhealth = OBJECT_HEALTH_HIGH
// Every vending machine has one of these.
/// `icon_state` when off. Defined on init.
@@ -358,6 +359,24 @@
return
return
+/obj/structure/machinery/vending/on_death(damage, damage_flags, damage_type, armor_penetration, obj/weapon)
+ var/turf/current_turf = get_turf(src)
+ if(current_turf)
+ var/list/stocked_products = list()
+ for(var/datum/data/vending_product/product in product_records)
+ if(product.amount > 0)
+ stocked_products += product
+
+ var/items_to_drop = rand(1, 5)
+ while(items_to_drop-- > 0 && length(stocked_products))
+ var/datum/data/vending_product/product = pick(stocked_products)
+ new product.product_path(current_turf)
+ product.amount--
+ if(product.amount < 1)
+ stocked_products -= product
+
+ . = ..()
+
/obj/structure/machinery/vending/emag_act(var/remaining_charges, var/mob/user)
if (!emagged)
src.emagged = 1
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index ae4bd4de385..2cad8a5c5a4 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -114,7 +114,7 @@
if(. != BULLET_ACT_HIT)
return .
- add_damage(proj_damage)
+ add_damage(proj_damage, hitting_projectile.damage_flags(), hitting_projectile.damage_type, hitting_projectile.armor_penetration, hitting_projectile)
/obj/structure/window/ex_act(severity)
switch(severity)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index abbf39d2328..ab9eff1bfc8 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -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()
diff --git a/code/modules/mob/living/carbon/human/human_powers.dm b/code/modules/mob/living/carbon/human/human_powers.dm
index 8b94f6fe909..d117a220282 100644
--- a/code/modules/mob/living/carbon/human/human_powers.dm
+++ b/code/modules/mob/living/carbon/human/human_powers.dm
@@ -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()
diff --git a/code/modules/mob/living/simple_animal/hostile/adhomai.dm b/code/modules/mob/living/simple_animal/hostile/adhomai.dm
index e49647b5a7d..2e56f87dbf3 100644
--- a/code/modules/mob/living/simple_animal/hostile/adhomai.dm
+++ b/code/modules/mob/living/simple_animal/hostile/adhomai.dm
@@ -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)
diff --git a/code/modules/mob/living/simple_animal/hostile/space_fauna.dm b/code/modules/mob/living/simple_animal/hostile/space_fauna.dm
index ed8cef6a75c..079214b7a7a 100644
--- a/code/modules/mob/living/simple_animal/hostile/space_fauna.dm
+++ b/code/modules/mob/living/simple_animal/hostile/space_fauna.dm
@@ -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
diff --git a/code/modules/organs/organ_external.dm b/code/modules/organs/organ_external.dm
index e75fbc3331c..bab91b6c150 100644
--- a/code/modules/organs/organ_external.dm
+++ b/code/modules/organs/organ_external.dm
@@ -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)
diff --git a/code/modules/overmap/ship_weaponry/_ship_ammunition.dm b/code/modules/overmap/ship_weaponry/_ship_ammunition.dm
index 5487794a2d7..25c7efec515 100644
--- a/code/modules/overmap/ship_weaponry/_ship_ammunition.dm
+++ b/code/modules/overmap/ship_weaponry/_ship_ammunition.dm
@@ -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! (JMP)")
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()
diff --git a/code/modules/overmap/ship_weaponry/projectiles/_overmap_projectiles.dm b/code/modules/overmap/ship_weaponry/projectiles/_overmap_projectiles.dm
index 271d704536c..49d6243b9cc 100644
--- a/code/modules/overmap/ship_weaponry/projectiles/_overmap_projectiles.dm
+++ b/code/modules/overmap/ship_weaponry/projectiles/_overmap_projectiles.dm
@@ -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("A bright star is getting closer from the sky...!"))
- 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]! (JMP)")
- 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)]! (JMP)")
- 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("A bright star is getting closer from the sky...!"))
+ 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]! (JMP)")
+ 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]! (JMP)")
+ 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)]! (JMP)")
+ 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)]! (JMP)")
+ 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)]! (JMP)")
+ 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)]! (JMP)")
+ 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)
diff --git a/code/modules/overmap/ship_weaponry/weaponry/autocannon.dm b/code/modules/overmap/ship_weaponry/weaponry/autocannon.dm
index 89102b085c0..bb0263de5d0 100644
--- a/code/modules/overmap/ship_weaponry/weaponry/autocannon.dm
+++ b/code/modules/overmap/ship_weaponry/weaponry/autocannon.dm
@@ -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.
+ . = ..()
diff --git a/code/modules/overmap/ship_weaponry/weaponry/blaster.dm b/code/modules/overmap/ship_weaponry/weaponry/blaster.dm
index 0cc3a9036f9..1b3895c9daa 100644
--- a/code/modules/overmap/ship_weaponry/weaponry/blaster.dm
+++ b/code/modules/overmap/ship_weaponry/weaponry/blaster.dm
@@ -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("\The [src] disintegrates [M]'s chest and blasts them into pieces!"))
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"
diff --git a/code/modules/overmap/ship_weaponry/weaponry/bruiser.dm b/code/modules/overmap/ship_weaponry/weaponry/bruiser.dm
index 5fe533279db..8768efada21 100644
--- a/code/modules/overmap/ship_weaponry/weaponry/bruiser.dm
+++ b/code/modules/overmap/ship_weaponry/weaponry/bruiser.dm
@@ -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)
. = ..()
diff --git a/code/modules/overmap/ship_weaponry/weaponry/coilgun.dm b/code/modules/overmap/ship_weaponry/weaponry/coilgun.dm
index 0de2ab9bebf..1e5f9f88a9b 100644
--- a/code/modules/overmap/ship_weaponry/weaponry/coilgun.dm
+++ b/code/modules/overmap/ship_weaponry/weaponry/coilgun.dm
@@ -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("\The [src] blows [M]'s chest apart and punches straight through!"))
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"
diff --git a/code/modules/overmap/ship_weaponry/weaponry/francisca.dm b/code/modules/overmap/ship_weaponry/weaponry/francisca.dm
index 128686d24e2..26f8660a9d9 100644
--- a/code/modules/overmap/ship_weaponry/weaponry/francisca.dm
+++ b/code/modules/overmap/ship_weaponry/weaponry/francisca.dm
@@ -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.
. = ..()
diff --git a/code/modules/overmap/ship_weaponry/weaponry/grauwolf.dm b/code/modules/overmap/ship_weaponry/weaponry/grauwolf.dm
index d9d26b16b43..058a128c32e 100644
--- a/code/modules/overmap/ship_weaponry/weaponry/grauwolf.dm
+++ b/code/modules/overmap/ship_weaponry/weaponry/grauwolf.dm
@@ -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)
diff --git a/code/modules/overmap/ship_weaponry/weaponry/lammergeier.dm b/code/modules/overmap/ship_weaponry/weaponry/lammergeier.dm
index 73d858354bf..8ffe1792590 100644
--- a/code/modules/overmap/ship_weaponry/weaponry/lammergeier.dm
+++ b/code/modules/overmap/ship_weaponry/weaponry/lammergeier.dm
@@ -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("\The [src] blows [M]'s chest apart and punches straight through!"))
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"
diff --git a/code/modules/overmap/ship_weaponry/weaponry/leviathan.dm b/code/modules/overmap/ship_weaponry/weaponry/leviathan.dm
index 98dc9afa324..cd8dc95be6a 100644
--- a/code/modules/overmap/ship_weaponry/weaponry/leviathan.dm
+++ b/code/modules/overmap/ship_weaponry/weaponry/leviathan.dm
@@ -159,10 +159,10 @@
M.visible_message(SPAN_DANGER("[M] evaporates as they are engulfed by the beam!"))
M.dust()
return
- explosion(target, 6, 6, 6)
+ explosion(get_turf(target), 6, 6, 6)
else
target.visible_message(SPAN_DANGER("A giant, purple laser descends from the sky!"))
- explosion(target, 30, 30, 30)
+ explosion(get_turf(target), 30, 30, 30)
/obj/structure/machinery/zat_lever
name = "activation lever"
diff --git a/code/modules/overmap/ship_weaponry/weaponry/light_coilgun.dm b/code/modules/overmap/ship_weaponry/weaponry/light_coilgun.dm
index f3d9e570948..e60ba2924c8 100644
--- a/code/modules/overmap/ship_weaponry/weaponry/light_coilgun.dm
+++ b/code/modules/overmap/ship_weaponry/weaponry/light_coilgun.dm
@@ -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("\The [src] blows [M]'s chest apart and punches straight through!"))
if(isturf(target) || isobj(target))
- explosion(target, 2, 3, 4)
+ explosion(epicenter, explosion_strength[1], explosion_strength[2], explosion_strength[3])
diff --git a/code/modules/overmap/ship_weaponry/weaponry/longbow.dm b/code/modules/overmap/ship_weaponry/weaponry/longbow.dm
index 02a7815b8f9..51d469aa739 100644
--- a/code/modules/overmap/ship_weaponry/weaponry/longbow.dm
+++ b/code/modules/overmap/ship_weaponry/weaponry/longbow.dm
@@ -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("\The [src] blows [M] apart and punches straight through!"))
@@ -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("\The [src] punches straight through \the [target]!"))
- explosion(target, 1, 2, 4)
+ explosion(epicenter, 1, 2, 4)
target.ex_act(1)
if(!QDELING(target) && target.density)
qdel(target)
diff --git a/code/modules/overmap/ship_weaponry/weaponry/nephilim.dm b/code/modules/overmap/ship_weaponry/weaponry/nephilim.dm
index f7eab092117..2d910fc41c8 100644
--- a/code/modules/overmap/ship_weaponry/weaponry/nephilim.dm
+++ b/code/modules/overmap/ship_weaponry/weaponry/nephilim.dm
@@ -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])
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index ecddd1499a9..a6399f6af36 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -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"
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 07fe18585a0..8a012fcc23e 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -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
diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm
index 7ce3d1727d5..f0f945fb636 100644
--- a/code/modules/projectiles/projectile/beams.dm
+++ b/code/modules/projectiles/projectile/beams.dm
@@ -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
diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm
index be1db6e3506..4d2a7d12837 100644
--- a/code/modules/projectiles/projectile/bullets.dm
+++ b/code/modules/projectiles/projectile/bullets.dm
@@ -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"
diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/energy.dm
index 091df069b5d..a19652b52be 100644
--- a/code/modules/projectiles/projectile/energy.dm
+++ b/code/modules/projectiles/projectile/energy.dm
@@ -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()
diff --git a/code/modules/projectiles/projectile/special.dm b/code/modules/projectiles/projectile/special.dm
index bab1b6e2fcb..8c9d8bd4c8b 100644
--- a/code/modules/projectiles/projectile/special.dm
+++ b/code/modules/projectiles/projectile/special.dm
@@ -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
..()
diff --git a/code/modules/psionics/abilities/hollow_purple.dm b/code/modules/psionics/abilities/hollow_purple.dm
index 75cf907b339..5bf9f9a8f85 100644
--- a/code/modules/psionics/abilities/hollow_purple.dm
+++ b/code/modules/psionics/abilities/hollow_purple.dm
@@ -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()
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index 7ca35691a6f..20beaa5d926 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -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)
. += ..()
diff --git a/code/modules/shieldgen/_energy_field.dm b/code/modules/shieldgen/_energy_field.dm
index bf74e5c8f50..4040952b431 100644
--- a/code/modules/shieldgen/_energy_field.dm
+++ b/code/modules/shieldgen/_energy_field.dm
@@ -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
diff --git a/code/modules/shieldgen/energy_field.dm b/code/modules/shieldgen/energy_field.dm
index 3c9de1eefaf..574e13da3b8 100644
--- a/code/modules/shieldgen/energy_field.dm
+++ b/code/modules/shieldgen/energy_field.dm
@@ -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.
diff --git a/code/modules/shieldgen/shield_capacitor.dm b/code/modules/shieldgen/shield_capacitor.dm
index 5bbd8d2f3e1..990438e4851 100644
--- a/code/modules/shieldgen/shield_capacitor.dm
+++ b/code/modules/shieldgen/shield_capacitor.dm
@@ -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 capacitors will increase capacity."
+ . += SPAN_NOTICE(" - The current capacity is [max_charge / 1e6] MJ")
+ . += "Upgraded microlasers will increase the charging rate."
+ . += SPAN_NOTICE(" - The current maximum charge rate is [max_charge_rate / 1e6] 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()
diff --git a/code/modules/shieldgen/shield_gen.dm b/code/modules/shieldgen/shield_gen.dm
index 74444065f45..7665d807b47 100644
--- a/code/modules/shieldgen/shield_gen.dm
+++ b/code/modules/shieldgen/shield_gen.dm
@@ -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 manipulators will increase the maximum field strengthen rate."
+ . += SPAN_NOTICE("\t- The current maximum field strengthen rate is [energy_field.max_strengthen_rate] renwicks per second.")
+ . += "Upgraded capacitors will increase the field conversion rate."
+ . += SPAN_NOTICE("\t- The current field conversion efficiency increase is: [(initial(energy_field.energy_conversion_rate) + (2 * capacitor_bonus)) / energy_field.energy_conversion_rate * 100]%")
+ . += "Upgraded micro lasers will decrease the energy field dispersion rate."
+ . += SPAN_NOTICE("\t- The current field dissipation rate decrease is: [(initial(energy_field.dissipation_rate) - (2 * micro_laser_bonus)) / energy_field.dissipation_rate * 100]%")
+
+
/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
diff --git a/code/modules/shieldgen/shield_gen_external.dm b/code/modules/shieldgen/shield_gen_external.dm
index 1467af01a87..b157521d9b9 100644
--- a/code/modules/shieldgen/shield_gen_external.dm
+++ b/code/modules/shieldgen/shield_gen_external.dm
@@ -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()
diff --git a/html/changelogs/Fenodyree-ShipCombatRework.yml b/html/changelogs/Fenodyree-ShipCombatRework.yml
new file mode 100644
index 00000000000..be4dda086d9
--- /dev/null
+++ b/html/changelogs/Fenodyree-ShipCombatRework.yml
@@ -0,0 +1,16 @@
+author: Fenodyree
+
+delete-after: True
+
+changes:
+ - rscadd: "Adds spalling when a projectile punches from a solid turf to an open turf. These are big chunks of shrapnel with low damage and high embed chance."
+ - rscadd: "Adds an admin only gun that fires ship weapon ammo."
+ - rscadd: "Makes several indestructible structures breakable: vending machines, computer frames, window frames and watertanks. They now also leave behind shrapnel instead of just steel sheets."
+ - rscadd: "Adds a negative maim chance, which reduces the chance of decapitation or limb loss."
+ - balance: "Reworks shields, they take more power and are generally weaker. In exchange they now block explosions and each generator can be upgraded with research components, in addition to accepting multiple capacitors."
+ - balance: "All non-exoplosive ship projectiles got buffs. All projectiles got additional anti-material. Armour piercing shells now penetrate many more things."
+ - balance: "Reworked overmap targetting. Shots now spawn on the map edge and aim at their target, instead of spawning near their target and aiming straight forwards."
+ - bugfix: "Fixes explosive projectiles destroying the thing they hit before the explosion can happen, causing them not to explode."
+ - bugfix: "Fixes projectiles not penetrating windows, because they hit the window frame which was indestructible."
+ - bugfix: "Fixes shrapnel getting misnamed, or named twice."
+ - bugfix: "Fixes shields having no click delay when attacked with melee weapons."
diff --git a/tgui/packages/tgui/interfaces/ShieldCapacitor.tsx b/tgui/packages/tgui/interfaces/ShieldCapacitor.tsx
index 5247d06608a..1f969faf5da 100644
--- a/tgui/packages/tgui/interfaces/ShieldCapacitor.tsx
+++ b/tgui/packages/tgui/interfaces/ShieldCapacitor.tsx
@@ -76,7 +76,7 @@ export const CapacitorWindow = (props, context) => {
minValue={0}
maxValue={data.max_charge}
>
- {data.stored_charge} / {data.max_charge} J
+ {data.stored_charge} / {data.max_charge} MJ
@@ -84,10 +84,12 @@ export const CapacitorWindow = (props, context) => {
value={data.charge_rate}
minValue={0}
maxValue={data.max_charge_rate}
- step={10000}
+ step={100}
stepPixelSize={3}
- onDrag={(e, v) => act('charge_rate', { charge_rate: v })}
- unit="W"
+ onDrag={(e, v) =>
+ act('charge_rate', { charge_rate: Math.round(v) })
+ }
+ unit="kW"
/>
diff --git a/tgui/packages/tgui/interfaces/ShieldGenerator.tsx b/tgui/packages/tgui/interfaces/ShieldGenerator.tsx
index 306b6a550eb..deca4f407fa 100644
--- a/tgui/packages/tgui/interfaces/ShieldGenerator.tsx
+++ b/tgui/packages/tgui/interfaces/ShieldGenerator.tsx
@@ -5,6 +5,7 @@ import { Window } from '../layouts';
export type ShieldData = {
owned_capacitor: BooleanLike;
+ owned_capacitor_count: number;
active: BooleanLike;
time_since_fail: number;
multiz: BooleanLike;
@@ -38,9 +39,9 @@ export const ShieldGenerator = (props, context) => {
}
>
-
+
{data.owned_capacitor ? (
- Connected
+ Connected ({data.owned_capacitor_count})
) : (
Not Found
)}
@@ -64,10 +65,10 @@ export const ShieldGenerator = (props, context) => {
{data.average_field} Renwick ({data.progress_field}%)
- {data.power_take} W
+ {data.power_take} kW
- {data.shield_power} W
+ {data.shield_power} kW