Files
Yogstation/code/modules/projectiles/projectile.dm
Skrem_7 7ef784c94f Adds K-41s DMR for nukies, adds alternative 7.62 ammunition, overhauls atom penetration code for projectiles (#16191)
* new gun + projectile penetrations

* some name oversights

* orca sprites
2022-10-29 15:06:42 +01:00

733 lines
27 KiB
Plaintext

#define MOVES_HITSCAN -1 //Not actually hitscan but close as we get without actual hitscan.
#define MUZZLE_EFFECT_PIXEL_INCREMENT 17 //How many pixels to move the muzzle flash up so your character doesn't look like they're shitting out lasers.
/obj/item/projectile
name = "projectile"
icon = 'icons/obj/projectiles.dmi'
icon_state = "bullet"
density = FALSE
anchored = TRUE
item_flags = ABSTRACT
pass_flags = PASSTABLE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
movement_type = FLYING
wound_bonus = CANT_WOUND // can't wound by default
hitsound = 'sound/weapons/pierce.ogg'
var/hitsound_wall = ""
resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
var/def_zone = "" //Aiming at
var/atom/movable/firer = null//Who shot it
var/atom/fired_from = null // the atom that the projectile was fired from (gun, turret)
var/suppressed = FALSE //Attack message
var/yo = null
var/xo = null
var/atom/original = null // the original target clicked
var/turf/starting = null // the projectile's starting turf
var/list/permutated = list() // we've passed through these atoms, don't try to hit them again
var/p_x = 16
var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center
//Fired processing vars
var/fired = FALSE //Have we been fired yet
var/paused = FALSE //for suspending the projectile midair
var/last_projectile_move = 0
var/last_process = 0
var/time_offset = 0
var/datum/point/vector/trajectory
var/trajectory_ignore_forcemove = FALSE //instructs forceMove to NOT reset our trajectory to the new location!
var/speed = 0.8 //Amount of deciseconds it takes for projectile to travel
var/Angle = 0
var/original_angle = 0 //Angle at firing
var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle
var/spread = 0 //amount (in degrees) of projectile spread
animate_movement = 0 //Use SLIDE_STEPS in conjunction with legacy
var/ricochets = 0
var/ricochets_max = 2
var/ricochet_chance = 30
var/force_hit = FALSE //If the object being hit can pass ths damage on to something else, it should not do it for this bullet.
//Atom penetration, set to mobs by default
var/penetrating = FALSE
var/penetrations = INFINITY
var/penetration_type = 0 //Set to 1 if you only want to have it penetrate objects. Set to 2 if you want it to penetrate objects and mobs.
//Hitscan
var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored.
var/list/beam_segments //assoc list of datum/point or datum/point/vector, start = end. Used for hitscan effect generation.
var/datum/point/beam_index
var/turf/hitscan_last //last turf touched during hitscanning.
var/tracer_type
var/muzzle_type
var/impact_type
//Fancy hitscan lighting effects!
var/hitscan_light_intensity = 1.5
var/hitscan_light_range = 0.75
var/hitscan_light_color_override
var/muzzle_flash_intensity = 3
var/muzzle_flash_range = 1.5
var/muzzle_flash_color_override
var/impact_light_intensity = 3
var/impact_light_range = 2
var/impact_light_color_override
//Homing
var/homing = FALSE
var/atom/homing_target
var/homing_turn_speed = 10 //Angle per tick.
var/homing_inaccuracy_min = 0 //in pixels for these. offsets are set once when setting target.
var/homing_inaccuracy_max = 0
var/homing_offset_x = 0
var/homing_offset_y = 0
var/ignore_source_check = FALSE
var/damage = 10
var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE are the only things that should be in here
var/nodamage = FALSE //Determines if the projectile will skip any damage inflictions
var/flag = "bullet" //Defines what armor to use when it hits things. Must be set to bullet, laser, energy,or bomb
var/projectile_type = /obj/item/projectile
var/range = 50 //This will de-increment every step. When 0, it will deletze the projectile.
var/decayedRange //stores original range
var/reflect_range_decrease = 5 //amount of original range that falls off when reflecting, so it doesn't go forever
var/reflectable = NONE // Can it be reflected or not?
//Effects
var/stun = 0
var/knockdown = 0
var/paralyze = 0
var/immobilize = 0
var/unconscious = 0
var/irradiate = 0
var/stutter = 0
var/slur = 0
var/eyeblur = 0
var/drowsy = 0
var/stamina = 0
var/jitter = 0
var/dismemberment = 0 //The higher the number, the greater the bonus to dismembering. 0 will not dismember at all.
var/impact_effect_type //what type of impact effect to show when hitting something
var/log_override = FALSE //is this type spammed enough to not log? (KAs)
var/temporary_unstoppable_movement = FALSE
///How much we want to drop both wound_bonus and bare_wound_bonus (to a minimum of 0 for the latter) per tile, for falloff purposes
var/wound_falloff_tile
/obj/item/projectile/Initialize()
. = ..()
permutated = list()
decayedRange = range
/obj/item/projectile/proc/Range()
range--
if(wound_bonus != CANT_WOUND)
wound_bonus += wound_falloff_tile
bare_wound_bonus = max(0, bare_wound_bonus + wound_falloff_tile)
if(range <= 0 && loc)
on_range()
/obj/item/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range
qdel(src)
//to get the correct limb (if any) for the projectile hit message
/mob/living/proc/check_limb_hit(hit_zone)
if(has_limbs)
return hit_zone
/mob/living/carbon/check_limb_hit(hit_zone)
if(get_bodypart(hit_zone))
return hit_zone
else //when a limb is missing the damage is actually passed to the chest
return BODY_ZONE_CHEST
/obj/item/projectile/proc/prehit(atom/target)
return TRUE
/obj/item/projectile/proc/on_hit(atom/target, blocked = FALSE)
if(fired_from)
SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle)
var/turf/target_loca = get_turf(target)
var/hitx
var/hity
if(target == original)
hitx = target.pixel_x + p_x - 16
hity = target.pixel_y + p_y - 16
else
hitx = target.pixel_x + rand(-8, 8)
hity = target.pixel_y + rand(-8, 8)
if(!nodamage && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_loca) && prob(75))
var/turf/closed/wall/W = target_loca
if(impact_effect_type && !hitscan)
new impact_effect_type(target_loca, hitx, hity)
W.add_dent(WALL_DENT_SHOT, hitx, hity)
if(penetrating && (penetration_type == 1 || penetration_type == 2) && penetrations > 0)
penetrations -= 1
return BULLET_ACT_FORCE_PIERCE
return BULLET_ACT_HIT
if(!isliving(target))
if(impact_effect_type && !hitscan)
new impact_effect_type(target_loca, hitx, hity)
if(penetrating && (penetration_type == 1 || penetration_type == 2) && penetrations > 0)
penetrations -= 1
return BULLET_ACT_FORCE_PIERCE
return BULLET_ACT_HIT
var/mob/living/L = target
if(blocked != 100) // not completely blocked
if(damage && L.blood_volume && damage_type == BRUTE)
var/mob/living/carbon/C = L
var/splatter_dir = dir
if(starting)
splatter_dir = get_dir(starting, target_loca)
if(isalien(L) || ispolysmorph(L))
new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir)
else if (iscarbon(L) && !(NOBLOOD in C.dna.species.species_traits))
new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir)
else
new /obj/effect/temp_visual/dir_setting/bloodsplatter/genericsplatter(target_loca, splatter_dir)
var/obj/item/bodypart/B = L.get_bodypart(def_zone)
if(B.status == BODYPART_ROBOTIC) // So if you hit a robotic, it sparks instead of bloodspatters
do_sparks(2, FALSE, target.loc)
if(prob(25))
new /obj/effect/decal/cleanable/oil(target_loca)
if(prob(33))
L.add_splatter_floor(target_loca)
else if(impact_effect_type && !hitscan)
new impact_effect_type(target_loca, hitx, hity)
var/organ_hit_text = ""
var/limb_hit = L.check_limb_hit(def_zone)//to get the correct message info.
if(limb_hit)
organ_hit_text = " in \the [parse_zone(limb_hit)]"
if(suppressed)
playsound(loc, hitsound, 5, 1, -1)
to_chat(L, span_userdanger("You're shot by \a [src][organ_hit_text]!"))
else
if(hitsound)
var/volume = vol_by_damage()
playsound(loc, hitsound, volume, 1, -1)
L.visible_message(span_danger("[L] is hit by \a [src][organ_hit_text]!"), \
span_userdanger("[L] is hit by \a [src][organ_hit_text]!"), null, COMBAT_MESSAGE_RANGE)
L.on_hit(src)
var/viruslist = "" // yogs - adds viruslist variable
var/reagent_note
if(reagents && reagents.reagent_list)
reagent_note = " REAGENTS:"
for(var/datum/reagent/R in reagents.reagent_list)
reagent_note += "[R.name] ([num2text(R.volume)])"
// yogs start - Checks blood for disease
if(istype(R, /datum/reagent/blood))
var/datum/reagent/blood/RR = R
for(var/datum/disease/D in RR.data["viruses"])
viruslist += " [D.name]"
if(istype(D, /datum/disease/advance))
var/datum/disease/advance/DD = D
viruslist += " \[ symptoms: "
for(var/datum/symptom/S in DD.symptoms)
viruslist += "[S.name] "
viruslist += "\]"
if(viruslist)
investigate_log("[firer] injected [src] using a projectile with [viruslist] [blocked == 100 ? "BLOCKED" : ""]", INVESTIGATE_VIROLOGY)
log_game("[firer] injected [src] using a projectile with [viruslist] [blocked == 100 ? "BLOCKED" : ""]")
// yogs end
if(ismob(firer))
log_combat(firer, L, "shot", src, reagent_note)
else
L.log_message("has been shot by [firer] with [src]", LOG_ATTACK, color="orange")
return L.apply_effects(stun, knockdown, unconscious, irradiate, slur, stutter, eyeblur, drowsy, blocked, stamina, jitter, paralyze, immobilize)
/obj/item/projectile/proc/vol_by_damage()
if(src.damage)
return clamp((src.damage) * 0.67, 30, 100)// Multiply projectile damage by 0.67, then CLAMP the value between 30 and 100
else
return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume
/obj/item/projectile/proc/on_ricochet(atom/A)
return
/obj/item/projectile/proc/store_hitscan_collision(datum/point/pcache)
beam_segments[beam_index] = pcache
beam_index = pcache
beam_segments[beam_index] = null
/obj/item/projectile/Bump(atom/A)
if(!trajectory)
qdel(src)
return
var/datum/point/pcache = trajectory.copy_to()
var/turf/T = get_turf(A)
if(check_ricochet(A) && check_ricochet_flag(A) && ricochets < ricochets_max)
ricochets++
if(A.handle_ricochet(src))
on_ricochet(A)
ignore_source_check = TRUE
decayedRange = max(0, decayedRange - reflect_range_decrease)
range = decayedRange
if(hitscan)
store_hitscan_collision(pcache)
return TRUE
var/distance = get_dist(T, starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations.
def_zone = ran_zone(def_zone, max(100-(7*distance), 5)) //Lower accurancy/longer range tradeoff. 7 is a balanced number to use.
if(isturf(A) && hitsound_wall)
var/volume = clamp(vol_by_damage() + 20, 0, 100)
if(suppressed)
volume = 5
playsound(loc, hitsound_wall, volume, 1, -1)
return process_hit(T, select_target(T, A))
#define QDEL_SELF 1 //Delete if we're not UNSTOPPABLE flagged non-temporarily
#define DO_NOT_QDEL 2 //Pass through.
#define FORCE_QDEL 3 //Force deletion.
/obj/item/projectile/proc/process_hit(turf/T, atom/target, qdel_self, hit_something = FALSE) //probably needs to be reworked entirely when pixel movement is done.
if(QDELETED(src) || !T || !target) //We're done, nothing's left.
if((qdel_self == FORCE_QDEL) || ((qdel_self == QDEL_SELF) && !temporary_unstoppable_movement && !CHECK_BITFIELD(movement_type, UNSTOPPABLE)))
qdel(src)
return hit_something
permutated |= target //Make sure we're never hitting it again. If we ever run into weirdness with piercing projectiles needing to hit something multiple times.. well.. that's a to-do.
if(!prehit(target))
return process_hit(T, select_target(T), qdel_self, hit_something) //Hit whatever else we can since that didn't work.
var/result = target.bullet_act(src, def_zone)
if(result == BULLET_ACT_FORCE_PIERCE)
if(!CHECK_BITFIELD(movement_type, UNSTOPPABLE))
temporary_unstoppable_movement = TRUE
ENABLE_BITFIELD(movement_type, UNSTOPPABLE)
return process_hit(T, select_target(T), qdel_self, TRUE) //Hit whatever else we can since we're piercing through but we're still on the same tile.
else if(result == BULLET_ACT_PENETRATE) // This is slightly different from ACT_TURF in that it goes through the first thing
return process_hit(T, select_target(T), qdel_self, TRUE)
else if(result == BULLET_ACT_TURF) //We hit the turf but instead we're going to also hit something else on it.
return process_hit(T, select_target(T), QDEL_SELF, TRUE)
else //Whether it hit or blocked, we're done!
qdel_self = QDEL_SELF
hit_something = TRUE
if((qdel_self == FORCE_QDEL) || ((qdel_self == QDEL_SELF) && !temporary_unstoppable_movement && !CHECK_BITFIELD(movement_type, UNSTOPPABLE)))
qdel(src)
return hit_something
#undef QDEL_SELF
#undef DO_NOT_QDEL
#undef FORCE_QDEL
/obj/item/projectile/proc/select_target(turf/T, atom/target) //Select a target from a turf.
if((original in T) && can_hit_target(original, permutated, TRUE, TRUE))
return original
if(target && can_hit_target(target, permutated, target == original, TRUE))
return target
var/list/mob/living/possible_mobs = typecache_filter_list(T, GLOB.typecache_mob)
var/list/mob/mobs = list()
for(var/mob/living/M in possible_mobs)
if(!can_hit_target(M, permutated, M == original, TRUE))
continue
mobs += M
var/mob/M = safepick(mobs)
if(M)
return M.lowest_buckled_mob()
var/list/obj/possible_objs = typecache_filter_list(T, GLOB.typecache_machine_or_structure)
var/list/obj/objs = list()
for(var/obj/O in possible_objs)
if(!can_hit_target(O, permutated, O == original, TRUE))
continue
objs += O
var/obj/O = safepick(objs)
if(O)
return O
//Nothing else is here that we can hit, hit the turf if we haven't.
if(!(T in permutated) && can_hit_target(T, permutated, T == original, TRUE))
return T
//Returns null if nothing at all was found.
/obj/item/projectile/proc/check_ricochet()
if(prob(ricochet_chance))
return TRUE
return FALSE
/obj/item/projectile/proc/check_ricochet_flag(atom/A)
if(A.flags_1 & CHECK_RICOCHET_1)
return TRUE
return FALSE
/obj/item/projectile/proc/return_predicted_turf_after_moves(moves, forced_angle) //I say predicted because there's no telling that the projectile won't change direction/location in flight.
if(!trajectory && isnull(forced_angle) && isnull(Angle))
return FALSE
var/datum/point/vector/current = trajectory
if(!current)
var/turf/T = get_turf(src)
current = new(T.x, T.y, T.z, pixel_x, pixel_y, isnull(forced_angle)? Angle : forced_angle, SSprojectiles.global_pixel_speed)
var/datum/point/vector/v = current.return_vector_after_increments(moves * SSprojectiles.global_iterations_per_move)
return v.return_turf()
/obj/item/projectile/proc/return_pathing_turfs_in_moves(moves, forced_angle)
var/turf/current = get_turf(src)
var/turf/ending = return_predicted_turf_after_moves(moves, forced_angle)
return getline(current, ending)
/obj/item/projectile/Process_Spacemove(movement_dir = 0)
return TRUE //Bullets don't drift in space
/obj/item/projectile/process()
last_process = world.time
if(!loc || !fired || !trajectory)
fired = FALSE
return PROCESS_KILL
if(paused || !isturf(loc))
last_projectile_move += world.time - last_process //Compensates for pausing, so it doesn't become a hitscan projectile when unpaused from charged up ticks.
return
var/elapsed_time_deciseconds = (world.time - last_projectile_move) + time_offset
time_offset = 0
var/required_moves = speed > 0? FLOOR(elapsed_time_deciseconds / speed, 1) : MOVES_HITSCAN //Would be better if a 0 speed made hitscan but everyone hates those so I can't make it a universal system :<
if(required_moves == MOVES_HITSCAN)
required_moves = SSprojectiles.global_max_tick_moves
else
if(required_moves > SSprojectiles.global_max_tick_moves)
var/overrun = required_moves - SSprojectiles.global_max_tick_moves
required_moves = SSprojectiles.global_max_tick_moves
time_offset += overrun * speed
time_offset += MODULUS(elapsed_time_deciseconds, speed)
for(var/i in 1 to required_moves)
pixel_move(1, FALSE)
/obj/item/projectile/proc/fire(angle, atom/direct_target)
if(fired_from)
SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original)
//If no angle needs to resolve it from xo/yo!
if(!log_override && firer && original)
log_combat(firer, original, "fired at", src, "from [get_area_name(src, TRUE)]")
if(direct_target)
if(prehit(direct_target))
direct_target.bullet_act(src, def_zone)
qdel(src)
return
if(isnum(angle))
setAngle(angle)
if(spread)
setAngle(Angle + ((rand() - 0.5) * spread))
var/turf/starting = get_turf(src)
if(isnull(Angle)) //Try to resolve through offsets if there's no angle set.
if(isnull(xo) || isnull(yo))
stack_trace("WARNING: Projectile [type] deleted due to being unable to resolve a target after angle was null!")
qdel(src)
return
var/turf/target = locate(clamp(starting + xo, 1, world.maxx), clamp(starting + yo, 1, world.maxy), starting.z)
setAngle(Get_Angle(src, target))
original_angle = Angle
if(!nondirectional_sprite)
var/matrix/M = new
M.Turn(Angle)
transform = M
trajectory_ignore_forcemove = TRUE
forceMove(starting)
trajectory_ignore_forcemove = FALSE
trajectory = new(starting.x, starting.y, starting.z, pixel_x, pixel_y, Angle, SSprojectiles.global_pixel_speed)
last_projectile_move = world.time
fired = TRUE
if(hitscan)
process_hitscan()
if(!(datum_flags & DF_ISPROCESSING))
START_PROCESSING(SSprojectiles, src)
pixel_move(1, FALSE) //move it now!
/obj/item/projectile/proc/setAngle(new_angle) //wrapper for overrides.
Angle = new_angle
if(!nondirectional_sprite)
var/matrix/M = new
M.Turn(Angle)
transform = M
if(trajectory)
trajectory.set_angle(new_angle)
return TRUE
/obj/item/projectile/forceMove(atom/target)
if(!isloc(target) || !isloc(loc) || !z)
return ..()
var/zc = target.z != z
var/old = loc
if(zc)
before_z_change(old, target)
. = ..()
if(trajectory && !trajectory_ignore_forcemove && isturf(target))
if(hitscan)
finalize_hitscan_and_generate_tracers(FALSE)
trajectory.initialize_location(target.x, target.y, target.z, 0, 0)
if(hitscan)
record_hitscan_start(RETURN_PRECISE_POINT(src))
if(zc)
after_z_change(old, target)
/obj/item/projectile/proc/after_z_change(atom/olcloc, atom/newloc)
/obj/item/projectile/proc/before_z_change(atom/oldloc, atom/newloc)
/obj/item/projectile/vv_edit_var(var_name, var_value)
switch(var_name)
if(NAMEOF(src, Angle))
setAngle(var_value)
return TRUE
else
return ..()
/obj/item/projectile/proc/set_pixel_speed(new_speed)
if(trajectory)
trajectory.set_speed(new_speed)
return TRUE
return FALSE
/obj/item/projectile/proc/record_hitscan_start(datum/point/pcache)
if(pcache)
beam_segments = list()
beam_index = pcache
beam_segments[beam_index] = null //record start.
/obj/item/projectile/proc/process_hitscan()
var/safety = range * 3
record_hitscan_start(RETURN_POINT_VECTOR_INCREMENT(src, Angle, MUZZLE_EFFECT_PIXEL_INCREMENT, 1))
while(loc && !QDELETED(src))
if(paused)
stoplag(1)
continue
if(safety-- <= 0)
if(loc)
Bump(loc)
if(!QDELETED(src))
qdel(src)
return //Kill!
pixel_move(1, TRUE)
/obj/item/projectile/proc/pixel_move(trajectory_multiplier, hitscanning = FALSE)
if(!loc || !trajectory)
return
last_projectile_move = world.time
if(!nondirectional_sprite && !hitscanning)
var/matrix/M = new
M.Turn(Angle)
transform = M
if(homing)
process_homing()
var/forcemoved = FALSE
for(var/i in 1 to SSprojectiles.global_iterations_per_move)
if(QDELETED(src))
return
trajectory.increment(trajectory_multiplier)
var/turf/T = trajectory.return_turf()
if(!istype(T))
qdel(src)
return
if(T.z != loc.z)
var/old = loc
before_z_change(loc, T)
trajectory_ignore_forcemove = TRUE
forceMove(T)
trajectory_ignore_forcemove = FALSE
after_z_change(old, loc)
if(!hitscanning)
pixel_x = trajectory.return_px()
pixel_y = trajectory.return_py()
forcemoved = TRUE
hitscan_last = loc
else if(T != loc)
step_towards(src, T)
hitscan_last = loc
if(!hitscanning && !forcemoved)
pixel_x = trajectory.return_px() - trajectory.mpx * trajectory_multiplier * SSprojectiles.global_iterations_per_move
pixel_y = trajectory.return_py() - trajectory.mpy * trajectory_multiplier * SSprojectiles.global_iterations_per_move
animate(src, pixel_x = trajectory.return_px(), pixel_y = trajectory.return_py(), time = 0.1 SECONDS, flags = ANIMATION_END_NOW)
Range()
/obj/item/projectile/proc/process_homing() //may need speeding up in the future performance wise.
if(!homing_target)
return FALSE
var/datum/point/PT = RETURN_PRECISE_POINT(homing_target)
PT.x += clamp(homing_offset_x, 1, world.maxx)
PT.y += clamp(homing_offset_y, 1, world.maxy)
var/angle = closer_angle_difference(Angle, angle_between_points(RETURN_PRECISE_POINT(src), PT))
setAngle(Angle + clamp(angle, -homing_turn_speed, homing_turn_speed))
/obj/item/projectile/proc/set_homing_target(atom/A)
if(!A || (!isturf(A) && !isturf(A.loc)))
return FALSE
homing = TRUE
homing_target = A
homing_offset_x = rand(homing_inaccuracy_min, homing_inaccuracy_max)
homing_offset_y = rand(homing_inaccuracy_min, homing_inaccuracy_max)
if(prob(50))
homing_offset_x = -homing_offset_x
if(prob(50))
homing_offset_y = -homing_offset_y
//Returns true if the target atom is on our current turf and above the right layer
//If direct target is true it's the originally clicked target.
/obj/item/projectile/proc/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE)
if(QDELETED(target))
return FALSE
if(!ignore_source_check && firer)
var/mob/M = firer
if(isliving(M))
var/mob/living/L = M
if((target in L.hasparasites()) && target.loc == L.loc)
return FALSE
if((target == firer) || ((target == firer.loc) && (ismecha(firer.loc) || isspacepod(firer.loc))) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target))) //cannot shoot yourself or your mech // yogs - or your spacepod)
return FALSE
if(!ignore_loc && (loc != target.loc))
return FALSE
if(target in passthrough)
return FALSE
if(target.density) //This thing blocks projectiles, hit it regardless of layer/mob stuns/etc.
return TRUE
if(!isliving(target))
if(target.layer < PROJECTILE_HIT_THRESHHOLD_LAYER)
return FALSE
else
var/mob/living/L = target
if(!direct_target)
if(!CHECK_BITFIELD(L.mobility_flags, MOBILITY_STAND) && (L in range(2, starting))) //if we're shooting over someone who's prone and nearby bc formations are cool and not going to be unbalanced
return FALSE
if(!CHECK_BITFIELD(L.mobility_flags, MOBILITY_USE | MOBILITY_STAND | MOBILITY_MOVE) || !(L.stat == CONSCIOUS)) //If they're able to 1. stand or 2. use items or 3. move, AND they are not softcrit, they are not stunned enough to dodge projectiles passing over.
return FALSE
return TRUE
//Spread is FORCED!
/obj/item/projectile/proc/preparePixelProjectile(atom/target, atom/source, params, spread = 0)
var/turf/curloc = get_turf(source)
var/turf/targloc = get_turf(target)
trajectory_ignore_forcemove = TRUE
forceMove(get_turf(source))
trajectory_ignore_forcemove = FALSE
starting = get_turf(source)
original = target
if(targloc || !params)
yo = targloc.y - curloc.y
xo = targloc.x - curloc.x
setAngle(Get_Angle(src, targloc) + spread)
if(isliving(source) && params)
var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, params)
p_x = calculated[2]
p_y = calculated[3]
setAngle(calculated[1] + spread)
else if(targloc)
yo = targloc.y - curloc.y
xo = targloc.x - curloc.x
setAngle(Get_Angle(src, targloc) + spread)
else
stack_trace("WARNING: Projectile [type] fired without either mouse parameters, or a target atom to aim at!")
qdel(src)
/proc/calculate_projectile_angle_and_pixel_offsets(mob/user, params)
var/list/mouse_control = params2list(params)
var/p_x = 0
var/p_y = 0
var/angle = 0
if(mouse_control["icon-x"])
p_x = text2num(mouse_control["icon-x"])
if(mouse_control["icon-y"])
p_y = text2num(mouse_control["icon-y"])
if(mouse_control["screen-loc"])
//Split screen-loc up into X+Pixel_X and Y+Pixel_Y
var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",")
//Split X+Pixel_X up into list(X, Pixel_X)
var/list/screen_loc_X = splittext(screen_loc_params[1],":")
//Split Y+Pixel_Y up into list(Y, Pixel_Y)
var/list/screen_loc_Y = splittext(screen_loc_params[2],":")
var/x = text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32
var/y = text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32
//Calculate the "resolution" of screen based on client's view and world's icon size. This will work if the user can view more tiles than average.
var/list/screenview = getviewsize(user.client.view)
var/screenviewX = screenview[1] * world.icon_size
var/screenviewY = screenview[2] * world.icon_size
var/ox = round(screenviewX/2) - user.client.pixel_x //"origin" x
var/oy = round(screenviewY/2) - user.client.pixel_y //"origin" y
angle = ATAN2(y - oy, x - ox)
return list(angle, p_x, p_y)
/obj/item/projectile/Crossed(atom/movable/AM) //A mob moving on a tile with a projectile is hit by it.
. = ..()
if(isliving(AM) && !(pass_flags & PASSMOB))
var/mob/living/L = AM
if(can_hit_target(L, permutated, (AM == original)))
Bump(AM)
/obj/item/projectile/Move(atom/newloc, dir = NONE)
. = ..()
if(.)
if(temporary_unstoppable_movement)
temporary_unstoppable_movement = FALSE
DISABLE_BITFIELD(movement_type, UNSTOPPABLE)
if(fired && can_hit_target(original, permutated, TRUE))
Bump(original)
/obj/item/projectile/Destroy()
if(hitscan)
finalize_hitscan_and_generate_tracers()
STOP_PROCESSING(SSprojectiles, src)
cleanup_beam_segments()
qdel(trajectory)
return ..()
/obj/item/projectile/proc/cleanup_beam_segments()
QDEL_LIST_ASSOC(beam_segments)
beam_segments = list()
qdel(beam_index)
/obj/item/projectile/proc/finalize_hitscan_and_generate_tracers(impacting = TRUE)
if(trajectory && beam_index)
var/datum/point/pcache = trajectory.copy_to()
beam_segments[beam_index] = pcache
generate_hitscan_tracers(null, null, impacting)
/obj/item/projectile/proc/generate_hitscan_tracers(cleanup = TRUE, duration = 3, impacting = TRUE)
if(!length(beam_segments))
return
if(tracer_type)
var/tempref = REF(src)
for(var/datum/point/p in beam_segments)
generate_tracer_between_points(p, beam_segments[p], tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity, tempref)
if(muzzle_type && duration > 0)
var/datum/point/p = beam_segments[1]
var/atom/movable/thing = new muzzle_type
p.move_atom_to_src(thing)
var/matrix/M = new
M.Turn(original_angle)
thing.transform = M
thing.color = color
thing.set_light(muzzle_flash_range, muzzle_flash_intensity, muzzle_flash_color_override? muzzle_flash_color_override : color)
QDEL_IN(thing, duration)
if(impacting && impact_type && duration > 0)
var/datum/point/p = beam_segments[beam_segments[beam_segments.len]]
var/atom/movable/thing = new impact_type
p.move_atom_to_src(thing)
var/matrix/M = new
M.Turn(Angle)
thing.transform = M
thing.color = color
thing.set_light(impact_light_range, impact_light_intensity, impact_light_color_override? impact_light_color_override : color)
QDEL_IN(thing, duration)
if(cleanup)
cleanup_beam_segments()
/obj/item/projectile/experience_pressure_difference()
return