mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-01-09 00:52:09 +00:00
* refactor: Movement cross/uncross implementation.
* wrong var name
* fix unit tests dropping PDAs into nowhere
* Add documentation.
* remove unused constants
* say which procs are off limits
* fix simpleanimal z change runtime
* helps not to leave merge conflicts
* kill me
* fix typecast
* fix projectile/table collision
* treadmills don't cause MC to crash anymore
* connect_loc is appropriate here
* fix windoors and teleporters
* fix bonfires and clarify docs
* fix proximity sensors
Tested with sensors in crates, sensors in modsuits
Tested new proximity component with firing projectiles at singularity
Tested new proximity component with portable flashes
Tested new proximity component with facehuggers
* lint
* fix: polarized access helper false positives
* Revert "fix: polarized access helper false positives"
This reverts commit 9814f98cf6.
* hopefully the right change for mindflayer steam
* Changes following cameras
* fix glass table collision
* appears to fix doorspam
* fix ore bags not picking up ore
* fix signatures of /Exited
* remove debug log
* remove duplicate signal registrar
* fix emptying bags into locations
* I don't trust these nested Move calls
* use connect_loc for upgraded resonator fields
* use moveToNullspace
* fix spiderweb crossing
* fix pass checking for windows from a tile off
* fix bluespace closet/transparency issues
* fix mechs not interacting with doors and probably other things
* fix debug
* fix telepete
* add some docs
* stop trying to shoehorn prox monitor into cards
* I should make sure things build
* kill override signal warning
* undef signal
* not many prox monitors survive going off like this
* small fixes to storage
* make moving wormholes respect signals
* use correct signals for pulse demon
* fix pulse heart too
* fix smoke signals
* may have fucked singulo projectile swerve
* fix singulo projectile arcing
* remove duplicate define
* just look at it
* hopefully last cleanups of incorrect signal usage
* fix squeaking
* may god have mercy on my soul
* Apply suggestions from code review
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: warriorstar-orion <orion@snowfrost.garden>
* lewc review
* Apply suggestions from code review
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Signed-off-by: warriorstar-orion <orion@snowfrost.garden>
* burza review
* fix bad args for grenade assemblies
* Update code/__DEFINES/is_helpers.dm
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: warriorstar-orion <orion@snowfrost.garden>
---------
Signed-off-by: warriorstar-orion <orion@snowfrost.garden>
Co-authored-by: DGamerL <daan.lyklema@gmail.com>
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
597 lines
23 KiB
Plaintext
597 lines
23 KiB
Plaintext
/// Is this a hitscan projectile or not, if so move like one
|
|
#define MOVES_HITSCAN -1
|
|
/// How many pixels to move the muzzle flash up so your character doesn't look like they're shitting out lasers.
|
|
#define MUZZLE_EFFECT_PIXEL_INCREMENT 17
|
|
|
|
/obj/item/projectile
|
|
name = "projectile"
|
|
icon = 'icons/obj/projectiles.dmi'
|
|
icon_state = "bullet"
|
|
density = FALSE
|
|
resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
|
|
anchored = TRUE //There's a reason this is here, Mport. God fucking damn it -Agouri. Find&Fix by Pete. The reason this is here is to stop the curving of emitter shots.
|
|
flags = ABSTRACT
|
|
pass_flags = PASSTABLE
|
|
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
|
hitsound = 'sound/weapons/pierce.ogg'
|
|
var/hitsound_wall = ""
|
|
var/def_zone = "" //Aiming at
|
|
var/mob/firer = null//Who shot it
|
|
var/atom/firer_source_atom = null //the gun or object this came from
|
|
var/obj/item/ammo_casing/ammo_casing = null
|
|
var/suppressed = FALSE //Attack message
|
|
var/yo = null
|
|
var/xo = null
|
|
var/current = 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/paused = FALSE //for suspending the projectile midair
|
|
var/p_x = 16
|
|
var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center
|
|
var/speed = 1 //Amount of deciseconds it takes for projectile to travel
|
|
var/Angle = null
|
|
var/original_angle = null //Angle at firing
|
|
var/spread = 0 //amount (in degrees) of projectile spread
|
|
animate_movement = NO_STEPS
|
|
|
|
var/ignore_source_check = FALSE
|
|
|
|
var/damage = 10
|
|
var/tile_dropoff = 0 //how much damage should be decremented as the bullet moves
|
|
var/tile_dropoff_s = 0 //same as above but for stamina
|
|
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 //Cael - bio and rad are also valid
|
|
var/projectile_type = "/obj/item/projectile"
|
|
var/range = 50 //This will de-increment every step. When 0, it will delete the projectile.
|
|
/// Determines the reflectability level of a projectile, either REFLECTABILITY_NEVER, REFLECTABILITY_PHYSICAL, REFLECTABILITY_ENERGY in order of ease to reflect.
|
|
var/reflectability = REFLECTABILITY_PHYSICAL
|
|
var/alwayslog = FALSE // ALWAYS log this projectile on hit even if it doesn't hit a living target. Useful for AOE explosion / EMP.
|
|
//Effects
|
|
var/stun = 0
|
|
var/weaken = 0
|
|
var/knockdown = 0
|
|
var/paralyze = 0
|
|
var/irradiate = 0
|
|
var/stutter = 0
|
|
var/slur = 0
|
|
var/eyeblur = 0
|
|
var/drowsy = 0
|
|
var/stamina = 0
|
|
var/jitter = 0
|
|
/// Number of times an object can pass through an object. -1 is infinite
|
|
var/forcedodge = 0
|
|
/// Does the projectile increase fire stacks / immolate mobs on hit? Applies fire stacks equal to the number on hit.
|
|
var/immolate = 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 //whether print to admin attack logs or just keep it in the diary
|
|
|
|
/// For when you want your projectile to have a chain coming out of the gun
|
|
var/chain = null
|
|
|
|
/// Last world.time the projectile proper moved
|
|
var/last_projectile_move = 0
|
|
/// Left over ticks in movement calculation
|
|
var/time_offset = 0
|
|
/// The projectile's trajectory
|
|
var/datum/point_precise/vector/trajectory
|
|
/// Instructs forceMove to NOT reset our trajectory to the new location!
|
|
var/trajectory_ignore_forcemove = FALSE
|
|
|
|
/// Does this projectile do extra damage to / break shields?
|
|
var/shield_buster = FALSE
|
|
var/forced_accuracy = FALSE
|
|
|
|
///Has the projectile been fired?
|
|
var/has_been_fired = FALSE
|
|
|
|
/// Does this projectile hit living non dense mobs?
|
|
var/always_hit_living_nondense = FALSE
|
|
|
|
//Hitscan
|
|
var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored.
|
|
var/list/beam_segments //assoc list of datum/point_precise or datum/point_precise/vector, start = end. Used for hitscan effect generation.
|
|
/// Last turf an angle was changed in for hitscan projectiles.
|
|
var/turf/last_angle_set_hitscan_store
|
|
var/datum/point_precise/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
|
|
var/hitscan_duration = 0.3 SECONDS
|
|
|
|
/// how many times we've ricochet'd so far (instance variable, not a stat)
|
|
var/ricochets = 0
|
|
/// how many times we can ricochet max
|
|
var/ricochets_max = 0
|
|
/// how many times we have to ricochet min (unless we hit an atom we can ricochet off)
|
|
var/min_ricochets = 0
|
|
/// 0-100 (or more, I guess), the base chance of ricocheting, before being modified by the atom we shoot and our chance decay
|
|
var/ricochet_chance = 0
|
|
/// 0-1 (or more, I guess) multiplier, the ricochet_chance is modified by multiplying this after each ricochet
|
|
var/ricochet_decay_chance = 0.7
|
|
/// 0-1 (or more, I guess) multiplier, the projectile's damage is modified by multiplying this after each ricochet
|
|
var/ricochet_decay_damage = 0.7
|
|
/// On ricochet, if nonzero, we consider all mobs within this range of our projectile at the time of ricochet to home in on like Revolver Ocelot, as governed by ricochet_auto_aim_angle
|
|
var/ricochet_auto_aim_range = 0
|
|
/// On ricochet, if ricochet_auto_aim_range is nonzero, we'll consider any mobs within this range of the normal angle of incidence to home in on, higher = more auto aim
|
|
var/ricochet_auto_aim_angle = 30
|
|
/// the angle of impact must be within this many degrees of the struck surface, set to 0 to allow any angle
|
|
var/ricochet_incidence_leeway = 40
|
|
/// Can our ricochet autoaim hit our firer?
|
|
var/ricochet_shoots_firer = TRUE
|
|
|
|
/obj/item/projectile/New()
|
|
return ..()
|
|
|
|
/obj/item/projectile/Initialize(mapload)
|
|
. = ..()
|
|
var/static/list/loc_connections = list(
|
|
COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
|
|
)
|
|
AddElement(/datum/element/connect_loc, loc_connections)
|
|
|
|
/obj/item/projectile/proc/Range()
|
|
range--
|
|
if(damage && tile_dropoff)
|
|
damage = max(0, damage - tile_dropoff) // decrement projectile damage based on dropoff value for each tile it moves
|
|
if(stamina && tile_dropoff_s)
|
|
stamina = max(0, stamina - tile_dropoff_s) // as above, but with stamina
|
|
if(range <= 0 && loc)
|
|
on_range()
|
|
if(!damage && !stamina && (tile_dropoff || tile_dropoff_s))
|
|
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)
|
|
|
|
/obj/item/projectile/proc/prehit(atom/target)
|
|
return TRUE
|
|
|
|
/obj/item/projectile/proc/on_hit(atom/target, blocked = 0, hit_zone)
|
|
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/simulated/wall/W = target_loca
|
|
if(impact_effect_type && !hitscan)
|
|
new impact_effect_type(target_loca, hitx, hity)
|
|
|
|
W.add_dent(PROJECTILE_IMPACT_WALL_DENT_SHOT, hitx, hity)
|
|
return 0
|
|
if(alwayslog)
|
|
add_attack_logs(firer, target, "Shot with a [type]")
|
|
if(!isliving(target))
|
|
if(impact_effect_type && !hitscan)
|
|
new impact_effect_type(target_loca, hitx, hity)
|
|
return 0
|
|
var/mob/living/L = target
|
|
var/mob/living/carbon/human/H
|
|
if(blocked != INFINITY) // not completely blocked
|
|
if(damage && L.blood_volume && damage_type == BRUTE)
|
|
var/splatter_dir = dir
|
|
if(starting)
|
|
splatter_dir = get_dir(starting, target_loca)
|
|
if(isalien(L))
|
|
new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir)
|
|
else
|
|
var/blood_color = "#C80000"
|
|
if(ishuman(target))
|
|
H = target
|
|
blood_color = H.dna.species.blood_color
|
|
new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir, blood_color)
|
|
if(prob(33))
|
|
var/list/shift = list("x" = 0, "y" = 0)
|
|
var/turf/step_over = get_step(target_loca, splatter_dir)
|
|
|
|
if(get_splatter_blockage(step_over, target, splatter_dir, target_loca)) //If you can't cross the tile or any of its relevant obstacles...
|
|
shift = pixel_shift_dir(splatter_dir) //Pixel shift the blood there instead (so you can't see wallsplatter through walls).
|
|
else
|
|
target_loca = step_over
|
|
L.add_splatter_floor(target_loca, shift_x = shift["x"], shift_y = shift["y"])
|
|
if(istype(H))
|
|
for(var/mob/living/carbon/human/M in step_over) //Bloody the mobs who're infront of the spray.
|
|
M.make_bloody_hands(H.get_blood_dna_list(), H.get_blood_color())
|
|
/* Uncomment when bloody_body stops randomly not transferring blood colour.
|
|
M.bloody_body(H) */
|
|
else if(impact_effect_type && !hitscan)
|
|
new impact_effect_type(target_loca, hitx, hity)
|
|
var/organ_hit_text = ""
|
|
if(L.has_limbs)
|
|
organ_hit_text = " in \the [parse_zone(def_zone)]"
|
|
if(suppressed)
|
|
playsound(loc, hitsound, 5, TRUE, -1)
|
|
to_chat(L, "<span class='userdanger'>You're shot by \a [src][organ_hit_text]!</span>")
|
|
else
|
|
if(hitsound)
|
|
var/volume = vol_by_damage()
|
|
playsound(loc, hitsound, volume, TRUE, -1)
|
|
L.visible_message("<span class='danger'>[L] is hit by \a [src][organ_hit_text]!</span>", \
|
|
"<span class='userdanger'>[L] is hit by \a [src][organ_hit_text]!</span>") //X has fired Y is now given by the guns so you cant tell who shot you if you could not see the shooter
|
|
if(immolate)
|
|
L.adjust_fire_stacks(immolate)
|
|
L.IgniteMob()
|
|
|
|
var/additional_log_text
|
|
if(blocked)
|
|
additional_log_text = " [ARMOUR_VALUE_TO_PERCENTAGE(blocked)]% blocked"
|
|
if(reagents && reagents.reagent_list)
|
|
var/reagent_note = "REAGENTS:"
|
|
for(var/datum/reagent/R in reagents.reagent_list)
|
|
reagent_note += R.id + " ("
|
|
reagent_note += num2text(R.volume) + ") "
|
|
additional_log_text = "[additional_log_text] (containing [reagent_note])"
|
|
|
|
var/were_affects_applied = L.apply_effects(stun, weaken, knockdown, paralyze, irradiate, slur, stutter, eyeblur, drowsy, blocked, stamina, jitter)
|
|
|
|
if(!log_override && firer && !alwayslog)
|
|
add_attack_logs(firer, L, "Shot with a [type][additional_log_text]")
|
|
|
|
return were_affects_applied
|
|
|
|
/obj/item/projectile/proc/get_splatter_blockage(turf/step_over, atom/target, splatter_dir, target_loca) //Check whether the place we want to splatter blood is blocked (i.e. by windows).
|
|
var/turf/step_cardinal = !(splatter_dir in list(NORTH, SOUTH, EAST, WEST)) ? get_step(target_loca, get_cardinal_dir(target_loca, step_over)) : null
|
|
|
|
if(step_over.density && !step_over.CanPass(target, step_over, 1)) //Preliminary simple check.
|
|
return TRUE
|
|
for(var/atom/movable/border_obstacle in step_over) //Check to see if we're blocked by a (non-full) window or some such. Do deeper investigation if we're splattering blood diagonally.
|
|
if(border_obstacle.flags&ON_BORDER && get_dir(step_cardinal ? step_cardinal : target_loca, step_over) == turn(border_obstacle.dir, 180))
|
|
return TRUE
|
|
|
|
/obj/item/projectile/proc/vol_by_damage()
|
|
if(damage)
|
|
return clamp((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/store_hitscan_collision(datum/point_precise/point_cache)
|
|
beam_segments[beam_index] = point_cache
|
|
beam_index = point_cache
|
|
beam_segments[beam_index] = null
|
|
|
|
/obj/item/projectile/Bump(atom/A)
|
|
if(check_ricochet(A) && check_ricochet_flag(A) && ricochets < ricochets_max && is_reflectable(REFLECTABILITY_PHYSICAL))
|
|
if(hitscan && ricochets_max > 10)
|
|
ricochets_max = 10 //I do not want a chucklefuck editing this higher, sorry.
|
|
ricochets++
|
|
if(A.handle_ricochet(src))
|
|
on_ricochet(A)
|
|
ignore_source_check = TRUE
|
|
range = initial(range)
|
|
return TRUE
|
|
if(firer && !ignore_source_check)
|
|
if(A == firer || (A == firer.loc && ismecha(A))) //cannot shoot yourself or your mech
|
|
loc = A.loc
|
|
return 0
|
|
|
|
var/distance = get_dist(get_turf(A), starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations.
|
|
if(!forced_accuracy)
|
|
if(get_dist(A, original) <= 1)
|
|
def_zone = ran_zone(def_zone, max(100 - (7 * distance), 5)) //Lower accurancy/longer range tradeoff. 7 is a balanced number to use.
|
|
else
|
|
def_zone = pick("head", "chest", "l_arm", "r_arm", "l_leg", "r_leg") // If we were aiming at one target but another one got hit, no accuracy is applied
|
|
|
|
if(isturf(A) && hitsound_wall)
|
|
var/volume = clamp(vol_by_damage() + 20, 0, 100)
|
|
if(suppressed)
|
|
volume = 5
|
|
playsound(loc, hitsound_wall, volume, TRUE, -1)
|
|
else if(ishuman(A))
|
|
var/mob/living/carbon/human/H = A
|
|
var/obj/item/organ/external/organ = H.get_organ(check_zone(def_zone))
|
|
if(isnull(organ))
|
|
return
|
|
|
|
var/turf/target_turf = get_turf(A)
|
|
prehit(A)
|
|
var/pre_permutation = A.atom_prehit(src)
|
|
var/permutation = -1
|
|
if(pre_permutation != ATOM_PREHIT_FAILURE)
|
|
permutation = A.bullet_act(src, def_zone) // searches for return value, could be deleted after run so check A isn't null
|
|
if(permutation == -1 || forcedodge)// the bullet passes through a dense object!
|
|
if(forcedodge)
|
|
forcedodge -= 1
|
|
loc = target_turf
|
|
if(A)
|
|
permutated.Add(A)
|
|
return 0
|
|
else
|
|
if(A && A.density && !ismob(A) && !(A.flags & ON_BORDER)) //if we hit a dense non-border obj or dense turf then we also hit one of the mobs on that tile.
|
|
var/list/mobs_list = list()
|
|
for(var/mob/living/L in target_turf)
|
|
mobs_list += L
|
|
if(length(mobs_list))
|
|
var/mob/living/picked_mob = pick(mobs_list)
|
|
prehit(picked_mob)
|
|
picked_mob.bullet_act(src, def_zone)
|
|
qdel(src)
|
|
|
|
/obj/item/projectile/Process_Spacemove(movement_dir = 0)
|
|
return 1 //Bullets don't drift in space
|
|
|
|
/obj/item/projectile/process()
|
|
if(!loc || !trajectory)
|
|
return PROCESS_KILL
|
|
if(paused || !isturf(loc))
|
|
last_projectile_move = world.time
|
|
return
|
|
var/elapsed_time_deciseconds = (world.time - last_projectile_move) + time_offset
|
|
time_offset = 0
|
|
var/required_moves = hitscan ? MOVES_HITSCAN : FLOOR(elapsed_time_deciseconds / speed, 1)
|
|
if(required_moves == MOVES_HITSCAN)
|
|
required_moves = SSprojectiles.global_max_tick_moves
|
|
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)
|
|
|
|
/obj/item/projectile/proc/pixel_move(trajectory_multiplier, hitscanning = FALSE)
|
|
if(!loc || !trajectory)
|
|
return
|
|
last_projectile_move = world.time
|
|
// Keep on course
|
|
if(!hitscanning)
|
|
var/matrix/M = new
|
|
M.Turn(Angle)
|
|
transform = M
|
|
// Iterate
|
|
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))
|
|
// if we've gone off of the map, we need to step back once so that hitscanning projectiles have a valid end turf
|
|
trajectory.increment(-trajectory_multiplier)
|
|
qdel(src)
|
|
return
|
|
if(T.z != loc.z)
|
|
trajectory_ignore_forcemove = TRUE
|
|
forceMove(T)
|
|
trajectory_ignore_forcemove = FALSE
|
|
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(original && (original.layer >= PROJECTILE_HIT_THRESHHOLD_LAYER || ismob(original)))
|
|
if(loc == get_turf(original) && !(original in permutated))
|
|
Bump(original, TRUE)
|
|
if(QDELETED(src)) //deleted on last move
|
|
return
|
|
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 = 1, flags = ANIMATION_END_NOW)
|
|
Range()
|
|
|
|
/obj/item/projectile/proc/fire(setAngle)
|
|
if(setAngle)
|
|
Angle = setAngle
|
|
if(!current || loc == current)
|
|
current = locate(clamp(x + xo, 1, world.maxx), clamp(y + yo, 1, world.maxy), z)
|
|
if(isnull(Angle))
|
|
Angle = round(get_angle(src, current))
|
|
original_angle = Angle
|
|
if(spread)
|
|
Angle += (rand() - 0.5) * spread
|
|
// Turn right away
|
|
var/matrix/M = new
|
|
M.Turn(Angle)
|
|
transform = M
|
|
// Start flying
|
|
trajectory = new(x, y, z, pixel_x, pixel_y, Angle, SSprojectiles.global_pixel_speed)
|
|
last_projectile_move = world.time
|
|
has_been_fired = TRUE
|
|
if(hitscan)
|
|
process_hitscan()
|
|
START_PROCESSING(SSprojectiles, src)
|
|
pixel_move(1, FALSE)
|
|
|
|
/obj/item/projectile/proc/reflect_back(atom/source, list/position_modifiers = list(0, 0, 0, 0, 0, -1, 1, -2, 2))
|
|
if(!starting)
|
|
return
|
|
var/new_x = starting.x + pick(position_modifiers)
|
|
var/new_y = starting.y + pick(position_modifiers)
|
|
var/turf/curloc = get_turf(source)
|
|
if(!curloc)
|
|
return
|
|
|
|
if(ismob(source))
|
|
firer = source // The reflecting mob will be the new firer
|
|
|
|
// redirect the projectile
|
|
original = locate(new_x, new_y, z)
|
|
starting = curloc
|
|
current = curloc
|
|
yo = new_y - curloc.y
|
|
xo = new_x - curloc.x
|
|
set_angle(get_angle(curloc, original))
|
|
|
|
/// A mob moving on a tile with a projectile is hit by it.
|
|
/obj/item/projectile/proc/on_atom_entered(datum/source, atom/movable/entered)
|
|
if(isliving(entered) && entered.density && !checkpass(PASSMOB))
|
|
Bump(entered, 1)
|
|
|
|
/obj/item/projectile/Destroy()
|
|
if(hitscan)
|
|
finalize_hitscan_and_generate_tracers()
|
|
STOP_PROCESSING(SSprojectiles, src)
|
|
ammo_casing = null
|
|
firer_source_atom = null
|
|
firer = null
|
|
return ..()
|
|
|
|
/obj/item/projectile/proc/dumbfire(dir)
|
|
current = get_ranged_target_turf(src, dir, world.maxx) //world.maxx is the range. Not sure how to handle this better.
|
|
fire()
|
|
|
|
|
|
/obj/item/projectile/proc/on_ricochet(atom/A)
|
|
if(!ricochet_auto_aim_angle || !ricochet_auto_aim_range)
|
|
return
|
|
|
|
var/mob/living/unlucky_sob
|
|
var/best_angle = ricochet_auto_aim_angle
|
|
for(var/mob/living/L in range(ricochet_auto_aim_range, src.loc))
|
|
if(L.stat == DEAD || !isInSight(src, L) || (!ricochet_shoots_firer && L == firer))
|
|
continue
|
|
var/our_angle = abs(closer_angle_difference(Angle, get_angle(src.loc, L.loc)))
|
|
if(our_angle < best_angle)
|
|
best_angle = our_angle
|
|
unlucky_sob = L
|
|
|
|
if(unlucky_sob)
|
|
set_angle(get_angle(src, unlucky_sob.loc))
|
|
|
|
/obj/item/projectile/proc/check_ricochet()
|
|
if(prob(ricochet_chance))
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/obj/item/projectile/proc/check_ricochet_flag(atom/A)
|
|
if((flag in list(ENERGY, LASER)) && (A.flags_ricochet & RICOCHET_SHINY))
|
|
return TRUE
|
|
|
|
if((flag in list(BOMB, BULLET)) && (A.flags_ricochet & RICOCHET_HARD))
|
|
return TRUE
|
|
|
|
return FALSE
|
|
|
|
/obj/item/projectile/set_angle(new_angle)
|
|
..()
|
|
|
|
Angle = new_angle
|
|
if(trajectory)
|
|
trajectory.set_angle(new_angle)
|
|
if(has_been_fired && hitscan && isloc(loc) && (loc != last_angle_set_hitscan_store))
|
|
last_angle_set_hitscan_store = loc
|
|
var/datum/point/point_cache = new (src)
|
|
point_cache = trajectory.copy_to()
|
|
store_hitscan_collision(point_cache)
|
|
return TRUE
|
|
|
|
/obj/item/projectile/proc/set_angle_centered(new_angle)
|
|
set_angle(new_angle)
|
|
var/list/coordinates = trajectory.return_coordinates()
|
|
trajectory.set_location(coordinates[1], coordinates[2], coordinates[3]) // Sets the trajectory to the center of the tile it bounced at
|
|
if(has_been_fired && hitscan && isloc(loc))// Handles hitscan projectiles
|
|
last_angle_set_hitscan_store = loc
|
|
var/datum/point_precise/point_cache = new (src)
|
|
point_cache.initialize_location(coordinates[1], coordinates[2], coordinates[3]) // Take the center of the hitscan collision tile
|
|
store_hitscan_collision(point_cache)
|
|
return TRUE
|
|
|
|
/obj/item/projectile/experience_pressure_difference()
|
|
return // Immune to gas flow.
|
|
|
|
/obj/item/projectile/forceMove(atom/target)
|
|
. = ..()
|
|
if(QDELETED(src)) // we coulda bumped something
|
|
return
|
|
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))
|
|
|
|
/obj/item/projectile/proc/is_reflectable(desired_reflectability_level)
|
|
if(reflectability == REFLECTABILITY_NEVER) //You'd trust coders not to try and override never reflectable things, but heaven help us I do not
|
|
return FALSE
|
|
if(reflectability < desired_reflectability_level)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/obj/item/projectile/proc/record_hitscan_start(datum/point_precise/point_cache)
|
|
if(point_cache)
|
|
beam_segments = list()
|
|
beam_index = point_cache
|
|
beam_segments[beam_index] = null //record start.
|
|
|
|
/obj/item/projectile/proc/process_hitscan()
|
|
//Safety here is to make hitscan stop if something goes wrong. Why is it equal to range * 10, when range is the maximum amount of tiles it can go? No clue.
|
|
var/safety = range * 10
|
|
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/finalize_hitscan_and_generate_tracers(impacting = TRUE)
|
|
if(trajectory && beam_index)
|
|
var/datum/point_precise/point_cache = trajectory.copy_to()
|
|
beam_segments[beam_index] = point_cache
|
|
generate_hitscan_tracers(null, hitscan_duration, impacting)
|
|
|
|
/obj/item/projectile/proc/generate_hitscan_tracers(cleanup = TRUE, duration = 3, impacting = TRUE)
|
|
if(!length(beam_segments))
|
|
return
|
|
if(tracer_type)
|
|
var/tempuid = UID()
|
|
for(var/datum/point_precise/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, tempuid)
|
|
if(muzzle_type && duration > 0)
|
|
var/datum/point_precise/p = beam_segments[1]
|
|
var/atom/movable/thing = new muzzle_type
|
|
p.move_atom_to_src(thing)
|
|
var/matrix/matrix = new
|
|
matrix.Turn(original_angle)
|
|
thing.transform = matrix
|
|
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_precise/p = beam_segments[beam_segments[length(beam_segments)]]
|
|
var/atom/movable/thing = new impact_type
|
|
p.move_atom_to_src(thing)
|
|
var/matrix/matrix = new
|
|
matrix.Turn(Angle)
|
|
thing.transform = matrix
|
|
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/proc/cleanup_beam_segments()
|
|
QDEL_LIST_ASSOC(beam_segments)
|
|
|
|
#undef MOVES_HITSCAN
|
|
#undef MUZZLE_EFFECT_PIXEL_INCREMENT
|