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
+9 -3
View File
@@ -6,6 +6,15 @@ They should also be used for when you want to effect the ENTIRE mob, like having
/obj/aura
var/mob/living/user
/obj/aura/Initialize(mapload, ...)
. = ..()
RegisterSignal(src, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(handle_bullet_act))
/obj/aura/proc/handle_bullet_act(datum/source, obj/projectile/projectile)
SIGNAL_HANDLER
return COMPONENT_BULLET_BLOCKED
/obj/aura/Destroy()
if(user)
user.remove_aura(src)
@@ -24,8 +33,5 @@ They should also be used for when you want to effect the ENTIRE mob, like having
/obj/aura/attackby(obj/item/attacking_item, mob/user)
return FALSE
/obj/aura/bullet_act(obj/projectile/P, def_zone)
return FALSE
/obj/aura/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
return FALSE
+12 -2
View File
@@ -3,13 +3,23 @@
icon = 'icons/effects/effects.dmi'
icon_state = "shield"
/obj/aura/personal_shield/handle_bullet_act(datum/source, obj/projectile/projectile)
user.visible_message(SPAN_WARNING("\The [user]'s [src.name] flashes before \the [projectile] can hit them!"))
flick("shield_impact", src)
playsound(user, 'sound/effects/basscannon.ogg', 35, TRUE)
return COMPONENT_BULLET_BLOCKED
/obj/aura/personal_shield/added_to(mob/living/L)
..()
playsound(user, 'sound/weapons/flash.ogg', 35, 1)
to_chat(user, SPAN_NOTICE("You feel your body prickle as \the [src] comes online."))
/obj/aura/personal_shield/bullet_act(obj/projectile/P, var/def_zone)
user.visible_message(SPAN_WARNING("\The [user]'s [src.name] flashes before \the [P] can hit them!"))
/obj/aura/personal_shield/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
SHOULD_CALL_PARENT(FALSE) //Ancient coders deserve the rope
user.visible_message(SPAN_WARNING("\The [user]'s [src.name] flashes before \the [hitting_projectile] can hit them!"))
flick("shield_impact", src)
playsound(user, 'sound/effects/basscannon.ogg', 35, TRUE)
+10 -3
View File
@@ -5,6 +5,11 @@
alpha = 75
layer = ABOVE_WINDOW_LAYER
/obj/aura/radiant_aura/handle_bullet_act(datum/source, obj/projectile/projectile)
if(projectile.check_armor == LASER)
user.visible_message(SPAN_WARNING("\The [projectile] refracts, bending into \the [user]'s aura."))
return COMPONENT_BULLET_BLOCKED
/obj/aura/radiant_aura/added_to(mob/living/user)
..()
to_chat(user, SPAN_NOTICE("A bubble of light appears around you, exuding protection and warmth."))
@@ -14,8 +19,10 @@
to_chat(user, SPAN_WARNING("Your protective aura dissipates, leaving you feeling cold and unsafe."))
return ..()
/obj/aura/radiant_aura/bullet_act(obj/projectile/P, var/def_zone)
if(P.check_armor == LASER)
user.visible_message(SPAN_WARNING("\The [P] refracts, bending into \the [user]'s aura."))
/obj/aura/radiant_aura/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
SHOULD_CALL_PARENT(FALSE) //Ancient coders deserve the rope
if(hitting_projectile.check_armor == LASER)
user.visible_message(SPAN_WARNING("\The [hitting_projectile] refracts, bending into \the [user]'s aura."))
return AURA_FALSE
return FALSE
+5 -1
View File
@@ -170,7 +170,11 @@
/obj/structure/foamedmetal/ex_act(severity)
qdel(src)
/obj/structure/foamedmetal/bullet_act()
/obj/structure/foamedmetal/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
if(metal == 1 || prob(50))
qdel(src)
+8 -2
View File
@@ -184,11 +184,17 @@ steam.start() -- spawns the effect
M.coughedtime = 0
/obj/effect/effect/smoke/bad/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(air_group || (height==0)) return 1
if(air_group || (height==0))
return TRUE
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover, /obj/projectile/beam))
var/obj/projectile/beam/B = mover
B.damage = (B.damage/2)
return 1
return TRUE
/////////////////////////////////////////////
// Sleep smoke
/////////////////////////////////////////////
@@ -9,7 +9,7 @@
light_range = 2
light_color = "#00ffff"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
appearance_flags = 0
appearance_flags = LONG_GLIDE
/obj/effect/projectile/singularity_pull()
return
@@ -40,7 +40,7 @@
apply_vars(angle_override, p_x, p_y, color_override, scaling)
return ..()
/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, new_loc, increment = 0)
/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, atom/new_loc, increment = 0)
var/mutable_appearance/look = new(src)
look.pixel_x = p_x
look.pixel_y = p_y
@@ -49,8 +49,16 @@
appearance = look
scale_to(1,scaling, FALSE)
turn_to(angle_override, FALSE)
if(!isnull(new_loc)) //If you want to null it just delete it...
if(!isnull(new_loc)) //If you want to null it just delete it...
forceMove(new_loc)
for(var/i in 1 to increment)
pixel_x += round((sin(angle_override)+16*sin(angle_override)*2), 1)
pixel_y += round((cos(angle_override)+16*cos(angle_override)*2), 1)
/obj/effect/projectile_lighting
var/owner
/obj/effect/projectile_lighting/Initialize(mapload, color, range, intensity, owner_key)
. = ..()
set_light(range, intensity, color)
owner = owner_key
@@ -1,12 +1,22 @@
/proc/generate_tracer_between_points(datum/point/starting, datum/point/ending, beam_type, color, qdel_in = 5) //Do not pass z-crossing points as that will not be properly (and likely will never be properly until it's absolutely needed) supported!
/proc/generate_tracer_between_points(datum/point/starting, datum/point/ending, beam_type, color, qdel_in = 5, light_range = 2, light_color_override, light_intensity = 1, instance_key) //Do not pass z-crossing points as that will not be properly (and likely will never be properly until it's absolutely needed) supported!
if(!istype(starting) || !istype(ending) || !ispath(beam_type))
return
if(starting.z != ending.z)
crash_with("Projectile tracer generation of cross-Z beam detected. This feature is not supported!") //Do it anyways though.
var/datum/point/midpoint = point_midpoint_points(starting, ending)
var/obj/effect/projectile/tracer/PB = new beam_type
if(isnull(light_color_override))
light_color_override = color
PB.apply_vars(angle_between_points(starting, ending), midpoint.return_px(), midpoint.return_py(), color, pixel_length_between_points(starting, ending) / world.icon_size, midpoint.return_turf(), 0)
. = PB
if(light_range > 0 && light_intensity > 0)
var/list/turf/line = get_line(starting.return_turf(), ending.return_turf())
tracing_line:
for(var/i in line)
var/turf/T = i
for(var/obj/effect/projectile_lighting/PL in T)
if(PL.owner == instance_key)
continue tracing_line
QDEL_IN(new /obj/effect/projectile_lighting(T, light_color_override, light_range, light_intensity, instance_key), qdel_in > 0? qdel_in : 5)
line = null
if(qdel_in)
QDEL_IN(PB, qdel_in)
+8 -3
View File
@@ -37,9 +37,12 @@
health -= damage
healthcheck()
/obj/effect/spider/bullet_act(var/obj/projectile/Proj)
..()
health -= Proj.get_structure_damage()
/obj/effect/spider/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
health -= hitting_projectile.get_structure_damage()
healthcheck()
/obj/effect/spider/proc/healthcheck()
@@ -69,6 +72,8 @@
/obj/effect/spider/stickyweb/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(air_group || height == 0)
return TRUE
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover, /mob/living/simple_animal/hostile/giant_spider))
return TRUE
else if(istype(mover, /mob/living))
+10 -5
View File
@@ -776,12 +776,17 @@ var/list/global/slot_flags_enumeration = list(
/obj/item/proc/ui_action_click()
attack_self(usr)
//RETURN VALUES
//handle_shield
//Return a negative value corresponding to the degree an attack is blocked. PROJECTILE_STOPPED stops the attack entirely, and is the default for projectile and non-projectile attacks
//Otherwise should return 0 to indicate that the attack is not affected in any way.
/**
* Called when a mob is hit by a projectile, item or by an attack
*
* Return one of the `BULLET_ACT_*` defines
*
* BULLET_ACT_HIT will let the attack continue, BULLET_ACT_BLOCK will block the attack
*/
/obj/item/proc/handle_shield(mob/user, var/on_back, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
return FALSE
SHOULD_NOT_SLEEP(TRUE)
return BULLET_ACT_HIT
/obj/item/proc/can_shield_back()
return
+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
+5 -2
View File
@@ -78,9 +78,12 @@
dismantle_material.place_sheet(loc)
qdel(src)
/obj/structure/bullet_act(obj/projectile/P, def_zone)
/obj/structure/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
bullet_ping(P)
if(. != BULLET_ACT_HIT)
return .
bullet_ping(hitting_projectile)
/obj/structure/proc/climb_on()
@@ -83,6 +83,8 @@
return prob(max(30,(100.0*health)/maxhealth))
/obj/structure/barricade/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(mover?.movement_type & PHASING)
return TRUE
if(air_group || (height==0))
return TRUE
if(istype(mover, /obj/projectile))
@@ -197,11 +199,14 @@
playsound(src, barricade_hitsound, 25, 1)
hit_barricade(attacking_item, user)
/obj/structure/barricade/bullet_act(obj/projectile/P)
bullet_ping(P)
var/damage_to_take = P.damage * P.anti_materiel_potential
/obj/structure/barricade/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
bullet_ping(hitting_projectile)
var/damage_to_take = hitting_projectile.damage * hitting_projectile.anti_materiel_potential
take_damage(damage_to_take)
return TRUE
/obj/structure/barricade/proc/barricade_deconstruct(deconstruct)
if(deconstruct && is_wired)
+2
View File
@@ -382,6 +382,8 @@ GLOBAL_LIST_EMPTY(total_active_bonfires)
AddOverlays("fireplace_glow")
/obj/structure/bonfire/fireplace/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(mover?.movement_type & PHASING)
return TRUE
if(get_dir(loc, target) == NORTH)
return !density
return TRUE
@@ -150,7 +150,8 @@
return content_size
/obj/structure/closet/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(air_group || (height==0 || wall_mounted)) return 1
if(air_group || (height==0 || wall_mounted))
return TRUE
return ..()
/obj/structure/closet/proc/can_open()
@@ -316,17 +317,19 @@
new /obj/item/stack/material/steel(get_turf(src))
qdel(src)
/obj/structure/closet/bullet_act(var/obj/projectile/Proj)
var/proj_damage = Proj.get_structure_damage()
/obj/structure/closet/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
var/proj_damage = hitting_projectile.get_structure_damage()
if(!proj_damage)
return
return BULLET_ACT_BLOCK
if(Proj.penetrating || istype(Proj, /obj/projectile/bullet))
var/distance = get_dist(Proj.starting, get_turf(loc))
if(hitting_projectile.penetrating || istype(hitting_projectile, /obj/projectile/bullet))
for(var/mob/living/L in contents)
Proj.attack_mob(L, distance)
hitting_projectile.Impact(L)
..()
damage(proj_damage)
/obj/structure/closet/attackby(obj/item/attacking_item, mob/user)
@@ -124,11 +124,13 @@
for(var/mob/M in src)
shatter(M)
/obj/structure/closet/statue/bullet_act(var/obj/projectile/Proj)
health -= Proj.get_structure_damage()
check_health()
/obj/structure/closet/statue/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
return
health -= hitting_projectile.get_structure_damage()
check_health()
/obj/structure/closet/statue/attack_generic(var/mob/user, damage, attacktext, environment_smash)
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
@@ -97,6 +97,8 @@
==========================
*/
/obj/structure/closet/crate/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(mover?.movement_type & PHASING)
return TRUE
if (istype(mover, /obj/structure/closet/crate))//Handle interaction with other crates
var/obj/structure/closet/crate/C = mover
if (tablestatus && tablestatus != C.tablestatus) // Crates can go under tables with crates on top of them, and vice versa
+7 -5
View File
@@ -20,12 +20,14 @@
layer = ABOVE_HUMAN_LAYER
opacity = 0
/obj/structure/curtain/bullet_act(obj/projectile/P, def_zone)
if(!P.nodamage)
visible_message(SPAN_WARNING("[P] tears [src] down!"))
/obj/structure/curtain/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
if(hitting_projectile.damage > 0)
visible_message(SPAN_WARNING("[hitting_projectile] tears [src] down!"))
qdel(src)
else
..(P, def_zone)
/obj/structure/curtain/attack_hand(mob/user)
playsound(get_turf(loc), 'sound/effects/curtain.ogg', 15, 1, -5)
+6 -3
View File
@@ -34,9 +34,12 @@
health_check()
/obj/structure/displaycase/bullet_act(var/obj/projectile/Proj)
health -= Proj.get_structure_damage()
..()
/obj/structure/displaycase/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
health -= hitting_projectile.get_structure_damage()
health_check()
/obj/structure/displaycase/proc/health_check()
@@ -48,6 +48,8 @@
/obj/structure/window_frame/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(air_group || (height == 0))
return TRUE
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover, /obj/structure/closet/crate))
return TRUE
if(istype(mover) && mover.pass_flags & PASSTABLE)
+8 -6
View File
@@ -41,16 +41,16 @@
health = 50
cover = 25
/obj/structure/girder/bullet_act(var/obj/projectile/Proj)
/obj/structure/girder/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
//Girders only provide partial cover. There's a chance that the projectiles will just pass through. (unless you are trying to shoot the girder)
if(Proj.original != src && !prob(cover))
return PROJECTILE_CONTINUE //pass through
if(hitting_projectile.original != src && !prob(cover))
return BULLET_ACT_HIT //pass through
var/damage = Proj.get_structure_damage()
var/damage = hitting_projectile.get_structure_damage()
if(!damage)
return
return BULLET_ACT_BLOCK
if(!istype(Proj, /obj/projectile/beam))
if(!istype(hitting_projectile, /obj/projectile/beam))
damage *= 0.4 //non beams do reduced damage
take_damage(damage)
@@ -376,6 +376,8 @@
/obj/structure/girder/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if (!mover)
return 1
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover,/obj/projectile) && density)
if (prob(50))
return 1
+8 -3
View File
@@ -31,10 +31,13 @@
visible_message(SPAN_WARNING(final_message))
qdel(src)
/obj/structure/gore/bullet_act(var/obj/projectile/Proj)
health -= Proj.damage
/obj/structure/gore/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
health -= hitting_projectile.damage
healthcheck()
return ..()
/obj/structure/gore/ex_act(severity)
switch(severity)
@@ -78,6 +81,8 @@
/obj/structure/gore/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(air_group)
return FALSE
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover) && mover.pass_flags & PASSGLASS)
return !opacity
return !density
+24 -18
View File
@@ -103,45 +103,54 @@
attack_generic(user,damage_dealt,attack_message)
/obj/structure/grille/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(air_group || (height==0)) return 1
if(air_group || (height==0))
return TRUE
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover) && mover.pass_flags & PASSGRILLE)
return 1
return TRUE
else
if(istype(mover, /obj/projectile))
return prob(30)
else
return !density
/obj/structure/grille/bullet_act(var/obj/projectile/Proj)
if(!Proj) return
/obj/structure/grille/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
if(!hitting_projectile)
return BULLET_ACT_BLOCK
//Flimsy grilles aren't so great at stopping projectiles. However they can absorb some of the impact
var/damage = Proj.get_structure_damage()
var/damage = hitting_projectile.get_structure_damage()
var/passthrough = 0
if(!damage) return
if(!damage)
return BULLET_ACT_BLOCK
//20% chance that the grille provides a bit more cover than usual. Support structure for example might take up 20% of the grille's area.
//If they click on the grille itself then we assume they are aiming at the grille itself and the extra cover behaviour is always used.
switch(Proj.damage_type)
switch(hitting_projectile.damage_type)
if(DAMAGE_BRUTE)
//bullets
if(Proj.original == src || prob(20))
Proj.damage *= between(0, Proj.damage/60, 0.5)
if(hitting_projectile.original == src || prob(20))
hitting_projectile.damage *= between(0, hitting_projectile.damage/60, 0.5)
if(prob(max((damage-10)/25, 0))*100)
passthrough = 1
else
Proj.damage *= between(0, Proj.damage/60, 1)
hitting_projectile.damage *= between(0, hitting_projectile.damage/60, 1)
passthrough = 1
if(DAMAGE_BURN)
//beams and other projectiles are either blocked completely by grilles or stop half the damage.
if(!(Proj.original == src || prob(20)))
Proj.damage *= 0.5
if(!(hitting_projectile.original == src || prob(20)))
hitting_projectile.damage *= 0.5
passthrough = 1
if(passthrough)
. = PROJECTILE_CONTINUE
damage = between(0, (damage - Proj.damage)*(Proj.damage_type == DAMAGE_BRUTE? 0.4 : 1), 10) //if the bullet passes through then the grille avoids most of the damage
. = BULLET_ACT_HIT
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
src.health -= damage*0.2
spawn(0) healthcheck() //spawn to make sure we return properly if the grille is deleted
@@ -313,7 +322,7 @@
/obj/structure/grille/cult/CanPass(atom/movable/mover, turf/target, height = 1.5, air_group = 0)
if(air_group)
return 0 //Make sure air doesn't drain
..()
. = ..()
/obj/structure/grille/crescent/attack_hand()
return
@@ -329,6 +338,3 @@
/obj/structure/grille/crescent/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
return
/obj/structure/grille/crescent/bullet_act()
return
+14 -9
View File
@@ -63,13 +63,18 @@
if(health <= 0)
topple()
/obj/structure/hadii_statue/bullet_act(var/obj/projectile/Proj)
/obj/structure/hadii_statue/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
if(toppled)
return
if(!Proj)
return
if(!Proj.damage)
visible_message(SPAN_WARNING("\The [Proj] bounces off \the [src]!"))
return
visible_message(SPAN_WARNING("\The [Proj] hits \the [src]!"))
do_integrity_check(Proj.damage)
return BULLET_ACT_BLOCK
if(!hitting_projectile)
return BULLET_ACT_BLOCK
if(!hitting_projectile.damage)
visible_message(SPAN_WARNING("\The [hitting_projectile] bounces off \the [src]!"))
return BULLET_ACT_BLOCK
. = ..()
if(. != BULLET_ACT_HIT)
return .
visible_message(SPAN_WARNING("\The [hitting_projectile] hits \the [src]!"))
do_integrity_check(hitting_projectile.damage)
+13 -4
View File
@@ -60,21 +60,28 @@
return ..()
/obj/structure/inflatable/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(mover?.movement_type & PHASING)
return TRUE
if(isprojectile(mover))
visible_message(SPAN_DANGER("\The [src] rapidly deflates!"))
deflate(TRUE)
return TRUE
return FALSE
/obj/structure/inflatable/bullet_act(var/obj/projectile/Proj)
var/proj_damage = Proj.get_structure_damage()
/obj/structure/inflatable/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
var/proj_damage = hitting_projectile.get_structure_damage()
if(!proj_damage)
return
bullet_ping(Proj)
. = ..()
if(. != BULLET_ACT_HIT)
return .
bullet_ping(hitting_projectile)
health -= proj_damage
..()
if(health <= 0)
deflate(TRUE)
return
@@ -203,6 +210,8 @@
/obj/structure/inflatable/door/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(air_group)
return state
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover, /obj/effect/beam))
return !opacity
if(isprojectile(mover))
+6 -3
View File
@@ -423,11 +423,14 @@
return
/obj/structure/janitorialcart/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(air_group || (height==0)) return 1
if(air_group || (height==0))
return TRUE
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover) && mover.pass_flags & PASSTABLE)
return 1
return TRUE
if(istype(mover, /mob/living) && mover == pulling)
return 1
return TRUE
else
if(istype(mover, /obj/projectile))
return prob(30)
@@ -67,6 +67,9 @@
if (!mover)
return 1
if(mover.movement_type & PHASING)
return TRUE
if(istype(mover,/obj/projectile) && density)
if (!occupied && prob(80))
//Wiry frame, usually wont be cover
+6 -3
View File
@@ -57,13 +57,16 @@
reflection.update_mirror_filters()
/obj/structure/mirror/bullet_act(var/obj/projectile/Proj)
if(prob(Proj.get_structure_damage() * 2))
/obj/structure/mirror/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
if(prob(hitting_projectile.get_structure_damage() * 2))
if(!shattered)
shatter()
else
playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, 1)
..()
/obj/structure/mirror/attackby(obj/item/attacking_item, mob/user)
if(shattered)
+11 -8
View File
@@ -56,27 +56,30 @@
return AIR_BLOCKED
return FALSE
/obj/structure/plasticflaps/CanPass(atom/A, turf/T)
if(istype(A) && A.pass_flags & PASSGLASS)
/obj/structure/plasticflaps/CanPass(atom/movable/mover, turf/target)
if(istype(mover) && mover.pass_flags & PASSGLASS)
return prob(60)
var/obj/structure/bed/B = A
if (istype(A, /obj/structure/bed) && B.buckled)//if it's a bed/chair and someone is buckled, it will not pass
if(mover?.movement_type & PHASING)
return TRUE
var/obj/structure/bed/B = mover
if (istype(mover, /obj/structure/bed) && B.buckled)//if it's a bed/chair and someone is buckled, it will not pass
return 0
if(istype(A, /obj/vehicle)) //no vehicles
if(istype(mover, /obj/vehicle)) //no vehicles
return 0
//Bots can always pass
if(isbot(A))
if(isbot(mover))
return TRUE
var/mob/living/M = A
var/mob/living/M = mover
if(istype(M))
if(M.lying)
return ..()
for(var/mob_type in mobs_can_pass)
if(istype(A, mob_type))
if(istype(mover, mob_type))
return ..()
return issmall(M)
+3 -1
View File
@@ -8,7 +8,7 @@
climbable = TRUE
layer = OBJ_LAYER
anchored = FALSE
pass_flags_self = LETPASSTHROW|PASSSTRUCTURE
pass_flags_self = LETPASSTHROW|PASSSTRUCTURE|PASSRAILING
atom_flags = ATOM_FLAG_CHECKS_BORDER
obj_flags = OBJ_FLAG_ROTATABLE|OBJ_FLAG_MOVES_UNSUPPORTED
@@ -88,6 +88,8 @@
. += FONT_SMALL(SPAN_NOTICE("\The [src] is <b>[anchored ? "" : "not"] screwed</b> to the floor."))
/obj/structure/railing/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover,/obj/projectile))
return TRUE
if(!istype(mover) || mover.pass_flags & PASSRAILING)
+11 -4
View File
@@ -86,7 +86,10 @@
return
/obj/structure/simple_door/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(air_group) return 0
if(air_group)
return FALSE
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover, /obj/effect/beam))
return !opacity
return !density
@@ -193,9 +196,13 @@
attack_hand(user)
return
/obj/structure/simple_door/bullet_act(var/obj/projectile/Proj)
health -= Proj.damage
bullet_ping(Proj)
/obj/structure/simple_door/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
health -= hitting_projectile.damage
bullet_ping(hitting_projectile)
CheckHealth()
/obj/structure/simple_door/proc/CheckHealth()
@@ -127,9 +127,6 @@
if(buckled)
buckled.forceMove(dest)
/obj/structure/bed/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
return ..()
/obj/structure/bed/ex_act(severity)
switch(severity)
if(1.0)
+8
View File
@@ -261,6 +261,8 @@ ABSTRACT_TYPE(/obj/structure/stairs/urban/road_ramp)
anchored = TRUE
/obj/structure/rod_railing/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover,/obj/projectile))
return TRUE
if(!istype(mover) || mover.pass_flags & PASSRAILING)
@@ -313,6 +315,8 @@ ABSTRACT_TYPE(/obj/structure/stairs/urban/road_ramp)
icon_state = "guard_top_end"
/obj/structure/road_barrier/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover,/obj/projectile))
return TRUE
if(!istype(mover) || mover.pass_flags & PASSRAILING)
@@ -343,6 +347,8 @@ ABSTRACT_TYPE(/obj/structure/stairs/urban/road_ramp)
can_be_unanchored = FALSE
/obj/structure/chainlink_fence/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)//So bullets will fly over and stuff.
if(mover?.movement_type & PHASING)
return TRUE
if(air_group || (height==0))
return TRUE
if(istype(mover, /obj/projectile))
@@ -378,6 +384,8 @@ ABSTRACT_TYPE(/obj/structure/stairs/urban/road_ramp)
can_be_unanchored = FALSE
/obj/structure/rope_railing/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover,/obj/projectile))
return TRUE
if(!istype(mover) || mover.pass_flags & PASSRAILING)
@@ -49,6 +49,8 @@
icon_state = "[facing]_[secure]windoor_assembly[state]"
/obj/structure/windoor_assembly/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover) && mover.pass_flags & PASSGLASS)
return 1
if(get_dir(loc, target) == dir) //Make sure looking at appropriate border
+9 -4
View File
@@ -138,12 +138,15 @@
qdel(src)
return
/obj/structure/window/bullet_act(var/obj/projectile/Proj)
var/proj_damage = Proj.get_structure_damage()
/obj/structure/window/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
var/proj_damage = hitting_projectile.get_structure_damage()
if(!proj_damage)
return
return BULLET_ACT_BLOCK
. = ..()
if(. != BULLET_ACT_HIT)
return .
..()
take_damage(proj_damage)
return
@@ -167,6 +170,8 @@
return (dir == SOUTHWEST || dir == SOUTHEAST || dir == NORTHWEST || dir == NORTHEAST)
/obj/structure/window/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover) && mover.pass_flags & PASSGLASS)
return 1
if(is_full_window())