Merge pull request #4719 from VOREStation/upstream-merge-5636

[MIRROR] [READY]Ports /tg/station pixel projectiles, processing subsystems, timer subsystems, and some misc stuff to make it all work
This commit is contained in:
Novacat
2019-03-28 14:52:51 -04:00
committed by GitHub
99 changed files with 3641 additions and 3234 deletions

View File

@@ -1,312 +0,0 @@
/obj/effect/projectile
icon = 'icons/effects/projectiles.dmi'
icon_state = "bolt"
plane = ABOVE_PLANE
/obj/effect/projectile/New(var/turf/location)
if(istype(location))
loc = location
/obj/effect/projectile/proc/set_transform(var/matrix/M)
if(istype(M))
transform = M
/obj/effect/projectile/proc/activate(var/kill_delay = 5)
update_light()
spawn(kill_delay)
qdel(src) //see effect_system.dm - sets loc to null and lets GC handle removing these effects
return
//----------------------------
// Laser beam
//----------------------------
/obj/effect/projectile/laser/tracer
icon_state = "beam"
light_range = 2
light_power = 0.5
light_color = "#FF0D00"
/obj/effect/projectile/laser/muzzle
icon_state = "muzzle_laser"
light_range = 2
light_power = 0.5
light_color = "#FF0D00"
/obj/effect/projectile/laser/impact
icon_state = "impact_laser"
light_range = 2
light_power = 0.5
light_color = "#FF0D00"
//----------------------------
// Blue laser beam
//----------------------------
/obj/effect/projectile/laser_blue/tracer
icon_state = "beam_blue"
light_range = 2
light_power = 0.5
light_color = "#0066FF"
/obj/effect/projectile/laser_blue/muzzle
icon_state = "muzzle_blue"
light_range = 2
light_power = 0.5
light_color = "#0066FF"
/obj/effect/projectile/laser_blue/impact
icon_state = "impact_blue"
light_range = 2
light_power = 0.5
light_color = "#0066FF"
//----------------------------
// Omni laser beam
//----------------------------
/obj/effect/projectile/laser_omni/tracer
icon_state = "beam_omni"
light_range = 2
light_power = 0.5
light_color = "#00C6FF"
/obj/effect/projectile/laser_omni/muzzle
icon_state = "muzzle_omni"
light_range = 2
light_power = 0.5
light_color = "#00C6FF"
/obj/effect/projectile/laser_omni/impact
icon_state = "impact_omni"
light_range = 2
light_power = 0.5
light_color = "#00C6FF"
//----------------------------
// Xray laser beam
//----------------------------
/obj/effect/projectile/xray/tracer
icon_state = "xray"
light_range = 2
light_power = 0.5
light_color = "#00CC33"
/obj/effect/projectile/xray/muzzle
icon_state = "muzzle_xray"
light_range = 2
light_power = 0.5
light_color = "#00CC33"
/obj/effect/projectile/xray/impact
icon_state = "impact_xray"
light_range = 2
light_power = 0.5
light_color = "#00CC33"
//----------------------------
// Heavy laser beam
//----------------------------
/obj/effect/projectile/laser_heavy/tracer
icon_state = "beam_heavy"
light_range = 3
light_power = 1
light_color = "#FF0D00"
/obj/effect/projectile/laser_heavy/muzzle
icon_state = "muzzle_beam_heavy"
light_range = 3
light_power = 1
light_color = "#FF0D00"
/obj/effect/projectile/laser_heavy/impact
icon_state = "impact_beam_heavy"
light_range = 3
light_power = 1
light_color = "#FF0D00"
//----------------------------
// Pulse laser beam
//----------------------------
/obj/effect/projectile/laser_pulse/tracer
icon_state = "u_laser"
light_range = 2
light_power = 0.5
light_color = "#0066FF"
/obj/effect/projectile/laser_pulse/muzzle
icon_state = "muzzle_u_laser"
light_range = 2
light_power = 0.5
light_color = "#0066FF"
/obj/effect/projectile/laser_pulse/impact
icon_state = "impact_u_laser"
light_range = 2
light_power = 0.5
light_color = "#0066FF"
//----------------------------
// Pulse muzzle effect only
//----------------------------
/obj/effect/projectile/pulse/muzzle
icon_state = "muzzle_pulse"
light_range = 2
light_power = 0.5
light_color = "#0066FF"
//----------------------------
// Emitter beam
//----------------------------
/obj/effect/projectile/emitter/tracer
icon_state = "emitter"
light_range = 2
light_power = 0.5
light_color = "#00CC33"
/obj/effect/projectile/emitter/muzzle
icon_state = "muzzle_emitter"
light_range = 2
light_power = 0.5
light_color = "#00CC33"
/obj/effect/projectile/emitter/impact
icon_state = "impact_emitter"
light_range = 2
light_power = 0.5
light_color = "#00CC33"
//----------------------------
// Stun beam
//----------------------------
/obj/effect/projectile/stun/tracer
icon_state = "stun"
light_range = 2
light_power = 0.5
light_color = "#FFFFFF"
/obj/effect/projectile/stun/muzzle
icon_state = "muzzle_stun"
light_range = 2
light_power = 0.5
light_color = "#FFFFFF"
/obj/effect/projectile/stun/impact
icon_state = "impact_stun"
light_range = 2
light_power = 0.5
light_color = "#FFFFFF"
//----------------------------
// Bullet
//----------------------------
/obj/effect/projectile/bullet/muzzle
icon_state = "muzzle_bullet"
light_range = 2
light_power = 0.5
light_color = "#FFFFFF"
//----------------------------
// Lightning beam
//----------------------------
/obj/effect/projectile/lightning/tracer
icon_state = "lightning"
light_range = 2
light_power = 0.5
light_color = "#00C6FF"
/obj/effect/projectile/lightning/muzzle
icon_state = "muzzle_lightning"
light_range = 2
light_power = 0.5
light_color = "#00C6FF"
/obj/effect/projectile/lightning/impact
icon_state = "impact_lightning"
light_range = 2
light_power = 0.5
light_color = "#00C6FF"
//----------------------------
// Dark matter stun
//----------------------------
/obj/effect/projectile/darkmatterstun/tracer
icon_state = "darkt"
light_range = 2
light_power = 0.5
light_color = "#8837A3"
/obj/effect/projectile/darkmatterstun/muzzle
icon_state = "muzzle_darkt"
light_range = 2
light_power = 0.5
light_color = "#8837A3"
/obj/effect/projectile/darkmatterstun/impact
icon_state = "impact_darkt"
light_range = 2
light_power = 0.5
light_color = "#8837A3"
//----------------------------
// Dark matter
//----------------------------
/obj/effect/projectile/darkmatter/tracer
icon_state = "darkb"
light_range = 2
light_power = 0.5
light_color = "#8837A3"
/obj/effect/projectile/darkmatter/muzzle
icon_state = "muzzle_darkb"
light_range = 2
light_power = 0.5
light_color = "#8837A3"
/obj/effect/projectile/darkmatter/impact
icon_state = "impact_darkb"
light_range = 2
light_power = 0.5
light_color = "#8837A3"
//----------------------------
// Inversion / Cult
//----------------------------
/obj/effect/projectile/inversion/tracer
icon_state = "invert"
light_range = 2
light_power = -2
light_color = "#FFFFFF"
/obj/effect/projectile/inversion/muzzle
icon_state = "muzzle_invert"
light_range = 2
light_power = -2
light_color = "#FFFFFF"
/obj/effect/projectile/inversion/impact
icon_state = "impact_invert"
light_range = 2
light_power = -2
light_color = "#FFFFFF"
//----------------------------
// Magnetohydronamic Howitzer
//----------------------------
/obj/effect/projectile/tungsten/tracer
icon_state = "mhd_laser"
light_range = 4
light_power = 3
light_color = "#3300ff"
/obj/effect/projectile/tungsten/muzzle
icon_state = "muzzle_mhd_laser"
light_range = 4
light_power = 3
light_color = "#3300ff"
/obj/effect/projectile/tungsten/impact
icon_state = "impact_mhd_laser"
light_range = 4
light_power = 3
light_color = "#3300ff"

