Ports hitscan from TG, adds it to *SELECT* things (#22754)

* this was just going to be a plasma cutter change but no had to make it look good, whats next, ai sat turrets (yes)

* ai turret

* Coca cola, don't forget the ice. Or the pulse

* almost done, pull master than map edit

* we ball

* ah, chasms.

* it's for the best. Fuck should not sleep checker though

* hitscan reflection limiting system

* Apply suggestions from code review

Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com>

* c-c-c-changes

---------

Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com>
Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com>
This commit is contained in:
Qwertytoforty
2023-11-01 20:05:27 +00:00
committed by GitHub
co-authored by Henri215 S34N
parent 49c9d6f65a
commit 357c62b6d7
43 changed files with 1946 additions and 1610 deletions
@@ -209,7 +209,7 @@
projectile_type = /obj/item/projectile/bullet/meteorshot
/obj/item/ammo_casing/shotgun/pulseslug
name = "pulse slug"
name = "proto pulse slug"
desc = "A delicate device which can be loaded into a shotgun. The primer acts as a button which triggers the gain medium and fires a powerful \
energy blast. While the heat and power drain limit it to one use, it can still allow an operator to engage targets that ballistic ammunition \
would have difficulty with."
@@ -42,14 +42,14 @@
fire_sound = 'sound/weapons/lasercannonfire.ogg'
/obj/item/ammo_casing/energy/laser/pulse
projectile_type = /obj/item/projectile/beam/pulse
projectile_type = /obj/item/projectile/beam/pulse/hitscan
muzzle_flash_color = LIGHT_COLOR_DARKBLUE
e_cost = 200
select_name = "DESTROY"
fire_sound = 'sound/weapons/pulse.ogg'
/obj/item/ammo_casing/energy/laser/scatter/pulse
projectile_type = /obj/item/projectile/beam/pulse
projectile_type = /obj/item/projectile/beam/pulse/hitscan
e_cost = 200
select_name = "ANNIHILATE"
fire_sound = 'sound/weapons/pulse.ogg'
@@ -286,7 +286,7 @@
select_name = "clown"
/obj/item/ammo_casing/energy/emitter
projectile_type = /obj/item/projectile/beam/emitter
projectile_type = /obj/item/projectile/beam/emitter/hitscan
muzzle_flash_color = LIGHT_COLOR_GREEN
fire_sound = 'sound/weapons/emitter.ogg'
e_cost = 100
@@ -417,42 +417,11 @@
reset_overloaded()
do_sparks(2, 1, src)
update_icon()
if(prob(25))
visible_message("<span class='danger'>[src] vents heated plasma!</span>")
var/turf/simulated/T = get_turf(src)
if(istype(T))
T.atmos_spawn_air(LINDA_SPAWN_TOXINS|LINDA_SPAWN_20C,15)
return
if(prob(50))
var/list/mob_targets = list()
for(var/mob/living/M in oview(get_turf(src), 7))
mob_targets += M
if(length(mob_targets))
var/mob/living/target = pick(mob_targets)
shootAt(target)
visible_message("<span class='danger'>[src] discharges a plasma bolt!</span>")
return
visible_message("<span class='danger'>[src] vents heated plasma!</span>")
var/turf/simulated/T = get_turf(src)
if(istype(T))
T.atmos_spawn_air(LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS|LINDA_SPAWN_20C, 20)
visible_message("<span class='danger'>[src] discharges a plasma bolt!</span>")
var/list/turf_targets = list()
for(var/turf/T in orange(get_turf(src), 7))
turf_targets += T
if(length(turf_targets))
var/turf/target = pick(turf_targets)
shootAt(target)
/obj/item/gun/energy/plasma_pistol/proc/shootAt(atom/movable/target)
var/turf/T = get_turf(src)
var/turf/U = get_turf(target)
if(!T || !U)
return
var/obj/item/projectile/energy/charged_plasma/O = new /obj/item/projectile/energy/charged_plasma(T)
playsound(get_turf(src), 'sound/weapons/marauder.ogg', 75, 1)
O.current = T
O.yo = U.y - T.y
O.xo = U.x - T.x
O.fire()
/obj/item/gun/energy/bsg
name = "\improper B.S.G"
+1 -1
View File
@@ -88,7 +88,7 @@
return FALSE
var/obj/dummy = new(user_turf)
dummy.pass_flags |= PASSTABLE | PASSGLASS | PASSGRILLE | PASSFENCE //Grille/Glass so it can be used through common windows
for(var/turf/turf in getline(user_turf,target))
for(var/turf/turf in get_line(user_turf,target))
if(turf.density)
qdel(dummy)
return FALSE
@@ -33,6 +33,39 @@
icon_state = "heavylaser"
damage = 40
/obj/item/projectile/beam/laser/ai_turret
damage = 17.5 //Slight loss in damage because hitscan
forcedodge = 1
hitscan = TRUE
muzzle_type = /obj/effect/projectile/muzzle/laser
tracer_type = /obj/effect/projectile/tracer/laser
impact_type = /obj/effect/projectile/impact/laser
impact_effect_type = null
hitscan_light_intensity = 3
hitscan_light_range = 0.75
hitscan_light_color_override = LIGHT_COLOR_DARKRED
muzzle_flash_intensity = 6
muzzle_flash_range = 2
muzzle_flash_color_override = LIGHT_COLOR_DARKRED
impact_light_intensity = 7
impact_light_range = 2.5
impact_light_color_override = LIGHT_COLOR_DARKRED
/obj/item/projectile/beam/laser/ai_turret/prehit(atom/target)
if(isAI(target))
damage = 0 //cheater cheater I don't want AI to die to stupid placement eater
nodamage = 1
if(isliving(target))
forcedodge = 0 //no peircing after hitting a mob to avoid rooms destroying itself. If someone does monkey chair on AI sat though, I swear to god
..()
/obj/item/projectile/beam/laser/ai_turret/heavylaser
damage = 35
muzzle_type = /obj/effect/projectile/muzzle/heavy_laser
tracer_type = /obj/effect/projectile/tracer/heavy_laser
impact_type = /obj/effect/projectile/impact/heavy_laser
/obj/item/projectile/beam/practice
name = "practice laser"
damage = 0
@@ -73,12 +106,31 @@
impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser
light_color = LIGHT_COLOR_DARKBLUE
/obj/item/projectile/beam/pulse/hitscan
impact_effect_type = null
light_color = null
hitscan = TRUE
muzzle_type = /obj/effect/projectile/muzzle/pulse
tracer_type = /obj/effect/projectile/tracer/pulse
impact_type = /obj/effect/projectile/impact/pulse
impact_effect_type = null
hitscan_light_intensity = 3
hitscan_light_range = 0.75
hitscan_light_color_override = LIGHT_COLOR_DARKBLUE
muzzle_flash_intensity = 6
muzzle_flash_range = 2
muzzle_flash_color_override = LIGHT_COLOR_DARKBLUE
impact_light_intensity = 7
impact_light_range = 2.5
impact_light_color_override = LIGHT_COLOR_DARKBLUE
/obj/item/projectile/beam/pulse/on_hit(atom/target, blocked = 0)
if(isturf(target) || isstructure(target) || ismachinery(target))
target.ex_act(2)
..()
/obj/item/projectile/beam/pulse/shot
name = "proto pulse"
damage = 40
/obj/item/projectile/beam/emitter
@@ -88,6 +140,22 @@
impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser
light_color = LIGHT_COLOR_GREEN
/obj/item/projectile/beam/emitter/hitscan
hitscan = TRUE
muzzle_type = /obj/effect/projectile/muzzle/laser/emitter
tracer_type = /obj/effect/projectile/tracer/laser/emitter
impact_type = /obj/effect/projectile/impact/laser/emitter
impact_effect_type = null
hitscan_light_intensity = 3
hitscan_light_range = 0.75
hitscan_light_color_override = LIGHT_COLOR_GREEN
muzzle_flash_intensity = 6
muzzle_flash_range = 2
muzzle_flash_color_override = LIGHT_COLOR_GREEN
impact_light_intensity = 7
impact_light_range = 2.5
impact_light_color_override = LIGHT_COLOR_GREEN
/obj/item/projectile/beam/emitter/singularity_pull()
return //don't want the emitters to miss
@@ -141,6 +209,23 @@
damage = 8
icon_state = "scatterlaser"
/obj/item/projectile/beam/immolator/weak/hitscan
color = LIGHT_COLOR_FIRE
hitscan = TRUE
muzzle_type = /obj/effect/projectile/muzzle/laser
tracer_type = /obj/effect/projectile/tracer/laser
impact_type = /obj/effect/projectile/impact/laser
impact_effect_type = null
hitscan_light_intensity = 3
hitscan_light_range = 0.75
hitscan_light_color_override = LIGHT_COLOR_FIRE
muzzle_flash_intensity = 6
muzzle_flash_range = 2
muzzle_flash_color_override = LIGHT_COLOR_FIRE
impact_light_intensity = 7
impact_light_range = 2.5
impact_light_color_override = LIGHT_COLOR_FIRE
/obj/item/projectile/beam/immolator/on_hit(atom/target, blocked = 0)
. = ..()
if(isliving(target))
@@ -9,7 +9,20 @@
/obj/item/projectile/magic/death
name = "bolt of death"
icon_state = "pulse1_bl"
icon_state = null
hitscan = TRUE
muzzle_type = /obj/effect/projectile/muzzle/death
tracer_type = /obj/effect/projectile/tracer/death
impact_type = /obj/effect/projectile/impact/death
hitscan_light_intensity = 3
hitscan_light_range = 0.75
hitscan_light_color_override = LIGHT_COLOR_PURPLE
muzzle_flash_intensity = 6
muzzle_flash_range = 2
muzzle_flash_color_override = LIGHT_COLOR_PURPLE
impact_light_intensity = 7
impact_light_range = 2.5
impact_light_color_override = LIGHT_COLOR_PURPLE
/obj/item/projectile/magic/fireball
name = "bolt of fireball"
@@ -240,7 +240,20 @@
range = 3
dismemberment = 20
sharp = TRUE
impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser
hitscan = TRUE
muzzle_type = /obj/effect/projectile/muzzle/plasma_cutter
tracer_type = /obj/effect/projectile/tracer/plasma_cutter
impact_type = /obj/effect/projectile/impact/plasma_cutter
impact_effect_type = null
hitscan_light_intensity = 3
hitscan_light_range = 0.75
hitscan_light_color_override = LIGHT_COLOR_CYAN
muzzle_flash_intensity = 6
muzzle_flash_range = 2
muzzle_flash_color_override = LIGHT_COLOR_CYAN
impact_light_intensity = 7
impact_light_range = 2.5
impact_light_color_override = LIGHT_COLOR_CYAN
/obj/item/projectile/plasma/prehit(atom/target)
. = ..()
+141 -11
View File
@@ -1,3 +1,8 @@
/// 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'
@@ -26,6 +31,7 @@
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 = 0
@@ -80,6 +86,32 @@
var/shield_buster = FALSE
var/forced_accuracy = FALSE
///Has the projectile been fired?
var/has_been_fired = 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
/obj/item/projectile/New()
return ..()
@@ -112,7 +144,7 @@
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)
if(impact_effect_type && !hitscan)
new impact_effect_type(target_loca, hitx, hity)
W.add_dent(WALL_DENT_SHOT, hitx, hity)
@@ -120,7 +152,7 @@
if(alwayslog)
add_attack_logs(firer, target, "Shot with a [type]")
if(!isliving(target))
if(impact_effect_type)
if(impact_effect_type && !hitscan)
new impact_effect_type(target_loca, hitx, hity)
return 0
var/mob/living/L = target
@@ -152,7 +184,7 @@
M.bloody_hands(H)
/* Uncomment when bloody_body stops randomly not transferring blood colour.
M.bloody_body(H) */
else if(impact_effect_type)
else if(impact_effect_type && !hitscan)
new impact_effect_type(target_loca, hitx, hity)
var/organ_hit_text = ""
if(L.has_limbs)
@@ -199,11 +231,18 @@
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, yes)
if(!yes) //prevents double bumps.
return
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)
@@ -265,7 +304,9 @@
return
var/elapsed_time_deciseconds = (world.time - last_projectile_move) + time_offset
time_offset = 0
var/required_moves = FLOOR(elapsed_time_deciseconds / speed, 1)
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
@@ -275,14 +316,15 @@
for(var/i in 1 to required_moves)
pixel_move(1)
/obj/item/projectile/proc/pixel_move(trajectory_multiplier)
/obj/item/projectile/proc/pixel_move(trajectory_multiplier, hitscanning = FALSE)
if(!loc || !trajectory)
return
last_projectile_move = world.time
// Keep on course
var/matrix/M = new
M.Turn(Angle)
transform = M
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)
@@ -297,17 +339,20 @@
trajectory_ignore_forcemove = TRUE
forceMove(T)
trajectory_ignore_forcemove = FALSE
pixel_x = trajectory.return_px()
pixel_y = trajectory.return_py()
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(!forcemoved)
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)
@@ -320,6 +365,7 @@
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
@@ -329,6 +375,9 @@
// 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)
@@ -358,6 +407,8 @@
Bump(AM, 1)
/obj/item/projectile/Destroy()
if(hitscan)
finalize_hitscan_and_generate_tracers()
STOP_PROCESSING(SSprojectiles, src)
ammo_casing = null
firer_source_atom = null
@@ -386,11 +437,22 @@
..()
Angle = new_angle
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)
/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
@@ -400,7 +462,11 @@
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
@@ -408,3 +474,67 @@
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[beam_segments.len]]
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