Projectile refactoring madness (#19878)

Refactored the projectile code, mostly in line with TG's now.
Refactored various procs that are used or depends on it.
Projectiles can now ricochet if enabled to.
Damage falloffs with distance.
Homing projectiles can now have accuracy falloff with distance.
Projectiles have a maximum range.
Muzzle flash is configurable per projectile.
Impact effect of the projectile is configurable per projectile.
Accuracy decreases with distance.
Projectiles work with signals and emits them, for easy hooking up from
other parts of the code.
Meatshielding is now less effective .
Impact sound is now configurable per projectile.

High risk.
This commit is contained in:
Fluffy
2024-09-23 10:12:57 +00:00
committed by GitHub
parent 914f69dcc8
commit c24b4c7097
203 changed files with 2925 additions and 1590 deletions
+5 -2
View File
@@ -291,8 +291,11 @@
t_air.merge(inside_air)
// When we shoot bubble, make it rip.
/obj/structure/closet/airbubble/bullet_act(var/obj/projectile/Proj)
..()
/obj/structure/closet/airbubble/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
ripped = TRUE
update_icon()
@@ -120,10 +120,13 @@
to_chat(M, SPAN_WARNING("Your chameleon-projector deactivates."))
master.disrupt()
/obj/effect/dummy/chameleon/bullet_act()
/obj/effect/dummy/chameleon/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
for(var/mob/M in src)
to_chat(M, SPAN_WARNING("Your chameleon-projector deactivates."))
..()
master.disrupt()
/obj/effect/dummy/chameleon/relaymove(mob/living/user, direction)
@@ -107,9 +107,12 @@
else
..()
/obj/item/device/magnetic_lock/bullet_act(var/obj/projectile/Proj)
takedamage(Proj.damage)
..()
/obj/item/device/magnetic_lock/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
takedamage(hitting_projectile.damage)
/obj/item/device/magnetic_lock/attackby(obj/item/attacking_item, mob/user)
if (status == STATUS_BROKEN)
+12 -10
View File
@@ -48,15 +48,17 @@
desc = "A shooting target with a threatening silhouette."
hp = 2350 // alium onest too kinda
/obj/item/target/bullet_act(var/obj/projectile/Proj)
var/p_x = Proj.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset Proj.p_x!"
var/p_y = Proj.p_y + pick(0,0,0,0,0,-1,1)
var/decaltype = (Proj.damage_flags & DAMAGE_FLAG_BULLET) ? DECAL_BULLET : DECAL_SCORCH
/obj/item/target/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
SHOULD_CALL_PARENT(FALSE) //Ancient coders deserve the rope
var/p_x = hitting_projectile.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset Proj.p_x!"
var/p_y = hitting_projectile.p_y + pick(0,0,0,0,0,-1,1)
var/decaltype = (hitting_projectile.damage_flags & DAMAGE_FLAG_BULLET) ? DECAL_BULLET : DECAL_SCORCH
virtual_icon = new(icon, icon_state)
if(virtual_icon.GetPixel(p_x, p_y)) // if the located pixel isn't blank (null)
hp -= Proj.damage
hp -= hitting_projectile.damage
if(hp <= 0)
visible_message(SPAN_WARNING("\The [src] breaks into tiny pieces and collapses!"))
qdel(src)
@@ -75,7 +77,7 @@
bmark.pixel_x--
bmark.pixel_y--
if(Proj.damage >= 20 || istype(Proj, /obj/projectile/beam/practice))
if(hitting_projectile.damage >= 20 || istype(hitting_projectile, /obj/projectile/beam/practice))
bmark.icon_state = "scorch"
bmark.set_dir(pick(NORTH,SOUTH,EAST,WEST)) // random scorch design
else
@@ -83,12 +85,12 @@
else // Bullets are hard. They make dents!
bmark.icon_state = "dent"
if(Proj.damage >= 10 && length(bullet_holes) <= 35) // maximum of 35 bullet holes
if(hitting_projectile.damage >= 10 && length(bullet_holes) <= 35) // maximum of 35 bullet holes
if(decaltype == DECAL_BULLET)
if(prob(Proj.damage+30)) // bullets make holes more commonly!
if(prob(hitting_projectile.damage+30)) // bullets make holes more commonly!
new /datum/bullethole(src, bmark.pixel_x, bmark.pixel_y) // create new bullet hole
else // Lasers!
if(prob(Proj.damage-10)) // lasers make holes less commonly
if(prob(hitting_projectile.damage-10)) // lasers make holes less commonly
new /datum/bullethole(src, bmark.pixel_x, bmark.pixel_y) // create new bullet hole
// draw bullet holes
@@ -101,7 +103,7 @@
icon = virtual_icon // apply bullet_holes over decals
return
return PROJECTILE_CONTINUE // the bullet/projectile goes through the target!
return BULLET_ACT_HIT // the bullet/projectile goes through the target!
// Small memory holder entity for transparent bullet holes
+5 -1
View File
@@ -168,7 +168,11 @@
if(prob(50))
qdel(src)
/obj/item/toy/balloon/bullet_act()
/obj/item/toy/balloon/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
burst()
/obj/item/toy/balloon/fire_act(temperature, volume)
@@ -12,20 +12,23 @@
P.damage = p_dam
P.pellets = fragments_per_projectile
P.range_step = p_range
P.shot_from = source
P.range = shard_range
P.name = "shrapnel"
P.launch_projectile(T)
P.preparePixelProjectile(T, get_turf(source))
P.firer = source
P.fired_from = source
P.fire()
if(can_cover)
for(var/mob/living/M in O)
//lying on a frag grenade while the grenade is on the ground causes you to absorb most of the shrapnel.
//you will most likely be dead, but others nearby will be spared the fragments that hit you instead.
if(M.lying && isturf(get_turf(source)))
P.attack_mob(M, 0, 0)
P.process_hit(get_turf(M), M)
else
P.attack_mob(M, 0, 100) //otherwise, allow a decent amount of fragments to pass
if(prob(20))
P.process_hit(get_turf(M), M)
//Fragmentation grenade projectile
/obj/projectile/bullet/pellet/fragment
@@ -38,18 +38,21 @@
P.damage = p_dam
P.balls = fragments_per_projectile
P.range_step = p_range
P.shot_from = source
P.range = shard_range
P.name = "rubber ball"
P.launch_projectile(T)
P.preparePixelProjectile(T, get_turf(source))
P.firer = source
P.fired_from = source
P.fire()
if(can_cover)
for(var/mob/living/M in O)
if(M.lying && isturf(get_turf(source)))
P.attack_mob(M, 0, 0)
P.process_hit(get_turf(M), M)
else
P.attack_mob(M, 0, 100)
if(prob(20))
P.process_hit(get_turf(M), M)
/obj/item/grenade/stinger/prime()
..()
+5 -1
View File
@@ -176,7 +176,11 @@
else if(attacking_item.force > 10 && deployed)
trigger(user)
/obj/item/landmine/bullet_act()
/obj/item/landmine/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
if(deployed)
trigger()
@@ -33,8 +33,8 @@
if(default_parry_check(user, attacker, damage_source) && prob(parry_chance * parry_bonus))
user.visible_message(SPAN_DANGER("\The [user] parries [attack_text] with \the [src]!"))
playsound(user.loc, 'sound/weapons/bladeparry.ogg', 50, 1)
return PROJECTILE_STOPPED
return FALSE
return BULLET_ACT_BLOCK
return BULLET_ACT_HIT
/obj/item/material/sword/perform_technique(var/mob/living/carbon/human/target, var/mob/living/carbon/human/user, var/target_zone)
var/armor_reduction = target.get_blocked_ratio(target_zone, DAMAGE_BRUTE, DAMAGE_FLAG_EDGE|DAMAGE_FLAG_SHARP, damage = force)*100
@@ -97,8 +97,8 @@
if(wielded && default_parry_check(user, attacker, damage_source) && prob(parry_chance))
user.visible_message(SPAN_DANGER("\The [user] parries [attack_text] with \the [src]!"))
playsound(user.loc, /singleton/sound_category/punchmiss_sound, 50, 1)
return PROJECTILE_STOPPED
return FALSE
return BULLET_ACT_BLOCK
return BULLET_ACT_HIT
/obj/item/material/twohanded/update_icon()
icon_state = "[base_icon][wielded]"
@@ -67,13 +67,13 @@
user.visible_message(SPAN_DANGER("\The [user] parries [attack_text] with \the [src]!"))
spark(src, 5)
playsound(user.loc, 'sound/weapons/blade.ogg', 50, 1)
return PROJECTILE_STOPPED
return BULLET_ACT_BLOCK
else
if(!active)
return FALSE //turn it on first!
return BULLET_ACT_HIT //turn it on first!
if(user.incapacitated())
return FALSE
return BULLET_ACT_HIT
//block as long as they are not directly behind us
var/bad_arc = reverse_direction(user.dir) //arc of directions from which we cannot block
@@ -87,7 +87,7 @@
if(shield_power <= 0)
to_chat(user, SPAN_DANGER("\The [src]'s integrated shield goes out! It will no longer assist in parrying."))
shield_power = 0
return FALSE
return BULLET_ACT_HIT
if(istype(damage_source, /obj/projectile/energy) || istype(damage_source, /obj/projectile/beam))
var/obj/projectile/P = damage_source
@@ -106,10 +106,10 @@
P.firer = user
P.old_style_target(locate(new_x, new_y, P.z))
return PROJECTILE_CONTINUE // complete projectile permutation
return BULLET_ACT_FORCE_PIERCE // complete projectile permutation
else
user.visible_message(SPAN_DANGER("\The [user] blocks [attack_text] with \the [src]!"))
return PROJECTILE_STOPPED
return BULLET_ACT_BLOCK
else if(istype(damage_source, /obj/projectile/bullet) && can_block_bullets)
var/reflectchance = (base_reflectchance) - round(damage/3)
@@ -117,7 +117,7 @@
reflectchance /= 2
if(prob(reflectchance))
user.visible_message(SPAN_DANGER("\The [user] blocks [attack_text] with \the [src]!"))
return PROJECTILE_STOPPED
return BULLET_ACT_BLOCK
/obj/item/melee/energy/get_print_info()
. = ..()
+14 -14
View File
@@ -43,12 +43,12 @@
var/shield_dir = on_back ? user.dir : GLOB.reverse_dir[user.dir]
if(user.incapacitated() || !(check_shield_arc(user, shield_dir, damage_source, attacker)))
return FALSE
return BULLET_ACT_HIT
if(prob(get_block_chance(user, damage, damage_source, attacker)))
user.visible_message(SPAN_DANGER("\The [user] blocks [attack_text] with \the [src]!"))
return PROJECTILE_STOPPED
return FALSE
return BULLET_ACT_BLOCK
return BULLET_ACT_HIT
/obj/item/shield/can_shield_back()
return TRUE
@@ -74,7 +74,8 @@
/obj/item/shield/riot/handle_shield(mob/user)
. = ..()
if(.) playsound(user.loc, 'sound/weapons/Genhit.ogg', 50, 1)
if(. != BULLET_ACT_HIT)
playsound(user.loc, 'sound/weapons/Genhit.ogg', 50, 1)
/obj/item/shield/riot/get_block_chance(mob/user, var/damage, atom/damage_source = null, mob/attacker = null)
if(istype(damage_source, /obj/projectile))
@@ -113,7 +114,8 @@
/obj/item/shield/buckler/handle_shield(mob/user)
. = ..()
if(.) playsound(user.loc, 'sound/weapons/Genhit.ogg', 50, 1)
if(. != BULLET_ACT_HIT)
playsound(user.loc, 'sound/weapons/Genhit.ogg', 50, 1)
/obj/item/shield/buckler/get_block_chance(mob/user, var/damage, atom/damage_source = null, mob/attacker = null)
if(istype(damage_source, /obj/projectile))
@@ -178,9 +180,7 @@
var/shield_dir = on_back ? user.dir : GLOB.reverse_dir[user.dir]
if(!active || user.incapacitated() || !(check_shield_arc(user, shield_dir, damage_source, attacker)))
return FALSE
if(.)
spark(user.loc, 5)
return BULLET_ACT_HIT
if(prob(get_block_chance(user, damage, damage_source, attacker)))
spark(user.loc, 5)
@@ -214,13 +214,13 @@
new_angle_s -= 360
P.set_angle(new_angle_s)
return PROJECTILE_CONTINUE // complete projectile permutation
return BULLET_ACT_FORCE_PIERCE // complete projectile permutation
else
user.visible_message(SPAN_DANGER("\The [user] blocks [attack_text] with \the [src]!"))
return PROJECTILE_STOPPED
return BULLET_ACT_BLOCK
else
user.visible_message(SPAN_DANGER("\The [user] blocks [attack_text] with \the [src]!"))
return PROJECTILE_STOPPED
return BULLET_ACT_BLOCK
/obj/item/shield/energy/get_block_chance(mob/user, damage, atom/damage_source = null, mob/attacker = null)
@@ -323,11 +323,11 @@
/obj/item/shield/riot/tact/handle_shield(mob/user)
if(!active)
return FALSE //turn it on first!
return BULLET_ACT_HIT //turn it on first!
. = ..()
if(.)
if(.) playsound(user.loc, 'sound/weapons/Genhit.ogg', 50, 1)
if(. != BULLET_ACT_HIT)
playsound(user.loc, 'sound/weapons/Genhit.ogg', 50, 1)
/obj/item/shield/riot/tact/attack_self(mob/living/user)
active = !active
+10 -2
View File
@@ -582,10 +582,14 @@
new /obj/item/stack/material/steel(get_turf(src))
qdel(src)
/obj/item/trap/animal/bullet_act(var/obj/projectile/Proj)
/obj/item/trap/animal/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
var/mob/living/captured_mob = captured ? captured.resolve() : null
if(captured_mob)
captured_mob.bullet_act(Proj)
captured_mob.bullet_act(arglist(args))
/obj/item/trap/animal/proc/release(var/mob/user, var/turf/target)
if(!target)
@@ -846,6 +850,10 @@
/obj/item/trap/animal/large/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(deployed)
return TRUE
if(mover?.movement_type & PHASING)
return TRUE
return FALSE
/obj/item/large_trap_foundation
+7 -4
View File
@@ -12,7 +12,7 @@
if(!QDELETED(src))
QDEL_IN(src, 1)
/obj/item/energy_net/throw_impact(atom/hit_atom)
/obj/item/energy_net/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
..()
var/mob/living/M = hit_atom
@@ -74,10 +74,13 @@
qdel(src)
return
/obj/effect/energy_net/bullet_act(var/obj/projectile/Proj)
health -= Proj.get_structure_damage()
/obj/effect/energy_net/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
health -= hitting_projectile.get_structure_damage()
health_check()
return FALSE
/obj/effect/energy_net/ex_act(var/severity = 2.0)
health = 0