View File

@@ -470,7 +470,8 @@
P.shot_from = src.name
P.silenced = silenced
P.launch(target)
P.old_style_target(target)
P.fire()
last_shot = world.time
@@ -645,24 +646,17 @@
/obj/item/weapon/gun/proc/process_projectile(obj/projectile, mob/user, atom/target, var/target_zone, var/params=null)
var/obj/item/projectile/P = projectile
if(!istype(P))
return 0 //default behaviour only applies to true projectiles
if(params)
P.set_clickpoint(params)
return FALSE //default behaviour only applies to true projectiles
//shooting while in shock
var/x_offset = 0
var/y_offset = 0
var/forcespread
if(istype(user, /mob/living/carbon))
var/mob/living/carbon/mob = user
if(mob.shock_stage > 120)
y_offset = rand(-2,2)
x_offset = rand(-2,2)
forcespread = rand(50, 50)
else if(mob.shock_stage > 70)
y_offset = rand(-1,1)
x_offset = rand(-1,1)
var/launched = !P.launch_from_gun(target, user, src, target_zone, x_offset, y_offset)
forcespread = rand(-25, 25)
var/launched = !P.launch_from_gun(target, target_zone, user, params, null, forcespread, src)
if(launched)
play_fire_sound(user, P)

View File

@@ -100,7 +100,7 @@
damage = 32
damage_type = BRUTE
check_armour = "bomb"
kill_count = 3 // Our "range" var is named "kill_count". Yes it is.
range = 3 // Our "range" var is named "kill_count". Yes it is.
var/pressure_decrease = 0.25
var/turf_aoe = FALSE
@@ -219,7 +219,7 @@
cost = 24 //so you can fit four plus a tracer cosmetic
/obj/item/borg/upgrade/modkit/range/modify_projectile(obj/item/projectile/kinetic/K)
K.kill_count += modifier
K.range += modifier
//Damage

View File

@@ -3,7 +3,7 @@
icon_state = "dart"
damage = 5
var/reagent_amount = 15
kill_count = 15 //shorter range
range = 15 //shorter range
muzzle_type = null

View File

@@ -79,9 +79,9 @@
damage_type = HALLOSS
light_color = "#8837A3"
muzzle_type = /obj/effect/projectile/darkmatterstun/muzzle
tracer_type = /obj/effect/projectile/darkmatterstun/tracer
impact_type = /obj/effect/projectile/darkmatterstun/impact
muzzle_type = /obj/effect/projectile/muzzle/darkmatterstun
tracer_type = /obj/effect/projectile/tracer/darkmatterstun
impact_type = /obj/effect/projectile/impact/darkmatterstun
/obj/item/projectile/beam/darkmatter
name = "dark matter bolt"
@@ -95,9 +95,9 @@
embed_chance = 0
muzzle_type = /obj/effect/projectile/darkmatter/muzzle
tracer_type = /obj/effect/projectile/darkmatter/tracer
impact_type = /obj/effect/projectile/darkmatter/impact
muzzle_type = /obj/effect/projectile/muzzle/darkmatter
tracer_type = /obj/effect/projectile/tracer/darkmatter
impact_type = /obj/effect/projectile/impact/darkmatter
/obj/item/projectile/energy/darkmatter
name = "dark matter pellet"

View File

