Files
FenodyreeAv cf585ef505 Ship combat rework (#22566)
Makes ship combat significantly more destructive (and a bit deadlier),
especially with non-explosive projectiles.

**Additions:**
- A shell that penetrates a solid (dense) structure will cause spalling.
This spall does 2-30 damage, depending on range and has a high embed
chance. These embedded chunks are large enough to be ripped out by hand.
(But you probably shouldn't). The pilot's suit has 30 ballistic armor,
so a prepared bridge crew can survive a lot of spall.
- Adds a gun that shoots any ship weapon that admins can spawn. Useful
for testing, or for adminbus where they don't want to have to spawn a
whole overmap ship.
- Made several structures destructible, vending machines, computer
frames, grilles (these were just bugged) and watertanks (and their
children). These structures could just block infinite shots and were
common enough to serve as effective armor for the horizon. These now
spawn shrapnel (of their material if it's set), in addition to sheets of
steel when destroyed.
- Added support for negative maim chance. This allows chosen projectiles
to be prevented from decapitating. No more accidentally gibbing the
bridge crew's heads. (Unless they get hit with something really really
big).

**Balance:**
- All non-explosive anti ship rounds got significant buffs. Typically
2-3x Anti-Material numbers.
- Armour piercing anti-ship rounds got massively increased penetration
stats. Typically 4-8 from 1-2.
- Doors now get destroyed in less hits, if they are hit by anti-material
projectiles. Hits to destroy -= Antimaterial / 2
- Francisca frag shells now shoot less fragments but deal more damage.
394 projectiles for every shot was a bit laggy, now it's only 280.

**Shields:**
Shields now take more power. 13-14 megawatts for a 10 strength shield.
Just inside the ship's generation capacity with both the INDRA and
Supermatter running.

Instead of deleting any projectile they are hit by, shields now have
different failure modes.
- Firstly, a piercing projectile has it's penetration reduced by the
shield strength. Eg A shot that would go through 10 walls, only
penetrates 4 after going through a 6 strength shield.
- Secondly, a projectile has it's damage multiplied by 1 - shield
strength / 10, then has shield strength subtracted from it's damage. Eg.
A damage 100 projectile, hitting a 5 strength shield, has it's damaged
reduced to 50, (100 * 0.5), then to 45. (50-5)
- Thirdly, an explosive projectile (that does not penetrate per the
first step) does not explode, but instead deals it's full damage to the
shield.

Hits to the shield now also damage the field itself, not just the shield
tile. As such, a very big hit on the shield can bring the whole thing
down, requiring an engineer to reset it.

Shields can now be upgraded with research components. Additionally,
shield generators can now support more than one capacitor. The
roundstart capacitor on the Horizon is only good enough to support a 6
strength shield.
The upgrades increase efficiency, strength loss over time and charge
speed.

**Overmap Targetting**
Previously, shells would spawn within 20 tiles of their target and aim
directly forwards. This was bugged for shots with burst, which would
instead all aim inwards and mostly miss. This is the reason the Grauwolf
(in addition to the explosion bug below) and Fransisca felt so useless,
only one of every shell in a burst would hit.

Shots now spawn at the map edge, based on the orientation of the
projectile and ship. Diagonals are now accounted for.
Shots are then aimed directly at the target selected on the console,
with 3 degrees of dispersion.

**Bugfixes:**
- Fixes lots of explosions that didn't pass a turf to the explosions
subsystem. This caused the explosion not to happen if the thing hit got
qdeled before the subsystem could get the turf to spawn the explosion
on.
- Embedded shrapnel no longer gets the initial projectile name, this
prevents it getting named shrapnelshrapnel.
- Fixes a bunch of projectile piercing bugs, mostly with grilles and
windows. Shots now go past window frames without hitting them.
- Fixes shields not having a click delay when attacked.

---------

Signed-off-by: FenodyreeAv <fenodyree.av@gmail.com>
Co-authored-by: Matt Atlas <mattiathebest2000@hotmail.it>
2026-06-03 16:52:13 +00:00

538 lines
15 KiB
Plaintext

/obj/projectile/beam
name = "laser"
icon_state = "laser"
ping_effect = "ping_s"
pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSRAILING
damage = 30
armor_penetration = 10
damage_type = DAMAGE_BURN
impact_sounds = list(BULLET_IMPACT_MEAT = SOUNDS_LASER_MEAT, BULLET_IMPACT_METAL = SOUNDS_LASER_METAL)
check_armor = LASER
eyeblur = 4
damage_flags = DAMAGE_FLAG_LASER
var/frequency = 1
hitscan = 1
invisibility = 101 //beam projectiles are invisible as they are rendered by the effect engine
// color = COLOR_RED
muzzle_type = /obj/effect/projectile/muzzle/laser
tracer_type = /obj/effect/projectile/tracer/laser
impact_type = /obj/effect/projectile/impact/laser
/obj/projectile/beam/practice
name = "laser"
damage = 5
armor_penetration = 0
damage_type = DAMAGE_PAIN
eyeblur = 0
/obj/projectile/beam/pistol
damage = 25
armor_penetration = 5
/obj/projectile/beam/pistol/scc
armor_penetration = 15
muzzle_type = /obj/effect/projectile/muzzle/laser/scc
tracer_type = /obj/effect/projectile/tracer/laser/scc
impact_type = /obj/effect/projectile/impact/laser/scc
/obj/projectile/beam/pistol/scc/weak
damage = 20
armor_penetration = 10
muzzle_type = /obj/effect/projectile/muzzle/laser/scc
tracer_type = /obj/effect/projectile/tracer/laser/scc
impact_type = /obj/effect/projectile/impact/laser/scc
/obj/projectile/beam/pistol/hegemony
icon = 'icons/obj/guns/faction/izweski_hegemony/hegemony_pistol.dmi'
icon_state = "hegemony_pistol"
damage = 30
muzzle_type = /obj/effect/projectile/muzzle/hegemony
tracer_type = /obj/effect/projectile/tracer/hegemony
impact_type = /obj/effect/projectile/impact/hegemony
/obj/projectile/beam/midlaser
damage = 30
armor_penetration = 20
/obj/projectile/beam/midlaser/skrell
armor_penetration = 0
/obj/projectile/beam/midlaser/skrell/heavy
damage = 40
armor_penetration = 20
/obj/projectile/beam/midlaser/hegemony
armor_penetration = 30
muzzle_type = /obj/effect/projectile/muzzle/hegemony
tracer_type = /obj/effect/projectile/tracer/hegemony
impact_type = /obj/effect/projectile/impact/hegemony
/obj/projectile/beam/noctiluca
damage = 20
armor_penetration = 40
/obj/projectile/beam/noctiluca/armor_piercing
name = "concentrated laser"
damage = 20
armor_penetration = 50
muzzle_type = /obj/effect/projectile/muzzle/laser/scc
tracer_type = /obj/effect/projectile/tracer/laser/scc
impact_type = /obj/effect/projectile/impact/laser/scc
/obj/projectile/beam/midlaser/ice
damage = 25
armor_penetration = 10
/obj/projectile/beam/heavylaser
name = "heavy laser"
icon_state = "heavylaser"
damage = 45
armor_penetration = 25
muzzle_type = /obj/effect/projectile/muzzle/heavy_laser
tracer_type = /obj/effect/projectile/tracer/heavy_laser
impact_type = /obj/effect/projectile/impact/heavy_laser
/obj/projectile/beam/heavylaser/mech
damage = 35
armor_penetration = 35
/obj/projectile/beam/xray
name = "xray beam"
icon_state = "xray"
damage = 15
armor_penetration = 50
muzzle_type = /obj/effect/projectile/muzzle/xray
tracer_type = /obj/effect/projectile/tracer/xray
impact_type = /obj/effect/projectile/impact/xray
/obj/projectile/beam/xray/mech
damage = 40
armor_penetration = 75
/obj/projectile/beam/pulse
name = "pulse"
icon_state = "u_laser"
damage = 50
armor_penetration = 50
muzzle_type = /obj/effect/projectile/muzzle/pulse
tracer_type = /obj/effect/projectile/tracer/pulse
impact_type = /obj/effect/projectile/impact/pulse
/obj/projectile/beam/pulse/mech
damage = 45
armor_penetration = 40
/obj/projectile/beam/pulse/on_hit(atom/target, blocked, def_zone)
if(isturf(target))
target.ex_act(2)
. = ..()
/obj/projectile/beam/pulse/heavy
name = "heavy pulse laser"
icon_state = "pulse1_bl"
var/life = 20
/obj/projectile/beam/pulse/heavy/Collide(atom/A)
A.bullet_act(src, def_zone)
src.life -= 10
if(life <= 0)
qdel(src)
/obj/projectile/beam/emitter
name = "emitter beam"
icon_state = "emitter"
damage = 0 // The actual damage is computed in /code/modules/power/singularity/emitter.dm
color = COLOR_SPRING_GREEN
muzzle_type = /obj/effect/projectile/muzzle/emitter
tracer_type = /obj/effect/projectile/tracer/emitter
impact_type = /obj/effect/projectile/impact/emitter
/obj/projectile/beam/laser_tag
name = "lasertag beam"
icon_state = "laser"
pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSRAILING
damage = 0
damage_type = DAMAGE_BURN
check_armor = LASER
var/laser_tag_color = "red"
muzzle_type = /obj/effect/projectile/muzzle/laser
tracer_type = /obj/effect/projectile/tracer/laser
impact_type = /obj/effect/projectile/impact/laser
/obj/projectile/beam/laser_tag/on_hit(atom/target, blocked, def_zone)
. = ..()
if(ishuman(target))
var/mob/living/carbon/human/H = target
var/obj/item/clothing/suit/armor/riot/laser_tag/LT = H.wear_suit
if(istype(LT) && laser_tag_color != LT.laser_tag_color)
LT.laser_hit()
return TRUE
/obj/projectile/beam/laser_tag/blue
name = "lasertag beam"
icon_state = "bluelaser"
laser_tag_color = "blue"
muzzle_type = /obj/effect/projectile/muzzle/laser/blue
tracer_type = /obj/effect/projectile/tracer/laser/blue
impact_type = /obj/effect/projectile/impact/laser/blue
/obj/projectile/beam/laser_tag/omni//A laser tag bolt that stuns EVERYONE
name = "lasertag beam"
icon_state = "omnilaser"
laser_tag_color = "omni"
muzzle_type = /obj/effect/projectile/muzzle/disabler
tracer_type = /obj/effect/projectile/tracer/disabler
impact_type = /obj/effect/projectile/impact/disabler
/obj/projectile/beam/sniper
name = "sniper beam"
icon_state = "xray"
damage = 50
armor_penetration = 20
stun = 3
weaken = 3
stutter = 3
muzzle_type = /obj/effect/projectile/muzzle/xray
tracer_type = /obj/effect/projectile/tracer/xray
impact_type = /obj/effect/projectile/impact/xray
/obj/projectile/beam/stun
name = "stun beam"
icon_state = "stun"
damage = 1
sharp = FALSE
eyeblur = 1
agony = 45
damage_type = DAMAGE_BURN
muzzle_type = /obj/effect/projectile/muzzle/stun
tracer_type = /obj/effect/projectile/tracer/stun
impact_type = /obj/effect/projectile/impact/stun
/obj/projectile/beam/disorient
name = "disorienting pulse"
icon_state = "stun"
damage = 1
sharp = FALSE
eyeblur = 1
agony = 30
damage_type = DAMAGE_BURN
muzzle_type = /obj/effect/projectile/muzzle/disabler
tracer_type = /obj/effect/projectile/tracer/disabler
impact_type = /obj/effect/projectile/impact/disabler
/obj/projectile/beam/disorient/on_hit(atom/target, blocked, def_zone)
if(ishuman(target) && blocked < 100 && !issilicon(target) && !isipc(target)) //Make them trip
var/mob/living/carbon/human/H = target
H.druggy = min(H.druggy + 15, 75)
//do some maths to determine amount of dizzy to add
var/makeDizzy = 250 - H.dizziness
makeDizzy = min(makeDizzy, 50) //This should make it go back to 50
H.make_dizzy(makeDizzy)
H.confused = 5
H.slurring = min(H.slurring + 15, 75)
. = ..()
/obj/projectile/beam/gatlinglaser
name = "diffused laser"
icon_state = "heavylaser"
damage = 20
armor_penetration = 35
muzzle_type = /obj/effect/projectile/muzzle/disabler
tracer_type = /obj/effect/projectile/tracer/disabler
impact_type = /obj/effect/projectile/impact/disabler
/obj/projectile/beam/mousegun
name = "electrical arc"
icon_state = "stun"
damage = 1
damage_type = DAMAGE_BURN
muzzle_type = /obj/effect/projectile/muzzle/stun
tracer_type = /obj/effect/projectile/tracer/stun
impact_type = /obj/effect/projectile/impact/stun
/obj/projectile/beam/mousegun/on_hit(atom/target, blocked, def_zone)
mousepulse(target, 1)
. = ..()
/obj/projectile/beam/mousegun/proc/mousepulse(turf/epicenter, range, log=0)
if(!epicenter)
return
if(!istype(epicenter, /turf))
epicenter = get_turf(epicenter.loc)
for(var/mob/living/M in range(range, epicenter))
var/distance = get_dist(epicenter, M)
if(distance < 0)
distance = 0
if(distance <= range)
if (M.mob_size <= 3 && (M.find_type() & TYPE_ORGANIC))
M.visible_message(SPAN_DANGER("\The [M] gets fried!"))
M.color = "#4d4d4d" //get fried
M.death()
spark(M, 3, GLOB.alldirs)
else if(iscarbon(M) && M.contents.len)
for(var/obj/item/holder/H in M.contents)
if(!H.contained)
continue
var/mob/living/A = H.contained
if(!istype(A))
continue
if(A.mob_size <= 3 && (A.find_type() & TYPE_ORGANIC))
H.release_mob()
A.visible_message(SPAN_DANGER("\The [A] gets fried!"))
A.color = "#4d4d4d" //get fried
A.death()
to_chat(M, 'sound/effects/basscannon.ogg')
return TRUE
/obj/projectile/beam/mousegun/emag
name = "diffuse electrical arc"
damage_type = DAMAGE_BURN
damage = 15
agony = 30
/obj/projectile/beam/mousegun/emag/mousepulse(turf/epicenter, range, log=0)
if(!epicenter)
return
if(!istype(epicenter, /turf))
epicenter = get_turf(epicenter.loc)
for(var/mob/living/M in range(range, epicenter))
var/distance = get_dist(epicenter, M)
if(distance < 0)
distance = 0
if(distance <= range)
if(M.mob_size <= 4 && (M.find_type() & TYPE_ORGANIC))
M.visible_message(SPAN_DANGER("[M] bursts like a balloon!"))
M.gib()
spark(M, 3, GLOB.alldirs)
else if(iscarbon(M) && M.contents.len)
for(var/obj/item/holder/H in M.contents)
if(!H.contained)
continue
var/mob/living/A = H.contained
if(!istype(A))
continue
if(A.mob_size <= 4 && (A.find_type() & TYPE_ORGANIC))
H.release_mob()
A.visible_message(SPAN_DANGER("[A] bursts like a balloon!"))
A.gib()
to_chat(M, 'sound/effects/basscannon.ogg')
return TRUE
/obj/projectile/beam/mousegun/xenofauna
damage = 10
/obj/projectile/beam/mousegun/xenofauna/mousepulse(atom/target, range, log)
if(is_type_in_list(target, list(/mob/living/simple_animal/hostile/retaliate/cavern_dweller, /mob/living/simple_animal/hostile/carp, /mob/living/simple_animal/carp, /mob/living/simple_animal/hostile/giant_spider)))
var/mob/living/simple_animal/SA = target
SA.take_organ_damage(0, 20)
return TRUE
/obj/projectile/beam/mousegun/xenofauna_holo
damage = 0
/obj/projectile/beam/mousegun/xenofauna_holo/on_hit(atom/target, blocked, def_zone)
. = ..()
if(istype(target, /mob/living/simple_animal/hostile/carp/holodeck))
var/mob/living/simple_animal/hostile/carp/holodeck/C = target
C.take_organ_damage(15)
return TRUE
/obj/projectile/beam/shotgun
name = "diffuse laser"
icon_state = "laser"
pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSRAILING
damage = 20
eyeblur = 4
/obj/projectile/beam/megaglaive
name = "thermal lance"
icon_state = "gauss"
damage = 10
incinerate = 2
armor_penetration = 10
muzzle_type = /obj/effect/projectile/muzzle/solar
tracer_type = /obj/effect/projectile/tracer/solar
impact_type = /obj/effect/projectile/impact/solar
/obj/projectile/beam/megaglaive/on_hit(atom/target, blocked, def_zone)
if(isturf(target))
if(istype(target, /turf/simulated/mineral))
if(prob(75)) //likely because its a mining tool
var/turf/simulated/mineral/M = target
if(prob(10))
M.GetDrilled(1)
else if(!M.emitter_blasts_taken)
M.emitter_blasts_taken += 1
else if(prob(33))
M.emitter_blasts_taken += 1
explosion(get_turf(target), -1, 0, 2)
. = ..()
/obj/projectile/beam/thermaldrill
name = "thermal drill"
icon_state = "gauss"
damage = 15
muzzle_type = /obj/effect/projectile/muzzle/solar
tracer_type = /obj/effect/projectile/tracer/solar
impact_type = /obj/effect/projectile/impact/solar
/obj/projectile/beam/thermaldrill/on_hit(atom/target, blocked, def_zone)
if(istype(target, /turf/simulated/mineral))
var/turf/simulated/mineral/mineral = target
mineral.GetDrilled(TRUE)
return ..()
//Beams of magical veil energy fired by empowered pylons. Some inbuilt armor penetration cuz magic.
//Ablative armor is still overwhelmingly useful
//These beams are very weak but rapid firing, ~twice per second.
/obj/projectile/beam/cult
name = "energy bolt"
//For projectiles name is only shown in onhit messages, so its more of a layman's description
//of what the projectile looks like
damage = 3.5 //Very weak
accuracy = 4 //Guided by magic, unlikely to miss
eyeblur = 0 //Not bright or blinding
var/mob/living/ignore
muzzle_type = /obj/effect/projectile/muzzle/cult
tracer_type = /obj/effect/projectile/tracer/cult
impact_type = /obj/effect/projectile/impact/cult
/obj/projectile/beam/cult/on_hit(atom/target, blocked, def_zone)
//Harmlessly passes through cultists and constructs
if (target == ignore)
return 0
if (iscultist(target))
return 0
return ..()
/obj/projectile/beam/cult/heavy
name = "glowing energy bolt"
damage = 10 //Stronger and better armor penetration, though still much weaker than a typical laser
armor_penetration = 10
muzzle_type = /obj/effect/projectile/muzzle/cult/heavy
tracer_type = /obj/effect/projectile/tracer/cult/heavy
impact_type = /obj/effect/projectile/impact/cult/heavy
/obj/projectile/beam/energy_net
name = "energy net projection"
icon_state = "xray"
damage = 0
damage_type = DAMAGE_PAIN
muzzle_type = /obj/effect/projectile/muzzle/xray
tracer_type = /obj/effect/projectile/tracer/xray
impact_type = /obj/effect/projectile/impact/xray
/obj/projectile/beam/energy_net/on_hit(atom/target, blocked, def_zone)
do_net(target)
. = ..()
/obj/projectile/beam/energy_net/proc/do_net(var/mob/M)
var/obj/item/energy_net/net = new (get_turf(M))
net.throw_impact(M)
/obj/projectile/beam/tachyon
name = "particle beam"
icon_state = "xray"
damage = 25
armor_penetration = 65
penetrating = 1
maim_rate = 5
muzzle_type = /obj/effect/projectile/muzzle/tachyon
tracer_type = /obj/effect/projectile/tracer/tachyon
impact_type = /obj/effect/projectile/impact/tachyon
/obj/projectile/beam/tesla
name = "tesla bolt"
icon_state = "lightning"
damage = 10
damage_type = DAMAGE_BURN
pass_flags = PASSTABLE | PASSGRILLE | PASSRAILING
range = 40
eyeblur = 0
muzzle_type = /obj/effect/projectile/muzzle/tesla
tracer_type = /obj/effect/projectile/tracer/tesla
impact_type = /obj/effect/projectile/impact/tesla
/obj/projectile/beam/tesla/on_hit(atom/target, blocked, def_zone)
. = ..()
if(isliving(target))
tesla_zap(target, 5, 5000)
/obj/projectile/beam/freezer
name = "freezing ray"
icon_state = "bluelaser"
pass_flags = PASSTABLE | PASSRAILING
damage = 15
damage_type = DAMAGE_BURN
check_armor = ENERGY
var/temperature_damage = 40
muzzle_type = /obj/effect/projectile/muzzle/laser/blue
tracer_type = /obj/effect/projectile/tracer/laser/blue
impact_type = /obj/effect/projectile/impact/laser/blue
/obj/projectile/beam/freezer/on_hit(atom/target, blocked, def_zone)
. = ..()
if(isliving(target))
var/mob/living/L = target
L.bodytemperature -= temperature_damage
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H.bodytemperature <= H.species.cold_level_2)
new /obj/structure/closet/statue/ice(H.loc, H)
H.visible_message(SPAN_WARNING("\The [H] freezes!"))
/obj/projectile/beam/stun/skrell
name = "particle stun beam"
icon_state = "beam_omni"
agony = 50
muzzle_type = /obj/effect/projectile/muzzle/disabler
tracer_type = /obj/effect/projectile/tracer/disabler
impact_type = /obj/effect/projectile/impact/disabler
/obj/projectile/beam/pulse/skrell
name = "particle lethal beam"