From 50371db6fef8113b2674efc0b1c22cc2bc59c7ac Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Fri, 5 Jul 2024 16:08:38 -0400 Subject: [PATCH] projectiles v8 - raycasting (#6544) Atomizing the physics solver out of #5934 Tldr this is way less overhead, complicated, and far more accurate than the old one --- citadel.dme | 2 +- code/__DEFINES/_flags/atom_flags.dm | 5 + code/__DEFINES/math.dm | 7 +- code/__HELPERS/game/turfs/line.dm | 205 ++++ code/__HELPERS/game/turfs/offsets.dm | 2 +- code/__HELPERS/math/multipliers.dm | 11 - code/__HELPERS/time.dm | 6 +- code/___compile_options.dm | 12 + .../subsystem/processing/processing.dm | 9 +- .../subsystem/processing/projectiles.dm | 10 - .../components/turfs/transition_border.dm | 4 + code/datums/position_point_vector.dm | 111 +- .../technomancer/spells/energy_siphon.dm | 2 +- .../projectiles/projectile_effects.dm | 1 + .../temporary_visuals/projectiles/tracer.dm | 16 +- code/game/objects/objs.dm | 2 +- code/game/objects/structures/girders.dm | 6 +- code/modules/materials/dynamics.dm | 24 +- code/modules/mining/kinetic_crusher.dm | 2 +- .../simple_mob/subtypes/humanoid/possessed.dm | 2 +- .../mechanical/mecha/adv_dark_gygax.dm | 5 +- .../simple_mob/subtypes/slime/feral/feral.dm | 2 +- code/modules/overmap/entity/physics.dm | 4 +- code/modules/overmap/legacy/ships/ship.dm | 4 +- .../projectiles/ammunition/ammo_casing.dm | 2 +- .../guns/energy/kinetic_accelerator.dm | 7 +- .../projectiles/guns/projectile/dartgun.dm | 2 +- code/modules/projectiles/projectile.dm | 1040 ++++++++++++----- code/modules/projectiles/projectile/arc.dm | 10 +- .../modules/projectiles/projectile/bullets.dm | 18 +- .../projectiles/projectile/bullets_vr.dm | 2 +- code/modules/projectiles/projectile/energy.dm | 12 +- code/modules/projectiles/projectile/hook.dm | 5 +- .../projectiles/projectile/magnetic.dm | 8 +- .../projectiles/projectile/reusable.dm | 2 +- .../modules/projectiles/projectile/scatter.dm | 2 +- .../modules/projectiles/projectile/special.dm | 2 +- code/modules/projectiles/projectile/trace.dm | 6 +- code/modules/projectiles/unsorted/magic.dm | 7 +- code/modules/spells/spell_projectile.dm | 9 +- .../spells/targeted/projectile/projectile.dm | 2 +- code/modules/vore/fluffstuff/guns/pummeler.dm | 2 +- code/modules/vore/fluffstuff/guns/secutor.dm | 2 +- code/modules/vore/fluffstuff/guns/sickshot.dm | 2 +- icons/obj/projectiles_impact.dmi | Bin 21200 -> 21566 bytes icons/obj/projectiles_muzzle.dmi | Bin 23870 -> 24182 bytes icons/system/color_32x32.dmi | Bin 201 -> 235 bytes 47 files changed, 1191 insertions(+), 405 deletions(-) create mode 100644 code/__HELPERS/game/turfs/line.dm delete mode 100644 code/__HELPERS/math/multipliers.dm diff --git a/citadel.dme b/citadel.dme index df96f0d9d481..3c900ec07e3e 100644 --- a/citadel.dme +++ b/citadel.dme @@ -382,6 +382,7 @@ #include "code\__HELPERS\files\paths.dm" #include "code\__HELPERS\files\walk.dm" #include "code\__HELPERS\game\depth.dm" +#include "code\__HELPERS\game\turfs\line.dm" #include "code\__HELPERS\game\turfs\offsets.dm" #include "code\__HELPERS\graphs\astar.dm" #include "code\__HELPERS\icons\alpha.dm" @@ -412,7 +413,6 @@ #include "code\__HELPERS\math\angle.dm" #include "code\__HELPERS\math\distance.dm" #include "code\__HELPERS\math\fractions.dm" -#include "code\__HELPERS\math\multipliers.dm" #include "code\__HELPERS\matrices\color_matrix.dm" #include "code\__HELPERS\matrices\transform_matrix.dm" #include "code\__HELPERS\misc\sonar.dm" diff --git a/code/__DEFINES/_flags/atom_flags.dm b/code/__DEFINES/_flags/atom_flags.dm index 5beef6496248..23dfa78116f4 100644 --- a/code/__DEFINES/_flags/atom_flags.dm +++ b/code/__DEFINES/_flags/atom_flags.dm @@ -59,11 +59,16 @@ DEFINE_BITFIELD(atom_flags, list( #define MOVABLE_NO_THROW_DAMAGE_SCALING (1<<1) /// Do not spin when thrown. #define MOVABLE_NO_THROW_SPIN (1<<2) +/// We are currently about to be yanked by a Moved() triggering a Move() +/// +/// * used so things like projectile hitscans know to yield +#define MOVABLE_IN_MOVED_YANK (1<<3) DEFINE_BITFIELD(movable_flags, list( BITFIELD(MOVABLE_NO_THROW_SPEED_SCALING), BITFIELD(MOVABLE_NO_THROW_DAMAGE_SCALING), BITFIELD(MOVABLE_NO_THROW_SPIN), + BITFIELD(MOVABLE_IN_MOVED_YANK), )) // Flags for pass_flags. - Used in /atom/movable/var/pass_flags, and /atom/var/pass_flags_self diff --git a/code/__DEFINES/math.dm b/code/__DEFINES/math.dm index dda5022df074..d23d6c4f7d88 100644 --- a/code/__DEFINES/math.dm +++ b/code/__DEFINES/math.dm @@ -41,7 +41,7 @@ #define WRAP(val, min, max) ( min == max ? min : (val) - (round(((val) - (min))/((max) - (min))) * ((max) - (min))) ) // Real modulus that handles decimals -#define MODULUS(x, y) ( (x) - (y) * round((x) / (y)) ) +#define MODULUS_F(x, y) ( (x) - (y) * round((x) / (y)) ) // Cotangent #define COT(x) (1 / tan(x)) @@ -128,12 +128,13 @@ // Will filter out extra rotations and negative rotations // E.g: 540 becomes 180. -180 becomes 180. -#define SIMPLIFY_DEGREES(degrees) (MODULUS((degrees), 360)) +#define SIMPLIFY_DEGREES(degrees) (MODULUS_F((degrees), 360)) -#define GET_ANGLE_OF_INCIDENCE(face, input) (MODULUS((face) - (input), 360)) +#define GET_ANGLE_OF_INCIDENCE(face, input) (MODULUS_F((face) - (input), 360)) //Finds the shortest angle that angle A has to change to get to angle B. Aka, whether to move clock or counterclockwise. /proc/closer_angle_difference(a, b) + // todo: optimize this shit if(!isnum(a) || !isnum(b)) return a = SIMPLIFY_DEGREES(a) diff --git a/code/__HELPERS/game/turfs/line.dm b/code/__HELPERS/game/turfs/line.dm new file mode 100644 index 000000000000..c07645d3eb27 --- /dev/null +++ b/code/__HELPERS/game/turfs/line.dm @@ -0,0 +1,205 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +/** + * line drawing algorithm + * + * basically, takes one pixel, + * and returns all turfs it touches as it passes through a certain angle + * for a certain number of pixels + * + * we can't use DDA or Bresenham because it's, ironically, not as accurate for + * our purposes. + * + * we have to bias full diagonal's (45, 135, 225, 315) to one side + * + * todo: make sure this is consistent with how byond would behave if using step; or not, i don't care lmao + * + * @params + * * starting - starting turf + * * starting_px - pixel x on starting turf. this is not pixel_x on atom, 1 is the bottomleft, 32 is topright. + * * starting_py - pixel y on starting turf. this is not pixel_y on atom. 1 is bottomleft, 32 is topright. + * * angle - angle, clockwise of north + * * distance - pixels to go forwards + * * include_start - include starting turf + * * diagonal_expand_north - get turfs above diagonal if perfect diagonal. if both expand params are null, we use the same priority as SS13's movement handler. + * * diagonal_expand_south - get turfs below diagonal if perfect diagonal. if both expand params are null, we use the same priority as SS13's movement handler. + */ +/proc/pixel_physics_raycast(turf/starting, starting_px, starting_py, angle, distance, include_start, diagonal_expand_north, diagonal_expand_south) + if(starting_px < 0 || starting_py < 0 || starting_px > 33 || starting_py > 33) + CRASH("starting_px or starting_py is not 0 < x < 33") + starting_px = clamp(starting_px, 1, 32) + starting_py = clamp(starting_py, 1, 32) + + . = list() + + if(include_start) + . += starting + + var/remaining_distance = distance + var/safety = world.maxx + world.maxy + + // if angle is completely cardinal or completely diagonal + // we use MODULUS_F because it's floating-compatible + if(!MODULUS_F(angle, 45)) + // normalize; we already know it's basically diagonal + angle = angle % 360 + if(angle < 0) + angle += 360 + // go do cardinal / diagonal specials + if(!(angle % 90)) + // cardinal + var/c_sdx + var/c_sdy + var/c_dist_to_next + var/turf/c_moving_into = starting + switch(angle) + if(0) + c_sdx = 0 + c_sdy = 1 + c_dist_to_next = (WORLD_ICON_SIZE + 0.5) - starting_py + if(90) + c_sdx = 1 + c_sdy = 0 + c_dist_to_next = (WORLD_ICON_SIZE + 0.5) - starting_px + if(180) + c_sdx = 0 + c_sdy = -1 + c_dist_to_next = starting_py - 0.5 + if(270) + c_sdx = -1 + c_sdy = 0 + c_dist_to_next = starting_px - 0.5 + + remaining_distance -= c_dist_to_next + while(remaining_distance >= 0) + c_moving_into = locate(c_moving_into.x + c_sdx, c_moving_into.y + c_sdy, c_moving_into.z) + if(!c_moving_into) + break + . += c_moving_into + remaining_distance -= WORLD_ICON_SIZE + return + else + var/is_diagonal_case + var/turf/d_moving_into = starting + var/d_diagonal_distance = (((WORLD_ICON_SIZE ** 2) * 2) ** 0.5) + var/d_dist_to_next + var/d_sdx + var/d_sdy + /// direction to go if we want to include the northern-most cardinal step + var/d_north_dir + /// direction to go if we want to include the southern-most cardinal step + var/d_south_dir + /// direction to go if using ss13 native movement to solve for the cardinal steps + var/d_native_dir + // we're diagonal + switch(angle) + if(45) + is_diagonal_case = round(starting_px, 1) == round(starting_py, 1) + d_sdx = 1 + d_sdy = 1 + d_dist_to_next = ((((WORLD_ICON_SIZE + 0.5) ** 2) * 2) ** 0.5) - (starting_px / WORLD_ICON_SIZE) * d_diagonal_distance + d_north_dir = WEST + d_south_dir = SOUTH + d_native_dir = WEST + if(135) + is_diagonal_case = round(starting_px, 1) == (WORLD_ICON_SIZE - round(starting_py, 1) + 1) + d_sdx = 1 + d_sdy = -1 + d_dist_to_next = ((((WORLD_ICON_SIZE + 0.5) ** 2) * 2) ** 0.5) - (starting_px / WORLD_ICON_SIZE) * d_diagonal_distance + d_north_dir = NORTH + d_south_dir = WEST + d_native_dir = WEST + if(225) + is_diagonal_case = round(starting_px, 1) == round(starting_py, 1) + d_sdx = -1 + d_sdy = -1 + d_dist_to_next = ((starting_px - 0.5) / WORLD_ICON_SIZE) * d_diagonal_distance + d_north_dir = NORTH + d_south_dir = EAST + d_native_dir = EAST + if(315) + is_diagonal_case = round(starting_px, 1) == (WORLD_ICON_SIZE - round(starting_py, 1) + 1) + d_sdx = -1 + d_sdy = 1 + d_dist_to_next = ((starting_px - 0.5) / WORLD_ICON_SIZE) * d_diagonal_distance + d_north_dir = EAST + d_south_dir = SOUTH + d_native_dir = EAST + // only do special diag stuff if it's a close enough to a perfect diagonal + if(is_diagonal_case) + remaining_distance -= d_dist_to_next + var/use_ss13_default_priority = isnull(diagonal_expand_north) && isnull(diagonal_expand_south) + while(remaining_distance >= 0) + d_moving_into = locate(d_moving_into.x + d_sdx, d_moving_into.y + d_sdy, d_moving_into.z) + if(!d_moving_into) + break + . += d_moving_into + if(use_ss13_default_priority) + . += get_step(d_moving_into, d_native_dir) + else + if(diagonal_expand_north) + // we actually want to get the one behind them; we don't need to null check either because of that + . += get_step(d_moving_into, d_north_dir) + if(diagonal_expand_south) + // we actually want to get the one behind them; we don't need to null check either because of that + . += get_step(d_moving_into, d_south_dir) + remaining_distance -= WORLD_ICON_SIZE + return + + // dx, dy for every distance pixel + var/ddx = sin(angle) + var/ddy = cos(angle) + + // sign of dx, dy as 1 or -1 + var/sdx = ddx > 0? 1 : -1 + var/sdy = ddy > 0? 1 : -1 + + var/cx = starting_px + var/cy = starting_py + + var/turf/moving_into = starting + while(safety-- > 0 && remaining_distance > 0) + var/d_next_horizontal = \ + (sdx? ((sdx > 0? (WORLD_ICON_SIZE + 0.5) - cx : -cx + 0.5) / ddx) : INFINITY) + var/d_next_vertical = \ + (sdy? ((sdy > 0? (WORLD_ICON_SIZE + 0.5) - cy : -cy + 0.5) / ddy) : INFINITY) + var/consumed = 0 + + if(d_next_horizontal == d_next_vertical) + // we're diagonal + if(d_next_horizontal <= remaining_distance) + moving_into = locate(moving_into.x + sdx, moving_into.y + sdy, moving_into.z) + consumed = d_next_horizontal + if(!moving_into) + break + cx = sdx > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) + cy = sdy > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) + else + break + else if(d_next_horizontal < d_next_vertical) + // closer is to move left/right + if(d_next_horizontal <= remaining_distance) + moving_into = locate(moving_into.x + sdx, moving_into.y, moving_into.z) + consumed = d_next_horizontal + if(!moving_into) + break + cx = sdx > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) + cy = cy + d_next_horizontal * ddy + else if(d_next_vertical < d_next_horizontal) + // closer is to move up/down + if(d_next_vertical <= remaining_distance) + moving_into = locate(moving_into.x, moving_into.y + sdy, moving_into.z) + consumed = d_next_vertical + if(!moving_into) + break + cx = cx + d_next_vertical * ddx + cy = sdy > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) + else + break + + remaining_distance -= consumed + + // if we need to move + if(moving_into) + . += moving_into diff --git a/code/__HELPERS/game/turfs/offsets.dm b/code/__HELPERS/game/turfs/offsets.dm index cdfabfefc60e..cd206ffe0742 100644 --- a/code/__HELPERS/game/turfs/offsets.dm +++ b/code/__HELPERS/game/turfs/offsets.dm @@ -54,7 +54,7 @@ // dir2angle is north-zero clockwise // turn_amount will therefore be how much we need to turn clockwise to get there // - // we know this will be a whole number so we use native % instead of MODULUS + // we know this will be a whole number so we use native % instead of MODULUS_F var/turn_amount = (dir2angle(new_dir) - dir2angle(old_dir)) % 360 // rotated x/y is where the entity will be after being rotated in its **current** diff --git a/code/__HELPERS/math/multipliers.dm b/code/__HELPERS/math/multipliers.dm deleted file mode 100644 index 515e4c848cfc..000000000000 --- a/code/__HELPERS/math/multipliers.dm +++ /dev/null @@ -1,11 +0,0 @@ -/** - * multiplied effect of an effect multiplier based on '1' being default - */ -/proc/multiply_effect_multiplier(multiplier, multiply_by) - if(multiply_by < 0) - multiply_by = -multiply_by - multiplier = -multiplier - if(multiplier > 0) - return multiplier ** multiply_by - else - return -((-multiplier) ** multiply_by) diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index ddcb8d2809c3..822b1d8d3c0c 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -149,21 +149,21 @@ GLOBAL_VAR_INIT(roundstart_hour, pick(2,7,12,17)) if(second < 60) return "[second] second[(second != 1)? "s":""]" var/minute = FLOOR(second / 60, 1) - second = MODULUS(second, 60) + second = MODULUS_F(second, 60) var/secondT if(second) secondT = " and [second] second[(second != 1)? "s":""]" if(minute < 60) return "[minute] minute[(minute != 1)? "s":""][secondT]" var/hour = FLOOR(minute / 60, 1) - minute = MODULUS(minute, 60) + minute = MODULUS_F(minute, 60) var/minuteT if(minute) minuteT = " and [minute] minute[(minute != 1)? "s":""]" if(hour < 24) return "[hour] hour[(hour != 1)? "s":""][minuteT][secondT]" var/day = FLOOR(hour / 24, 1) - hour = MODULUS(hour, 24) + hour = MODULUS_F(hour, 24) var/hourT if(hour) hourT = " and [hour] hour[(hour != 1)? "s":""]" diff --git a/code/___compile_options.dm b/code/___compile_options.dm index a5085fa062e9..d4040eb80394 100644 --- a/code/___compile_options.dm +++ b/code/___compile_options.dm @@ -189,12 +189,24 @@ // #define AO_USE_LIGHTING_OPACITY // ## Overlays + /** * A reasonable number of maximum overlays an object needs. * If you think you need more, rethink it. */ #define MAX_ATOM_OVERLAYS 100 +// ## Projectiles + +/** + * Enable raycast visuals + */ +// #define CF_PROJECTILE_RAYCAST_VISUALS + +#ifdef CF_PROJECTILE_RAYCAST_VISUALS + #warn Visualization of projectile raycast algorithm enabled. +#endif + // ## Timers // #define TIMER_LOOP_DEBUGGING diff --git a/code/controllers/subsystem/processing/processing.dm b/code/controllers/subsystem/processing/processing.dm index 1e3a57f5c4fe..3e4829857a2a 100644 --- a/code/controllers/subsystem/processing/processing.dm +++ b/code/controllers/subsystem/processing/processing.dm @@ -18,7 +18,11 @@ SUBSYSTEM_DEF(processing) currentrun = processing.Copy() //cache for sanic speed (lists are references anyways) var/list/current_run = currentrun - var/dt = (subsystem_flags & SS_TICKER)? (wait * world.tick_lag) : max(world.tick_lag, wait * 0.1) + // tick_lag is in deciseconds + // in ticker, our wait is that many ds + // in non-ticker, our wait is either wait in ds, or a minimum of tick_lag in ds + // we convert it to seconds with * 0.1 + var/dt = (subsystem_flags & SS_TICKER? (wait * world.tick_lag) : max(world.tick_lag, wait)) * 0.1 while(current_run.len) var/datum/thing = current_run[current_run.len] @@ -44,6 +48,9 @@ SUBSYSTEM_DEF(processing) * - Probability of something happening, do `if(DT_PROB(25, delta_time))`, not `if(prob(25))`. This way, if the subsystem wait is e.g. lowered, there won't be a higher chance of this event happening per second * * If you override this do not call parent, as it will return PROCESS_KILL. This is done to prevent objects that dont override process() from staying in the processing list + * + * @params + * * delta_time - time that should have elapsed, in seconds */ /datum/proc/process(delta_time) set waitfor = FALSE diff --git a/code/controllers/subsystem/processing/projectiles.dm b/code/controllers/subsystem/processing/projectiles.dm index 08771ed6a15d..d0005da211b3 100644 --- a/code/controllers/subsystem/processing/projectiles.dm +++ b/code/controllers/subsystem/processing/projectiles.dm @@ -4,13 +4,3 @@ PROCESSING_SUBSYSTEM_DEF(projectiles) stat_tag = "PP" priority = FIRE_PRIORITY_PROJECTILES subsystem_flags = SS_NO_INIT - var/global_max_tick_moves = 10 - var/global_pixel_speed = 2 - var/global_iterations_per_move = 16 - -/datum/controller/subsystem/processing/projectiles/proc/set_pixel_speed(new_speed) - global_pixel_speed = new_speed - for(var/i in processing) - var/obj/projectile/P = i - if(istype(P)) //there's non projectiles on this too. - P.set_pixel_speed(new_speed) diff --git a/code/datums/components/turfs/transition_border.dm b/code/datums/components/turfs/transition_border.dm index a0a7f598d861..7d01434fa28c 100644 --- a/code/datums/components/turfs/transition_border.dm +++ b/code/datums/components/turfs/transition_border.dm @@ -50,6 +50,8 @@ /datum/component/transition_border/proc/transit(datum/source, atom/movable/AM) if(AM.atom_flags & ATOM_ABSTRACT) return // nah. + if(AM.movable_flags & MOVABLE_IN_MOVED_YANK) + return // we're already in a yank var/turf/our_turf = parent var/z_index = SSmapping.level_index_in_dir(our_turf.z, dir) if(isnull(z_index)) @@ -57,7 +59,9 @@ qdel(src) return // todo: this is shit but we have to yield to prevent a Moved() before Moved() + AM.movable_flags |= MOVABLE_IN_MOVED_YANK spawn(0) + AM.movable_flags &= ~MOVABLE_IN_MOVED_YANK if(AM.loc != our_turf) return var/turf/target diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm index ed30566a0e6a..6f9121469b92 100644 --- a/code/datums/position_point_vector.dm +++ b/code/datums/position_point_vector.dm @@ -9,7 +9,10 @@ #define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) {new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED)} #define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT) -/datum/position //For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess. +/** + * Stores x/y/z and pixel_x/pixel_y + */ +/datum/position var/x = 0 var/y = 0 var/z = 0 @@ -66,7 +69,12 @@ /proc/angle_between_points(datum/point/a, datum/point/b) return arctan((b.y - a.y), (b.x - a.x)) -/datum/point //A precise point on the map in absolute pixel locations based on world.icon_size. Pixels are FROM THE EDGE OF THE MAP! +/** + * A precise point on the map. + * + * x/y are absolute pixels from map edge, so 1, 1 = the lower-left most pixel on the zlevel, not the center of turf (1,1)! + */ +/datum/point var/x = 0 var/y = 0 var/z = 0 @@ -99,9 +107,9 @@ /datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) if(!isnull(tile_x)) - x = ((tile_x - 1) * world.icon_size) + world.icon_size / 2 + p_x + 1 + x = ((tile_x - 1) * WORLD_ICON_SIZE) + WORLD_ICON_SIZE / 2 + p_x + 1 if(!isnull(tile_y)) - y = ((tile_y - 1) * world.icon_size) + world.icon_size / 2 + p_y + 1 + y = ((tile_y - 1) * WORLD_ICON_SIZE) + WORLD_ICON_SIZE / 2 + p_y + 1 if(!isnull(tile_z)) z = tile_z @@ -109,29 +117,84 @@ var/turf/T = return_turf() return "\ref[src] aX [x] aY [y] aZ [z] pX [return_px()] pY [return_py()] mX [T.x] mY [T.y] mZ [T.z]" -/datum/point/proc/move_atom_to_src(atom/movable/AM) - AM.forceMove(return_turf()) - AM.pixel_x = return_px() - AM.pixel_y = return_py() - +/** + * angle is clockwise from north + */ +/datum/point/proc/shift_in_projectile_angle(angle, distance) + x += sin(angle) * distance + y += cos(angle) * distance + +/** + * doesn't use set base pixel x/y + * + * if not on a turf, we return null + */ +/datum/point/proc/instantiate_movable_with_unmanaged_offsets(typepath, ...) + ASSERT(ispath(typepath, /atom/movable)) + // todo: inline everything + var/turf/where = return_turf() + if(!where) + return + var/atom/movable/created = new typepath(arglist(list(where) + args.Copy(2))) + created.pixel_x = return_px() + created.pixel_y = return_py() + return created + +/** + * return rounded pixel x + */ +/datum/point/proc/return_px() + // 1 = -15, + // 32 = +16 + // we start at 16, 16 + . = x % WORLD_ICON_SIZE + if(!.) + return 16 + . -= 16 + +/** + * return rounded pixel y + */ +/datum/point/proc/return_py() + // 1 = -15, + // 32 = +16 + // we start at 16, 16 + . = y % WORLD_ICON_SIZE + if(!.) + return 16 + . -= 16 + +/** + * return turf + */ /datum/point/proc/return_turf() - return locate(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z) - + return locate( + ceil(floor(x) / WORLD_ICON_SIZE), + ceil(floor(y) / WORLD_ICON_SIZE), + z, + ) + +/** + * extract closest in-bounds turf + * + * does not check for map transitions + */ /datum/point/proc/clamped_return_turf() - return locate(clamp(CEILING(x / world.icon_size, 1), 1, world.maxx), clamp(CEILING(y / world.icon_size, 1), 1, world.maxy), z) - + return locate( + clamp(ceil(floor(x) / WORLD_ICON_SIZE), 1, world.maxx), + clamp(ceil(floor(y) / WORLD_ICON_SIZE), 1, world.maxy), + z, + ) + +/** + * return list(x, y, z) + */ /datum/point/proc/return_coordinates() //[turf_x, turf_y, z] - return list(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z) - -/datum/point/proc/return_position() - return new /datum/position(src) - -/datum/point/proc/return_px() - return MODULUS(x, world.icon_size) - 16 - 1 - -/datum/point/proc/return_py() - return MODULUS(y, world.icon_size) - 16 - 1 - + return list( + ceil(floor(x) / WORLD_ICON_SIZE), + ceil(floor(y) / WORLD_ICON_SIZE), + z, + ) /datum/point/vector /// Pixels per iteration diff --git a/code/game/gamemodes/technomancer/spells/energy_siphon.dm b/code/game/gamemodes/technomancer/spells/energy_siphon.dm index e45adfb79698..cc51d809667d 100644 --- a/code/game/gamemodes/technomancer/spells/energy_siphon.dm +++ b/code/game/gamemodes/technomancer/spells/energy_siphon.dm @@ -170,7 +170,7 @@ /obj/projectile/beam/lightning/energy_siphon name = "energy stream" icon_state = "lightning" - range = 6 // Backup plan in-case the effect somehow misses the Technomancer. + range = WORLD_ICON_SIZE * 6 power = 5 // This fires really fast, so this may add up if someone keeps standing in the beam. penetrating = 5 diff --git a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm index 91b9d7812e24..cc2c6be080ad 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm @@ -1,4 +1,5 @@ /obj/effect/projectile + SET_APPEARANCE_FLAGS(PIXEL_SCALE) name = "pew" icon = 'icons/obj/projectiles.dmi' icon_state = "nothing" diff --git a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm index a47d99f188fe..de36387ee2c9 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm @@ -1,12 +1,6 @@ -/datum/beam_legacy_components_cache - var/list/beam_components = list() - -/datum/beam_legacy_components_cache/Destroy() - for(var/component in beam_components) - qdel(component) - return ..() - -/proc/generate_tracer_between_points(datum/point/starting, datum/point/ending, datum/beam_legacy_components_cache/beam_components, 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! +//Do not pass z-crossing points as that will not be properly (and likely will never be properly until it's absolutely needed) supported! +// todo: when do we rework +/proc/generate_tracer_between_points(datum/point/starting, datum/point/ending, list/beam_components, beam_type, color, qdel_in = 5, light_range = 2, light_color_override, light_intensity = 1, instance_key) if(!istype(starting) || !istype(ending) || !ispath(beam_type)) return var/datum/point/midpoint = point_midpoint_points(starting, ending) @@ -29,9 +23,9 @@ for(var/obj/effect/projectile_lighting/PL in T) if(PL.owner == instance_key) continue tracing_line - beam_components.beam_components += new /obj/effect/projectile_lighting(T, light_color_override, light_range, light_intensity, instance_key) + beam_components += new /obj/effect/projectile_lighting(T, light_color_override, light_range, light_intensity, instance_key) line = null - beam_components.beam_components += PB + beam_components += PB /obj/effect/projectile/tracer name = "beam" diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 262543cb0161..1de47c3f95af 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -911,7 +911,7 @@ return FALSE . = TRUE // todo: mobility flags - var/extra_time = MODULUS(time, interval) + var/extra_time = MODULUS_F(time, interval) var/i for(i in 1 to round(time / interval)) if(!do_after(escapee, interval, mobility_flags = MOBILITY_CAN_RESIST)) diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm index 7c9f05c87ac7..70bfc3343574 100644 --- a/code/game/objects/structures/girders.dm +++ b/code/game/objects/structures/girders.dm @@ -45,7 +45,11 @@ set_multiplied_integrity(1) else name = "[material_reinforcing.display_name]-reinforced [material_structure.display_name] girder" - set_multiplied_integrity(material_structure.relative_integrity * multiply_effect_multiplier(material_reinforcing.relative_integrity, 0.5)) + // ()'s to coerce them into instances, and not text strings. + set_multiplied_integrity(SSmaterials.dynamic_calculate_relative_integrity(list( + (material_structure) = 1, + (material_reinforcing) = 0.5, + ))) // todo: refactor if(material_color) diff --git a/code/modules/materials/dynamics.dm b/code/modules/materials/dynamics.dm index 8f5c505bc163..1136de3b9d2a 100644 --- a/code/modules/materials/dynamics.dm +++ b/code/modules/materials/dynamics.dm @@ -1,7 +1,10 @@ //* This file is explicitly licensed under the MIT license. *// //* Copyright (c) 2023 Citadel Station developers. *// -//* Page has all balancing parameters + algorithms for dynamic attribute computations for things like armor +//? Page has all balancing parameters + algorithms for dynamic attribute computations for things like armor ?// +//? Prefix subsystem procs with 'dynamic_', please! ?// + +//* Armor *// /** * creates an armor datum based off of our stats @@ -188,6 +191,25 @@ wall_armor_cache[cache_key] = resolved return resolved +//* Integrity *// + +/** + * gets overall integrity multiplier from a list of materials associated to significances + */ +/datum/controller/subsystem/materials/proc/dynamic_calculate_relative_integrity(list/datum/material/materials) + var/total = 0 + var/pieces = 0 + + for(var/datum/material/material as anything in materials) + var/significance = materials[material] + + pieces += significance + total += material.relative_integrity * significance + + return total / pieces + +//* Melee *// + /** * get melee stats * autodetect with initial damage modes of item diff --git a/code/modules/mining/kinetic_crusher.dm b/code/modules/mining/kinetic_crusher.dm index d88f2a49e095..18744726f271 100644 --- a/code/modules/mining/kinetic_crusher.dm +++ b/code/modules/mining/kinetic_crusher.dm @@ -310,7 +310,7 @@ damage = 0 //We're just here to mark people. This is still a melee weapon. damage_type = BRUTE damage_flag = ARMOR_BOMB - range = 6 + range = WORLD_ICON_SIZE * 6 accuracy = INFINITY // NO. // log_override = TRUE var/obj/item/kinetic_crusher/hammer_synced diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/possessed.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/possessed.dm index f7a7f74eea10..c3e1564fca60 100644 --- a/code/modules/mob/living/simple_mob/subtypes/humanoid/possessed.dm +++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/possessed.dm @@ -105,7 +105,7 @@ "\The A few last desperate seals give out with a weary series of pops, and the suit contorts with the final pressure differentials resolved: the suit tangles and leaks, and finally compacts back into it's rightful shape.", "\The Tightening, the suit re-attempts to remain it's current form, before it collapses under the stress, supporting mechanisms closing in on themselves like a noose with nothing left to catch on.", "\The The suit makes a noise akin to clockwork binding, and shutters, before something imperceptible gives with an abysmal noise and the suit returns to it's default form."))) - gib() + // gib() if(rand(1,2) == 1) new rig1(droploc) else diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm index 78d3e27ba788..f9d04e65ecb5 100644 --- a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm @@ -124,16 +124,13 @@ damage = 20 damage_type = BURN damage_flag = ARMOR_LASER + speed = 32 // 10 tiles a second /obj/projectile/energy/homing_bolt/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) ..() if(target) set_homing_target(target) -/obj/projectile/energy/homing_bolt/fire(angle, atom/direct_target) - ..() - set_pixel_speed(0.5) - #define ELECTRIC_ZAP_POWER 20000 // Charges a tesla shot, while emitting a dangerous electric field. The exosuit is immune to electric damage while this is ongoing. diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm b/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm index e3f869109741..479a70b93768 100644 --- a/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm +++ b/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm @@ -68,7 +68,7 @@ damage_type = BRUTE damage_flag = ARMOR_MELEE armor_penetration = 30 - speed = 2 + speed = 32 / 2 // ~5 tiles/second icon_scale_x = 2 // It hits like a truck. icon_scale_y = 2 sharp = TRUE diff --git a/code/modules/overmap/entity/physics.dm b/code/modules/overmap/entity/physics.dm index 6d45c07ea346..208d0160b287 100644 --- a/code/modules/overmap/entity/physics.dm +++ b/code/modules/overmap/entity/physics.dm @@ -27,8 +27,8 @@ var/new_turf_x = CEILING(new_pos_pix_x / WORLD_ICON_SIZE, 1) var/new_turf_y = CEILING(new_pos_pix_y / WORLD_ICON_SIZE, 1) - var/new_pixel_x = MODULUS(new_pos_pix_x, WORLD_ICON_SIZE) - (WORLD_ICON_SIZE / 2) - 1 - var/new_pixel_y = MODULUS(new_pos_pix_y, WORLD_ICON_SIZE) - (WORLD_ICON_SIZE / 2) - 1 + var/new_pixel_x = MODULUS_F(new_pos_pix_x, WORLD_ICON_SIZE) - (WORLD_ICON_SIZE / 2) - 1 + var/new_pixel_y = MODULUS_F(new_pos_pix_y, WORLD_ICON_SIZE) - (WORLD_ICON_SIZE / 2) - 1 var/new_loc = locate(new_turf_x, new_turf_y, z) diff --git a/code/modules/overmap/legacy/ships/ship.dm b/code/modules/overmap/legacy/ships/ship.dm index 4e3e8325b559..78148dc31f46 100644 --- a/code/modules/overmap/legacy/ships/ship.dm +++ b/code/modules/overmap/legacy/ships/ship.dm @@ -201,11 +201,11 @@ /obj/overmap/entity/visitable/ship/proc/ETA() . = INFINITY if(vel_x) - var/offset = MODULUS(OVERMAP_DIST_TO_PIXEL(pos_x), WORLD_ICON_SIZE) + var/offset = MODULUS_F(OVERMAP_DIST_TO_PIXEL(pos_x), WORLD_ICON_SIZE) var/dist_to_go = (vel_x > 0) ? (WORLD_ICON_SIZE - offset) : offset . = min(., (dist_to_go / OVERMAP_DIST_TO_PIXEL(abs(vel_x))) * 10) if(vel_y) - var/offset = MODULUS(OVERMAP_DIST_TO_PIXEL(pos_y), WORLD_ICON_SIZE) + var/offset = MODULUS_F(OVERMAP_DIST_TO_PIXEL(pos_y), WORLD_ICON_SIZE) var/dist_to_go = (vel_y > 0) ? (WORLD_ICON_SIZE - offset) : offset . = min(., (dist_to_go / OVERMAP_DIST_TO_PIXEL(abs(vel_y))) * 10) . = max(., 0) diff --git a/code/modules/projectiles/ammunition/ammo_casing.dm b/code/modules/projectiles/ammunition/ammo_casing.dm index d27d97faf58e..2c12ce235bf8 100644 --- a/code/modules/projectiles/ammunition/ammo_casing.dm +++ b/code/modules/projectiles/ammunition/ammo_casing.dm @@ -108,7 +108,7 @@ /obj/item/ammo_casing/proc/init_projectile() if(istype(stored)) CRASH("double init?") - stored = new projectile_type + stored = new projectile_type(src) return stored /obj/item/ammo_casing/update_icon_state() diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm index 9f0a45944492..a5d8cbf30dfa 100644 --- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm +++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm @@ -207,7 +207,7 @@ damage = 30 damage_type = BRUTE damage_flag = ARMOR_BOMB - range = 4 + range = WORLD_ICON_SIZE * 4 // log_override = TRUE var/pressure_decrease_active = FALSE @@ -241,7 +241,7 @@ pressure_decrease_active = TRUE return ..() -/obj/projectile/kinetic/on_range() +/obj/projectile/kinetic/legacy_on_range() strike_thing() ..() @@ -362,8 +362,7 @@ cost = 25 /obj/item/ka_modkit/range/modify_projectile(obj/projectile/kinetic/K) - K.range += modifier - + K.range += modifier * WORLD_ICON_SIZE //Damage /obj/item/ka_modkit/damage diff --git a/code/modules/projectiles/guns/projectile/dartgun.dm b/code/modules/projectiles/guns/projectile/dartgun.dm index fa3f25d45563..6a96b6ffe293 100644 --- a/code/modules/projectiles/guns/projectile/dartgun.dm +++ b/code/modules/projectiles/guns/projectile/dartgun.dm @@ -3,7 +3,7 @@ icon_state = "dart" damage = 5 var/reagent_amount = 15 - range = 15 //shorter range + range = WORLD_ICON_SIZE * 15 muzzle_type = null diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 37d219c36bde..4469da184087 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -1,7 +1,20 @@ -///Not actually hitscan but close as we get without actual hitscan. -#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 +/** + * ## Physics Specifications + * + * We track physics as absolute pixel on a tile, not byond's pixel x/y + * thus the first pixel at bottom left of tile is 1, 1 + * and the last pixel at top right is 32, 32 (for a world icon size of 32 pixels) + * + * We cross over to the next tile at above 32, for up/right, + * and to the last tile at below 1, for bottom/left. + * + * The code might handle it based on how it's implemented, + * but as long as the error is 1 pixel or below, it's not a big deal. + * + * The reason we're so accurate (1 pixel/below is pretty insanely strict) is + * so players have the projectile act like what the screen says it should be like; + * hence why projectiles can realistically path across corners based on their 'hitbox center'. + */ /obj/projectile name = "projectile" icon = 'icons/obj/projectiles.dmi' @@ -13,23 +26,88 @@ mouse_opacity = MOUSE_OPACITY_TRANSPARENT depth_level = INFINITY // nothing should be passing over us from depth - ////TG PROJECTILE SYTSEM - //Projectile stuff - var/range = 50 - var/originalRange + //* Physics - Configuration *// + + /// speed, in pixels per decisecond + var/speed = 32 / 0.55 // ~18 tiles/second + /// are we a hitscan projectile? + var/hitscan = FALSE + /// angle, in degrees **clockwise of north** + var/angle + /// max distance in pixels + /// + /// * please set this to a multiple of [WORLD_ICON_SIZE] so we scale with tile size. + var/range = WORLD_ICON_SIZE * 50 + // todo: lifespan + + //* Physics - Tracers *// + + /// tracer /datum/point's + var/list/tracer_vertices + /// first point is a muzzle effect + var/tracer_muzzle_flash + /// last point is an impact + var/tracer_impact_effect + /// default tracer duration + var/tracer_duration = 5 + + //* Physics - State *// + + /// paused? if so, we completely get passed over during processing + var/paused = FALSE + /// currently hitscanning + var/hitscanning = FALSE + /// a flag to prevent movement hooks from resetting our physics on a forced movement + var/trajectory_ignore_forcemove = FALSE + /// cached value: move this much x for this much distance + /// basically, dx / distance + var/calculated_dx + /// cached value: move this much y for this much distance + /// basically, dy / distance + var/calculated_dy + /// cached sign of dx; 1, -1, or 0 + var/calculated_sdx + /// cached sign of dy; 1, -1, or 0 + var/calculated_sdy + /// our current pixel location on turf + /// byond pixel_x rounds, and we don't want that + /// + /// * at below 0 or at equals to WORLD_ICON_SIZE, we move to the next turf + var/current_px + /// our current pixel location on turf + /// byond pixel_y rounds, and we don't want that + /// + /// * at below 0 or at equals to WORLD_ICON_SIZE, we move to the next turf + var/current_py + /// the pixel location we're moving to, or the [current_px] after this iteration step + /// + /// * used so stuff like hitscan deflections work based on the actual raycasted collision step, and not the prior step. + var/next_px + /// the pixel location we're moving to, or the [current_px] after this iteration step + /// + /// * used so stuff like hitscan deflections work based on the actual raycasted collision step, and not the prior step. + var/next_py + /// used to track if we got kicked forwards after calling Move() + var/trajectory_kick_forwards = 0 + /// to avoid going too fast when kicked forwards by a mirror, if we overshoot the pixels we're + /// supposed to move this gets set to penalize the next move with a weird algorithm + /// that i won't bother explaining + var/trajectory_penalty_applied = 0 + /// currently travelled distance in pixels + var/distance_travelled + /// if we get forcemoved, this gets reset to 0 as a trip + /// this way, we know exactly how far we moved + var/distance_travelled_this_iteration + /// where the physics loop and/or some other thing moving us is trying to move to + /// used to determine where to draw hitscan tracers + // todo: this being here is kinda a symptom that things are handled weirdly but whatever + // optimally physics loop should handle tracking for stuff like animations, not require on hit processing to check turfs + var/turf/trajectory_moving_to //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/ignore_source_check = FALSE - var/speed = 0.55 //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 @@ -39,16 +117,11 @@ 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. /// do we have a tracer? if not we completely ignore hitscan logic var/has_tracer = TRUE var/tracer_type var/muzzle_type var/impact_type - var/datum/beam_legacy_components_cache/beam_components var/miss_sounds var/ricochet_sounds @@ -68,7 +141,9 @@ //Homing var/homing = FALSE var/atom/homing_target - var/homing_turn_speed = 10 //Angle per tick. + // angle per deciseconds + // this is smoother the less time between SSprojectiles fires + var/homing_turn_speed = 10 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 @@ -151,104 +226,22 @@ 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 + // todo: currently unimplemneted var/vacuum_traversal = TRUE //Determines if the projectile can exist in vacuum, if false, the projectile will be deleted if it enters vacuum. - var/temporary_unstoppable_movement = FALSE var/no_attack_log = FALSE -/obj/projectile/proc/Range() - range-- - if(range <= 0 && loc) - on_range() +/obj/projectile/Destroy() + // stop processing + STOP_PROCESSING(SSprojectiles, src) + // cleanup + cleanup_hitscan_tracers() + return ..() -/obj/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range +/obj/projectile/proc/legacy_on_range() //if we want there to be effects when they reach the end of their range + finalize_hitscan_tracers(impact_effect = FALSE, kick_forwards = 8) qdel(src) -/obj/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/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/projectile/proc/set_pixel_speed(new_speed) - if(trajectory) - trajectory.set_speed(new_speed) - return TRUE - return FALSE - -/obj/projectile/proc/record_hitscan_start(datum/point/pcache) - if(!has_tracer) - return - if(!pcache) - return - beam_segments = list() - beam_index = pcache - beam_segments[beam_index] = null //record start. - -/obj/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/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/projectile/Crossed(atom/movable/AM) //A mob moving on a tile with a projectile is hit by it. if(AM.is_incorporeal()) return @@ -258,138 +251,64 @@ if(can_hit_target(L, permutated, (AM == original))) Bump(AM) -/obj/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/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/projectile/process(delta_time) - 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/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/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) + var/is_a_jump = isturf(target) != isturf(loc) || target.z != z || !trajectory_ignore_forcemove + if(is_a_jump) + record_hitscan_end() + render_hitscan_tracers() . = ..() - 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/projectile/proc/fire(angle, atom/direct_target) + if(!.) + stack_trace("projectile forcemove failed; please do not try to forcemove projectiles to invalid locations!") + distance_travelled_this_iteration = 0 + if(!trajectory_ignore_forcemove) + reset_physics_to_turf() + if(is_a_jump) + record_hitscan_start() + +/obj/projectile/proc/fire(set_angle_to, atom/direct_target) if(only_submunitions) // refactor projectiles whwen holy shit this is awful lmao + // todo: this should make a muzzle flash qdel(src) return //If no angle needs to resolve it from xo/yo! if(direct_target) direct_target.bullet_act(src, def_zone) + // todo: this should make a muzzle flash qdel(src) return - if(isnum(angle)) - setAngle(angle) + if(isnum(set_angle_to)) + set_angle(set_angle_to) + + // setup physics + setup_physics() + var/turf/starting = get_turf(src) - if(isnull(Angle)) //Try to resolve through offsets if there's no angle set. + if(isnull(angle)) //Try to resolve through offsets if there's no angle set. if(isnull(xo) || isnull(yo)) stack_trace("WARNING: Projectile [type] deleted due to being unable to resolve a target after angle was null!") qdel(src) return var/turf/target = locate(clamp(starting + xo, 1, world.maxx), clamp(starting + yo, 1, world.maxy), starting.z) - setAngle(get_visual_angle(src, target)) + set_angle(get_visual_angle(src, target)) if(dispersion) - setAngle(Angle + rand(-dispersion, dispersion)) - original_angle = Angle - trajectory_ignore_forcemove = TRUE + set_angle(angle + rand(-dispersion, dispersion)) + original_angle = angle 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 + // kickstart if(hitscan) - . = process_hitscan() - START_PROCESSING(SSprojectiles, src) - pixel_move(1, FALSE) //move it now! + physics_hitscan() + else + START_PROCESSING(SSprojectiles, src) + physics_iteration(WORLD_ICON_SIZE, SSprojectiles.wait) /obj/projectile/Move(atom/newloc, dir = NONE) . = ..() if(.) - if(temporary_unstoppable_movement) - temporary_unstoppable_movement = FALSE - movement_type &= ~MOVEMENT_UNSTOPPABLE if(fired && can_hit_target(original, permutated, TRUE)) Bump(original) -/obj/projectile/proc/after_z_change(atom/olcloc, atom/newloc) - -/obj/projectile/proc/before_z_change(atom/oldloc, atom/newloc) - -/obj/projectile/proc/before_move() - return - -/obj/projectile/proc/after_move() - return - -/obj/projectile/proc/store_hitscan_collision(datum/point/pcache) - if(!has_tracer) - return - beam_segments[beam_index] = pcache - beam_index = pcache - beam_segments[beam_index] = null - //Spread is FORCED! /obj/projectile/proc/preparePixelProjectile(atom/target, atom/source, params, spread = 0) var/turf/curloc = get_turf(source) @@ -410,18 +329,18 @@ if(targloc || !params) yo = targloc.y - curloc.y xo = targloc.x - curloc.x - setAngle(get_visual_angle(src, targloc) + spread) + set_angle(get_visual_angle(src, targloc) + spread) if(isliving(source) && params) var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, params) p_x = calculated[2] p_y = calculated[3] - setAngle(calculated[1] + spread) + set_angle(calculated[1] + spread) else if(targloc) yo = targloc.y - curloc.y xo = targloc.x - curloc.x - setAngle(get_visual_angle(src, targloc) + spread) + set_angle(get_visual_angle(src, targloc) + spread) else stack_trace("WARNING: Projectile [type] fired without either mouse parameters, or a target atom to aim at!") qdel(src) @@ -464,21 +383,7 @@ source = get_turf(src) starting = get_turf(source) original = target - setAngle(get_visual_angle(source, target)) - -/obj/projectile/Destroy() - if(hitscan) - finalize_hitscan_and_generate_tracers() - STOP_PROCESSING(SSprojectiles, src) - qdel(trajectory) - return ..() - -/obj/projectile/proc/cleanup_beam_segments() - if(!has_tracer) - return - QDEL_LIST_ASSOC(beam_segments) - beam_segments = list() - qdel(beam_index) + set_angle(get_visual_angle(source, target)) /obj/projectile/proc/vol_by_damage() if(damage) @@ -486,44 +391,6 @@ else return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume. -/obj/projectile/proc/finalize_hitscan_and_generate_tracers(impacting = TRUE) - if(!has_tracer) - return - if(trajectory && beam_index) - var/datum/point/pcache = trajectory.copy_to() - beam_segments[beam_index] = pcache - generate_hitscan_tracers(null, null, impacting) - -/obj/projectile/proc/generate_hitscan_tracers(cleanup = TRUE, duration = 5, impacting = TRUE) - if(!length(beam_segments)) - return - beam_components = new - if(tracer_type) - var/tempref = "\ref[src]" - for(var/datum/point/p in beam_segments) - generate_tracer_between_points(p, beam_segments[p], beam_components, 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) - beam_components.beam_components += thing - 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) - beam_components.beam_components += thing - QDEL_IN(beam_components, duration) - //Returns true if the target atom is on our current turf and above the right layer //If direct target is true it's the originally clicked target. /obj/projectile/proc/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE) @@ -613,6 +480,18 @@ if(A) on_impact(A) + + if(hitscanning) + if(trajectory_moving_to) + // create tracers + var/datum/point/visual_impact_point = get_intersection_point(trajectory_moving_to) + // kick it forwards a bit + visual_impact_point.shift_in_projectile_angle(angle, 2) + // draw + finalize_hitscan_tracers(visual_impact_point, impact_effect = TRUE) + else + finalize_hitscan_tracers(impact_effect = TRUE, kick_forwards = 32) + qdel(src) return TRUE @@ -743,7 +622,7 @@ SM.damage = damage_override if(submunition_constant_spread) SM.dispersion = 0 - var/calculated = Angle + round((count / amt - 0.5) * submunition_spread_max, 1) + var/calculated = angle + round((count / amt - 0.5) * submunition_spread_max, 1) SM.launch_projectile(target, target_zone, user, params, calculated) else SM.dispersion = rand(temp_min_spread, submunition_spread_max) / 10 @@ -803,7 +682,618 @@ /obj/projectile/proc/get_final_damage(atom/target) return run_damage_vulnerability(target) -//? Targeting +//* Hitscan Visuals *// + +/** + * returns a /datum/point based on where we currently are + */ +/obj/projectile/proc/get_tracer_point() + RETURN_TYPE(/datum/point) + var/datum/point/point = new + if(trajectory_moving_to) + // we're in move. use next px/py to respect 1. kick forwards 2. deflections + point.x = (trajectory_moving_to.x - 1) * WORLD_ICON_SIZE + next_px + point.y = (trajectory_moving_to.y - 1) * WORLD_ICON_SIZE + next_py + else + point.x = (x - 1) * WORLD_ICON_SIZE + current_px + point.y = (y - 1) * WORLD_ICON_SIZE + current_py + point.z = z + return point + +/** + * * returns a /datum/point based on where we'll be when we loosely intersect a tile + * * returns null if we'll never intersect it + * * returns our current point if we're already loosely intersecting it + * * loosely intersecting means that we are level with the tile in either x or y. + */ +/obj/projectile/proc/get_intersection_point(turf/colliding) + RETURN_TYPE(/datum/point) + ASSERT(!isnull(angle)) + + // calculate hwere we are + var/our_x = (x - 1) * WORLD_ICON_SIZE + current_px + var/our_y = (y - 1) * WORLD_ICON_SIZE + current_py + + // calculate how far we have to go to touch their closest x / y axis + var/d_to_reach_x + var/d_to_reach_y + + if(colliding.x != x) + switch(calculated_sdx) + if(0) + return + if(1) + if(colliding.x < x) + return + d_to_reach_x = (((colliding.x - 1) * WORLD_ICON_SIZE + 0.5) - our_x) / calculated_dx + if(-1) + if(colliding.x > x) + return + d_to_reach_x = (((colliding.x - 0) * WORLD_ICON_SIZE + 0.5) - our_x) / calculated_dx + else + d_to_reach_x = 0 + + if(colliding.y != y) + switch(calculated_sdy) + if(0) + return + if(1) + if(colliding.y < y) + return + d_to_reach_y = (((colliding.y - 1) * WORLD_ICON_SIZE + 0.5) - our_y) / calculated_dy + if(-1) + if(colliding.y > y) + return + d_to_reach_y = (((colliding.y - 0) * WORLD_ICON_SIZE + 0.5) - our_y) / calculated_dy + else + d_to_reach_y = 0 + + var/needed_distance = max(d_to_reach_x, d_to_reach_y) + + // calculate if we'll actually be touching the tile once we go that far + var/future_x = our_x + needed_distance * calculated_dx + var/future_y = our_y + needed_distance * calculated_dy + // let's be slightly lenient and do 1 instead of 0.5 + if(future_x < (colliding.x - 1) * WORLD_ICON_SIZE && future_x > (colliding.x) * WORLD_ICON_SIZE + 1 && \ + future_y < (colliding.y - 1) * WORLD_ICON_SIZE && future_y > (colliding.y) * WORLD_ICON_SIZE + 1) + return // not gonna happen + + // make the point based on how far we need to go + var/datum/point/point = new + point.x = future_x + point.y = future_y + point.z = z + return point + +/** + * records the start of a hitscan + * + * this can edit the point passed in! + */ +/obj/projectile/proc/record_hitscan_start(datum/point/point, muzzle_marker, kick_forwards) + if(!hitscanning) + return + if(isnull(point)) + point = get_tracer_point() + tracer_vertices = list(point) + tracer_muzzle_flash = muzzle_marker + + // kick forwards + point.shift_in_projectile_angle(angle, kick_forwards) + +/** + * ends the hitscan tracer + * + * this can edit the point passed in! + */ +/obj/projectile/proc/record_hitscan_end(datum/point/point, impact_marker, kick_forwards) + if(!hitscanning) + return + if(isnull(point)) + point = get_tracer_point() + tracer_vertices += point + tracer_impact_effect = impact_marker + + // kick forwards + point.shift_in_projectile_angle(angle, kick_forwards) + +/** + * records a deflection (change in angle, aka generate new tracer) + */ +/obj/projectile/proc/record_hitscan_deflection(datum/point/point) + if(!hitscanning) + return + if(isnull(point)) + point = get_tracer_point() + // there's no way you need more than 25 + // if this is hit, fix your shit, don't bump this up; there's absolutely no reason for example, + // to simulate reflectors working !!25!! times. + if(length(tracer_vertices) >= 25) + CRASH("tried to add more than 25 vertices to a hitscan tracer") + tracer_vertices += point + +/obj/projectile/proc/render_hitscan_tracers(duration = tracer_duration) + // don't stay too long + ASSERT(duration >= 0 && duration <= 10 SECONDS) + // check everything + if(!has_tracer || !duration || !length(tracer_vertices)) + return + var/list/atom/movable/beam_components = list() + + // muzzle + if(muzzle_type && tracer_muzzle_flash) + var/datum/point/starting = tracer_vertices[1] + var/atom/movable/muzzle_effect = starting.instantiate_movable_with_unmanaged_offsets(muzzle_type) + if(muzzle_effect) + // turn it + var/matrix/muzzle_transform = matrix() + muzzle_transform.Turn(original_angle) + muzzle_effect.transform = muzzle_transform + muzzle_effect.color = color + muzzle_effect.set_light(muzzle_flash_range, muzzle_flash_intensity, muzzle_flash_color_override? muzzle_flash_color_override : color) + // add to list + beam_components += muzzle_effect + // impact + if(impact_type && tracer_impact_effect) + var/datum/point/starting = tracer_vertices[length(tracer_vertices)] + var/atom/movable/impact_effect = starting.instantiate_movable_with_unmanaged_offsets(impact_type) + if(impact_effect) + // turn it + var/matrix/impact_transform = matrix() + impact_transform.Turn(angle) + impact_effect.transform = impact_transform + impact_effect.color = color + impact_effect.set_light(impact_light_range, impact_light_intensity, impact_light_color_override? impact_light_color_override : color) + // add to list + beam_components += impact_effect + // path tracers + if(tracer_type) + var/tempref = "\ref[src]" + for(var/i in 1 to length(tracer_vertices) - 1) + var/j = i + 1 + var/datum/point/first_point = tracer_vertices[i] + var/datum/point/second_point = tracer_vertices[j] + generate_tracer_between_points(first_point, second_point, beam_components, tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity, tempref) + + QDEL_LIST_IN(beam_components, duration) + + +/obj/projectile/proc/cleanup_hitscan_tracers() + QDEL_NULL(tracer_vertices) + +/obj/projectile/proc/finalize_hitscan_tracers(datum/point/end_point, impact_effect, kick_forwards) + // if end wasn't recorded yet and we're still on a turf, record end + if(isnull(tracer_impact_effect) && loc) + record_hitscan_end(end_point, impact_marker = impact_effect, kick_forwards = kick_forwards) + // render & cleanup + render_hitscan_tracers() + cleanup_hitscan_tracers() + +//* Physics - Configuration *// + +/** + * sets our angle + */ +/obj/projectile/proc/set_angle(new_angle) + angle = new_angle + + // update sprite + if(!nondirectional_sprite) + var/matrix/M = new + M.Turn(angle) + transform = M + + // update trajectory + calculated_dx = sin(new_angle) + calculated_dy = cos(new_angle) + calculated_sdx = calculated_dx == 0? 0 : (calculated_dx > 0? 1 : -1) + calculated_sdy = calculated_dy == 0? 0 : (calculated_dy > 0? 1 : -1) + + // record our tracer's change + if(hitscanning) + record_hitscan_deflection() + +/** + * sets our speed in pixels per decisecond + */ +/obj/projectile/proc/set_speed(new_speed) + speed = clamp(new_speed, 1, WORLD_ICON_SIZE * 5) + +/** + * sets our angle and speed + */ +/obj/projectile/proc/set_velocity(new_angle, new_speed) + // this is so this can be micro-optimized later but for once i'm not going to do it early for no reason + set_speed(new_speed) + set_angle(new_angle) + +/** + * todo: this is somewhat mildly terrible + */ +/obj/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 + +/** + * initializes physics vars + */ +/obj/projectile/proc/setup_physics() + distance_travelled = 0 + +/** + * called after an unhandled forcemove is detected, or other event + * that should reset our on-turf state + */ +/obj/projectile/proc/reset_physics_to_turf() + // we use this because we can center larger than 32x32 projectiles + // without disrupting physics this way + // + // we add by (WORLD_ICON_SIZE / 2) because + // pixel_x / pixel_y starts at center, + // + current_px = pixel_x - base_pixel_x + (WORLD_ICON_SIZE / 2) + current_py = pixel_y - base_pixel_y + (WORLD_ICON_SIZE / 2) + // interrupt active move logic + trajectory_moving_to = null + +//* Physics - Processing *// + +/obj/projectile/process(delta_time) + if(paused) + return + delta_time *= 10 // sigh im fucking mad but whatever why are we using delta_time as seconds and not deciseconds + physics_iteration(delta_time * speed, delta_time) + +/** + * immediately processes hitscan + */ +/obj/projectile/proc/physics_hitscan(safety = 250, resuming) + // setup + if(!resuming) + hitscanning = TRUE + record_hitscan_start(muzzle_marker = TRUE, kick_forwards = 16) + + // just move as many times as we can + while(!QDELETED(src) && loc) + // check safety + safety-- + if(safety <= 0) + // if you're here, you shouldn't be. do not bump safety up, fix whatever + // you're doing because no one should be making projectiles go more than 250 + // tiles in a single life. + stack_trace("projectile hit iteration limit for hitscan") + break + + // move forwards by 1 tile length + distance_travelled += physics_step(WORLD_ICON_SIZE) + // if we're being yanked, yield + if(movable_flags & MOVABLE_IN_MOVED_YANK) + spawn(0) + physics_hitscan(safety, TRUE) + return + + // see if we're done + if(distance_travelled >= range) + legacy_on_range() + break + + hitscanning = FALSE + +/** + * ticks forwards a number of pixels + * + * todo: potential lazy animate support for performance, as we honestly don't need to animate at full fps if the server's above 20fps + * + * * delta_tiem is in deciseconds, not seconds. + */ +/obj/projectile/proc/physics_iteration(pixels, delta_time, additional_animation_length) + // setup iteration + var/safety = 10 + var/pixels_remaining = pixels + distance_travelled_this_iteration = 0 + + // apply penalty + var/penalizing = clamp(trajectory_penalty_applied, 0, pixels_remaining) + pixels_remaining -= penalizing + trajectory_penalty_applied -= penalizing + + // clamp to max distance + pixels_remaining = min(pixels_remaining, range - distance_travelled) + + // move as many times as we need to + // + // * break if we're loc = null (by deletion or otherwise) + // * break if we get paused + while(pixels_remaining > 0) + // check safety + safety-- + if(safety <= 0) + CRASH("ran out of safety! what happened?") + + // move + var/pixels_moved = physics_step(pixels_remaining) + distance_travelled += pixels_moved + distance_travelled_this_iteration += pixels_moved + pixels_remaining -= pixels_moved + // we're being yanked, yield + if(movable_flags & MOVABLE_IN_MOVED_YANK) + spawn(0) + physics_iteration(pixels_remaining, delta_time, distance_travelled_this_iteration) + return + if(!loc || paused) + break + + // penalize next one if we were kicked forwards forcefully too far + trajectory_penalty_applied = max(0, -pixels_remaining) + + // if we don't have a loc anymore just bail + if(!loc) + return + + // if we're at max range + if(distance_travelled >= range) + // todo: egh + legacy_on_range() + if(QDELETED(src)) + return + + // process homing + physics_tick_homing(delta_time) + + // perform animations + // we assume at this point any deflections that should have happened, has happened, + // so we just do a naive animate based on our current loc and pixel x/y + // + // todo: animation needs to take into account angle changes, + // but that's expensive as shit so uh lol + // + // the reason we use distance_travelled_this_iteration is so if something disappears + // by forceMove or whatnot, + // we won't have it bounce from its previous location to the new one as it's not going + // to be accurate anymore + // + // so instead, as of right now, we backtrack via how much we know we moved. + var/final_px = base_pixel_x + current_px - (WORLD_ICON_SIZE / 2) + var/final_py = base_pixel_y + current_py - (WORLD_ICON_SIZE / 2) + var/anim_dist = distance_travelled_this_iteration + additional_animation_length + pixel_x = final_px - (anim_dist * sin(angle)) + pixel_y = final_py - (anim_dist * cos(angle)) + + animate( + src, + delta_time, + flags = ANIMATION_END_NOW, + pixel_x = final_px, + pixel_y = final_py, + ) + +/** + * based on but exactly http://www.cs.yorku.ca/~amana/research/grid.pdf + * + * move into the next tile, or the specified number of pixels, + * whichever is less pixels moved + * + * this will modify our current_px/current_py as necessary + * + * @return pixels moved + */ +/obj/projectile/proc/physics_step(limit) + // distance to move in our angle to get to next turf for horizontal and vertical + var/d_next_horizontal = \ + (calculated_sdx? ((calculated_sdx > 0? (WORLD_ICON_SIZE + 0.5) - current_px : -current_px + 0.5) / calculated_dx) : INFINITY) + var/d_next_vertical = \ + (calculated_sdy? ((calculated_sdy > 0? (WORLD_ICON_SIZE + 0.5) - current_py : -current_py + 0.5) / calculated_dy) : INFINITY) + var/turf/move_to_target + + /** + * explanation on why current and next are done: + * + * projectiles track their pixel x/y on turf, not absolute pixel x/y from edge of map + * this is done to make it simpler to reason about, but is not necessarily the most simple + * or efficient way to do things. + * + * part of the problems with this approach is that Move() is not infallible. the projectile can be blocked. + * if we immediately set current pixel x/y, if the projectile is intercepted by a Bump, we now dont' know the 'real' + * position of the projectile because it's out of sync with where it should be + * + * now, things that require math operations on it don't know the actual location of the projectile until this proc + * rolls it back + * + * so instead, we never touch current px/py until the move is known to be successful, then we set it + * to the stored next px/py + * + * this way, things accessing can mutate our state freely without worrying about needing to handle rollbacks + * + * this entire system however adds overhead + * if we want to not have overhead, we'll need to rewrite hit processing and have it so moves are fully illegal to fail + * but doing that is literally not possible because anything can reject a move for any reason whatsoever + * and we cannot control that, so, instead, we make projectiles track in absolute pixel x/y coordinates from edge of map + * + * that way, we don't even need to care about where the .loc is, we just know where the projectile is supposed to be by + * knowing where it isn't, and by taking the change in its pixels the projectile controller can tell the projectile + * where to go- + * + * (all shitposting aside, this is for future work; it works right now and we have an API to do set angle, kick forwards, etc) + * (so i'm not going to touch this more because it's 4 AM and honestly this entire raycaster is already far less overhead) + * (than the old system of a 16-loop of brute forced 2 pixel increments) + */ + + if(d_next_horizontal == d_next_vertical) + // we're diagonal + if(d_next_horizontal <= limit) + move_to_target = locate(x + calculated_sdx, y + calculated_sdy, z) + . = d_next_horizontal + if(!move_to_target) + // we hit the world edge and weren't transit; time to get deleted. + if(hitscanning) + finalize_hitscan_tracers(impact_effect = FALSE) + qdel(src) + return + next_px = calculated_sdx > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) + next_py = calculated_sdy > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) + else if(d_next_horizontal < d_next_vertical) + // closer is to move left/right + if(d_next_horizontal <= limit) + move_to_target = locate(x + calculated_sdx, y, z) + . = d_next_horizontal + if(!move_to_target) + // we hit the world edge and weren't transit; time to get deleted. + if(hitscanning) + finalize_hitscan_tracers(impact_effect = FALSE) + qdel(src) + return + next_px = calculated_sdx > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) + next_py = current_py + d_next_horizontal * calculated_dy + else if(d_next_vertical < d_next_horizontal) + // closer is to move up/down + if(d_next_vertical <= limit) + move_to_target = locate(x, y + calculated_sdy, z) + . = d_next_vertical + if(!move_to_target) + // we hit the world edge and weren't transit; time to get deleted. + if(hitscanning) + finalize_hitscan_tracers(impact_effect = FALSE) + qdel(src) + return + next_px = current_px + d_next_vertical * calculated_dx + next_py = calculated_sdy > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) + + // if we need to move + if(move_to_target) + var/atom/old_loc = loc + trajectory_moving_to = move_to_target + if(!Move(move_to_target) && ((loc != move_to_target) || !trajectory_moving_to)) + // if we don't successfully move, don't change anything, we didn't move. + . = 0 + if(loc == old_loc) + stack_trace("projectile failed to move, but is still on turf instead of deleted or relocated.") + qdel(src) // bye + else + // only do these if we successfully move, or somehow end up in that turf anyways + if(trajectory_kick_forwards) + . += trajectory_kick_forwards + trajectory_kick_forwards = 0 + current_px = next_px + current_py = next_py + #ifdef CF_PROJECTILE_RAYCAST_VISUALS + new /atom/movable/render/projectile_raycast(move_to_target, current_px, current_py, "#77ff77") + #endif + trajectory_moving_to = null + else + // not moving to another tile, so, just move on current tile + if(trajectory_kick_forwards) + trajectory_kick_forwards = 0 + stack_trace("how did something kick us forwards when we didn't even move?") + . = limit + current_px += limit * calculated_dx + current_py += limit * calculated_dy + next_px = current_px + next_py = current_py + #ifdef CF_PROJECTILE_RAYCAST_VISUALS + new /atom/movable/render/projectile_raycast(loc, current_px, current_py, "#ff3333") + #endif + +#ifdef CF_PROJECTILE_RAYCAST_VISUALS +GLOBAL_VAR_INIT(projectile_raycast_debug_visual_delay, 2 SECONDS) + +/atom/movable/render/projectile_raycast + plane = OBJ_PLANE + icon = 'icons/system/color_32x32.dmi' + icon_state = "white-pixel" + +/** + * px, py are absolute pixel coordinates on the tile, not pixel_x / pixel_y of this renderer! + */ +/atom/movable/render/projectile_raycast/Initialize(mapload, px, py, color) + src.pixel_x = px - 1 + src.pixel_y = py - 1 + src.color = color + . = ..() + QDEL_IN(src, GLOB.projectile_raycast_debug_visual_delay) +#endif + +/** + * immediately, without processing, kicks us forward a number of pixels + * + * since we immediately cross over into a turf when entering, + * things like mirrors/reflectors will immediately set angle + * + * it looks ugly and is pretty bad to just reflect off the edge of a turf so said things can + * call this proc to kick us forwards by a bit + */ +/obj/projectile/proc/physics_kick_forwards(pixels) + trajectory_kick_forwards += pixels + next_px += pixels * calculated_dx + next_py += pixels * calculated_dy + +/** + * only works during non-hitscan + * + * this is called once per tick + * homing is smoother the higher fps the server / SSprojectiles runs at + * + * todo: this is somewhat mildly terrible + * todo: this has absolutely no arc/animation support; this is bad + */ +/obj/projectile/proc/physics_tick_homing(delta_time) + // checks if they're 1. on a turf, 2. on our z + // todo: should we add support for tracking something even if it leaves a turf? + if(homing_target?.z != z) + // bye bye! + return FALSE + // todo: this assumes single-tile objects. at some point, we should upgrade this to be unnecessarily expensive and always center-mass. + var/dx = (homing_target.x - src.x) * WORLD_ICON_SIZE + (0 - current_px) + var/dy = (homing_target.y - src.y) * WORLD_ICON_SIZE + (0 - current_py) + // say it with me, arctan() + // is CCW of east if (dx, dy) + // and CW of north if (dy, dx) + // where dx and dy is distance in x/y pixels from us to them. + + var/nudge_towards = closer_angle_difference(arctan(dy, dx)) + var/max_turn_speed = homing_turn_speed * delta_time + + set_angle(angle + clamp(nudge_towards, -max_turn_speed, max_turn_speed)) + +//* Physics - Querying *// + +/** + * predict what turf we'll be in after going forwards a certain amount of pixels + * + * doesn't actually sim; so this will go through walls/obstacles! + * + * * if we go out of bounds, we will return null; this doesn't level-wrap + */ +/obj/projectile/proc/physics_predicted_turf_after_iteration(pixels) + // -1 at the end if 0, because: + // + // -32 is go back 1 tile and be at the 1st pixel (as 0 is going back) + // 0 is go back 1 tile and be at the 32nd pixel. + var/incremented_px = (current_px + pixels * calculated_dx) || - 1 + var/incremented_py = (current_py + pixels * calculated_dy) || - 1 + + var/incremented_tx = floor(incremented_px / 32) + var/incremented_ty = floor(incremented_py / 32) + + return locate(x + incremented_tx, y + incremented_ty, z) + +/** + * predict what turfs we'll hit, excluding the current turf, after going forwards + * a certain amount of pixels + * + * doesn't actually sim; so this will go through walls/obstacles! + */ +/obj/projectile/proc/physics_predicted_turfs_during_iteration(pixels) + return pixel_physics_raycast(loc, current_px, current_py, angle, pixels) + +//* Targeting *// /** * Checks if something is a valid target when directly clicked. diff --git a/code/modules/projectiles/projectile/arc.dm b/code/modules/projectiles/projectile/arc.dm index ba2b2be7fadb..d4fb271b296e 100644 --- a/code/modules/projectiles/projectile/arc.dm +++ b/code/modules/projectiles/projectile/arc.dm @@ -44,7 +44,7 @@ var/datum/point/starting_point = new(starting) return pixel_length_between_points(current_point, starting_point) -/obj/projectile/arc/on_range() +/obj/projectile/arc/legacy_on_range() if(loc) on_impact(loc) return ..() @@ -64,17 +64,17 @@ /obj/projectile/arc/fire(angle, atom/direct_target) ..() // The trajectory must exist for set_pixel_speed() to work. - set_pixel_speed(projectile_speed_modifier) // Slows it down and makes the distance checking more accurate. + set_speed(32 * projectile_speed_modifier) -/obj/projectile/arc/pixel_move(trajectory_multiplier, hitscanning = FALSE) +/obj/projectile/arc/physics_iteration(pixels) // Do the other important stuff first. - ..(trajectory_multiplier, hitscanning) + . = ..() // Test to see if its time to 'hit the ground'. var/pixels_flown = distance_flown() if(pixels_flown >= distance_to_fly) - on_range() // This will also cause the projectile to be deleted. + legacy_on_range() // This will also cause the projectile to be deleted. else // Handle visual projectile turning in flight. diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm index e14c5a103822..759ec43f3a73 100644 --- a/code/modules/projectiles/projectile/bullets.dm +++ b/code/modules/projectiles/projectile/bullets.dm @@ -129,11 +129,13 @@ agony = 10 // brute easily heals, agony not so much armor_penetration = 30 // reduces shield blockchance accuracy = -20 // he do miss actually - speed = 0.4 // if the pathfinder gets a funny burst rifle, they deserve a rival - // that's 2x projectile speed btw + // if the pathfinder gets a funny burst rifle, they deserve a rival + // ~25 tiles/second + speed = 32 / 0.4 /obj/projectile/bullet/pistol/medium/ap/suppressor/turbo // spicy boys - speed = 0.2 // this is 4x projectile speed + // ~50 tiles/second + speed = 32 / 0.2 /obj/projectile/bullet/pistol/strong // .357 and .44 caliber stuff. High power pistols like the Mateba or Desert Eagle. Sacrifice capacity for power. fire_sound = 'sound/weapons/weaponsounds_heavypistolshot.ogg' @@ -300,7 +302,8 @@ SA_bonus_damage = 45 // 70 total on animals. SA_vulnerability = MOB_CLASS_ANIMAL embed_chance = -1 - speed = 0.4 + // ~25 tiles/second + speed = 32 / 0.4 /obj/projectile/bullet/rifle/a762/silver // Hunting Demons with bolt action rifles. damage = 20 @@ -387,7 +390,8 @@ /obj/projectile/bullet/musket // Big Slow and bad against armor. fire_sound = 'sound/weapons/weaponsounds_heavypistolshot.ogg' damage = 60 - speed = 1.2 + // ~8.3 tiles/second + speed = 32 / 1.2 armor_penetration = -50 /obj/projectile/bullet/musket/silver // What its a classic @@ -486,7 +490,7 @@ //incendiary = 2 //The Trail of Fire doesn't work. flammability = 4 agony = 30 - range = 4 + range = WORLD_ICON_SIZE * 4 vacuum_traversal = 0 /obj/projectile/bullet/incendiary/flamethrower/weak @@ -494,7 +498,7 @@ /obj/projectile/bullet/incendiary/flamethrower/large damage = 15 - range = 6 + range = WORLD_ICON_SIZE * 6 /obj/projectile/bullet/incendiary/caseless name = "12.7mm phoron slug" diff --git a/code/modules/projectiles/projectile/bullets_vr.dm b/code/modules/projectiles/projectile/bullets_vr.dm index 257cb4aef5e1..a81985107008 100644 --- a/code/modules/projectiles/projectile/bullets_vr.dm +++ b/code/modules/projectiles/projectile/bullets_vr.dm @@ -9,7 +9,7 @@ name = "chemical shell" icon_state = "bullet" damage = 10 - range = 15 //if the shell hasn't hit anything after travelling this far it just explodes. + range = WORLD_ICON_SIZE * 15 //if the shell hasn't hit anything after travelling this far it just explodes. flash_strength = 15 brightness = 15 diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/energy.dm index 3479abf411f9..0e734ded4a56 100644 --- a/code/modules/projectiles/projectile/energy.dm +++ b/code/modules/projectiles/projectile/energy.dm @@ -12,7 +12,7 @@ icon_state = "bullet" fire_sound = 'sound/weapons/gunshot_pathetic.ogg' damage = 5 - range = 15 //if the shell hasn't hit anything after travelling this far it just explodes. + range = WORLD_ICON_SIZE * 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" @@ -171,7 +171,7 @@ icon_state = "plasma_stun" fire_sound = 'sound/weapons/blaster.ogg' armor_penetration = 10 - range = 4 + range = WORLD_ICON_SIZE * 4 damage = 5 agony = 55 damage_type = BURN @@ -223,20 +223,20 @@ /obj/projectile/energy/phase name = "phase wave" icon_state = "phase" - range = 25 + range = WORLD_ICON_SIZE * 25 damage = 5 SA_bonus_damage = 45 // 50 total on animals SA_vulnerability = MOB_CLASS_ANIMAL /obj/projectile/energy/phase/light - range = 15 + range = WORLD_ICON_SIZE * 15 SA_bonus_damage = 35 // 40 total on animals /obj/projectile/energy/phase/heavy - range = 20 + range = WORLD_ICON_SIZE * 20 SA_bonus_damage = 55 // 60 total on animals /obj/projectile/energy/phase/heavy/cannon - range = 30 + range = WORLD_ICON_SIZE * 30 damage = 15 SA_bonus_damage = 60 // 75 total on animals diff --git a/code/modules/projectiles/projectile/hook.dm b/code/modules/projectiles/projectile/hook.dm index 987beff181ee..f01bdb905784 100644 --- a/code/modules/projectiles/projectile/hook.dm +++ b/code/modules/projectiles/projectile/hook.dm @@ -9,7 +9,8 @@ var/beam_state = "b_beam" damage = 5 - speed = 2 + // ~5 tiles/second + speed = 32 / 2 damage_type = BURN damage_flag = ARMOR_ENERGY armor_penetration = 15 @@ -29,7 +30,7 @@ /obj/projectile/energy/hook/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) var/expected_distance = get_dist(target, loc) - range = expected_distance // So the hook hits the ground if no mob is hit. + range = WORLD_ICON_SIZE * 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 diff --git a/code/modules/projectiles/projectile/magnetic.dm b/code/modules/projectiles/projectile/magnetic.dm index e1728daf1685..1705a9c1c65b 100644 --- a/code/modules/projectiles/projectile/magnetic.dm +++ b/code/modules/projectiles/projectile/magnetic.dm @@ -56,7 +56,7 @@ penetrating = 2 embed_chance = 0 armor_penetration = 40 - range = 20 + range = WORLD_ICON_SIZE * 20 var/searing = 0 //Does this fuelrod ignore shields? var/detonate_travel = 0 //Will this fuelrod explode when it reaches maximum distance? @@ -125,7 +125,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 - range = 75 + range = WORLD_ICON_SIZE * 75 searing = 1 detonate_travel = 1 detonate_mob = 1 @@ -149,7 +149,7 @@ penetrating = 0 damage_flag = ARMOR_MELEE irradiate = 20 - range = 6 + range = WORLD_ICON_SIZE * 6 /obj/projectile/bullet/magnetic/bore/Bump(atom/A, forced=0) if(istype(A, /turf/simulated/mineral)) @@ -174,4 +174,4 @@ penetrating = 0 damage_flag = ARMOR_MELEE irradiate = 20 - range = 12 + range = WORLD_ICON_SIZE * 12 diff --git a/code/modules/projectiles/projectile/reusable.dm b/code/modules/projectiles/projectile/reusable.dm index 41dbc2937b83..a2f5fabbb96d 100644 --- a/code/modules/projectiles/projectile/reusable.dm +++ b/code/modules/projectiles/projectile/reusable.dm @@ -16,7 +16,7 @@ handle_drop() //handle_shatter() -/obj/projectile/bullet/reusable/on_range() +/obj/projectile/bullet/reusable/legacy_on_range() handle_drop() ..() diff --git a/code/modules/projectiles/projectile/scatter.dm b/code/modules/projectiles/projectile/scatter.dm index 075891889c47..11f78d5ee412 100644 --- a/code/modules/projectiles/projectile/scatter.dm +++ b/code/modules/projectiles/projectile/scatter.dm @@ -13,7 +13,7 @@ damage = 8 spread_submunition_damage = TRUE only_submunitions = TRUE - range = 0 // Immediately deletes itself after firing, as its only job is to fire other projectiles. + range = WORLD_ICON_SIZE * 0 // Immediately deletes itself after firing, as its only job is to fire other projectiles. submunition_spread_max = 30 submunition_spread_min = 2 diff --git a/code/modules/projectiles/projectile/special.dm b/code/modules/projectiles/projectile/special.dm index 918337188c10..8cffa1a2ca4d 100644 --- a/code/modules/projectiles/projectile/special.dm +++ b/code/modules/projectiles/projectile/special.dm @@ -334,7 +334,7 @@ impact_sounds = 'sound/items/bikehorn.ogg' icon = 'icons/obj/items.dmi' icon_state = "banana" - range = 200 + range = WORLD_ICON_SIZE * 200 /obj/projectile/bullet/honker/Initialize(mapload) . = ..() diff --git a/code/modules/projectiles/projectile/trace.dm b/code/modules/projectiles/projectile/trace.dm index c7433c1e2053..321a12a71806 100644 --- a/code/modules/projectiles/projectile/trace.dm +++ b/code/modules/projectiles/projectile/trace.dm @@ -25,10 +25,8 @@ has_tracer = FALSE var/list/hit = list() -/obj/projectile/test/process_hitscan() - . = ..() - if(!QDELING(src)) - qdel(src) +/obj/projectile/test/fire(angle, atom/direct_target) + ..() return hit /obj/projectile/test/Bump(atom/A) diff --git a/code/modules/projectiles/unsorted/magic.dm b/code/modules/projectiles/unsorted/magic.dm index 52ed8b77d14c..aa8735a1f020 100644 --- a/code/modules/projectiles/unsorted/magic.dm +++ b/code/modules/projectiles/unsorted/magic.dm @@ -403,13 +403,12 @@ damage = 0 var/proxdet = TRUE -/obj/projectile/magic/aoe/Range() +/obj/projectile/magic/aoe/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() if(proxdet) for(var/mob/living/L in range(1, get_turf(src))) if(L.stat != DEAD && L != firer && !L.anti_magic_check()) - return Bump(L) - ..() - + Bump(L) /obj/projectile/magic/aoe/lightning name = "lightning bolt" diff --git a/code/modules/spells/spell_projectile.dm b/code/modules/spells/spell_projectile.dm index 0247ce783579..60538b4ba7b1 100644 --- a/code/modules/spells/spell_projectile.dm +++ b/code/modules/spells/spell_projectile.dm @@ -7,7 +7,7 @@ var/spell/targeted/projectile/carried penetrating = 0 - range = 10 //set by the duration of the spell + range = WORLD_ICON_SIZE * 10 //set by the duration of the spell var/proj_trail = 0 //if it leaves a trail var/proj_trail_lifespan = 0 //deciseconds @@ -24,9 +24,10 @@ /obj/projectile/spell_projectile/legacy_ex_act() return -/obj/projectile/spell_projectile/before_move() - if(proj_trail && src && src.loc) //pretty trails - var/obj/effect/overlay/trail = new /obj/effect/overlay(src.loc) +/obj/projectile/spell_projectile/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() + if(proj_trail && old_loc) //pretty trails + var/obj/effect/overlay/trail = new /obj/effect/overlay(old_loc) trails += trail trail.icon = proj_trail_icon trail.icon_state = proj_trail_icon_state diff --git a/code/modules/spells/targeted/projectile/projectile.dm b/code/modules/spells/targeted/projectile/projectile.dm index a7b81eef3db6..ee66d07df196 100644 --- a/code/modules/spells/targeted/projectile/projectile.dm +++ b/code/modules/spells/targeted/projectile/projectile.dm @@ -29,7 +29,7 @@ If the spell_projectile is seeking, it will update its target every process and projectile.shot_from = user //fired from the user projectile.hitscan = !proj_step_delay - projectile.speed = proj_step_delay + projectile.speed = 32 / proj_step_delay if(istype(projectile, /obj/projectile/spell_projectile)) var/obj/projectile/spell_projectile/SP = projectile SP.carried = src //casting is magical diff --git a/code/modules/vore/fluffstuff/guns/pummeler.dm b/code/modules/vore/fluffstuff/guns/pummeler.dm index 932e1dccae58..1c4283e0b106 100644 --- a/code/modules/vore/fluffstuff/guns/pummeler.dm +++ b/code/modules/vore/fluffstuff/guns/pummeler.dm @@ -34,7 +34,7 @@ damage_flag = ARMOR_MELEE embed_chance = 0 vacuum_traversal = 0 - range = 6 //Scary name, but just deletes the projectile after this range + range = WORLD_ICON_SIZE * 6 //Scary name, but just deletes the projectile after this range /obj/projectile/pummel/on_hit(var/atom/movable/target, var/blocked = 0) if(isliving(target)) diff --git a/code/modules/vore/fluffstuff/guns/secutor.dm b/code/modules/vore/fluffstuff/guns/secutor.dm index b7646c38dc9f..dabf9b8d152c 100644 --- a/code/modules/vore/fluffstuff/guns/secutor.dm +++ b/code/modules/vore/fluffstuff/guns/secutor.dm @@ -74,7 +74,7 @@ light_range = 2 light_power = 0.6 light_color = "#cea036" - range = 20 + range = WORLD_ICON_SIZE * 20 damage = 5 //minor penalty for repeated FF SA_bonus_damage = 25 // 30 total on animals - lowest of all phasers SA_vulnerability = MOB_CLASS_ANIMAL diff --git a/code/modules/vore/fluffstuff/guns/sickshot.dm b/code/modules/vore/fluffstuff/guns/sickshot.dm index 1fdbb998dab9..efa27a577727 100644 --- a/code/modules/vore/fluffstuff/guns/sickshot.dm +++ b/code/modules/vore/fluffstuff/guns/sickshot.dm @@ -32,7 +32,7 @@ damage_flag = ARMOR_MELEE embed_chance = 0 vacuum_traversal = 0 - range = 5 //Scary name, but just deletes the projectile after this range + range = WORLD_ICON_SIZE * 5 //Scary name, but just deletes the projectile after this range /obj/projectile/sickshot/on_hit(var/atom/movable/target, var/blocked = 0) if(isliving(target)) diff --git a/icons/obj/projectiles_impact.dmi b/icons/obj/projectiles_impact.dmi index 3788e515562acf307cf9419f90bcbf80ea008574..af9947dc64ca5621c993318ee36fa310688fd39c 100644 GIT binary patch literal 21566 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 i#^3#nJid%7a_u>u(io3hZ&-cH5*?BuV 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 Z{{b&B705Cs@fH98002ovPDHLkV1nho2CD!7 diff --git a/icons/obj/projectiles_muzzle.dmi b/icons/obj/projectiles_muzzle.dmi index b361df9276315d32c68ba874e37f0eb2ddf5320d..ab4eefab272edfd4f76e02f4b78d40ace532bae8 100644 GIT binary patch literal 24182 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 ChkQl= diff --git a/icons/system/color_32x32.dmi b/icons/system/color_32x32.dmi index 826ba84db3dcbdc28d6823f030147ad576fe06bf..b12a311523350985078892041fd6afd2e42fd8d0 100644 GIT binary patch delta 148 zcmX@f_?pqBGr-TCmrII^fq{Y7)59eQNIL+r0wXh!oDuxyIFMos@Ck7}aNxlI|Npf! z+qVEk7)yfuf*Bm1-ADs+iYJ;RxCe&56iW$P^6Wv->NO8uJ!dLoVvrM5w_*x0^aCmp t@pN$vk(iqNgP(;*!Qhlbx(P!j!@h_H#B_Q#>8OxPD*FKwni&Zg2&UvF@$4ga)QJiAa4N!<5Ow>bwCz_r>mdK II;Vst0K&c>-v9sr