@@ -1,37 +1,84 @@
/*
#define BRUTE "brute"
#define BURN "burn"
#define TOX "tox"
#define OXY "oxy"
#define CLONE "clone"
#define ADD "add"
#define SET "set"
*/
#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 = 1
unacidable = 1
anchored = 1 //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.
density = FALSE
anchored = TRUE
unacidable = TRUE
pass_flags = PASSTABLE
mouse_opacity = 0
var/bumped = 0 //Prevents it from hitting more than one guy at once
////TG PROJECTILE SYTSEM
//Projectile stuff
var/range = 50
var/originalRange
//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
//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
//Targetting
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
//Misc/Polaris variables
var/def_zone = "" //Aiming at
var/mob/firer = null//Who shot it
var/silenced = 0 //Attack message
var/yo = null
var/xo = null
var/current = null
var/shot_from = "" // name of the object which shot us
var/atom/original = null // the target clicked (not necessarily where the projectile is headed). Should probably be renamed to 'target' or something.
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
var/accuracy = 0
var/dispersion = 0.0
@@ -45,7 +92,6 @@
var/check_armour = "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/penetrating = 0 //If greater than zero, the projectile will pass through dense objects as specified by on_penetrate()
var/kill_count = 50 //This will de-increment every process(). When 0, it will delete the projectile.
//Effects
var/incendiary = 0 //1 for ignite on hit, 2 for trail of fire. 3 maybe later for burst of fire around the impact point. - Mech
var/flammability = 0 //Amount of fire stacks to add for the above.
@@ -64,178 +110,365 @@
embed_chance = 0 //Base chance for a projectile to embed
var/hitscan = 0 // whether the projectile should be hitscan
var/step_delay = 1 // the delay between iterations if not a hitscan projectile
// effect types to be used
var/muzzle_type
var/tracer_type
var/impact_type
var/fire_sound = 'sound/weapons/Gunshot_old.ogg' // Can be overriden in gun.dm's fire_sound var. It can also be null but I don't know why you'd ever want to do that. -Ace
var/vacuum_traversal = 1 //Determines if the projectile can exist in vacuum, if false, the projectile will be deleted if it enters vacuum.
var/vacuum_traversal = TRUE //Determines if the projectile can exist in vacuum, if false, the projectile will be deleted if it enters vacuum.
var/datum/plot_vector/trajectory // used to plot the path of the projectile
var/datum/vector_loc/location // current location of the projectile in pixel space
var/matrix/effect_transform // matrix to rotate and scale projectile effects - putting it here so it doesn't
// have to be recreated multiple times
/obj/item/projectile/proc/Range()
range--
if(range <= 0 && loc)
on_range()
//TODO: make it so this is called more reliably, instead of sometimes by bullet_act() and sometimes not
/obj/item/projectile/proc/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null)
if(blocked >= 100) return 0//Full block
if(!isliving(target)) return 0
// if(isanimal(target)) return 0
var/mob/living/L = target
L.apply_effects(stun, weaken, paralyze, irradiate, stutter, eyeblur, drowsy, agony, blocked, incendiary, flammability) // add in AGONY!
if(modifier_type_to_apply)
L.add_modifier(modifier_type_to_apply, modifier_duration)
return 1
/obj/item/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range
qdel(src)
//called when the projectile stops flying because it collided with something
/obj/item/projectile/proc/on_impact(var/atom/A)
impact_effect(effect_transform) // generate impact effect
if(damage && damage_type == BURN)
var/turf/T = get_turf(A)
if(T)
T.hotspot_expose(700, 5)
/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/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(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)
before_move()
step_towards(src, T)
hitscan_last = loc
after_move()
if(can_hit_target(original, permutated))
Bump(original)
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/Crossed(atom/movable/AM) //A mob moving on a tile with a projectile is hit by it.
..()
if(isliving(AM) && (AM.density || AM == original))
Bump(AM)
/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
/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/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/fire(angle, atom/direct_target)
//If no angle needs to resolve it from xo/yo!
if(direct_target)
direct_target.bullet_act(src, def_zone)
qdel(src)
return
if(isnum(angle))
setAngle(angle)
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))
crash_with("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))
if(dispersion)
setAngle(Angle + rand(-dispersion, dispersion))
original_angle = Angle
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
permutated = list()
originalRange = range
fired = TRUE
if(hitscan)
process_hitscan()
START_PROCESSING(SSprojectiles, src)
pixel_move(1, FALSE) //move it now!
/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/proc/before_move()
return
//Checks if the projectile is eligible for embedding. Not that it necessarily will.
/obj/item/projectile/proc/can_embed()
//embed must be enabled and damage type must be brute
if(embed_chance == 0 || damage_type != BRUTE)
return 0
return 1
/obj/item/projectile/proc/after_move()
return
/obj/item/projectile/proc/get_structure_damage()
if(damage_type == BRUTE || damage_type == BURN)
return damage
return 0
/obj/item/projectile/proc/store_hitscan_collision(datum/point/pcache)
beam_segments[beam_index] = pcache
beam_index = pcache
beam_segments[beam_index] = null
//return 1 if the projectile should be allowed to pass through after all, 0 if not.
/obj/item/projectile/proc/check_penetrate(var/atom/A)
return 1
//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)
/obj/item/projectile/proc/check_fire(atom/target as mob, var/mob/living/user as mob) //Checks if you can hit them or not.
check_trajectory(target, user, pass_flags, flags)
if(isliving(source) && params)
var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, params)
p_x = calculated[2]
p_y = calculated[3]
//sets the click point of the projectile using mouse input params
/obj/item/projectile/proc/set_clickpoint(var/params)
setAngle(calculated[1] + spread)
else if(targloc)
yo = targloc.y - curloc.y
xo = targloc.x - curloc.x
setAngle(Get_Angle(src, targloc) + spread)
else
crash_with("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"], ",")
//randomize clickpoint a bit based on dispersion
if(dispersion)
var/radius = round((dispersion*0.443)*world.icon_size*0.8) //0.443 = sqrt(pi)/4 = 2a, where a is the side length of a square that shares the same area as a circle with diameter = dispersion
p_x = between(0, p_x + rand(-radius, radius), world.icon_size)
p_y = between(0, p_y + rand(-radius, radius), world.icon_size)
//Split X+Pixel_X up into list(X, Pixel_X)
var/list/screen_loc_X = splittext(screen_loc_params[1],":")
//called to launch a projectile
/obj/item/projectile/proc/launch(atom/target, var/target_zone, var/x_offset=0, var/y_offset=0, var/angle_offset=0)
var/turf/curloc = get_turf(src)
var/turf/targloc = get_turf(target)
if (!istype(targloc) || !istype(curloc))
return 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
if(combustion)
curloc.hotspot_expose(700, 5)
//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 = user.client? getviewsize(user.client.view) : world.view
var/screenviewX = screenview[1] * world.icon_size
var/screenviewY = screenview[2] * world.icon_size
if(targloc == curloc) //Shooting something in the same turf
target.bullet_act(src, target_zone)
on_impact(target)
qdel(src)
return 0
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/proc/redirect(x, y, starting, source)
old_style_target(locate(x, y, z), starting? get_turf(starting) : get_turf(source))
/obj/item/projectile/proc/old_style_target(atom/target, atom/source)
if(!source)
source = get_turf(src)
starting = source
original = target
def_zone = target_zone
setAngle(Get_Angle(source, target))
spawn()
setup_trajectory(curloc, targloc, x_offset, y_offset, angle_offset) //plot the initial trajectory
process()
/obj/item/projectile/Destroy()
if(hitscan)
finalize_hitscan_and_generate_tracers()
STOP_PROCESSING(SSprojectiles, src)
cleanup_beam_segments()
qdel(trajectory)
return ..()
return 0
/obj/item/projectile/proc/cleanup_beam_segments()
QDEL_LIST_ASSOC(beam_segments)
beam_segments = list()
qdel(beam_index)
//called to launch a projectile from a gun
/obj/item/projectile/proc/launch_from_gun(atom/target, mob/user, obj/item/weapon/gun/launcher, var/target_zone, var/x_offset=0, var/y_offset=0)
if(user == target) //Shooting yourself
user.bullet_act(src, target_zone)
on_impact(user)
qdel(src)
return 0
loc = get_turf(user) //move the projectile out into the world
firer = user
shot_from = launcher.name
silenced = launcher.silenced
return launch(target, target_zone, x_offset, y_offset)
//Used to change the direction of the projectile in flight.
/obj/item/projectile/proc/redirect(var/new_x, var/new_y, var/atom/starting_loc, var/mob/new_firer=null)
var/turf/new_target = locate(new_x, new_y, src.z)
original = new_target
if(new_firer)
firer = src
setup_trajectory(starting_loc, new_target)
//Called when the projectile intercepts a mob. Returns 1 if the projectile hit the mob, 0 if it missed and should keep flying.
/obj/item/projectile/proc/attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier=0)
if(!istype(target_mob))
return
//roll to-hit
miss_modifier = max(15*(distance-2) - accuracy + miss_modifier + target_mob.get_evasion(), 0)
var/hit_zone = get_zone_with_miss_chance(def_zone, target_mob, miss_modifier, ranged_attack=(distance > 1 || original != target_mob)) //if the projectile hits a target we weren't originally aiming at then retain the chance to miss
var/result = PROJECTILE_FORCE_MISS
if(hit_zone)
def_zone = hit_zone //set def_zone, so if the projectile ends up hitting someone else later (to be implemented), it is more likely to hit the same part
result = target_mob.bullet_act(src, def_zone)
if(result == PROJECTILE_FORCE_MISS)
if(!silenced)
visible_message("<span class='notice'>\The [src] misses [target_mob] narrowly!</span>")
return 0
//hit messages
if(silenced)
to_chat(target_mob, "<span class='danger'>You've been hit in the [parse_zone(def_zone)] by \the [src]!</span>")
/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
visible_message("<span class='danger'>\The [target_mob] is hit by \the [src] in the [parse_zone(def_zone)]!</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
return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume.
//admin logs
if(!no_attack_log)
if(istype(firer, /mob) && istype(target_mob))
add_attack_logs(firer,target_mob,"Shot with \a [src.type] projectile")
/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)
//sometimes bullet_act() will want the projectile to continue flying
if (result == PROJECTILE_CONTINUE)
return 0
/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()
return 1
//Returns true if the target atom is on our current turf and above the right layer
/obj/item/projectile/proc/can_hit_target(atom/target, var/list/passthrough)
return (target && ((target.layer >= TABLE_LAYER) || ismob(target)) && (loc == get_turf(target)) && (!(target in passthrough)))
/obj/item/projectile/Bump(atom/A as mob|obj|turf|area, forced=0)
if(A == src)
return 0 //no
/obj/item/projectile/Bump(atom/A)
if(A in permutated)
return FALSE
if(firer && !reflected)
if(A == firer || (A == firer.loc && istype(A, /obj/mecha))) //cannot shoot yourself or your mech
trajectory_ignore_forcemove = TRUE
forceMove(get_turf(A))
trajectory_ignore_forcemove = FALSE
return FALSE
if(A == firer)
loc = A.loc
return 0 //cannot shoot yourself
var/distance = get_dist(starting, get_turf(src))
var/turf/target_turf = get_turf(A)
var/passthrough = FALSE
if((bumped && !forced) || (A in permutated))
return 0
var/passthrough = 0 //if the projectile should continue flying
var/distance = get_dist(starting,loc)
bumped = 1
if(ismob(A))
var/mob/M = A
if(istype(A, /mob/living))
@@ -246,13 +479,13 @@
var/shield_chance = min(80, (30 * (M.mob_size / 10))) //Small mobs have a harder time keeping a dead body as a shield than a human-sized one. Unathi would have an easier job, if they are made to be SIZE_LARGE in the future. -Mech
if(prob(shield_chance))
visible_message("<span class='danger'>\The [M] uses [G.affecting] as a shield!</span>")
if(Bump(G.affecting, forced=1))
if(Bump(G.affecting))
return
else
visible_message("<span class='danger'>\The [M] tries to use [G.affecting] as a shield, but fails!</span>")
else
visible_message("<span class='danger'>\The [M] uses [G.affecting] as a shield!</span>")
if(Bump(G.affecting, forced=1))
if(Bump(G.affecting))
return //If Bump() returns 0 (keep going) then we continue on to attack M.
passthrough = !attack_mob(M, distance)
@@ -269,234 +502,113 @@
//penetrating projectiles can pass through things that otherwise would not let them
if(!passthrough && penetrating > 0)
if(check_penetrate(A))
passthrough = 1
passthrough = TRUE
penetrating--
//the bullet passes through a dense object!
if(passthrough)
//move ourselves onto A so we can continue on our way.
if(A)
if(istype(A, /turf))
loc = A
else
loc = A.loc
permutated.Add(A)
bumped = 0 //reset bumped variable!
return 0
//stop flying
on_impact(A)
density = 0
invisibility = 101
trajectory_ignore_forcemove = TRUE
forceMove(target_turf)
permutated.Add(A)
trajectory_ignore_forcemove = FALSE
return FALSE
if(A)
on_impact(A)
qdel(src)
return TRUE
//TODO: make it so this is called more reliably, instead of sometimes by bullet_act() and sometimes not
/obj/item/projectile/proc/on_hit(atom/target, blocked = 0, def_zone)
if(blocked >= 100) return 0//Full block
if(!isliving(target)) return 0
// if(isanimal(target)) return 0
var/mob/living/L = target
L.apply_effects(stun, weaken, paralyze, irradiate, stutter, eyeblur, drowsy, agony, blocked, incendiary, flammability) // add in AGONY!
if(modifier_type_to_apply)
L.add_modifier(modifier_type_to_apply, modifier_duration)
return 1
/obj/item/projectile/ex_act()
return //explosions probably shouldn't delete projectiles
//called when the projectile stops flying because it Bump'd with something
/obj/item/projectile/proc/on_impact(atom/A)
if(damage && damage_type == BURN)
var/turf/T = get_turf(A)
if(T)
T.hotspot_expose(700, 5)
/obj/item/projectile/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
//Checks if the projectile is eligible for embedding. Not that it necessarily will.
/obj/item/projectile/proc/can_embed()
//embed must be enabled and damage type must be brute
if(embed_chance == 0 || damage_type != BRUTE)
return 0
return 1
/obj/item/projectile/process()
var/first_step = 1
/obj/item/projectile/proc/get_structure_damage()
if(damage_type == BRUTE || damage_type == BURN)
return damage
return 0
spawn while(src && src.loc)
if(kill_count-- < 1)
on_impact(src.loc) //for any final impact behaviours
qdel(src)
return
if((!( current ) || loc == current))
current = locate(min(max(x + xo, 1), world.maxx), min(max(y + yo, 1), world.maxy), z)
if((x == 1 || x == world.maxx || y == 1 || y == world.maxy))
qdel(src)
return
//return 1 if the projectile should be allowed to pass through after all, 0 if not.
/obj/item/projectile/proc/check_penetrate(atom/A)
return 1
trajectory.increment() // increment the current location
location = trajectory.return_location(location) // update the locally stored location data
update_light() //energy projectiles will look glowy and fun
/obj/item/projectile/proc/check_fire(atom/target as mob, mob/living/user as mob) //Checks if you can hit them or not.
check_trajectory(target, user, pass_flags, flags)
if(!location)
qdel(src) // if it's left the world... kill it
return
/obj/item/projectile/CanPass()
return TRUE
if (is_below_sound_pressure(get_turf(src)) && !vacuum_traversal) //Deletes projectiles that aren't supposed to bein vacuum if they leave pressurised areas
qdel(src)
return
//Called when the projectile intercepts a mob. Returns 1 if the projectile hit the mob, 0 if it missed and should keep flying.
/obj/item/projectile/proc/attack_mob(mob/living/target_mob, distance, miss_modifier = 0)
if(!istype(target_mob))
return
before_move()
Move(location.return_turf())
after_move()
//roll to-hit
miss_modifier = max(15*(distance-2) - accuracy + miss_modifier + target_mob.get_evasion(), 0)
var/hit_zone = get_zone_with_miss_chance(def_zone, target_mob, miss_modifier, ranged_attack=(distance > 1 || original != target_mob)) //if the projectile hits a target we weren't originally aiming at then retain the chance to miss
if(!bumped && !isturf(original))
if(loc == get_turf(original))
if(!(original in permutated))
if(Bump(original))
return
var/result = PROJECTILE_FORCE_MISS
if(hit_zone)
def_zone = hit_zone //set def_zone, so if the projectile ends up hitting someone else later (to be implemented), it is more likely to hit the same part
result = target_mob.bullet_act(src, def_zone)
if(first_step)
muzzle_effect(effect_transform)
first_step = 0
else if(!bumped)
tracer_effect(effect_transform)
if(result == PROJECTILE_FORCE_MISS)
if(!silenced)
visible_message("<span class='notice'>\The [src] misses [target_mob] narrowly!</span>")
return FALSE
if(incendiary >= 2) //This should cover the bases of 'Why is there fuel here?' in a much cleaner way than previous.
if(src && src.loc) //Safety.
if(!src.loc.density)
var/trail_volume = (flammability * 0.20)
new /obj/effect/decal/cleanable/liquid_fuel/flamethrower_fuel(src.loc, trail_volume, src.dir)
if(!hitscan)
sleep(step_delay) //add delay between movement iterations if it's not a hitscan weapon
/obj/item/projectile/proc/before_move()
return
/obj/item/projectile/proc/after_move()
return
/obj/item/projectile/proc/setup_trajectory(turf/startloc, turf/targloc, var/x_offset = 0, var/y_offset = 0)
// setup projectile state
starting = startloc
current = startloc
yo = targloc.y - startloc.y + y_offset
xo = targloc.x - startloc.x + x_offset
// trajectory dispersion
var/offset = 0
if(dispersion)
var/radius = round(dispersion*9, 1)
offset = rand(-radius, radius)
// plot the initial trajectory
trajectory = new()
trajectory.setup(starting, original, pixel_x, pixel_y, angle_offset=offset)
// generate this now since all visual effects the projectile makes can use it
effect_transform = new()
effect_transform.Scale(trajectory.return_hypotenuse(), 1)
effect_transform.Turn(-trajectory.return_angle()) //no idea why this has to be inverted, but it works
transform = turn(transform, -(trajectory.return_angle() + 90)) //no idea why 90 needs to be added, but it works
/obj/item/projectile/proc/muzzle_effect(var/matrix/T)
//hit messages
if(silenced)
return
to_chat(target_mob, "<span class='danger'>You've been hit in the [parse_zone(def_zone)] by \the [src]!</span>")
else
visible_message("<span class='danger'>\The [target_mob] is hit by \the [src] in the [parse_zone(def_zone)]!</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(ispath(muzzle_type))
var/obj/effect/projectile/M = new muzzle_type(get_turf(src))
//admin logs
if(!no_attack_log)
if(istype(firer, /mob) && istype(target_mob))
add_attack_logs(firer,target_mob,"Shot with \a [src.type] projectile")
if(istype(M))
M.set_transform(T)
M.pixel_x = location.pixel_x
M.pixel_y = location.pixel_y
M.update_light()
M.activate()
//sometimes bullet_act() will want the projectile to continue flying
if (result == PROJECTILE_CONTINUE)
return FALSE
/obj/item/projectile/proc/tracer_effect(var/matrix/M)
if(ispath(tracer_type))
var/obj/effect/projectile/P = new tracer_type(location.loc)
return TRUE
if(istype(P))
P.set_transform(M)
P.pixel_x = location.pixel_x
P.pixel_y = location.pixel_y
P.update_light()
if(!hitscan)
P.activate(step_delay) //if not a hitscan projectile, remove after a single delay
else
P.activate()
/obj/item/projectile/proc/impact_effect(var/matrix/M)
if(ispath(tracer_type) && location)
var/obj/effect/projectile/P = new impact_type(location.loc)
if(istype(P))
P.set_transform(M)
P.pixel_x = location.pixel_x
P.pixel_y = location.pixel_y
P.update_light()
P.activate()
//"Tracing" projectile
/obj/item/projectile/test //Used to see if you can hit them.
invisibility = 101 //Nope! Can't see me!
yo = null
xo = null
var/result = 0 //To pass the message back to the gun.
var/atom/movable/result_ref = null // The thing that got hit that made the check return true.
/obj/item/projectile/test/Bump(atom/A as mob|obj|turf|area)
if(A == firer)
loc = A.loc
return //cannot shoot yourself
if(istype(A, /obj/item/projectile))
return
if(istype(A, /obj/structure/foamedmetal)) //Turrets can detect through foamed metal, but will have to blast through it. Similar to windows, if someone runs behind it, a person should probably just not shoot.
return
if(istype(A, /obj/structure/girder)) //They see you there.
return
if(istype(A, /obj/structure/door_assembly)) //And through there.
return
if(istype(A, /obj/structure)) //Unanchored things you can shove around will still keep the turret or other firing at your position. Aim intent still functions.
var/obj/structure/S = A
if(!S.anchored)
return
if(istype(A, /mob/living) || istype(A, /obj/mecha) || istype(A, /obj/vehicle))
result_ref = A
result = 2 //We hit someone, return 1!
return
result = 1
return
/obj/item/projectile/test/launch(atom/target)
var/turf/curloc = get_turf(src)
var/turf/targloc = get_turf(target)
if(!curloc || !targloc)
return 0
/obj/item/projectile/proc/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0)
original = target
def_zone = check_zone(target_zone)
firer = user
var/direct_target
if(get_turf(target) == get_turf(src))
direct_target = target
//plot the initial trajectory
setup_trajectory(curloc, targloc)
return process(targloc)
preparePixelProjectile(target, user? user : get_turf(src), params, forced_spread)
return fire(angle_override, direct_target)
/obj/item/projectile/test/process(var/turf/targloc)
while(src) //Loop on through!
if(result)
return result_ref
// return (result - 1)
if((!( targloc ) || loc == targloc))
targloc = locate(min(max(x + xo, 1), world.maxx), min(max(y + yo, 1), world.maxy), z) //Finding the target turf at map edge
//called to launch a projectile from a gun
/obj/item/projectile/proc/launch_from_gun(atom/target, target_zone, mob/user, params, angle_override, forced_spread, obj/item/weapon/gun/launcher)
trajectory.increment() // increment the current location
location = trajectory.return_location(location) // update the locally stored location data
shot_from = launcher.name
silenced = launcher.silenced
Move(location.return_turf())
var/mob/living/M = locate() in get_turf(src)
if(istype(M)) //If there is someting living...
result_ref = M
return result_ref //Return 1
else
M = locate() in get_step(src,targloc)
if(istype(M))
result_ref = M
return result_ref
//Helper proc to check if you can hit them or not.
/proc/check_trajectory(atom/target as mob|obj, atom/firer as mob|obj, var/pass_flags=PASSTABLE|PASSGLASS|PASSGRILLE, flags=null)
if(!istype(target) || !istype(firer))
return 0
var/obj/item/projectile/test/trace = new /obj/item/projectile/test(get_turf(firer)) //Making the test....
//Set the flags and pass flags to that of the real projectile...
if(!isnull(flags))
trace.flags = flags
trace.pass_flags = pass_flags
var/output = trace.launch(target) //Test it!
qdel(trace) //No need for it anymore
return output //Send it back to the gun!
return launch_projectile(target, target_zone, user, params, angle_override, forced_spread)

