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
+1
View File
@@ -75,6 +75,7 @@
#define LYING_MOB_LAYER 3.8
//#define MOB_LAYER 4 //For easy recordkeeping; this is a byond define
#define ABOVE_MOB_LAYER 4.1
#define HITSCAN_LAYER 4.2
#define WALL_OBJ_LAYER 4.25
#define EDGED_TURF_LAYER 4.3
#define ON_EDGED_TURF_LAYER 4.35
+1 -1
View File
@@ -95,7 +95,7 @@
var/BT = get_turf(B)
if(AT == BT)
return 1
var/list/line = getline(A, B)
var/list/line = get_line(A, B)
for(var/turf/T in line)
if(T == AT || T == BT)
break
+41 -35
View File
@@ -173,43 +173,49 @@ Turf and target are seperate in case you want to teleport some distance from a t
/////////////////////////////////////////////////////////////////////////
/**
* Gets the turfs which are between the two given atoms. Including their positions
* Only works for atoms on the same Z level which is not 0. So an atom located in a non turf won't work
* Arguments:
* * M - The source atom
* * N - The target atom
* Get a list of turfs in a line from `starting_atom` to `ending_atom`.
*
* Uses the ultra-fast [Bresenham Line-Drawing Algorithm](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm).
*/
/proc/getline(atom/M, atom/N)//Ultra-Fast Bresenham Line-Drawing Algorithm
if(!M.z || M.z != N.z) // Same Z level and not 0. Else all below breaks
return list()
var/px=M.x //starting x
var/py=M.y
var/line[] = list(locate(px,py,M.z))
var/dx=N.x-px //x distance
var/dy=N.y-py
var/dxabs=abs(dx)//Absolute value of x distance
var/dyabs=abs(dy)
var/sdx=SIGN(dx) //Sign of x distance (+ or -)
var/sdy=SIGN(dy)
var/x=dxabs>>1 //Counters for steps taken, setting to distance/2
var/y=dyabs>>1 //Bit-shifting makes me l33t. It also makes getline() unnessecarrily fast.
var/j //Generic integer for counting
if(dxabs>=dyabs) //x distance is greater than y
for(j=0;j<dxabs;j++)//It'll take dxabs steps to get there
y+=dyabs
if(y>=dxabs) //Every dyabs steps, step once in y direction
y-=dxabs
py+=sdy
px+=sdx //Step on in x direction
line+=locate(px,py,M.z)//Add the turf to the list
/proc/get_line(atom/starting_atom, atom/ending_atom)
var/current_x_step = starting_atom.x//start at x and y, then add 1 or -1 to these to get every turf from starting_atom to ending_atom
var/current_y_step = starting_atom.y
var/starting_z = starting_atom.z
var/list/line = list(get_step(starting_atom, 0))//get_turf(atom) is faster than locate(x, y, z) //Get turf isn't defined yet so we use get step
var/x_distance = ending_atom.x - current_x_step //x distance
var/y_distance = ending_atom.y - current_y_step
var/abs_x_distance = abs(x_distance)//Absolute value of x distance
var/abs_y_distance = abs(y_distance)
var/x_distance_sign = SIGN(x_distance) //Sign of x distance (+ or -)
var/y_distance_sign = SIGN(y_distance)
var/x = abs_x_distance >> 1 //Counters for steps taken, setting to distance/2
var/y = abs_y_distance >> 1 //Bit-shifting makes me l33t. It also makes get_line() unnessecarrily fast.
if(abs_x_distance >= abs_y_distance) //x distance is greater than y
for(var/distance_counter in 0 to (abs_x_distance - 1))//It'll take abs_x_distance steps to get there
y += abs_y_distance
if(y >= abs_x_distance) //Every abs_y_distance steps, step once in y direction
y -= abs_x_distance
current_y_step += y_distance_sign
current_x_step += x_distance_sign //Step on in x direction
line += locate(current_x_step, current_y_step, starting_z)//Add the turf to the list
else
for(j=0;j<dyabs;j++)
x+=dxabs
if(x>=dyabs)
x-=dyabs
px+=sdx
py+=sdy
line+=locate(px,py,M.z)
for(var/distance_counter in 0 to (abs_y_distance - 1))
x += abs_x_distance
if(x >= abs_y_distance)
x -= abs_y_distance
current_x_step += x_distance_sign
current_y_step += y_distance_sign
line += locate(current_x_step, current_y_step, starting_z)
return line
//Same as the thing below just for density and without support for atoms.
@@ -96,7 +96,7 @@
//Checks for obstacles from A to B
var/obj/dummy = new(source.loc)
dummy.pass_flags |= PASSTABLE
for(var/turf/turf as anything in getline(source, target))
for(var/turf/turf as anything in get_line(source, target))
for(var/atom/movable/AM in turf)
if(!AM.CanPass(dummy, turf, 1))
qdel(dummy)
@@ -335,11 +335,11 @@
unlock_sound = 'sound/items/rped.ogg'
/datum/AI_Module/upgrade_turrets/upgrade(mob/living/silicon/ai/AI)
for(var/obj/machinery/porta_turret/turret in GLOB.machines)
for(var/obj/machinery/porta_turret/ai_turret/turret in GLOB.machines)
var/turf/T = get_turf(turret)
if(is_station_level(T.z))
turret.health += 30
turret.eprojectile = /obj/item/projectile/beam/laser/heavylaser //Once you see it, you will know what it means to FEAR.
turret.eprojectile = /obj/item/projectile/beam/laser/ai_turret/heavylaser //Once you see it, you will know what it means to FEAR.
turret.eshot_sound = 'sound/weapons/lasercannonfire.ogg'
//Hostile Station Lockdown: Locks, bolts, and electrifies every airlock on the station. After 90 seconds, the doors reset.
@@ -137,7 +137,7 @@
return FALSE
playsound(T, 'sound/magic/lightningshock.ogg', 50, TRUE)
O.Beam(target, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 1 SECONDS)
for(var/turf/working in getline(O, T))
for(var/turf/working in get_line(O, T))
for(var/mob/living/L in working)
if(!electrocute_mob(L, C.powernet, user)) // give a little bit of non-lethal counterplay against insuls
L.Jitter(5 SECONDS)
+16 -1
View File
@@ -67,6 +67,10 @@
var/has_cover = TRUE //Hides the cover
/// Deployment override to allow turret popup on/under dense turfs/objects, for admin/CC turrets
var/deployment_override = FALSE
/// What lethal mode projectile with the turret start with?
var/initial_eprojectile = null
/// What non-lethal mode projectile with the turret start with?
var/initial_projectile = null
/obj/machinery/porta_turret/Initialize(mapload)
@@ -142,8 +146,12 @@
egun = 1
if(/obj/item/gun/energy/pulse/turret)
eprojectile = /obj/item/projectile/beam/pulse
eprojectile = /obj/item/projectile/beam/pulse/hitscan
eshot_sound = 'sound/weapons/pulse.ogg'
if(initial_eprojectile)
eprojectile = initial_eprojectile
if(initial_projectile)
projectile = initial_projectile
GLOBAL_LIST_EMPTY(turret_icons)
/obj/machinery/porta_turret/update_icon_state()
@@ -761,6 +769,13 @@ GLOBAL_LIST_EMPTY(turret_icons)
A.throw_at(target, scan_range, 1)
return A
/obj/machinery/porta_turret/ai_turret
initial_eprojectile = /obj/item/projectile/beam/laser/ai_turret
/obj/machinery/porta_turret/ai_turret/disable
name = "hallway turret"
initial_projectile = /obj/item/projectile/beam/disabler
/obj/machinery/porta_turret/centcom
name = "\improper Centcomm turret"
enabled = FALSE
+4 -3
View File
@@ -156,17 +156,18 @@
icon_state = "mecha_pulse"
energy_drain = 120
origin_tech = "materials=3;combat=6;powerstorage=4"
projectile = /obj/item/projectile/beam/pulse/heavy
projectile = /obj/item/projectile/beam/pulse/hitscan/heavy
fire_sound = 'sound/weapons/marauder.ogg'
harmful = TRUE
/obj/item/projectile/beam/pulse/heavy
/obj/item/projectile/beam/pulse/hitscan/heavy
name = "heavy pulse laser"
icon_state = "pulse1_bl"
var/life = 20
/obj/item/projectile/beam/pulse/heavy/Bump(atom/A)
/obj/item/projectile/beam/pulse/hitscan/heavy/Bump(atom/A)
A.bullet_act(src, def_zone)
life -= 10
if(ismob(A))
@@ -0,0 +1,54 @@
//These are directional impact effects used by hitscan projectiles. Unlike current impact effect, they rotate to face the point the hitscan beam came from.
/obj/effect/projectile/impact
name = "beam impact"
icon = 'icons/obj/projectiles_impact.dmi'
/obj/effect/projectile/impact/laser
name = "laser impact"
icon_state = "impact_laser"
/obj/effect/projectile/impact/laser/blue
name = "laser impact"
icon_state = "impact_blue"
/obj/effect/projectile/impact/disabler
name = "disabler impact"
icon_state = "impact_omni"
/obj/effect/projectile/impact/xray
name = "\improper X-ray impact"
icon_state = "impact_xray"
/obj/effect/projectile/impact/pulse
name = "pulse impact"
icon_state = "impact_u_laser"
/obj/effect/projectile/impact/plasma_cutter
name = "plasma impact"
icon_state = "impact_plasmacutter"
/obj/effect/projectile/impact/stun
name = "stun impact"
icon_state = "impact_stun"
/obj/effect/projectile/impact/heavy_laser
name = "heavy laser impact"
icon_state = "impact_beam_heavy"
/obj/effect/projectile/impact/wormhole
icon_state = "wormhole_g"
/obj/effect/projectile/impact/laser/emitter
name = "emitter impact"
icon_state = "impact_emitter"
/obj/effect/projectile/impact/solar
name = "solar impact"
icon_state = "impact_solar"
/obj/effect/projectile/impact/sniper
icon_state = "sniper"
/obj/effect/projectile/impact/death
icon_state = "impact_hcult"
@@ -0,0 +1,48 @@
//These are directional muzzle effects used by hitscan projectiles. Unlike current muzzle effect, they rotate to face the point the hitscan beam came from.
/obj/effect/projectile/muzzle
name = "muzzle flash"
icon = 'icons/obj/projectiles_muzzle.dmi'
/obj/effect/projectile/muzzle/laser
icon_state = "muzzle_laser"
/obj/effect/projectile/muzzle/laser/blue
icon_state = "muzzle_blue"
/obj/effect/projectile/muzzle/disabler
icon_state = "muzzle_omni"
/obj/effect/projectile/muzzle/xray
icon_state = "muzzle_xray"
/obj/effect/projectile/muzzle/pulse
icon_state = "muzzle_u_laser"
/obj/effect/projectile/muzzle/plasma_cutter
icon_state = "muzzle_plasmacutter"
/obj/effect/projectile/muzzle/stun
icon_state = "muzzle_stun"
/obj/effect/projectile/muzzle/heavy_laser
icon_state = "muzzle_beam_heavy"
/obj/effect/projectile/muzzle/wormhole
icon_state = "wormhole_g"
/obj/effect/projectile/muzzle/laser/emitter
name = "emitter flash"
icon_state = "muzzle_emitter"
/obj/effect/projectile/muzzle/solar
icon_state = "muzzle_solar"
/obj/effect/projectile/muzzle/sniper
icon_state = "sniper"
/obj/effect/projectile/muzzle/bullet
icon_state = "muzzle_bullet"
/obj/effect/projectile/muzzle/death
icon_state = "muzzle_hcult"
@@ -0,0 +1,66 @@
//This is the parent of all projectile effects, impact muzzle and tracer.
/obj/effect/projectile
name = "pew"
icon = 'icons/obj/projectiles.dmi'
icon_state = "nothing"
layer = HITSCAN_LAYER
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
appearance_flags = LONG_GLIDE
/obj/effect/projectile/singularity_pull()
return
/obj/effect/projectile/singularity_act()
return
/obj/effect/projectile/proc/scale_to(nx,ny,override=TRUE)
var/matrix/M
if(!override)
M = transform
else
M = new
M.Scale(nx, ny)
transform = M
/obj/effect/projectile/proc/turn_to(angle,override=TRUE)
var/matrix/M
if(!override)
M = transform
else
M = new
M.Turn(angle)
transform = M
/obj/effect/projectile/New(angle_override, p_x, p_y, color_override, scaling = 1)
if(angle_override && p_x && p_y && color_override && scaling)
apply_vars(angle_override, p_x, p_y, color_override, scaling)
return ..()
/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, new_loc, increment = 0)
var/mutable_appearance/look = new(src)
look.appearance_flags = RESET_COLOR | RESET_ALPHA
look.pixel_x = p_x
look.pixel_y = p_y
if(color_override)
look.color = color_override
look.appearance_flags = RESET_ALPHA
else
look.appearance_flags = RESET_COLOR | RESET_ALPHA
icon_state = null
add_overlay(look)
scale_to(1, scaling, FALSE)
turn_to(angle_override, FALSE)
if(!isnull(new_loc)) //If you want to null it just delete it...
forceMove(new_loc)
for(var/i in 1 to increment)
pixel_x += round((sin(angle_override) + 16 * sin(angle_override) * 2), 1)
pixel_y += round((cos(angle_override) + 16 * cos(angle_override) * 2), 1)
/obj/effect/projectile_lighting
var/owner
/obj/effect/projectile_lighting/Initialize(mapload, color, range, intensity, owner_key)
. = ..()
set_light(range, intensity, color)
owner = owner_key
@@ -0,0 +1,99 @@
//These are the visual tracers, the beams you see with hitscan effects. This is the proc that generates said tracer
/proc/generate_tracer_between_points(datum/position/starting, datum/position/ending, beam_type, color, qdel_in = 5, light_range = 2, light_color_override, light_intensity = 1, instance_key) //Do not pass z-crossing points as that will not be properly (and likely will never be properly until it's absolutely needed) supported!
if(isnull(starting))
return
if(isnull(ending))
return
if(isnull(beam_type))
return
var/datum/position/midpoint = point_midpoint_points(starting, ending)
var/performance_counter
for(var/obj/effect/projectile/tracer/counter in midpoint.return_turf())
performance_counter++
if(performance_counter >= 3)
return //Damage still happens, this should stop engineers with looping emitters doing shenanigins that makes the server cry
var/obj/effect/projectile/tracer/PB = new beam_type
if(isnull(light_color_override))
light_color_override = color
PB.apply_vars(angle_between_points(starting, ending), midpoint.return_px(), midpoint.return_py(), color, pixel_length_between_points(starting, ending) / world.icon_size, midpoint.return_turf(), 0)
. = PB
if(light_range > 0 && light_intensity > 0)
var/list/turf/line = get_line(starting.return_turf(), ending.return_turf())
tracing_line:
for(var/i in line)
var/turf/T = i
for(var/obj/effect/projectile_lighting/PL in T)
if(PL.owner == locateUID(instance_key))
continue tracing_line
QDEL_IN(new /obj/effect/projectile_lighting(T, light_color_override, light_range, light_intensity, instance_key), qdel_in > 0? qdel_in : 5)
line = null
if(qdel_in)
QDEL_IN(PB, qdel_in)
/obj/effect/projectile/tracer
name = "beam"
icon = 'icons/obj/projectiles_tracer.dmi'
/obj/effect/projectile/tracer/laser
name = "laser"
icon_state = "beam"
/obj/effect/projectile/tracer/laser/blue
icon_state = "beam_blue"
/obj/effect/projectile/tracer/disabler
name = "disabler"
icon_state = "beam_omni"
/obj/effect/projectile/tracer/xray
name = "\improper X-ray laser"
icon_state = "xray"
/obj/effect/projectile/tracer/pulse
name = "pulse laser"
icon_state = "u_laser"
/obj/effect/projectile/tracer/plasma_cutter
name = "plasma blast"
icon_state = "plasmacutter"
/obj/effect/projectile/tracer/stun
name = "stun beam"
icon_state = "stun"
/obj/effect/projectile/tracer/heavy_laser
name = "heavy laser"
icon_state = "beam_heavy"
/obj/effect/projectile/tracer/solar
name = "solar beam"
icon_state = "solar"
/obj/effect/projectile/tracer/solar/thin
icon_state = "solar_thin"
/obj/effect/projectile/tracer/solar/thinnest
icon_state = "solar_thinnest"
//BEAM RIFLE
/obj/effect/projectile/tracer/tracer/beam_rifle
icon_state = "tracer_beam"
/obj/effect/projectile/tracer/tracer/aiming
icon_state = "pixelbeam_greyscale"
plane = ABOVE_LIGHTING_PLANE
/obj/effect/projectile/tracer/wormhole
icon_state = "wormhole_g"
/obj/effect/projectile/tracer/laser/emitter
name = "emitter beam"
icon_state = "emitter"
/obj/effect/projectile/tracer/sniper
icon_state = "sniper"
/obj/effect/projectile/tracer/death
icon_state = "hcult"
@@ -89,7 +89,7 @@
if(user.get_active_hand() == src) // Make sure our user is still holding us
var/turf/target_turf = get_turf(target)
if(target_turf)
var/turflist = getline(user, target_turf)
var/turflist = get_line(user, target_turf)
add_attack_logs(user, target, "Flamethrowered at [target.x],[target.y],[target.z]")
flame_turf(turflist)
+2
View File
@@ -30,6 +30,8 @@
/obj/effect/spawner,
/obj/structure/railing,
/obj/machinery/atmospherics/pipe/simple,
/obj/effect/projectile,
/obj/effect/projectile_lighting,
/mob/living/simple_animal/hostile/megafauna //failsafe
))
var/drop_x = 1
@@ -195,7 +195,7 @@
should_recharge_after_cast = TRUE
return
var/wall_count
for(var/turf/T in getline(target_turf, start_turf))
for(var/turf/T in get_line(target_turf, start_turf))
if(max_walls <= wall_count)
break
new /obj/structure/blood_barrier(T)
+1 -1
View File
@@ -368,7 +368,7 @@
alert_admins_on_craft = TRUE
/datum/crafting_recipe/pulseslug
name = "Pulse Slug Shell"
name = "Proto Pulse Slug Shell"
result = list(/obj/item/ammo_casing/shotgun/pulseslug)
reqs = list(/obj/item/ammo_casing/shotgun/techshell = 1,
/obj/item/stock_parts/capacitor/adv = 2,
@@ -305,7 +305,7 @@
if(STAGE_ATTACK)
if(!eating)
for(var/I in getline(src, get_turf(H)))
for(var/I in get_line(src, get_turf(H)))
var/turf/T = I
for(var/obj/structure/O in T)
if(istype(O, /obj/structure/closet))
@@ -370,7 +370,7 @@
/mob/living/simple_animal/hostile/proc/CheckFriendlyFire(atom/A)
if(check_friendly_fire)
for(var/turf/T in getline(src,A)) // Not 100% reliable but this is faster than simulating actual trajectory
for(var/turf/T in get_line(src,A)) // Not 100% reliable but this is faster than simulating actual trajectory
for(var/mob/living/L in T)
if(L == src || L == A)
continue
@@ -239,7 +239,7 @@ Difficulty: Medium
turf_dist_to_target += get_dist(dash_target, O)
if(get_dist(src, O) >= MINER_DASH_RANGE && turf_dist_to_target <= self_dist_to_target && !islava(O) && !ischasm(O))
var/valid = TRUE
for(var/turf/T in getline(own_turf, O))
for(var/turf/T in get_line(own_turf, O))
if(is_blocked_turf(T, TRUE))
valid = FALSE
continue
@@ -267,7 +267,7 @@ Difficulty: Medium
if(!check)
break
T = check
return (getline(src, T) - get_turf(src))
return (get_line(src, T) - get_turf(src))
/mob/living/simple_animal/hostile/megafauna/dragon/proc/fire_line(list/turfs)
SLEEP_CHECK_DEATH(0)
@@ -168,7 +168,7 @@ Difficulty: Medium
/mob/living/simple_animal/hostile/megafauna/legion/proc/fire_disintegration_laser(location)
playsound(loc, 'sound/weapons/marauder.ogg', 200, TRUE)
Beam(location, icon_state = "death_laser", time = 2 SECONDS, maxdistance = INFINITY, beam_type = /obj/effect/ebeam/disintegration)
for(var/turf/t in getline(src, location))
for(var/turf/t in get_line(src, location))
if(ismineralturf(t))
var/turf/simulated/mineral/M = t
M.gets_drilled(src)
@@ -23,7 +23,7 @@
health = 200
maxHealth = 200
speed = 8
projectiletype = /obj/item/projectile/beam/immolator/weak
projectiletype = /obj/item/projectile/beam/immolator/weak/hitscan
projectilesound = 'sound/weapons/laser3.ogg'
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
@@ -94,7 +94,7 @@
for(var/mob/living/L in view(grasp_range, src))
if(L == src || faction_check_mob(L) || (L in grasping) || L == target)
continue
for(var/t in getline(src,L))
for(var/t in get_line(src,L))
for(var/a in t)
var/atom/A = a
if(A.density && A != L)
@@ -107,7 +107,7 @@
/mob/living/simple_animal/hostile/venus_human_trap/OpenFire(atom/the_target)
for(var/turf/T in getline(src,target))
for(var/turf/T in get_line(src,target))
if (T.density)
return
for(var/obj/O in T)
+1 -1
View File
@@ -282,7 +282,7 @@
L.forceMove(locate(locx,locy,mobloc.z))
spawn(0)
var/limit = 2//For only two trailing shadows.
for(var/turf/T in getline(mobloc, L.loc))
for(var/turf/T in get_line(mobloc, L.loc))
new /obj/effect/temp_visual/dir_setting/ninja/shadow(T, L.dir)
limit--
if(limit<=0)
@@ -34,7 +34,7 @@
/// Locked by an ID card
var/locked = FALSE
var/projectile_type = /obj/item/projectile/beam/emitter
var/projectile_type = /obj/item/projectile/beam/emitter/hitscan
var/projectile_sound = 'sound/weapons/emitter.ogg'
var/datum/effect_system/spark_spread/sparks
@@ -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
+1 -1
View File
@@ -214,7 +214,7 @@
/obj/machinery/bsa/full/proc/fire(mob/user, turf/bullseye, target)
var/turf/point = get_front_turf()
for(var/turf/T in getline(get_step(point,dir),get_target_turf()))
for(var/turf/T in get_line(get_step(point,dir),get_target_turf()))
T.ex_act(1)
for(var/atom/A in T)
A.ex_act(1)
+1 -1
View File
@@ -154,7 +154,7 @@
var/kill_range = 14
/obj/machinery/satellite/meteor_shield/proc/space_los(meteor)
for(var/turf/T in getline(src,meteor))
for(var/turf/T in get_line(src,meteor))
if(!isspaceturf(T))
return FALSE
return TRUE