View File

@@ -9,12 +9,16 @@
/obj/item/projectile/arc
name = "arcing shot"
icon_state = "fireball" // WIP
step_delay = 2 // Travel a bit slower, to really sell the arc visuals.
speed = 2 // Travel a bit slower, to really sell the arc visuals.
movement_type = UNSTOPPABLE
plane = ABOVE_PLANE // Since projectiles are 'in the air', they might visually overlap mobs while in flight, so the projectile needs to be above their plane.
var/target_distance = null // How many tiles the impact site is.
var/fired_dir = null // Which direction was the projectile fired towards. Needed to invert the projectile turning based on if facing left or right.
var/obj/effect/projectile_shadow/shadow = null // Visual indicator for the projectile's 'true' position. Needed due to being bound to two dimensions in reality.
/obj/item/projectile/arc/Bump()
return
/obj/item/projectile/arc/Initialize()
shadow = new(get_turf(src))
return ..()
@@ -23,33 +27,32 @@
QDEL_NULL(shadow)
return ..()
/obj/item/projectile/arc/Bump(atom/A, forced=0)
return 0
// if(get_turf(src) != original)
// return 0
// else
// return ..()
// This is a test projectile in the sense that its testing the code to make sure it works,
// as opposed to a 'can I hit this thing' projectile.
/obj/item/projectile/arc/test/on_impact(turf/T)
new /obj/effect/explosion(T)
return ..()
/obj/item/projectile/arc/launch(atom/target, target_zone, x_offset=0, y_offset=0, angle_offset=0)
var/expected_distance = get_dist(target, loc)
kill_count = expected_distance // So the projectile "hits the ground."
/obj/item/projectile/arc/old_style_target(target, source)
var/source_loc = get_turf(source) || get_turf(src)
var/expected_distance = get_dist(target, source_loc)
range = expected_distance // So the projectile "hits the ground."
target_distance = expected_distance
fired_dir = get_dir(loc, target)
..() // Does the regular launching stuff.
fired_dir = get_dir(source_loc, target)
..()
if(fired_dir & EAST)
transform = turn(transform, -45)
else if(fired_dir & WEST)
transform = turn(transform, 45)
/obj/item/projectile/arc/on_range()
on_impact(loc)
return ..()
// Visuals.
/obj/item/projectile/arc/after_move()
if(QDELETED(src))
return
// Handle projectile turning in flight.
// This won't turn if fired north/south, as it looks weird.
var/turn_per_step = 90 / target_distance
@@ -73,7 +76,7 @@
var/projectile_position = arc_progress / target_distance
var/sine_position = projectile_position * 180
var/pixel_z_position = arc_max_height * sin(sine_position)
animate(src, pixel_z = pixel_z_position, time = step_delay)
animate(src, pixel_z = pixel_z_position, time = speed)
// Update our shadow.
shadow.forceMove(loc)

View File

@@ -15,9 +15,9 @@
light_power = 0.5
light_color = "#FF0D00"
muzzle_type = /obj/effect/projectile/laser/muzzle
tracer_type = /obj/effect/projectile/laser/tracer
impact_type = /obj/effect/projectile/laser/impact
muzzle_type = /obj/effect/projectile/muzzle/laser
tracer_type = /obj/effect/projectile/tracer/laser
impact_type = /obj/effect/projectile/impact/laser
/obj/item/projectile/beam/practice
name = "laser"
@@ -54,9 +54,9 @@
light_power = 1
light_color = "#FF0D00"
muzzle_type = /obj/effect/projectile/laser_heavy/muzzle
tracer_type = /obj/effect/projectile/laser_heavy/tracer
impact_type = /obj/effect/projectile/laser_heavy/impact
muzzle_type = /obj/effect/projectile/muzzle/laser_heavy
tracer_type = /obj/effect/projectile/tracer/laser_heavy
impact_type = /obj/effect/projectile/impact/laser_heavy
/obj/item/projectile/beam/heavylaser/fakeemitter
name = "emitter beam"
@@ -64,9 +64,9 @@
fire_sound = 'sound/weapons/emitter.ogg'
light_color = "#00CC33"
muzzle_type = /obj/effect/projectile/emitter/muzzle
tracer_type = /obj/effect/projectile/emitter/tracer
impact_type = /obj/effect/projectile/emitter/impact
muzzle_type = /obj/effect/projectile/muzzle/emitter
tracer_type = /obj/effect/projectile/tracer/emitter
impact_type = /obj/effect/projectile/impact/emitter
/obj/item/projectile/beam/heavylaser/cannon
damage = 80
@@ -81,9 +81,9 @@
armor_penetration = 50
light_color = "#00CC33"
muzzle_type = /obj/effect/projectile/xray/muzzle
tracer_type = /obj/effect/projectile/xray/tracer
impact_type = /obj/effect/projectile/xray/impact
muzzle_type = /obj/effect/projectile/muzzle/xray
tracer_type = /obj/effect/projectile/tracer/xray
impact_type = /obj/effect/projectile/impact/xray
/obj/item/projectile/beam/cyan
name = "cyan beam"
@@ -91,9 +91,9 @@
damage = 40
light_color = "#00C6FF"
muzzle_type = /obj/effect/projectile/laser_omni/muzzle
tracer_type = /obj/effect/projectile/laser_omni/tracer
impact_type = /obj/effect/projectile/laser_omni/impact
muzzle_type = /obj/effect/projectile/muzzle/laser_omni
tracer_type = /obj/effect/projectile/tracer/laser_omni
impact_type = /obj/effect/projectile/impact/laser_omni
/obj/item/projectile/beam/pulse
name = "pulse"
@@ -103,9 +103,9 @@
armor_penetration = 100
light_color = "#0066FF"
muzzle_type = /obj/effect/projectile/laser_pulse/muzzle
tracer_type = /obj/effect/projectile/laser_pulse/tracer
impact_type = /obj/effect/projectile/laser_pulse/impact
muzzle_type = /obj/effect/projectile/muzzle/laser_pulse
tracer_type = /obj/effect/projectile/tracer/laser_pulse
impact_type = /obj/effect/projectile/impact/laser_pulse
/obj/item/projectile/beam/pulse/on_hit(var/atom/target, var/blocked = 0)
if(isturf(target))
@@ -119,9 +119,9 @@
damage = 0 // The actual damage is computed in /code/modules/power/singularity/emitter.dm
light_color = "#00CC33"
muzzle_type = /obj/effect/projectile/emitter/muzzle
tracer_type = /obj/effect/projectile/emitter/tracer
impact_type = /obj/effect/projectile/emitter/impact
muzzle_type = /obj/effect/projectile/muzzle/emitter
tracer_type = /obj/effect/projectile/tracer/emitter
impact_type = /obj/effect/projectile/impact/emitter
/obj/item/projectile/beam/lastertag/blue
name = "lasertag beam"
@@ -134,9 +134,9 @@
combustion = FALSE
muzzle_type = /obj/effect/projectile/laser_blue/muzzle
tracer_type = /obj/effect/projectile/laser_blue/tracer
impact_type = /obj/effect/projectile/laser_blue/impact
muzzle_type = /obj/effect/projectile/muzzle/laser_blue
tracer_type = /obj/effect/projectile/tracer/laser_blue
impact_type = /obj/effect/projectile/impact/laser_blue
/obj/item/projectile/beam/lastertag/blue/on_hit(var/atom/target, var/blocked = 0)
if(istype(target, /mob/living/carbon/human))
@@ -173,9 +173,9 @@
combustion = FALSE
muzzle_type = /obj/effect/projectile/laser_omni/muzzle
tracer_type = /obj/effect/projectile/laser_omni/tracer
impact_type = /obj/effect/projectile/laser_omni/impact
muzzle_type = /obj/effect/projectile/muzzle/laser_omni
tracer_type = /obj/effect/projectile/tracer/laser_omni
impact_type = /obj/effect/projectile/impact/laser_omni
/obj/item/projectile/beam/lastertag/omni/on_hit(var/atom/target, var/blocked = 0)
if(istype(target, /mob/living/carbon/human))
@@ -192,9 +192,9 @@
armor_penetration = 10
light_color = "#00CC33"
muzzle_type = /obj/effect/projectile/xray/muzzle
tracer_type = /obj/effect/projectile/xray/tracer
impact_type = /obj/effect/projectile/xray/impact
muzzle_type = /obj/effect/projectile/muzzle/xray
tracer_type = /obj/effect/projectile/tracer/xray
impact_type = /obj/effect/projectile/impact/xray
/obj/item/projectile/beam/stun
name = "stun beam"
@@ -208,9 +208,9 @@
combustion = FALSE
muzzle_type = /obj/effect/projectile/stun/muzzle
tracer_type = /obj/effect/projectile/stun/tracer
impact_type = /obj/effect/projectile/stun/impact
muzzle_type = /obj/effect/projectile/muzzle/stun
tracer_type = /obj/effect/projectile/tracer/stun
impact_type = /obj/effect/projectile/impact/stun
/obj/item/projectile/beam/stun/weak
name = "weak stun beam"

View File

@@ -7,9 +7,9 @@
damage_type = HALLOSS
light_color = "#00CECE"
muzzle_type = /obj/effect/projectile/laser_omni/muzzle
tracer_type = /obj/effect/projectile/laser_omni/tracer
impact_type = /obj/effect/projectile/laser_omni/impact
muzzle_type = /obj/effect/projectile/muzzle/laser_omni
tracer_type = /obj/effect/projectile/tracer/laser_omni
impact_type = /obj/effect/projectile/impact/laser_omni
/obj/item/projectile/beam/stun
agony = 35
@@ -22,9 +22,9 @@
damage_type = HALLOSS
light_color = "#00CC33"
muzzle_type = /obj/effect/projectile/xray/muzzle
tracer_type = /obj/effect/projectile/xray/tracer
impact_type = /obj/effect/projectile/xray/impact
muzzle_type = /obj/effect/projectile/muzzle/xray
tracer_type = /obj/effect/projectile/tracer/xray
impact_type = /obj/effect/projectile/impact/xray
/obj/item/projectile/beam/energy_net/on_hit(var/atom/netted)
do_net(netted)
@@ -38,6 +38,6 @@
icon_state = "bluelaser"
light_color = "#0066FF"
muzzle_type = /obj/effect/projectile/laser_blue/muzzle
tracer_type = /obj/effect/projectile/laser_blue/tracer
impact_type = /obj/effect/projectile/laser_blue/impact
muzzle_type = /obj/effect/projectile/muzzle/laser_blue
tracer_type = /obj/effect/projectile/tracer/laser_blue
impact_type = /obj/effect/projectile/impact/laser_blue

View File

@@ -10,7 +10,7 @@
sharp = 1
var/mob_passthrough_check = 0
muzzle_type = /obj/effect/projectile/bullet/muzzle
muzzle_type = /obj/effect/projectile/muzzle/bullet
/obj/item/projectile/bullet/on_hit(var/atom/target, var/blocked = 0)
if (..(target, blocked))
@@ -257,12 +257,12 @@
incendiary = 2
flammability = 4
agony = 30
kill_count = 4
range = 4
vacuum_traversal = 0
/obj/item/projectile/bullet/incendiary/flamethrower/large
damage = 15
kill_count = 6
range = 6
/* Practice rounds and blanks */

View File

@@ -9,7 +9,7 @@
name = "chemical shell"
icon_state = "bullet"
damage = 10
kill_count = 15 //if the shell hasn't hit anything after travelling this far it just explodes.
range = 15 //if the shell hasn't hit anything after travelling this far it just explodes.
flash_strength = 15
brightness = 15

View File

@@ -12,7 +12,7 @@
icon_state = "bullet"
fire_sound = 'sound/weapons/gunshot_pathetic.ogg'
damage = 5
kill_count = 15 //if the shell hasn't hit anything after travelling this far it just explodes.
range = 15 //if the shell hasn't hit anything after travelling this far it just explodes.
var/flash_range = 0
var/brightness = 7
var/light_colour = "#ffffff"
@@ -165,7 +165,7 @@
icon_state = "plasma_stun"
fire_sound = 'sound/weapons/blaster.ogg'
armor_penetration = 10
kill_count = 4
range = 4
damage = 5
agony = 55
damage_type = BURN
@@ -212,25 +212,25 @@
light_color = "#0000FF"
embed_chance = 0
muzzle_type = /obj/effect/projectile/pulse/muzzle
muzzle_type = /obj/effect/projectile/muzzle/pulse
/obj/item/projectile/energy/phase
name = "phase wave"
icon_state = "phase"
kill_count = 6
range = 6
damage = 5
SA_bonus_damage = 45 // 50 total on animals
SA_vulnerability = SA_ANIMAL
/obj/item/projectile/energy/phase/light
kill_count = 4
range = 4
SA_bonus_damage = 35 // 40 total on animals
/obj/item/projectile/energy/phase/heavy
kill_count = 8
range = 8
SA_bonus_damage = 55 // 60 total on animals
/obj/item/projectile/energy/phase/heavy/cannon
kill_count = 10
range = 10
damage = 15
SA_bonus_damage = 60 // 75 total on animals

View File

@@ -9,7 +9,7 @@
var/beam_state = "b_beam"
damage = 5
step_delay = 2
speed = 2
damage_type = BURN
check_armour = "energy"
armor_penetration = 15
@@ -27,9 +27,9 @@
var/list/help_messages = list("slaps", "pokes", "nudges", "bumps", "pinches")
var/done_mob_unique = FALSE // Has the projectile already done something to a mob?
/obj/item/projectile/energy/hook/launch(atom/target, target_zone, x_offset=0, y_offset=0, angle_offset=0)
/obj/item/projectile/energy/hook/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0)
var/expected_distance = get_dist(target, loc)
kill_count = expected_distance // So the hook hits the ground if no mob is hit.
range = expected_distance // So the hook hits the ground if no mob is hit.
target_distance = expected_distance
if(firer) // Needed to ensure later checks in impact and on hit function.
launcher_intent = firer.a_intent

View File

@@ -34,7 +34,7 @@
penetrating = 2
embed_chance = 0
armor_penetration = 40
kill_count = 20
range = 20
var/searing = 0 //Does this fuelrod ignore shields?
var/detonate_travel = 0 //Will this fuelrod explode when it reaches maximum distance?
@@ -50,7 +50,7 @@
if(energetic_impact)
var/eye_coverage = 0
for(var/mob/living/carbon/M in viewers(world.view, location))
for(var/mob/living/carbon/M in viewers(world.view, get_turf(src)))
eye_coverage = 0
if(iscarbon(M))
eye_coverage = M.eyecheck()
@@ -103,7 +103,7 @@
armor_penetration = 100
penetrating = 100 //Theoretically, this shouldn't stop flying for a while, unless someone lines it up with a wall or fires it into a mountain.
irradiate = 120
kill_count = 75
range = 75
searing = 1
detonate_travel = 1
detonate_mob = 1
@@ -127,7 +127,7 @@
penetrating = 0
check_armour = "melee"
irradiate = 20
kill_count = 6
range = 6
/obj/item/projectile/bullet/magnetic/bore/Bump(atom/A, forced=0)
if(istype(A, /turf/simulated/mineral))

View File

@@ -9,10 +9,6 @@
var/base_spread = 90 //lower means the pellets spread more across body parts. If zero then this is considered a shrapnel explosion instead of a shrapnel cone
var/spread_step = 10 //higher means the pellets spread more across body parts with distance
/obj/item/projectile/bullet/pellet/Bumped()
. = ..()
bumped = 0 //can hit all mobs in a tile. pellets is decremented inside attack_mob so this should be fine.
/obj/item/projectile/bullet/pellet/proc/get_pellets(var/distance)
var/pellet_loss = round((distance - 1)/range_step) //pellets lost due to distance
return max(pellets - pellet_loss, 1)

View File

@@ -219,7 +219,7 @@
embed_chance = 0 // nope
nodamage = 1
damage_type = HALLOSS
muzzle_type = /obj/effect/projectile/bullet/muzzle
muzzle_type = /obj/effect/projectile/muzzle/bullet
/obj/item/projectile/bola
name = "bola"

View File

@@ -0,0 +1,38 @@
//Helper proc to check if you can hit them or not.
/proc/check_trajectory(atom/target as mob|obj, atom/firer as mob|obj, var/pass_flags=PASSTABLE|PASSGLASS|PASSGRILLE, flags=null)
if(!istype(target) || !istype(firer))
return 0
var/obj/item/projectile/test/trace = new /obj/item/projectile/test(get_turf(firer)) //Making the test....
//Set the flags and pass flags to that of the real projectile...
if(!isnull(flags))
trace.flags = flags
trace.pass_flags = pass_flags
return trace.launch_projectile(target) //Test it!
/obj/item/projectile/proc/_check_fire(atom/target as mob, var/mob/living/user as mob) //Checks if you can hit them or not.
check_trajectory(target, user, pass_flags, flags)
//"Tracing" projectile
/obj/item/projectile/test //Used to see if you can hit them.
invisibility = 101 //Nope! Can't see me!
hitscan = TRUE
nodamage = TRUE
damage = 0
var/list/hit = list()
/obj/item/projectile/test/process_hitscan()
. = ..()
if(!QDELING(src))
qdel(src)
return hit
/obj/item/projectile/test/Bump(atom/A)
if(A != src)
hit |= A
return ..()
/obj/item/projectile/test/attack_mob()
return