diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dfd2d329190..c2d3fa09709 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -94,6 +94,10 @@ /code/game/area/ @san7890 /icons/area/ @san7890 +# SmArtKar + +/code/modules/projectiles/projectile.dm @SmArtKar + # stylemistake /code/__DEFINES/chat.dm @stylemistake diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index aabd281bd7d..2c20765d351 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -224,10 +224,6 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( #define SUPPRESSED_QUIET 1 ///standard suppressed #define SUPPRESSED_VERY 2 /// no message -//Projectile Reflect -#define REFLECT_NORMAL (1<<0) -#define REFLECT_FAKEPROJECTILE (1<<1) - //His Grace. #define HIS_GRACE_SATIATED 0 //He hungers not. If bloodthirst is set to this, His Grace is asleep. #define HIS_GRACE_PECKISH 20 //Slightly hungry. diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 8b5778602e0..69ccaa5fa64 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -380,9 +380,9 @@ // /obj/projectile signals (sent to the firer) -///from base of /obj/projectile/proc/on_hit(), like COMSIG_PROJECTILE_ON_HIT but on the projectile itself and with the hit limb (if any): (atom/movable/firer, atom/target, angle, hit_limb, blocked) +///from base of /obj/projectile/proc/on_hit(), like COMSIG_PROJECTILE_ON_HIT but on the projectile itself and with the hit limb (if any): (atom/movable/firer, atom/target, angle, hit_limb, blocked, pierce_hit) #define COMSIG_PROJECTILE_SELF_ON_HIT "projectile_self_on_hit" -///from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, angle, hit_limb, blocked) +///from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, angle, hit_limb, blocked, pierce_hit) #define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit" ///from base of /obj/projectile/proc/fire(): (obj/projectile, atom/original_target) #define COMSIG_PROJECTILE_BEFORE_FIRE "projectile_before_fire" @@ -393,11 +393,11 @@ ///sent to targets during the process_hit proc of projectiles #define COMSIG_PROJECTILE_PREHIT "com_proj_prehit" #define PROJECTILE_INTERRUPT_HIT (1<<0) -///from /obj/projectile/pixel_move(): () -#define COMSIG_PROJECTILE_PIXEL_STEP "projectile_pixel_step" +///from /obj/projectile/process_movement(): () +#define COMSIG_PROJECTILE_MOVE_PROCESS_STEP "projectile_move_process_step" ///sent to self during the process_hit proc of projectiles #define COMSIG_PROJECTILE_SELF_PREHIT "com_proj_prehit" -///from the base of /obj/projectile/Range(): () +///from the base of /obj/projectile/reduce_range(): () #define COMSIG_PROJECTILE_RANGE "projectile_range" ///from the base of /obj/projectile/on_range(): () #define COMSIG_PROJECTILE_RANGE_OUT "projectile_range_out" diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm index aa13298e339..35c8d3edaf6 100644 --- a/code/__DEFINES/maths.dm +++ b/code/__DEFINES/maths.dm @@ -29,6 +29,9 @@ /// Gets the sign of x, returns -1 if negative, 0 if 0, 1 if positive #define SIGN(x) ( ((x) > 0) - ((x) < 0) ) +/// Returns the integer closest to 0 from a division +#define SIGNED_FLOOR_DIVISION(x, y) (SIGN(x) * FLOOR(abs(x) / y, 1)) + #define CEILING(x, y) ( -round(-(x) / (y)) * (y) ) #define ROUND_UP(x) ( -round(-(x))) diff --git a/code/__DEFINES/projectiles.dm b/code/__DEFINES/projectiles.dm index f93faab62de..c3e861b56c9 100644 --- a/code/__DEFINES/projectiles.dm +++ b/code/__DEFINES/projectiles.dm @@ -77,12 +77,27 @@ #define RETURN_PRECISE_POSITION(A) new /datum/position(A) #define RETURN_PRECISE_POINT(A) new /datum/point(A) -#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)) - ///The self charging rate of energy guns that magically recharge themselves, in watts. #define STANDARD_ENERGY_GUN_SELF_CHARGE_RATE (0.05 * STANDARD_CELL_CHARGE) /// Macro to turn a number of laser shots into an energy cost, based on the above define /// e.g. LASER_SHOTS(12, STANDARD_CELL_CHARGE) means 12 shots #define LASER_SHOTS(X, MAX_CHARGE) (((100 * MAX_CHARGE) - ((100 * MAX_CHARGE) % X)) / (100 * X)) // I wish I could just use round, but it can't be used in datum members + +/// How far do the projectile hits the prone mob +#define MAX_RANGE_HIT_PRONE_TARGETS 10 + +/// Queued for impact deletion (simple qdel) +#define PROJECTILE_IMPACT_DELETE "impact_delete" +/// Queued for range deletion (on_range call) +#define PROJECTILE_RANGE_DELETE "range_delete" + +/// Projectile either hasn't impacted anything, or pierced through the target +#define PROJECTILE_IMPACT_PASSED "impact_passed" +/// Projectile has been "deleted" before bullet_act call has occured +#define PROJECTILE_IMPACT_INTERRUPTED "impact_interrupted" +/// Projectile has successfully impacted something and is scheduled for deletion +#define PROJECTILE_IMPACT_SUCCESSFUL "impact_successful" + +/// For how long projectile tracers linger +#define PROJECTILE_TRACER_DURATION 0.3 SECONDS diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm index 040e9694429..bb2df78ab34 100644 --- a/code/__HELPERS/maths.dm +++ b/code/__HELPERS/maths.dm @@ -238,7 +238,7 @@ return min(new_value, threshold * -1) /// Takes two values x and y, and returns 1/((1/x) + y) -/// Useful for providing an additive modifier to a value that is used as a divisor, such as `/obj/projectile/var/speed` +/// Useful for providing an additive modifier to a value that is used as a divisor /proc/reciprocal_add(x, y) return 1/((1/x)+y) diff --git a/code/controllers/subsystem/processing/projectiles.dm b/code/controllers/subsystem/processing/projectiles.dm index 48d465dd0fd..0124296a3a2 100644 --- a/code/controllers/subsystem/processing/projectiles.dm +++ b/code/controllers/subsystem/processing/projectiles.dm @@ -3,21 +3,18 @@ PROCESSING_SUBSYSTEM_DEF(projectiles) wait = 1 stat_tag = "PP" flags = SS_NO_INIT|SS_TICKER - 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) - -/datum/controller/subsystem/processing/projectiles/vv_edit_var(var_name, var_value) - switch(var_name) - if(NAMEOF(src, global_pixel_speed)) - set_pixel_speed(var_value) - return TRUE - else - return ..() + /* + * Maximum amount of pixels a projectile can pass per tick *unless* its a hitscan projectile. + * This prevents projectiles from turning into essentially hitscans if SSprojectiles starts chugging + * and projectiles accumulate a bunch of overtime they try to process next tick to fly through half the map. + * Shouldn't really be increased past 5 tiles per tick because this maxes out at 100 FPS (recommended as of now) + * and making a projectile faster than that will make it look jumpy because it'll be passing inconsistent + * amounts of pixels per tick. + */ + var/max_pixels_per_tick = ICON_SIZE_ALL * 5 + /* + * How many pixels a projectile with a speed value of 1 passes in a tick. Currently all speed values + * assume that 1 speed = 1 tile per decisecond, but this is a variable so that admins/debuggers can edit + * in order to debug projectile behavior by evenly slowing or speeding all of them up. + */ + var/pixels_per_decisecond = ICON_SIZE_ALL diff --git a/code/datums/actions/mobs/create_legion_turrets.dm b/code/datums/actions/mobs/create_legion_turrets.dm index 71427893f43..34a0c6852d6 100644 --- a/code/datums/actions/mobs/create_legion_turrets.dm +++ b/code/datums/actions/mobs/create_legion_turrets.dm @@ -78,8 +78,7 @@ return //Now we generate the tracer. var/angle = get_angle(our_turf, target_turf) - var/datum/point/vector/V = new(our_turf.x, our_turf.y, our_turf.z, 0, 0, angle) - generate_tracer_between_points(V, V.return_vector_after_increments(6), /obj/effect/projectile/tracer/legion/tracer, 0, shot_delay, 0, 0, 0, null) + our_turf.Beam(target_turf, 'icons/effects/beam.dmi', "blood_light", time = shot_delay) playsound(src, 'sound/machines/airlock/airlockopen.ogg', 100, TRUE) addtimer(CALLBACK(src, PROC_REF(fire_beam), angle), shot_delay) @@ -105,11 +104,6 @@ hitscan = TRUE projectile_piercing = ALL -/// Used for the legion turret tracer. -/obj/effect/projectile/tracer/legion/tracer - icon = 'icons/effects/beam.dmi' - icon_state = "blood_light" - /// Used for the legion turret beam. /obj/effect/projectile/tracer/legion icon = 'icons/effects/beam.dmi' diff --git a/code/datums/actions/mobs/projectileattack.dm b/code/datums/actions/mobs/projectileattack.dm index 933f94d0025..f833e05dde2 100644 --- a/code/datums/actions/mobs/projectileattack.dm +++ b/code/datums/actions/mobs/projectileattack.dm @@ -49,7 +49,7 @@ if(!isnum(speed_multiplier)) speed_multiplier = projectile_speed_multiplier our_projectile.speed *= speed_multiplier - our_projectile.preparePixelProjectile(endloc, startloc, null, projectile_spread) + our_projectile.aim_projectile(endloc, startloc, null, projectile_spread) our_projectile.firer = firer if(target) our_projectile.original = target @@ -224,7 +224,7 @@ cooldown_time = 10 SECONDS projectile_type = /obj/projectile/colossus/wendigo_shockwave shot_angles = list(-20, -10, 0, 10, 20) - projectile_speed_multiplier = 4 + projectile_speed_multiplier = 0.25 /datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/colossus @@ -378,7 +378,7 @@ if(enraged) projectile_speed_multiplier = 1 else - projectile_speed_multiplier = 1.5 + projectile_speed_multiplier = 0.66 var/shots_per = 24 for(var/shoot_times in 1 to 8) var/offset = shoot_times % 2 diff --git a/code/datums/components/dart_insert.dm b/code/datums/components/dart_insert.dm index ad869903051..459da9d217c 100644 --- a/code/datums/components/dart_insert.dm +++ b/code/datums/components/dart_insert.dm @@ -135,8 +135,6 @@ /datum/component/dart_insert/proc/apply_var_modifiers(obj/projectile/projectile) var_modifiers = istype(modifier_getter) ? modifier_getter.Invoke() : list() projectile.damage += var_modifiers["damage"] - if(var_modifiers["speed"]) - var_modifiers["speed"] = reciprocal_add(projectile.speed, var_modifiers["speed"]) - projectile.speed projectile.speed += var_modifiers["speed"] projectile.armour_penetration += var_modifiers["armour_penetration"] projectile.wound_bonus += var_modifiers["wound_bonus"] diff --git a/code/datums/components/mirv.dm b/code/datums/components/mirv.dm index 52b4053babb..ead33d476d4 100644 --- a/code/datums/components/mirv.dm +++ b/code/datums/components/mirv.dm @@ -32,12 +32,12 @@ var/turf/target_turf = get_turf(target) for(var/turf/shootat_turf in RANGE_TURFS(radius, target) - RANGE_TURFS(radius-1, target)) - var/obj/projectile/P = new projectile_type(target_turf) + var/obj/projectile/proj = new projectile_type(target_turf) //Shooting Code: - P.range = radius+1 + proj.range = radius+1 if(override_projectile_range) - P.range = override_projectile_range - P.preparePixelProjectile(shootat_turf, target) - P.firer = firer // don't hit ourself that would be really annoying - P.impacted = list(WEAKREF(target) = TRUE) // don't hit the target we hit already with the flak - P.fire() + proj.range = override_projectile_range + proj.aim_projectile(shootat_turf, target) + proj.firer = firer // don't hit ourself that would be really annoying + proj.impacted = list(WEAKREF(target) = TRUE) // don't hit the target we hit already with the flak + proj.fire() diff --git a/code/datums/components/parry.dm b/code/datums/components/parry.dm index 2486796d378..e24bb4d2480 100644 --- a/code/datums/components/parry.dm +++ b/code/datums/components/parry.dm @@ -23,7 +23,7 @@ /// Callback for special effects upon parrying var/datum/callback/parry_callback -/datum/component/parriable_projectile/Initialize(parry_speed_mult = 0.8, parry_damage_mult = 1.15, boost_speed_mult = 0.6, boost_damage_mult = 1.5, parry_trait = TRAIT_MINING_PARRYING, grace_period = 0.25 SECONDS, datum/callback/parry_callback = null) +/datum/component/parriable_projectile/Initialize(parry_speed_mult = 1.25, parry_damage_mult = 1.15, boost_speed_mult = 1.6, boost_damage_mult = 1.5, parry_trait = TRAIT_MINING_PARRYING, grace_period = 0.25 SECONDS, datum/callback/parry_callback = null) if(!isprojectile(parent)) return COMPONENT_INCOMPATIBLE src.parry_speed_mult = parry_speed_mult @@ -41,13 +41,13 @@ . = ..() /datum/component/parriable_projectile/RegisterWithParent() - RegisterSignal(parent, COMSIG_PROJECTILE_PIXEL_STEP, PROC_REF(on_moved)) + RegisterSignal(parent, COMSIG_PROJECTILE_MOVE_PROCESS_STEP, PROC_REF(on_moved)) RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(before_move)) RegisterSignal(parent, COMSIG_PROJECTILE_BEFORE_MOVE, PROC_REF(before_move)) RegisterSignal(parent, COMSIG_PROJECTILE_SELF_PREHIT, PROC_REF(before_hit)) /datum/component/parriable_projectile/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_PROJECTILE_PIXEL_STEP, COMSIG_MOVABLE_MOVED, COMSIG_PROJECTILE_BEFORE_MOVE, COMSIG_PROJECTILE_SELF_PREHIT)) + UnregisterSignal(parent, list(COMSIG_PROJECTILE_MOVE_PROCESS_STEP, COMSIG_MOVABLE_MOVED, COMSIG_PROJECTILE_BEFORE_MOVE, COMSIG_PROJECTILE_SELF_PREHIT)) /datum/component/parriable_projectile/proc/before_move(obj/projectile/source) SIGNAL_HANDLER @@ -71,7 +71,7 @@ /datum/component/parriable_projectile/proc/on_moved(obj/projectile/source) SIGNAL_HANDLER - if (!isturf(source.loc)) + if (!isturf(source.loc) || parry_turfs[source.loc]) return parry_turfs[source.loc] = world.time + grace_period RegisterSignal(source.loc, COMSIG_CLICK, PROC_REF(on_turf_click)) @@ -95,6 +95,9 @@ attempt_parry(source, user) /datum/component/parriable_projectile/proc/attempt_parry(obj/projectile/source, mob/user) + if (QDELETED(source) || source.deletion_queued) + return + if (SEND_SIGNAL(user, COMSIG_LIVING_PROJECTILE_PARRIED, source) & INTERCEPT_PARRY_EFFECTS) return diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm index e9b7fad34fc..41f18a6cb18 100644 --- a/code/datums/components/pellet_cloud.dm +++ b/code/datums/components/pellet_cloud.dm @@ -224,10 +224,10 @@ break ///One of our pellets hit something, record what it was and check if we're done (terminated == num_pellets) -/datum/component/pellet_cloud/proc/pellet_hit(obj/projectile/P, atom/movable/firer, atom/target, Angle, hit_zone) +/datum/component/pellet_cloud/proc/pellet_hit(obj/projectile/proj, atom/movable/firer, atom/target, Angle, hit_zone) SIGNAL_HANDLER - pellets -= P + pellets -= proj terminated++ hits++ var/obj/item/bodypart/hit_part @@ -237,34 +237,34 @@ hit_part = hit_carbon.get_bodypart(hit_zone) if(hit_part) target = hit_part - if(P.wound_bonus != CANT_WOUND) // handle wounding + if(proj.wound_bonus != CANT_WOUND) // handle wounding // unfortunately, due to how pellet clouds handle finalizing only after every pellet is accounted for, that also means there might be a short delay in dealing wounds if one pellet goes wide // while buckshot may reach a target or miss it all in one tick, we also have to account for possible ricochets that may take a bit longer to hit the target if(isnull(wound_info_by_part[hit_part])) wound_info_by_part[hit_part] = list(0, 0, 0) - wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE] += P.damage // these account for decay - wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS] += P.wound_bonus - wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS] += P.bare_wound_bonus - P.wound_bonus = CANT_WOUND // actual wounding will be handled aggregate + wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE] += proj.damage // these account for decay + wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS] += proj.wound_bonus + wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS] += proj.bare_wound_bonus + proj.wound_bonus = CANT_WOUND // actual wounding will be handled aggregate else if(isobj(target)) var/obj/hit_object = target - if(hit_object.damage_deflection > P.damage || !P.damage) + if(hit_object.damage_deflection > proj.damage || !proj.damage) damage = FALSE LAZYADDASSOC(targets_hit[target], "hits", 1) LAZYSET(targets_hit[target], "damage", damage) if(targets_hit[target]["hits"] == 1) - RegisterSignal(target, COMSIG_QDELETING, PROC_REF(on_target_qdel), override=TRUE) - UnregisterSignal(P, list(COMSIG_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT)) + RegisterSignal(target, COMSIG_QDELETING, PROC_REF(on_target_qdel), override = TRUE) + UnregisterSignal(proj, list(COMSIG_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT)) if(terminated == num_pellets) finalize() ///One of our pellets disappeared due to hitting their max range (or just somehow got qdel'd), remove it from our list and check if we're done (terminated == num_pellets) -/datum/component/pellet_cloud/proc/pellet_range(obj/projectile/P) +/datum/component/pellet_cloud/proc/pellet_range(obj/projectile/proj) SIGNAL_HANDLER - pellets -= P + pellets -= proj terminated++ - UnregisterSignal(P, list(COMSIG_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT)) + UnregisterSignal(proj, list(COMSIG_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT)) if(terminated == num_pellets) finalize() @@ -279,18 +279,18 @@ pellet.firer = parent // don't hit ourself that would be really annoying pellet.impacted = list(WEAKREF(parent) = TRUE) // don't hit the target we hit already with the flak pellet.suppressed = SUPPRESSED_VERY // set the projectiles to make no message so we can do our own aggregate message - pellet.preparePixelProjectile(target, parent) + pellet.aim_projectile(target, parent) RegisterSignal(pellet, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(pellet_hit)) RegisterSignals(pellet, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_QDELETING), PROC_REF(pellet_range)) pellets += pellet pellet.fire() if(landmine_victim) - pellet.process_hit_loop(target) + pellet.impact(target) ///All of our pellets are accounted for, time to go target by target and tell them how many things they got hit by. /datum/component/pellet_cloud/proc/finalize() - var/obj/projectile/P = projectile_type - var/proj_name = initial(P.name) + var/obj/projectile/proj_type = projectile_type + var/proj_name = initial(proj_type.name) for(var/atom/target in targets_hit) var/num_hits = targets_hit[target]["hits"] @@ -303,24 +303,24 @@ hit_part = null //so the visible_message later on doesn't generate extra text. else target = hit_part.owner - if(wound_info_by_part[hit_part] && (initial(P.damage_type) == BRUTE || initial(P.damage_type) == BURN)) // so a cloud of disablers that deal stamina don't inadvertently end up causing burn wounds) + if(wound_info_by_part[hit_part] && (initial(proj_type.damage_type) == BRUTE || initial(proj_type.damage_type) == BURN)) // so a cloud of disablers that deal stamina don't inadvertently end up causing burn wounds) var/damage_dealt = wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE] var/w_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS] var/bw_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS] - var/wounding_type = (initial(P.damage_type) == BRUTE) ? WOUND_BLUNT : WOUND_BURN // sharpness is handled in the wound rolling + var/wounding_type = (initial(proj_type.damage_type) == BRUTE) ? WOUND_BLUNT : WOUND_BURN // sharpness is handled in the wound rolling wound_info_by_part -= hit_part // technically this only checks armor worn the moment that all the pellets resolve rather than as each one hits you, // but this isn't important enough to warrant all the extra loops of mostly redundant armor checks var/mob/living/carbon/hit_carbon = target - var/armor_factor = hit_carbon.getarmor(hit_part, initial(P.armor_flag)) + var/armor_factor = hit_carbon.getarmor(hit_part, initial(proj_type.armor_flag)) armor_factor = min(ARMOR_MAX_BLOCK, armor_factor) //cap damage reduction at 90% if(armor_factor > 0) - if(initial(P.weak_against_armour) && armor_factor >= 0) + if(initial(proj_type.weak_against_armour) && armor_factor >= 0) armor_factor *= ARMOR_WEAKENED_MULTIPLIER damage_dealt *= max(0, 1 - armor_factor*0.01) - hit_part.painless_wound_roll(wounding_type, damage_dealt, w_bonus, bw_bonus, initial(P.sharpness)) + hit_part.painless_wound_roll(wounding_type, damage_dealt, w_bonus, bw_bonus, initial(proj_type.sharpness)) var/limb_hit_text = "" if(hit_part) diff --git a/code/datums/components/splat.dm b/code/datums/components/splat.dm index d22613204bb..0ae407166ab 100644 --- a/code/datums/components/splat.dm +++ b/code/datums/components/splat.dm @@ -48,9 +48,9 @@ /datum/component/splat/UnregisterFromParent() UnregisterSignal(parent, list(COMSIG_MOVABLE_IMPACT, COMSIG_PROJECTILE_SELF_ON_HIT)) -/datum/component/splat/proc/projectile_splat(obj/projectile/source, atom/firer, atom/target, angle, hit_limb_zone, blocked) +/datum/component/splat/proc/projectile_splat(obj/projectile/source, atom/firer, atom/target, angle, hit_limb_zone, blocked, pierce_hit) SIGNAL_HANDLER - if(blocked != 100) + if(blocked != 100 && !pierce_hit) splat(source, target) /datum/component/splat/proc/throw_splat(atom/movable/source, atom/hit_atom, datum/thrownthing/throwing_datum, caught) diff --git a/code/datums/elements/embed.dm b/code/datums/elements/embed.dm index fbaf638bdd5..90787f85817 100644 --- a/code/datums/elements/embed.dm +++ b/code/datums/elements/embed.dm @@ -98,12 +98,16 @@ * That's awful, and it'll limit us to drop-deletable shrapnels in the worry of stuff like * arrows and harpoons being embeddable even when not let loose by their weapons. */ -/datum/element/embed/proc/check_embed_projectile(obj/projectile/source, atom/movable/firer, atom/hit, angle, hit_zone, blocked) +/datum/element/embed/proc/check_embed_projectile(obj/projectile/source, atom/movable/firer, atom/hit, angle, hit_zone, blocked, pierce_hit) SIGNAL_HANDLER + if (pierce_hit) + return + if(!source.can_embed_into(hit) || blocked) Detach(source) return // we don't care + var/payload_type = source.shrapnel_type var/obj/item/payload = new payload_type(get_turf(hit)) payload.set_embed(source.get_embed()) diff --git a/code/datums/elements/projectile_drop.dm b/code/datums/elements/projectile_drop.dm index 36e189d6c3a..0b1992ef560 100644 --- a/code/datums/elements/projectile_drop.dm +++ b/code/datums/elements/projectile_drop.dm @@ -28,8 +28,10 @@ //Just to be safe, knowing it won't be spawned multiple times. Detach(source) -/datum/element/projectile_drop/proc/spawn_drop_if_not_embeddable(obj/projectile/source, atom/movable/firer, atom/hit, angle, hit_zone) +/datum/element/projectile_drop/proc/spawn_drop_if_not_embeddable(obj/projectile/source, atom/movable/firer, atom/hit, angle, hit_zone, blocked, pierce_hit) SIGNAL_HANDLER + if (pierce_hit) + return if(source.can_embed_into(hit)) Detach(source) return diff --git a/code/datums/elements/selfknockback.dm b/code/datums/elements/selfknockback.dm index d330b30debc..416240b7cfd 100644 --- a/code/datums/elements/selfknockback.dm +++ b/code/datums/elements/selfknockback.dm @@ -47,15 +47,15 @@ clamping the Knockback_Force value below. */ usertarget.throw_at(move_target, knockback_force, knockback_speed) usertarget.visible_message(span_warning("[usertarget] gets thrown back by the force of \the [I] impacting \the [attacktarget]!"), span_warning("The force of \the [I] impacting \the [attacktarget] sends you flying!")) -/datum/element/selfknockback/proc/Projectile_SelfKnockback(obj/projectile/P) +/datum/element/selfknockback/proc/Projectile_SelfKnockback(obj/projectile/proj) SIGNAL_HANDLER - if(!P.firer) + if(!proj.firer) return - var/knockback_force = Get_Knockback_Force(clamp(CEILING((P.damage / 10), 1), 1, 5)) + var/knockback_force = Get_Knockback_Force(clamp(CEILING((proj.damage / 10), 1), 1, 5)) var/knockback_speed = Get_Knockback_Speed(clamp(knockback_force, 1, 5)) - var/atom/movable/knockback_target = P.firer - var/move_target = get_edge_target_turf(knockback_target, angle2dir(P.original_angle+180)) + var/atom/movable/knockback_target = proj.firer + var/move_target = get_edge_target_turf(knockback_target, angle2dir(proj.original_angle+180)) knockback_target.throw_at(move_target, knockback_force, knockback_speed) diff --git a/code/datums/mutations/sight.dm b/code/datums/mutations/sight.dm index d3627167cb5..d32c35a6239 100644 --- a/code/datums/mutations/sight.dm +++ b/code/datums/mutations/sight.dm @@ -171,7 +171,7 @@ var/obj/projectile/beam/laser/laser_eyes/LE = new(source.loc) LE.firer = source LE.def_zone = ran_zone(source.zone_selected) - LE.preparePixelProjectile(target, source, modifiers) + LE.aim_projectile(target, source, modifiers) INVOKE_ASYNC(LE, TYPE_PROC_REF(/obj/projectile, fire)) playsound(source, 'sound/items/weapons/taser2.ogg', 75, TRUE) diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm index b8b697c053b..02281e2437a 100644 --- a/code/datums/position_point_vector.dm +++ b/code/datums/position_point_vector.dm @@ -1,15 +1,11 @@ /proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below. - var/datum/point/P = new - P.x = a.x + (b.x - a.x) * 0.5 - P.y = a.y + (b.y - a.y) * 0.5 - P.z = a.z - return P + return new /datum/point(_z = a.z, _pixel_x = (a.return_px() + b.return_px()) * 0.5, _pixel_y = (a.return_py() + b.return_py()) * 0.5) /proc/pixel_length_between_points(datum/point/a, datum/point/b) - return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2)) + return sqrt(((b.return_px() - a.return_px()) ** 2) + ((b.return_py() - a.return_py()) ** 2)) /proc/angle_between_points(datum/point/a, datum/point/b) - return ATAN2((b.y - a.y), (b.x - a.x)) + return ATAN2(b.return_py() - a.return_py(), b.return_px() - a.return_px()) /// 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. /datum/position @@ -29,8 +25,8 @@ _x = T.x _y = T.y _z = T.z - _pixel_x = P.return_px() - _pixel_y = P.return_py() + _pixel_x = P.pixel_x + _pixel_y = P.pixel_y else if(isatom(_x)) var/atom/A = _x _x = A.x @@ -61,6 +57,8 @@ var/x = 0 var/y = 0 var/z = 0 + var/pixel_x = 0 + var/pixel_y = 0 /datum/point/proc/valid() return x && y && z @@ -89,143 +87,88 @@ _pixel_y = A.pixel_y initialize_location(_x, _y, _z, _pixel_x, _pixel_y) -/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) +/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x, p_y) if(!isnull(tile_x)) - x = ((tile_x - 1) * ICON_SIZE_X) + ICON_SIZE_X * 0.5 + p_x + 1 + x = tile_x if(!isnull(tile_y)) - y = ((tile_y - 1) * ICON_SIZE_Y) + ICON_SIZE_Y * 0.5 + p_y + 1 + y = tile_y if(!isnull(tile_z)) z = tile_z + if(!isnull(p_x)) + var/x_offset = SIGNED_FLOOR_DIVISION(p_x, ICON_SIZE_X) + x += x_offset + pixel_x = p_x - x_offset * ICON_SIZE_X + if(!isnull(p_y)) + var/y_offset = SIGNED_FLOOR_DIVISION(p_y, ICON_SIZE_Y) + y += y_offset + pixel_y = p_y - y_offset * ICON_SIZE_Y + +/datum/point/proc/increment(p_x, p_y) + var/x_offset = SIGNED_FLOOR_DIVISION(p_x, ICON_SIZE_X) + x += x_offset + pixel_x += p_x - x_offset * ICON_SIZE_X + var/y_offset = SIGNED_FLOOR_DIVISION(p_y, ICON_SIZE_Y) + y += y_offset + pixel_y += p_y - y_offset * ICON_SIZE_Y /datum/point/proc/debug_out() var/turf/T = return_turf() - return "[text_ref(src)] aX [x] aY [y] aZ [z] pX [return_px()] pY [return_py()] mX [T.x] mY [T.y] mZ [T.z]" + return "[text_ref(src)] aX [x] aY [y] aZ [z] pX [pixel_x] pY [pixel_y] 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() + AM.pixel_x = pixel_x + AM.pixel_y = pixel_y /datum/point/proc/return_turf() - return locate(CEILING(x / ICON_SIZE_X, 1), CEILING(y / ICON_SIZE_Y, 1), z) + return locate(x + SIGNED_FLOOR_DIVISION(pixel_x, ICON_SIZE_X), y + SIGNED_FLOOR_DIVISION(pixel_y, ICON_SIZE_Y), z) /datum/point/proc/return_coordinates() //[turf_x, turf_y, z] - return list(CEILING(x / ICON_SIZE_X, 1), CEILING(y / ICON_SIZE_Y, 1), z) + return list(x + SIGNED_FLOOR_DIVISION(pixel_x, ICON_SIZE_X), y + SIGNED_FLOOR_DIVISION(pixel_y, ICON_SIZE_Y), z) /datum/point/proc/return_position() return new /datum/position(src) /datum/point/proc/return_px() - return MODULUS(x, ICON_SIZE_X) - (ICON_SIZE_X/2) - 1 + return x * ICON_SIZE_X + pixel_x /datum/point/proc/return_py() - return MODULUS(y, ICON_SIZE_Y) - (ICON_SIZE_Y/2) - 1 + return y * ICON_SIZE_Y + pixel_y -/datum/point/vector - /// Pixels per iteration - var/speed = ICON_SIZE_ALL - var/iteration = 0 +/datum/vector + var/magnitude = 1 var/angle = 0 - /// Calculated x movement amounts to prevent having to do trig every step. - var/mpx = 0 - /// Calculated y movement amounts to prevent having to do trig every step. - var/mpy = 0 - var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location(). - var/starting_y = 0 - var/starting_z = 0 + // Calculated coordinate amounts to prevent having to do trig every step. + var/pixel_x = 0 + var/pixel_y = 0 + var/total_x = 0 + var/total_y = 0 -/datum/point/vector/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0, _angle, _speed, initial_increment = 0) - ..() - initialize_trajectory(_speed, _angle) - if(initial_increment) - increment(initial_increment) - -/datum/point/vector/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) +/datum/vector/New(new_magnitude, new_angle) . = ..() - starting_x = x - starting_y = y - starting_z = z + initialize_trajectory(new_magnitude, new_angle) -/// Same effect as initiliaze_location, but without setting the starting_x/y/z -/datum/point/vector/proc/set_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) - if(!isnull(tile_x)) - x = ((tile_x - 1) * ICON_SIZE_X) + ICON_SIZE_X * 0.5 + p_x + 1 - if(!isnull(tile_y)) - y = ((tile_y - 1) * ICON_SIZE_Y) + ICON_SIZE_Y * 0.5 + p_y + 1 - if(!isnull(tile_z)) - z = tile_z - -/datum/point/vector/copy_to(datum/point/vector/v = new) - ..(v) - v.speed = speed - v.iteration = iteration - v.angle = angle - v.mpx = mpx - v.mpy = mpy - v.starting_x = starting_x - v.starting_y = starting_y - v.starting_z = starting_z - return v - -/datum/point/vector/proc/initialize_trajectory(pixel_speed, new_angle) - if(!isnull(pixel_speed)) - speed = pixel_speed +/datum/vector/proc/initialize_trajectory(new_magnitude, new_angle) + if(!isnull(new_magnitude)) + magnitude = new_magnitude set_angle(new_angle) /// Calculations use "byond angle" where north is 0 instead of 90, and south is 180 instead of 270. -/datum/point/vector/proc/set_angle(new_angle) +/datum/vector/proc/set_angle(new_angle) if(isnull(angle)) return angle = new_angle update_offsets() -/datum/point/vector/proc/update_offsets() - mpx = sin(angle) * speed - mpy = cos(angle) * speed +/datum/vector/proc/update_offsets() + pixel_x = sin(angle) + pixel_y = cos(angle) + total_x = pixel_x * magnitude + total_y = pixel_y * magnitude -/datum/point/vector/proc/set_speed(new_speed) - if(isnull(new_speed) || speed == new_speed) +/datum/vector/proc/set_speed(new_magnitude) + if(isnull(new_magnitude) || magnitude == new_magnitude) return - speed = new_speed - update_offsets() - -/datum/point/vector/proc/increment(multiplier = 1) - iteration++ - x += mpx * (multiplier) - y += mpy * (multiplier) - -/datum/point/vector/proc/return_vector_after_increments(amount = 7, multiplier = 1, force_simulate = FALSE) - var/datum/point/vector/v = copy_to() - if(force_simulate) - for(var/i in 1 to amount) - v.increment(multiplier) - else - v.increment(multiplier * amount) - return v - -/datum/point/vector/proc/on_z_change() - return - -/datum/point/vector/processed //pixel_speed is per decisecond. - var/last_process = 0 - var/last_move = 0 - var/paused = FALSE - -/datum/point/vector/processed/Destroy() - STOP_PROCESSING(SSprojectiles, src) - return ..() - -/datum/point/vector/processed/proc/start() - last_process = world.time - last_move = world.time - START_PROCESSING(SSprojectiles, src) - -/datum/point/vector/processed/process() - if(paused) - last_move += world.time - last_process - last_process = world.time - return - var/needed_time = world.time - last_move - last_process = world.time - last_move = world.time - increment(needed_time / SSprojectiles.wait) + magnitude = new_magnitude + total_x = pixel_x * magnitude + total_y = pixel_y * magnitude diff --git a/code/datums/proximity_monitor/fields/timestop.dm b/code/datums/proximity_monitor/fields/timestop.dm index 3b8001426a0..8411e2c3bb3 100644 --- a/code/datums/proximity_monitor/fields/timestop.dm +++ b/code/datums/proximity_monitor/fields/timestop.dm @@ -204,11 +204,11 @@ freeze_atom(i) freeze_turf(target) -/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/projectile/P) - P.paused = TRUE +/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/projectile/proj) + proj.paused = TRUE -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/projectile/P) - P.paused = FALSE +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/projectile/proj) + proj.paused = FALSE /datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/victim) frozen_mobs += victim diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm index 25c133e46d1..02fe573ef36 100644 --- a/code/datums/status_effects/debuffs/debuffs.dm +++ b/code/datums/status_effects/debuffs/debuffs.dm @@ -512,7 +512,7 @@ new/obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, owner.dir) playsound(spawn_turf, 'sound/effects/curse/curse2.ogg', 80, TRUE, -1) var/obj/projectile/curse_hand/C = new (spawn_turf) - C.preparePixelProjectile(owner, spawn_turf) + C.aim_projectile(owner, spawn_turf) C.fire() /obj/effect/temp_visual/curse diff --git a/code/game/machinery/dna_infuser/infuser_actions.dm b/code/game/machinery/dna_infuser/infuser_actions.dm index 48f92b9ae68..466bed9e17e 100644 --- a/code/game/machinery/dna_infuser/infuser_actions.dm +++ b/code/game/machinery/dna_infuser/infuser_actions.dm @@ -50,7 +50,7 @@ span_bold("You spit ink."), ) var/obj/projectile/ink_spit/ink = new /obj/projectile/ink_spit(caller.loc) - ink.preparePixelProjectile(target, caller, modifiers) + ink.aim_projectile(target, caller, modifiers) ink.firer = caller ink.fire() playsound(caller, 'sound/items/weapons/pierce.ogg', 20, TRUE, -1) diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index e64e01bbcf2..32e29a2e5dd 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -655,7 +655,7 @@ DEFINE_BITFIELD(turret_flags, list( //Shooting Code: - A.preparePixelProjectile(target, T) + A.aim_projectile(target, T) A.firer = src A.fired_from = src if(ignore_faction) diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm index 21a51ba9bcb..dbb5ad3d8bd 100644 --- a/code/game/objects/effects/portals.dm +++ b/code/game/objects/effects/portals.dm @@ -146,6 +146,7 @@ var/turf/real_target = get_link_target_turf() if(!istype(real_target)) return FALSE + if(!force && (!ismecha(moving) && !isprojectile(moving) && moving.anchored && !allow_anchored)) return var/no_effect = FALSE @@ -156,8 +157,8 @@ var/turf/start_turf = get_turf(moving) if(do_teleport(moving, real_target, innate_accuracy_penalty, no_effects = no_effect, channel = teleport_channel, forced = force_teleport)) if(isprojectile(moving)) - var/obj/projectile/P = moving - P.ignore_source_check = TRUE + var/obj/projectile/proj = moving + proj.ignore_source_check = TRUE new /obj/effect/temp_visual/portal_animation(start_turf, src, moving) playsound(start_turf, SFX_PORTAL_ENTER, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) playsound(real_target, SFX_PORTAL_ENTER, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) 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 2d112aa2059..e9e043a5981 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm @@ -36,26 +36,19 @@ 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, atom/new_loc, increment = 0) - var/mutable_appearance/look = new(src) - SET_PLANE_EXPLICIT(look, plane, new_loc || src) - look.pixel_x = p_x - look.pixel_y = p_y +/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, increment = 0) + pixel_x = p_x + pixel_y = p_y if(color_override) - look.color = color_override - appearance = look - scale_to(1,scaling, FALSE) + color = color_override + 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/abstract/projectile_lighting -/obj/effect/projectile_lighting/Initialize(mapload, color, range, intensity, owner_key) +/obj/effect/abstract/projectile_lighting/Initialize(mapload, color, range, intensity) . = ..() set_light(range, intensity, color) - owner = owner_key diff --git a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm index 8c4ea163232..e2323ce4413 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm @@ -1,25 +1,3 @@ -/proc/generate_tracer_between_points(datum/point/starting, datum/point/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(!istype(starting) || !istype(ending) || !ispath(beam_type)) - return - var/datum/point/midpoint = point_midpoint_points(starting, ending) - 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) / ICON_SIZE_ALL, 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 == 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/weapons/guns/projectiles_tracer.dmi' diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm index c1ed690a314..d8b7c1999ae 100644 --- a/code/game/objects/items/devices/traitordevices.dm +++ b/code/game/objects/items/devices/traitordevices.dm @@ -530,4 +530,4 @@ effective or pretty fucking useless. /obj/projectile/bullet/toolbox_turret damage = 10 - speed = 0.6 + speed = 1.6 diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index 005bcff7677..880c73ceced 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -493,7 +493,7 @@ blown_kiss.fired_from = user blown_kiss.firer = user // don't hit ourself that would be really annoying blown_kiss.impacted = list(WEAKREF(user) = TRUE) // just to make sure we don't hit the wearer - blown_kiss.preparePixelProjectile(target, user) + blown_kiss.aim_projectile(target, user) blown_kiss.fire() qdel(src) return ITEM_INTERACT_SUCCESS @@ -520,7 +520,7 @@ blown_kiss.fired_from = offerer blown_kiss.firer = offerer // don't hit ourself that would be really annoying blown_kiss.impacted = list(WEAKREF(offerer) = TRUE) // just to make sure we don't hit the wearer - blown_kiss.preparePixelProjectile(taker, offerer) + blown_kiss.aim_projectile(taker, offerer) blown_kiss.suppressed = SUPPRESSED_VERY // this also means it's a direct offer blown_kiss.fire() qdel(src) @@ -545,7 +545,7 @@ hitsound = 'sound/effects/emotes/kiss.ogg' hitsound_wall = 'sound/effects/emotes/kiss.ogg' pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - speed = 1.6 + speed = 0.66 damage_type = BRUTE damage = 0 // love can't actually hurt you armour_penetration = 100 // but if it could, it would cut through even the thickest plate diff --git a/code/game/objects/items/robot/items/food.dm b/code/game/objects/items/robot/items/food.dm index 3dd15b508cc..6eba8e8fa76 100644 --- a/code/game/objects/items/robot/items/food.dm +++ b/code/game/objects/items/robot/items/food.dm @@ -199,7 +199,7 @@ desc = "Oh noes! A fast-moving gumball!" icon_state = "gumball" damage = 0 - speed = 0.5 + speed = 2 embed_type = null /obj/projectile/bullet/gumball/Initialize(mapload) @@ -232,7 +232,7 @@ desc = "Oh noes! A fast-moving lollipop!" icon_state = "lollipop_1" damage = 0 - speed = 0.5 + speed = 2 embed_type = null var/head_color diff --git a/code/game/objects/items/robot/items/tools.dm b/code/game/objects/items/robot/items/tools.dm index 708563cb751..6e16388b4b1 100644 --- a/code/game/objects/items/robot/items/tools.dm +++ b/code/game/objects/items/robot/items/tools.dm @@ -36,9 +36,9 @@ var/projectile_damage_tick_ecost_coefficient = 10 /** * Speed coefficient - * Higher the coefficient slower the projectile. + * Higher the coefficient faster the projectile. */ - var/projectile_speed_coefficient = 1.5 + var/projectile_speed_coefficient = 0.66 /// Energy cost per tracked projectile per second var/projectile_tick_speed_ecost = 75 /// Projectile sent out by the dampener @@ -170,8 +170,8 @@ SIGNAL_HANDLER tracked -= projectile - projectile.damage *= (1 / projectile_damage_coefficient) - projectile.speed *= (1 / projectile_speed_coefficient) + projectile.damage /= projectile_damage_coefficient + projectile.speed /= projectile_speed_coefficient projectile.cut_overlay(projectile_effect) //bare minimum omni-toolset for modularity diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm index ff08886e7d3..0930de71c0e 100644 --- a/code/game/objects/items/shooting_range.dm +++ b/code/game/objects/items/shooting_range.dm @@ -82,6 +82,6 @@ desc = "A shooting target that looks like a useless clown." max_integrity = 2000 -/obj/item/target/clown/bullet_act(obj/projectile/P) +/obj/item/target/clown/bullet_act(obj/projectile/proj) . = ..() playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 7c8173fae36..40713320eeb 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -206,10 +206,10 @@ GLOBAL_LIST_EMPTY(objects_by_id_tag) SHOULD_CALL_PARENT(FALSE) CRASH("Unimplemented.") -/obj/handle_ricochet(obj/projectile/P) +/obj/handle_ricochet(obj/projectile/proj) . = ..() if(. && receive_ricochet_damage_coeff) - take_damage(P.damage * receive_ricochet_damage_coeff, P.damage_type, P.armor_flag, 0, REVERSE_DIR(P.dir), P.armour_penetration) // pass along receive_ricochet_damage_coeff damage to the structure for the ricochet + take_damage(proj.damage * receive_ricochet_damage_coeff, proj.damage_type, proj.armor_flag, 0, REVERSE_DIR(proj.dir), proj.armour_penetration) // pass along receive_ricochet_damage_coeff damage to the structure for the ricochet /// Handles exposing an object to reagents. /obj/expose_reagents(list/reagents, datum/reagents/source, methods=TOUCH, volume_modifier=1, show_message=TRUE) diff --git a/code/game/objects/structures/deployable_turret.dm b/code/game/objects/structures/deployable_turret.dm index e9162294c8f..a02481d8ef7 100644 --- a/code/game/objects/structures/deployable_turret.dm +++ b/code/game/objects/structures/deployable_turret.dm @@ -195,7 +195,7 @@ target = target_turf var/obj/projectile/projectile_to_fire = new projectile_type(targets_from) playsound(src, firesound, 75, TRUE) - projectile_to_fire.preparePixelProjectile(target, targets_from) + projectile_to_fire.aim_projectile(target, targets_from) projectile_to_fire.firer = user projectile_to_fire.fired_from = src projectile_to_fire.fire() diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 5fd3b2b435a..b785c48ceb3 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -248,13 +248,13 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) to_chat(user, span_warning("A chill runs down your spine as [src] shatters...")) user.AddComponent(/datum/component/omen, incidents_left = 7) -/obj/structure/mirror/bullet_act(obj/projectile/P) - if(broken || !isliving(P.firer) || !P.damage) +/obj/structure/mirror/bullet_act(obj/projectile/proj) + if(broken || !isliving(proj.firer) || !proj.damage) return ..() . = ..() if(broken) // breaking a mirror truly gets you bad luck! - var/mob/living/unlucky_dude = P.firer + var/mob/living/unlucky_dude = proj.firer to_chat(unlucky_dude, span_warning("A chill runs down your spine as [src] shatters...")) unlucky_dude.AddComponent(/datum/component/omen, incidents_left = 7) diff --git a/code/game/objects/structures/reflector.dm b/code/game/objects/structures/reflector.dm index 532ecebde0e..d470008bb9c 100644 --- a/code/game/objects/structures/reflector.dm +++ b/code/game/objects/structures/reflector.dm @@ -75,8 +75,8 @@ /obj/structure/reflector/proc/auto_reflect(obj/projectile/proj, pdir, turf/ploc, pangle) proj.ignore_source_check = TRUE - proj.range = proj.decayedRange - proj.decayedRange = max(proj.decayedRange--, 0) + proj.range = proj.maximum_range + proj.maximum_range = max(proj.maximum_range--, 0) return BULLET_ACT_FORCE_PIERCE /obj/structure/reflector/tool_act(mob/living/user, obj/item/tool, list/modifiers) @@ -196,7 +196,7 @@ if(abs(incidence) > 90 && abs(incidence) < 270) return FALSE var/new_angle = SIMPLIFY_DEGREES(rotation_angle + incidence) - proj.set_angle_centered(new_angle) + proj.set_angle_centered(loc, new_angle) return ..() //DOUBLE @@ -220,7 +220,8 @@ /obj/structure/reflector/double/auto_reflect(obj/projectile/proj, pdir, turf/ploc, pangle) var/incidence = GET_ANGLE_OF_INCIDENCE(rotation_angle, (proj.angle + 180)) var/new_angle = SIMPLIFY_DEGREES(rotation_angle + incidence) - proj.set_angle_centered(new_angle) + proj.forceMove(loc) + proj.set_angle_centered(loc, new_angle) return ..() //BOX @@ -241,8 +242,8 @@ admin = TRUE anchored = TRUE -/obj/structure/reflector/box/auto_reflect(obj/projectile/P) - P.set_angle_centered(rotation_angle) +/obj/structure/reflector/box/auto_reflect(obj/projectile/proj) + proj.set_angle_centered(loc, rotation_angle) return ..() /obj/structure/reflector/ex_act() diff --git a/code/modules/admin/verbs/adminfun.dm b/code/modules/admin/verbs/adminfun.dm index 93b398397c2..984ced4c0bf 100644 --- a/code/modules/admin/verbs/adminfun.dm +++ b/code/modules/admin/verbs/adminfun.dm @@ -194,7 +194,7 @@ ADMIN_VERB_AND_CONTEXT_MENU(admin_smite, R_ADMIN|R_FUN, "Smite", "Smite a player divine_wrath.original = target divine_wrath.def_zone = body_zone divine_wrath.spread = 0 - divine_wrath.preparePixelProjectile(target, source_turf) + divine_wrath.aim_projectile(target, source_turf) divine_wrath.fire() /client/proc/punish_log(whom, punishment) diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index 150c61af29a..c6bfa9cca42 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -1426,7 +1426,7 @@ Striking a noncultist, however, will tear their flesh."} qdel(src) return FALSE var/obj/projectile/projectile = hitby - if(projectile.reflectable & REFLECT_NORMAL) + if(projectile.reflectable) return FALSE //To avoid reflection chance double-dipping with block chance . = ..() if(.) diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm index 9c29d15ba67..6fec632e510 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm @@ -117,7 +117,7 @@ new /obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, victim.dir) playsound(spawn_turf, 'sound/effects/curse/curse2.ogg', 80, TRUE, -1) var/obj/projectile/curse_hand/hel/hand = new (spawn_turf) - hand.preparePixelProjectile(victim, spawn_turf) + hand.aim_projectile(victim, spawn_turf) if (QDELETED(hand)) // safety check if above fails - above has a stack trace if it does fail return hand.fire() diff --git a/code/modules/antagonists/heretic/magic/furious_steel.dm b/code/modules/antagonists/heretic/magic/furious_steel.dm index dceb9023bdb..d72c7fc7c04 100644 --- a/code/modules/antagonists/heretic/magic/furious_steel.dm +++ b/code/modules/antagonists/heretic/magic/furious_steel.dm @@ -102,7 +102,7 @@ name = "blade" icon = 'icons/effects/eldritch.dmi' icon_state = "dio_knife" - speed = 2 + speed = 0.5 damage = 25 armour_penetration = 100 sharpness = SHARP_EDGED diff --git a/code/modules/antagonists/heretic/magic/moon_parade.dm b/code/modules/antagonists/heretic/magic/moon_parade.dm index 6d18c2113f4..49f8c428082 100644 --- a/code/modules/antagonists/heretic/magic/moon_parade.dm +++ b/code/modules/antagonists/heretic/magic/moon_parade.dm @@ -26,12 +26,11 @@ icon_state = "lunar_parade" damage = 0 damage_type = BURN - speed = 1 + speed = 0.2 range = 75 ricochets_max = 40 ricochet_chance = 500 ricochet_incidence_leeway = 0 - pixel_speed_multiplier = 0.2 projectile_piercing = PASSMOB|PASSVEHICLE ///looping sound datum for our projectile. var/datum/looping_sound/moon_parade/soundloop diff --git a/code/modules/antagonists/heretic/magic/rust_wave.dm b/code/modules/antagonists/heretic/magic/rust_wave.dm index b109a068042..1464829aa4c 100644 --- a/code/modules/antagonists/heretic/magic/rust_wave.dm +++ b/code/modules/antagonists/heretic/magic/rust_wave.dm @@ -119,4 +119,4 @@ /obj/projectile/magic/aoe/rust_wave/short range = 7 - speed = 2 + speed = 0.5 diff --git a/code/modules/antagonists/heretic/magic/star_blast.dm b/code/modules/antagonists/heretic/magic/star_blast.dm index e6f7a96811e..ad36cf9186a 100644 --- a/code/modules/antagonists/heretic/magic/star_blast.dm +++ b/code/modules/antagonists/heretic/magic/star_blast.dm @@ -24,10 +24,9 @@ icon_state = "star_ball" damage = 20 damage_type = BURN - speed = 1 + speed = 0.2 range = 100 knockdown = 4 SECONDS - pixel_speed_multiplier = 0.2 /// Effect for when the ball hits something var/obj/effect/explosion_effect = /obj/effect/temp_visual/cosmic_explosion /// The range at which people will get marked with a star mark. diff --git a/code/modules/clothing/shoes/gunboots.dm b/code/modules/clothing/shoes/gunboots.dm index de74703d449..6c9da817146 100644 --- a/code/modules/clothing/shoes/gunboots.dm +++ b/code/modules/clothing/shoes/gunboots.dm @@ -61,7 +61,7 @@ shot.firer = wearer // don't hit ourself that would be really annoying shot.impacted = list(WEAKREF(wearer) = TRUE) shot.def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) // they're fired from boots after all - shot.preparePixelProjectile(target, wearer) + shot.aim_projectile(target, wearer) if(!shot.suppressed) wearer.visible_message(span_danger("[wearer]'s [name] fires \a [shot]!"), "", blind_message = span_hear("You hear a gunshot!"), vision_distance=COMBAT_MESSAGE_RANGE) shot.fire() diff --git a/code/modules/experisci/experiment/physical_experiments.dm b/code/modules/experisci/experiment/physical_experiments.dm index 6b88e19e4a3..60303814e44 100644 --- a/code/modules/experisci/experiment/physical_experiments.dm +++ b/code/modules/experisci/experiment/physical_experiments.dm @@ -21,9 +21,9 @@ /datum/experiment/physical/meat_wall_explosion/check_progress() . += EXPERIMENT_PROG_BOOL("Fire an emitter at a tracked meat wall", is_complete()) -/datum/experiment/physical/meat_wall_explosion/proc/check_experiment(datum/source, obj/projectile/Proj) +/datum/experiment/physical/meat_wall_explosion/proc/check_experiment(datum/source, obj/projectile/proj) SIGNAL_HANDLER - if(istype(Proj, /obj/projectile/beam/emitter)) + if(istype(proj, /obj/projectile/beam/emitter)) finish_experiment(linked_experiment_handler) /datum/experiment/physical/meat_wall_explosion/finish_experiment(datum/component/experiment_handler/experiment_handler) diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm index f554a475ad4..08482ac6be5 100644 --- a/code/modules/fishing/fishing_rod.dm +++ b/code/modules/fishing/fishing_rod.dm @@ -425,13 +425,13 @@ casting = TRUE var/obj/projectile/fishing_cast/cast_projectile = new(get_turf(src)) cast_projectile.range = get_cast_range(user) - cast_projectile.decayedRange = get_cast_range(user) + cast_projectile.maximum_range = get_cast_range(user) cast_projectile.owner = src cast_projectile.original = target cast_projectile.fired_from = src cast_projectile.firer = user cast_projectile.impacted = list(WEAKREF(user) = TRUE) - cast_projectile.preparePixelProjectile(target, user) + cast_projectile.aim_projectile(target, user) cast_projectile.fire() COOLDOWN_START(src, casting_cd, 1 SECONDS) diff --git a/code/modules/hallucination/stray_bullet.dm b/code/modules/hallucination/stray_bullet.dm index 11f88c8c964..b670cd869e9 100644 --- a/code/modules/hallucination/stray_bullet.dm +++ b/code/modules/hallucination/stray_bullet.dm @@ -16,7 +16,7 @@ var/obj/projectile/hallucination/fake_projectile = new fake_type(start, src) - fake_projectile.preparePixelProjectile(hallucinator, start) + fake_projectile.aim_projectile(hallucinator, start) fake_projectile.fire() QDEL_IN(src, 10 SECONDS) // Should clean up the projectile if it somehow gets stuck. @@ -32,7 +32,6 @@ ricochets_max = 0 ricochet_chance = 0 damage = 0 - projectile_type = /obj/projectile/hallucination log_override = TRUE do_not_log = TRUE /// Our parent hallucination that's created us @@ -213,7 +212,7 @@ ricochets_max = 50 ricochet_chance = 80 - reflectable = REFLECT_NORMAL // No idea if this works + reflectable = TRUE /obj/projectile/hallucination/laser/apply_effect_to_hallucinator(mob/living/afflicted) afflicted.adjustStaminaLoss(20) @@ -259,7 +258,7 @@ ricochets_max = 50 ricochet_chance = 80 - reflectable = REFLECT_NORMAL // No idea if this works + reflectable = TRUE /obj/projectile/hallucination/disabler/apply_effect_to_hallucinator(mob/living/afflicted) afflicted.adjustStaminaLoss(30) diff --git a/code/modules/mapfluff/ruins/lavalandruin_code/watcher_grave.dm b/code/modules/mapfluff/ruins/lavalandruin_code/watcher_grave.dm index 6d3ef03c3f0..d25581bd3f3 100644 --- a/code/modules/mapfluff/ruins/lavalandruin_code/watcher_grave.dm +++ b/code/modules/mapfluff/ruins/lavalandruin_code/watcher_grave.dm @@ -254,8 +254,7 @@ icon_state = "ice_2" damage = 10 damage_type = BRUTE // Mining mobs don't take a lot of burn damage so we'll pretend - speed = 1 - pixel_speed_multiplier = 0.5 + speed = 0.5 /obj/projectile/baby_watcher_blast/Initialize(mapload) . = ..() diff --git a/code/modules/mining/equipment/grapple_gun.dm b/code/modules/mining/equipment/grapple_gun.dm index fa7f0b0b735..99144d84867 100644 --- a/code/modules/mining/equipment/grapple_gun.dm +++ b/code/modules/mining/equipment/grapple_gun.dm @@ -179,7 +179,7 @@ icon_state = "grapple_hook" damage = 0 range = 9 - speed = 0.1 + speed = 10 can_hit_turfs = TRUE hitsound = 'sound/items/weapons/zipline_hit.ogg' diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm index 1a83ad0f303..c5bdfa9daa2 100644 --- a/code/modules/mining/equipment/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher.dm @@ -157,7 +157,7 @@ destabilizer.icon_state = "[projectile_icon]" for(var/obj/item/crusher_trophy/attached_trophy as anything in trophies) attached_trophy.on_projectile_fire(destabilizer, user) - destabilizer.preparePixelProjectile(target, user, modifiers) + destabilizer.aim_projectile(target, user, modifiers) destabilizer.firer = user playsound(user, 'sound/items/weapons/plasma_cutter.ogg', 100, TRUE) destabilizer.fire() @@ -423,7 +423,7 @@ marker.name = "deadly [marker.name]" marker.icon_state = "chronobolt" marker.damage = bonus_value - marker.speed = 2 + marker.speed = 0.5 deadly_shot = FALSE /obj/item/crusher_trophy/blaster_tubes/on_mark_detonation(mob/living/target, mob/living/user) diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm index d355014c6ad..3b33119aa24 100644 --- a/code/modules/mining/lavaland/megafauna_loot.dm +++ b/code/modules/mining/lavaland/megafauna_loot.dm @@ -553,7 +553,7 @@ return COOLDOWN_START(src, attack_cooldown, 3 SECONDS) var/obj/projectile/projectile = new /obj/projectile/soulscythe(get_turf(src)) - projectile.preparePixelProjectile(attacked_atom, src) + projectile.aim_projectile(attacked_atom, src) projectile.firer = src projectile.fire(null, attacked_atom) visible_message(span_danger("[src] fires at [attacked_atom]!"), span_notice("You fire at [attacked_atom]!")) diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index 58b7b437d7c..bb621570516 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -145,8 +145,8 @@ /obj/structure/closet/crate/necropolis/colossus name = "colossus chest" -/obj/structure/closet/crate/necropolis/colossus/bullet_act(obj/projectile/P) - if(istype(P, /obj/projectile/colossus)) +/obj/structure/closet/crate/necropolis/colossus/bullet_act(obj/projectile/proj) + if(istype(proj, /obj/projectile/colossus)) return BULLET_ACT_FORCE_PIERCE return ..() diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm index 0b051248a39..55a1b9557c2 100644 --- a/code/modules/mining/ores_coins.dm +++ b/code/modules/mining/ores_coins.dm @@ -341,8 +341,8 @@ GLOBAL_LIST_INIT(sand_recipes, list(\ else return ..() -/obj/item/gibtonite/bullet_act(obj/projectile/P) - GibtoniteReaction(P.firer, "A projectile has primed for detonation a") +/obj/item/gibtonite/bullet_act(obj/projectile/proj) + GibtoniteReaction(proj.firer, "A projectile has primed for detonation a") return ..() /obj/item/gibtonite/ex_act() diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm index 350106d707f..f85b50c5eb1 100644 --- a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm @@ -4,8 +4,7 @@ damage = 5 damage_type = BURN armor_flag = ENERGY - speed = 1 - pixel_speed_multiplier = 0.25 + speed = 0.25 temperature = -75 /datum/action/cooldown/mob_cooldown/ice_demon_teleport diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm b/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm index 4e9e0459587..a5b47c7d22f 100644 --- a/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm +++ b/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm @@ -6,7 +6,7 @@ light_range = 2 armor_flag = ENERGY light_color = LIGHT_COLOR_DIM_YELLOW - speed = 1.6 + speed = 0.66 hitsound = 'sound/items/weapons/sear.ogg' hitsound_wall = 'sound/items/weapons/effects/searwall.ogg' nondirectional_sprite = TRUE diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm index 8e43e43e72c..bd480cc6972 100644 --- a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm @@ -72,7 +72,7 @@ icon_state = "neurotoxin" hitsound = 'sound/items/weapons/sear.ogg' damage = 20 - speed = 2 + speed = 0.5 range = 20 jitter = 3 SECONDS stutter = 3 SECONDS diff --git a/code/modules/mob/living/carbon/alien/adult/alien_powers.dm b/code/modules/mob/living/carbon/alien/adult/alien_powers.dm index bfdcd1a5fd2..b9cdd8581e4 100644 --- a/code/modules/mob/living/carbon/alien/adult/alien_powers.dm +++ b/code/modules/mob/living/carbon/alien/adult/alien_powers.dm @@ -295,7 +295,7 @@ Doesn't work on other aliens/AI.*/ span_alertalien("You spit neurotoxin."), ) var/obj/projectile/neurotoxin/neurotoxin = new /obj/projectile/neurotoxin(caller.loc) - neurotoxin.preparePixelProjectile(target, caller, modifiers) + neurotoxin.aim_projectile(target, caller, modifiers) neurotoxin.firer = caller neurotoxin.fire() caller.newtonian_move(get_angle(target, caller)) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 63fd4aad17b..7ff695e4a74 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -1494,3 +1494,9 @@ return head.adjustBleedStacks(5) visible_message(span_notice("[src] gets a nosebleed."), span_warning("You get a nosebleed.")) + +/mob/living/carbon/check_hit_limb_zone_name(hit_zone) + if(get_bodypart(hit_zone)) + return hit_zone + // When a limb is missing the damage is actually passed to the chest + return BODY_ZONE_CHEST diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index fd76fa07ddd..2c5d2df0517 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -62,12 +62,12 @@ return null -/mob/living/carbon/check_projectile_dismemberment(obj/projectile/P, def_zone) +/mob/living/carbon/check_projectile_dismemberment(obj/projectile/proj, def_zone) var/obj/item/bodypart/affecting = get_bodypart(def_zone) - if(affecting && affecting.can_dismember() && !(affecting.bodypart_flags & BODYPART_UNREMOVABLE) && affecting.get_damage() >= (affecting.max_damage - P.dismemberment)) - affecting.dismember(P.damtype) - if(P.catastropic_dismemberment) - apply_damage(P.damage, P.damtype, BODY_ZONE_CHEST, wound_bonus = P.wound_bonus) //stops a projectile blowing off a limb effectively doing no damage. Mostly relevant for sniper rifles. + if(affecting && affecting.can_dismember() && !(affecting.bodypart_flags & BODYPART_UNREMOVABLE) && affecting.get_damage() >= (affecting.max_damage - proj.dismemberment)) + affecting.dismember(proj.damtype) + if(proj.catastropic_dismemberment) + apply_damage(proj.damage, proj.damtype, BODY_ZONE_CHEST, wound_bonus = proj.wound_bonus) //stops a projectile blowing off a limb effectively doing no damage. Mostly relevant for sniper rifles. /mob/living/carbon/try_catch_item(obj/item/item, skip_throw_mode_check = FALSE, try_offhand = FALSE) . = ..() diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 8df7c758e2b..36c9182d32b 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -45,11 +45,10 @@ return covering_part /mob/living/carbon/human/bullet_act(obj/projectile/bullet, def_zone, piercing_hit = FALSE) - if(bullet.firer == src && bullet.original == src) //can't block or reflect when shooting yourself return ..() - if(bullet.reflectable & REFLECT_NORMAL) + if(bullet.reflectable) if(check_reflect(def_zone)) // Checks if you've passed a reflection% check visible_message( span_danger("The [bullet.name] gets reflected by [src]!"), @@ -61,11 +60,8 @@ playsound(src, held_item.block_sound, BLOCK_SOUND_VOLUME, TRUE) // Find a turf near or on the original location to bounce to if(!isturf(loc)) //Open canopy mech (ripley) check. if we're inside something and still got hit - bullet.force_hit = TRUE //The thing we're in passed the bullet to us. Pass it back, and tell it to take the damage. - loc.bullet_act(bullet, def_zone, piercing_hit) - return BULLET_ACT_HIT + return loc.bullet_act(bullet, def_zone, piercing_hit) bullet.reflect(src) - return BULLET_ACT_FORCE_PIERCE // complete projectile permutation if(check_block(bullet, bullet.damage, "the [bullet.name]", PROJECTILE_ATTACK, bullet.armour_penetration, bullet.damage_type)) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index b64a009ac1a..029a4376437 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -3004,3 +3004,8 @@ GLOBAL_LIST_EMPTY(fire_appearances) REMOVE_TRAIT(src, TRAIT_BLOCKING_PROJECTILES, BLOCKING_TRAIT) cut_overlay(selected_overlay) update_transform(0.8) + +/// Returns the string form of the def_zone we have hit. +/mob/living/proc/check_hit_limb_zone_name(hit_zone) + if(has_limbs) + return hit_zone diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 5eb78154373..71dc34b986f 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -174,16 +174,15 @@ /mob/living/check_projectile_armor(def_zone, obj/projectile/impacting_projectile, is_silent) return run_armor_check(def_zone, impacting_projectile.armor_flag, "","",impacting_projectile.armour_penetration, "", is_silent, impacting_projectile.weak_against_armour) -/mob/living/proc/check_projectile_dismemberment(obj/projectile/P, def_zone) - return 0 +/mob/living/proc/check_projectile_dismemberment(obj/projectile/proj, def_zone) + return /obj/item/proc/get_volume_by_throwforce_and_or_w_class() if(throwforce && w_class) return clamp((throwforce + w_class) * 5, 30, 100)// Add the item's throwforce to its weight class and multiply by 5, then clamp the value between 30 and 100 - else if(w_class) + if(w_class) return clamp(w_class * 8, 20, 100) // Multiply the item's weight class by 8, then clamp the value between 20 and 100 - else - return 0 + return 0 /mob/living/proc/set_combat_mode(new_mode, silent = TRUE) if(combat_mode == new_mode) diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm index df9db2defb1..81fad948ae1 100644 --- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm +++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm @@ -86,7 +86,7 @@ var/obj/projectile/fired_bullet = new projectile(loc) playsound(src, shoot_sound, 50, TRUE) - fired_bullet.preparePixelProjectile(target, src) + fired_bullet.aim_projectile(target, src) fired_bullet.fire() /mob/living/simple_animal/bot/secbot/ed209/emp_act(severity) diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm index 5e9018384be..e1117e3200f 100644 --- a/code/modules/mob/living/simple_animal/bot/mulebot.dm +++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm @@ -243,14 +243,14 @@ return TRUE -/mob/living/simple_animal/bot/mulebot/bullet_act(obj/projectile/Proj) +/mob/living/simple_animal/bot/mulebot/bullet_act(obj/projectile/proj) . = ..() if(. && !QDELETED(src)) //Got hit and not blown up yet. if(prob(50) && !isnull(load)) unload(0) if(prob(25)) visible_message(span_danger("Something shorts out inside [src]!")) - wires.cut_random(source = Proj.firer) + wires.cut_random(source = proj.firer) /mob/living/simple_animal/bot/mulebot/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm index 0f60aac10fa..d627053179b 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -297,15 +297,15 @@ update_appearance() return TRUE -/mob/living/simple_animal/bot/secbot/bullet_act(obj/projectile/Proj) +/mob/living/simple_animal/bot/secbot/bullet_act(obj/projectile/proj) . = ..() if(. != BULLET_ACT_HIT) return - if(istype(Proj, /obj/projectile/beam) || istype(Proj, /obj/projectile/bullet)) - if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE)) - if(Proj.is_hostile_projectile() && Proj.damage < src.health && ishuman(Proj.firer)) - retaliate(Proj.firer) + if(istype(proj, /obj/projectile/beam) || istype(proj, /obj/projectile/bullet)) + if((proj.damage_type == BURN) || (proj.damage_type == BRUTE)) + if(proj.is_hostile_projectile() && proj.damage < src.health && ishuman(proj.firer)) + retaliate(proj.firer) /mob/living/simple_animal/bot/secbot/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) if(!(bot_mode_flags & BOT_MODE_ON)) diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 201c83497df..583f2ba1d6c 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -165,11 +165,11 @@ FindTarget(list(source)) return ..() -/mob/living/simple_animal/hostile/bullet_act(obj/projectile/P) +/mob/living/simple_animal/hostile/bullet_act(obj/projectile/proj) if(stat == CONSCIOUS && !target && AIStatus != AI_OFF && !client) - if(P.firer && get_dist(src, P.firer) <= aggro_vision_range) - FindTarget(list(P.firer)) - Goto(P.starting, move_to_delay, 3) + if(proj.firer && get_dist(src, proj.firer) <= aggro_vision_range) + FindTarget(list(proj.firer)) + Goto(proj.starting, move_to_delay, 3) return ..() //////////////HOSTILE MOB TARGETING AND AGGRESSION//////////// diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm index ae3b10b1199..1f544c0d322 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm @@ -106,7 +106,7 @@ Difficulty: Medium /obj/projectile/kinetic/miner damage = 20 - speed = 0.9 + speed = 1.1 icon_state = "ka_tracer" range = 4 diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm index f1e34612713..248b73c6677 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm @@ -297,9 +297,9 @@ Difficulty: Hard if(.) recovery_time = world.time + 20 // can only attack melee once every 2 seconds but rapid_melee gives higher priority -/mob/living/simple_animal/hostile/megafauna/bubblegum/bullet_act(obj/projectile/P) +/mob/living/simple_animal/hostile/megafauna/bubblegum/bullet_act(obj/projectile/proj) if(BUBBLEGUM_IS_ENRAGED) - visible_message(span_danger("[src] deflects the projectile; [p_they()] can't be hit with ranged weapons while enraged!"), span_userdanger("You deflect the projectile!")) + visible_message(span_danger("[src] deflects the [proj]! [p_They()] can't be hit with ranged weapons while enraged!"), span_userdanger("You deflect the projectile!")) playsound(src, SFX_BULLET_MISS, 300, TRUE) return BULLET_ACT_BLOCK return ..() diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 3af739f736c..08a90c640d8 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -182,7 +182,7 @@ icon_state = "chronobolt" damage = 25 armour_penetration = 100 - speed = 2 + speed = 0.5 damage_type = BRUTE pass_flags = PASSTABLE plane = GAME_PLANE @@ -278,12 +278,12 @@ ActivationReaction(user, ACTIVATE_WEAPON) ..() -/obj/machinery/anomalous_crystal/bullet_act(obj/projectile/P, def_zone) +/obj/machinery/anomalous_crystal/bullet_act(obj/projectile/proj, def_zone) . = ..() - if(istype(P, /obj/projectile/magic)) - ActivationReaction(P.firer, ACTIVATE_MAGIC, P.damage_type) + if(istype(proj, /obj/projectile/magic)) + ActivationReaction(proj.firer, ACTIVATE_MAGIC, proj.damage_type) return - ActivationReaction(P.firer, P.armor_flag, P.damage_type) + ActivationReaction(proj.firer, proj.armor_flag, proj.damage_type) /obj/machinery/anomalous_crystal/proc/ActivationReaction(mob/user, method, damtype) if(!COOLDOWN_FINISHED(src, cooldown_timer)) @@ -405,9 +405,9 @@ /obj/machinery/anomalous_crystal/emitter/ActivationReaction(mob/user, method) if(..()) - var/obj/projectile/P = new generated_projectile(get_turf(src)) - P.firer = src - P.fire(dir2angle(dir)) + var/obj/projectile/proj = new generated_projectile(get_turf(src)) + proj.firer = src + proj.fire(dir2angle(dir)) /obj/machinery/anomalous_crystal/dark_reprise //Revives anyone nearby, but turns them into shadowpeople and renders them uncloneable, so the crystal is your only hope of getting up again if you go down. observer_desc = "When activated, this crystal revives anyone nearby, but turns them into Shadowpeople and makes them unclonable, making the crystal their only hope of getting up again." diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm index 8c32b2a3c47..8cdf9141ab8 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm @@ -126,7 +126,7 @@ Difficulty: Extremely Hard if(FROST_MINER_SHOULD_ENRAGE) INVOKE_ASYNC(src, PROC_REF(check_enraged)) return COMPONENT_BLOCK_ABILITY_START - var/projectile_speed_multiplier = 1 - enraged * 0.5 + var/projectile_speed_multiplier = 1 + enraged frost_orbs.projectile_speed_multiplier = projectile_speed_multiplier snowball_machine_gun.projectile_speed_multiplier = projectile_speed_multiplier ice_shotgun.projectile_speed_multiplier = projectile_speed_multiplier @@ -198,8 +198,7 @@ Difficulty: Extremely Hard icon_state = "ice_1" damage = 20 armour_penetration = 100 - speed = 1 - pixel_speed_multiplier = 0.1 + speed = 0.1 range = 500 homing_turn_speed = 3 damage_type = BURN @@ -214,8 +213,7 @@ Difficulty: Extremely Hard icon_state = "nuclear_particle" damage = 5 armour_penetration = 100 - speed = 1 - pixel_speed_multiplier = 0.333 + speed = 0.33 range = 150 damage_type = BRUTE explode_hit_objects = FALSE @@ -225,8 +223,7 @@ Difficulty: Extremely Hard icon_state = "ice_2" damage = 15 armour_penetration = 100 - speed = 1 - pixel_speed_multiplier = 0.333 + speed = 0.33 range = 150 damage_type = BRUTE diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index 9383718e7bd..509ca49ab34 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -573,8 +573,8 @@ Difficulty: Hard if(mover == caster.pulledby) return if(isprojectile(mover)) - var/obj/projectile/P = mover - if(P.firer == caster) + var/obj/projectile/proj = mover + if(proj.firer == caster) return if(mover != caster) return FALSE diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm index a7db8524924..aad198801ad 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm @@ -198,11 +198,9 @@ Difficulty: Hard /obj/projectile/colossus/wendigo_shockwave name = "wendigo shockwave" - speed = 2 - /// If wave movement is enabled - var/wave_movement = FALSE + speed = 0.5 /// Amount the angle changes every pixel move - var/wave_speed = 15 + var/wave_speed = 0.5 /// Amount of movements this projectile has made var/pixel_moves = 0 @@ -210,18 +208,16 @@ Difficulty: Hard damage = 15 /obj/projectile/colossus/wendigo_shockwave/wave - speed = 8 - wave_movement = TRUE - wave_speed = 10 + speed = 0.125 + homing = TRUE + wave_speed = 0.3 /obj/projectile/colossus/wendigo_shockwave/wave/alternate - wave_speed = -10 + wave_speed = -0.3 -/obj/projectile/colossus/wendigo_shockwave/pixel_move(trajectory_multiplier, hitscanning = FALSE) - . = ..() - if(wave_movement) - pixel_moves++ - set_angle(original_angle + pixel_moves * wave_speed) +/obj/projectile/colossus/wendigo_shockwave/process_homing() + pixel_moves++ + set_angle(original_angle + pixel_moves * wave_speed) /obj/item/wendigo_blood name = "bottle of wendigo blood" diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm index f57ea3e0582..5fbd6cda5cb 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm @@ -101,8 +101,8 @@ if(mover == set_target) return FALSE if(isprojectile(mover)) - var/obj/projectile/P = mover - if(P.firer == set_target) + var/obj/projectile/proj = mover + if(proj.firer == set_target) return FALSE #define IGNORE_PROC_IF_NOT_TARGET(X) /mob/living/simple_animal/hostile/asteroid/curseblob/##X(AM) { if (AM == set_target) return ..(); } @@ -119,8 +119,8 @@ IGNORE_PROC_IF_NOT_TARGET(attack_larva) IGNORE_PROC_IF_NOT_TARGET(attack_animal) -/mob/living/simple_animal/hostile/asteroid/curseblob/bullet_act(obj/projectile/Proj) - if(Proj.firer != set_target) +/mob/living/simple_animal/hostile/asteroid/curseblob/bullet_act(obj/projectile/proj) + if(proj.firer != set_target) return BULLET_ACT_BLOCK return ..() diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm index b12be32587e..48316d25020 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm @@ -135,7 +135,7 @@ H = new /obj/projectile/herald(startloc) else H = new /obj/projectile/herald/teleshot(startloc) - H.preparePixelProjectile(marker, startloc) + H.aim_projectile(marker, startloc) H.firer = src if(target) H.original = target @@ -228,7 +228,7 @@ icon_state= "chronobolt" damage = 20 armour_penetration = 60 - speed = 2 + speed = 0.5 damage_type = BRUTE pass_flags = PASSTABLE @@ -276,7 +276,7 @@ var/turf/startloc = get_turf(owner) var/obj/projectile/herald/H = null H = new /obj/projectile/herald(startloc) - H.preparePixelProjectile(marker, startloc) + H.aim_projectile(marker, startloc) H.firer = owner H.fire(set_angle) diff --git a/code/modules/mob/living/simple_animal/hostile/ooze.dm b/code/modules/mob/living/simple_animal/hostile/ooze.dm index a44cae6f139..7f6a5c5cd36 100644 --- a/code/modules/mob/living/simple_animal/hostile/ooze.dm +++ b/code/modules/mob/living/simple_animal/hostile/ooze.dm @@ -359,7 +359,7 @@ // Why is this in InterceptClickOn() and not Activate()? // Well, we need to use the params of the click intercept - // for passing into preparePixelProjectile, so we'll handle it here instead. + // for passing into aim_projectile, so we'll handle it here instead. // We just need to make sure Pre-activate and Activate return TRUE so we make it this far caller.visible_message( span_nicegreen("[caller] launches a mending globule!"), @@ -372,7 +372,7 @@ var/modifiers = params2list(params) var/obj/projectile/globule/globule = new(caller.loc) - globule.preparePixelProjectile(target, caller, modifiers) + globule.aim_projectile(target, caller, modifiers) globule.def_zone = caller.zone_selected globule.fire() diff --git a/code/modules/mob/living/sneeze.dm b/code/modules/mob/living/sneeze.dm index ebf61620834..af638d7c71a 100644 --- a/code/modules/mob/living/sneeze.dm +++ b/code/modules/mob/living/sneeze.dm @@ -53,7 +53,7 @@ suppressed = SUPPRESSED_VERY range = 4 - speed = 4 + speed = 0.25 spread = 40 damage_type = BRUTE damage = 0 diff --git a/code/modules/mod/modules/modules_antag.dm b/code/modules/mod/modules/modules_antag.dm index ec550f3cfde..d09781451f4 100644 --- a/code/modules/mod/modules/modules_antag.dm +++ b/code/modules/mod/modules/modules_antag.dm @@ -313,7 +313,7 @@ if(!.) return var/obj/projectile/flame = new /obj/projectile/bullet/incendiary/fire(mod.wearer.loc) - flame.preparePixelProjectile(target, mod.wearer) + flame.aim_projectile(target, mod.wearer) flame.firer = mod.wearer playsound(src, 'sound/items/modsuit/flamethrower.ogg', 75, TRUE) INVOKE_ASYNC(flame, TYPE_PROC_REF(/obj/projectile, fire)) diff --git a/code/modules/mod/modules/modules_engineering.dm b/code/modules/mod/modules/modules_engineering.dm index 9c96d9edd82..abe09594500 100644 --- a/code/modules/mod/modules/modules_engineering.dm +++ b/code/modules/mod/modules/modules_engineering.dm @@ -96,7 +96,7 @@ if(!.) return var/obj/projectile/tether = new /obj/projectile/tether(mod.wearer.loc, src) - tether.preparePixelProjectile(target, mod.wearer) + tether.aim_projectile(target, mod.wearer) tether.firer = mod.wearer playsound(src, 'sound/items/weapons/batonextend.ogg', 25, TRUE) INVOKE_ASYNC(tether, TYPE_PROC_REF(/obj/projectile, fire)) @@ -164,14 +164,8 @@ firer.AddComponent(/datum/component/tether, target, 7, "MODtether", parent_module = parent_module) return - var/hitx - var/hity - if(target == original) - hitx = target.pixel_x + p_x - 16 - hity = target.pixel_y + p_y - 16 - else - hitx = target.pixel_x + rand(-8, 8) - hity = target.pixel_y + rand(-8, 8) + var/hitx = impact_x + var/hity = impact_y if (!isnull(last_turf) && last_turf != target && last_turf != target.loc) var/turf_dir = get_dir(last_turf, get_turf(target)) diff --git a/code/modules/mod/modules/modules_medical.dm b/code/modules/mod/modules/modules_medical.dm index 7c4650ff81a..f71814fc7db 100644 --- a/code/modules/mod/modules/modules_medical.dm +++ b/code/modules/mod/modules/modules_medical.dm @@ -165,7 +165,7 @@ return var/atom/movable/fired_organ = pop(organ_list) var/obj/projectile/organ/projectile = new /obj/projectile/organ(mod.wearer.loc, fired_organ) - projectile.preparePixelProjectile(target, mod.wearer) + projectile.aim_projectile(target, mod.wearer) projectile.firer = mod.wearer playsound(src, 'sound/vehicles/mecha/hydraulic.ogg', 25, TRUE) INVOKE_ASYNC(projectile, TYPE_PROC_REF(/obj/projectile, fire)) diff --git a/code/modules/mod/modules/modules_ninja.dm b/code/modules/mod/modules/modules_ninja.dm index a6ade06a909..5f7abdd712b 100644 --- a/code/modules/mod/modules/modules_ninja.dm +++ b/code/modules/mod/modules/modules_ninja.dm @@ -323,7 +323,7 @@ if(IS_SPACE_NINJA(mod.wearer) && isliving(target)) mod.wearer.say("Get over here!", forced = type) var/obj/projectile/net = new /obj/projectile/energy_net(mod.wearer.loc, src) - net.preparePixelProjectile(target, mod.wearer) + net.aim_projectile(target, mod.wearer) net.firer = mod.wearer playsound(src, 'sound/items/weapons/punchmiss.ogg', 25, TRUE) INVOKE_ASYNC(net, TYPE_PROC_REF(/obj/projectile, fire)) diff --git a/code/modules/mod/modules/modules_security.dm b/code/modules/mod/modules/modules_security.dm index 09032318304..dff5a000b92 100644 --- a/code/modules/mod/modules/modules_security.dm +++ b/code/modules/mod/modules/modules_security.dm @@ -310,7 +310,7 @@ /// Debuff multiplier on projectiles. var/debuff_multiplier = 0.66 /// Speed multiplier on projectiles, higher means slower. - var/speed_multiplier = 2.5 + var/speed_multiplier = 0.4 /// List of all tracked projectiles. var/list/tracked_projectiles = list() /// Effect image on projectiles. diff --git a/code/modules/mod/modules/modules_supply.dm b/code/modules/mod/modules/modules_supply.dm index bc34cf8781e..2c4a5955aed 100644 --- a/code/modules/mod/modules/modules_supply.dm +++ b/code/modules/mod/modules/modules_supply.dm @@ -528,7 +528,7 @@ if(!.) return var/obj/projectile/bomb = new /obj/projectile/bullet/mining_bomb(mod.wearer.loc) - bomb.preparePixelProjectile(target, mod.wearer) + bomb.aim_projectile(target, mod.wearer) bomb.firer = mod.wearer playsound(src, 'sound/items/weapons/gun/general/grenade_launch.ogg', 75, TRUE) INVOKE_ASYNC(bomb, TYPE_PROC_REF(/obj/projectile, fire)) diff --git a/code/modules/mod/modules/modules_timeline.dm b/code/modules/mod/modules/modules_timeline.dm index bfb22da0c20..da36c2153d0 100644 --- a/code/modules/mod/modules/modules_timeline.dm +++ b/code/modules/mod/modules/modules_timeline.dm @@ -222,7 +222,7 @@ //fire projectile var/obj/projectile/energy/chrono_beam/chrono_beam = new /obj/projectile/energy/chrono_beam(get_turf(src)) chrono_beam.tem_weakref = WEAKREF(src) - chrono_beam.preparePixelProjectile(target, mod.wearer) + chrono_beam.aim_projectile(target, mod.wearer) chrono_beam.firer = mod.wearer playsound(src, 'sound/items/modsuit/time_anchor_set.ogg', 50, TRUE) INVOKE_ASYNC(chrono_beam, TYPE_PROC_REF(/obj/projectile, fire)) diff --git a/code/modules/power/rtg.dm b/code/modules/power/rtg.dm index dff4a732b93..69693af3a4a 100644 --- a/code/modules/power/rtg.dm +++ b/code/modules/power/rtg.dm @@ -73,10 +73,10 @@ tesla_zap(source = src, zap_range = 5, power = power_gen * 20) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(explosion), src, 2, 3, 4, null, 8), 10 SECONDS) // Not a normal explosion. -/obj/machinery/power/rtg/abductor/bullet_act(obj/projectile/Proj) +/obj/machinery/power/rtg/abductor/bullet_act(obj/projectile/proj) . = ..() - if(!going_kaboom && istype(Proj) && Proj.damage > 0 && ((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE))) - log_bomber(Proj.firer, "triggered a", src, "explosion via projectile") + if(!going_kaboom && istype(proj) && proj.damage > 0 && ((proj.damage_type == BURN) || (proj.damage_type == BRUTE))) + log_bomber(proj.firer, "triggered a", src, "explosion via projectile") overload() /obj/machinery/power/rtg/abductor/blob_act(obj/structure/blob/B) diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm index b15cd334a8e..5ea2da3513d 100644 --- a/code/modules/projectiles/ammunition/_firing.dm +++ b/code/modules/projectiles/ammunition/_firing.dm @@ -88,7 +88,7 @@ var/direct_target if(target && curloc.Adjacent(targloc, target=targloc, mover=src)) //if the target is right on our location or adjacent (including diagonally if reachable) we'll skip the travelling code in the proj's fire() direct_target = target - loaded_projectile.preparePixelProjectile(target, fired_from, params2list(params), spread) + loaded_projectile.aim_projectile(target, fired_from, params2list(params), spread) var/obj/projectile/loaded_projectile_cache = loaded_projectile loaded_projectile = null loaded_projectile_cache.fire(null, direct_target) diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm index d7a24c9deba..41152b170ad 100644 --- a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm +++ b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm @@ -60,7 +60,7 @@ desc = "Quite the sticky situation..." icon_state = "sticky_arrow_projectile" damage = 30 - speed = 0.75 + speed = 1.3 range = 20 embed_type = /datum/embed_data/arrow/sticky diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm index 08cce7d7315..b1a34c30eb9 100644 --- a/code/modules/projectiles/guns/energy/laser.dm +++ b/code/modules/projectiles/guns/energy/laser.dm @@ -151,11 +151,11 @@ var/size_per_tile = 0.1 var/max_scale = 4 -/obj/projectile/beam/laser/accelerator/Range() +/obj/projectile/beam/laser/accelerator/reduce_range() ..() damage += 7 transform = matrix() - transform *= min(1 + (decayedRange - range) * size_per_tile, max_scale) + transform *= min(1 + (maximum_range - range) * size_per_tile, max_scale) ///X-ray gun diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index 8072f62e464..0cd4148e709 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -431,7 +431,7 @@ playsound(user.loc, 'sound/effects/coin2.ogg', 50, TRUE) user.visible_message(span_warning("[user] flips a coin towards [target]!"), span_danger("You flip a coin towards [target]!")) var/obj/projectile/bullet/coin/new_coin = new(get_turf(user), target_turf, user) - new_coin.preparePixelProjectile(target_turf, user) + new_coin.aim_projectile(target_turf, user) new_coin.fire() return ITEM_INTERACT_SUCCESS diff --git a/code/modules/projectiles/guns/special/blastcannon.dm b/code/modules/projectiles/guns/special/blastcannon.dm index a7c35c9122e..befb622251a 100644 --- a/code/modules/projectiles/guns/special/blastcannon.dm +++ b/code/modules/projectiles/guns/special/blastcannon.dm @@ -193,7 +193,7 @@ SSexplosions.shake_the_room(start_turf, max(heavy, medium, light, 0), (capped_heavy * 15) + (capped_medium * 20), capped_heavy, capped_medium) var/obj/projectile/blastwave/blastwave = new(loc, heavy, medium, light) - blastwave.preparePixelProjectile(target, start_turf, params2list(modifiers), spread) + blastwave.aim_projectile(target, start_turf, params2list(modifiers), spread) blastwave.fire() cached_firer = null cached_target = null @@ -314,7 +314,7 @@ /obj/projectile/blastwave/is_hostile_projectile() return TRUE -/obj/projectile/blastwave/Range() +/obj/projectile/blastwave/reduce_range() . = ..() if(QDELETED(src)) return diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 3d2834edc62..cbe396e0773 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -1,7 +1,8 @@ -#define MOVES_HITSCAN -1 //Not actually hitscan but close as we get without actual hitscan. -#define MUZZLE_EFFECT_PIXEL_INCREMENT 17 //How many pixels to move the muzzle flash up so your character doesn't look like they're shitting out lasers. -#define MAX_RANGE_HIT_PRONE_TARGETS 10 //How far do the projectile hits the prone mob +/// 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 /obj/projectile name = "projectile" @@ -15,30 +16,56 @@ generic_canpass = FALSE blocks_emissive = EMISSIVE_BLOCK_GENERIC layer = MOB_LAYER - //The sound this plays on impact. + /// The sound this plays on impact. var/hitsound = 'sound/items/weapons/pierce.ogg' - var/hitsound_wall = "" + /// Sound played when the projectile hits a wall + var/hitsound_wall resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/def_zone = "" //Aiming at - var/atom/movable/firer = null//Who shot it - var/datum/fired_from = null // the thing that the projectile was fired from (gun, turret, spell) - var/suppressed = FALSE //Attack message - var/yo = null - var/xo = null - var/atom/original = null // the original target clicked - var/turf/starting = null // the projectile's starting turf + /// Zone at which the projectile is aimed at + var/def_zone = "" + /// Atom who shot the projectile (Not the gun, the guy who shot the gun) + var/atom/movable/firer = null + /// The thing that the projectile was fired from (gun, turret, spell) + var/datum/fired_from = null + /// One of three suppression states: NONE displays the hit message and produces a loud sound, + /// QUIET makes a quiet sound and only lets the victim know they've been shot, and VERY only makes a very quiet sound with no messages + var/suppressed = SUPPRESSED_NONE + /// Original clicked target + var/atom/original = null + /// Initial target x coordinate offset of the projectile + VAR_FINAL/xo = null + /// Initial target y coordinate offset of the projectile + VAR_FINAL/yo = null + /// Projectile's starting turf + var/turf/starting = null + /// pixel_x where the player clicked. Default is the center. var/p_x = 16 - var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center + /// pixel_y where the player clicked. Default is the center + var/p_y = 16 + /// X coordinate at which the projectile entered a new turf + var/entry_x + /// Y coordinate at which the projectile entered a new turf + var/entry_y + /// X coordinate at which the projectile visually impacted the target + var/impact_x + /// Y coordinate at which the projectile visually impacted the target + var/impact_y + /// Turf of the last atom we've impacted + VAR_FINAL/turf/last_impact_turf = null - //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! + /// If the projectile was fired already + var/fired = FALSE + /// If the projectile is suspended mid-air + var/paused = FALSE + /// Last time the projectile moved, used for lag compensation if SSprojectiles starts chugging + VAR_PRIVATE/last_projectile_move = 0 + /// Last time the projectile was processed, also used for lag compensation + VAR_PRIVATE/last_process = 0 + /// How many pixels we missed last tick due to lag or speed cap + VAR_PRIVATE/overrun = 0 + /// Projectile's movement vector - this caches sine/cosine of our angle to cut down on trig calculations + var/datum/vector/movement_vector /// We already impacted these things, do not impact them again. Used to make sure we can pierce things we want to pierce. Lazylist, typecache style (object = TRUE) for performance. var/list/impacted = list() /// If TRUE, we can hit our firer. @@ -66,38 +93,34 @@ var/projectile_phasing = NONE /// Bitflag for things the projectile should hit, but pierce through without deleting itself. Defers to projectile_phasing. Uses pass_flags flags. var/projectile_piercing = NONE - /// number of times we've pierced something. Incremented BEFORE bullet_act and on_hit proc! + /// Number of times we've pierced something. Incremented BEFORE bullet_act and on_hit proc! var/pierces = 0 - /// how many times this projectile can pierce something before deleting + /// How many times this projectile can pierce something before deleting var/max_pierces = 0 /// If objects are below this layer, we pass through them var/hit_threshhold = PROJECTILE_HIT_THRESHHOLD_LAYER - /// During each fire of SSprojectiles, the number of deciseconds since the last fire of SSprojectiles - /// is divided by this var, and the result truncated to the next lowest integer is - /// the number of times the projectile's `pixel_move` proc will be called. - var/speed = 0.8 - - /// This var is multiplied by SSprojectiles.global_pixel_speed to get how many pixels - /// the projectile moves during each iteration of the movement loop - /// - /// If you want to make a fast-moving projectile, you should keep this equal to 1 and - /// reduce the value of `speed`. If you want to make a slow-moving projectile, make - /// `speed` a modest value like 1 and set this to a low value like 0.2. - var/pixel_speed_multiplier = 1 + /// How many tiles we pass in a single SSprojectiles tick + var/speed = 1.25 /// The current angle of the projectile. Initially null, so if the arg is missing from [/fire()], we can calculate it from firer and target as fallback. var/angle - var/original_angle = 0 //Angle at firing - var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle - var/spread = 0 //amount (in degrees) of projectile spread - animate_movement = NO_STEPS //Use SLIDE_STEPS in conjunction with legacy - /// how many times we've ricochet'd so far (instance variable, not a stat) + /// Angle at the moment of firing + var/original_angle = 0 + /// Set TRUE to prevent projectiles from having their sprites rotated based on firing angle + var/nondirectional_sprite = FALSE + /// Random spread done projectile-side for convinience + var/spread = 0 + /// Gliding does not enjoy something getting moved multiple turfs in a tick, which is why we animate it manually + animate_movement = NO_STEPS + + // Ricochet logic + /// How many times we've ricochet'd so far (instance variable, not a stat) var/ricochets = 0 - /// how many times we can ricochet max + /// How many times we can ricochet max var/ricochets_max = 0 - /// how many times we have to ricochet min (unless we hit an atom we can ricochet off) + /// How many times we have to ricochet min (unless we hit an atom we can ricochet off) var/min_ricochets = 0 /// 0-100 (or more, I guess), the base chance of ricocheting, before being modified by the atom we shoot and our chance decay var/ricochet_chance = 0 @@ -114,18 +137,22 @@ /// Can our ricochet autoaim hit our firer? var/ricochet_shoots_firer = TRUE - ///If the object being hit can pass the damage on to something else, it should not do it for this bullet - var/force_hit = FALSE + // Hitscan logic + /// Wherever this projectile is hitscan. Hitscan projectiles are processed until the end of their path instantly upon being fired and leave a tracer in their path + var/hitscan = FALSE + /// Associated list of coordinate points in which we changed trajectories in order to calculate hitscan tracers + /// Value points to the next point in the beam + var/list/datum/point/beam_points + /// Last point in the beam + var/datum/point/last_point + /// Next forceMove will not create tracer end/start effects + var/free_hitscan_forceMove = 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 or datum/point/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/beam_index - var/turf/hitscan_last //last turf touched during hitscanning. + /// Hitscan tracer effect left behind the projectile var/tracer_type + /// Hitscan muzzle effect spawned on the firer var/muzzle_type + /// Hitscan impact effect spawned on the target var/impact_type //Fancy hitscan lighting effects! @@ -139,11 +166,16 @@ var/impact_light_range = 2 var/impact_light_color_override - //Homing + // Homing + /// If the projectile is homing. Warning - this changes projectile's processing logic, reverting it to segmented processing instead of new raymarching logic var/homing = FALSE + /// Target the projectile is homing on var/atom/homing_target - var/homing_turn_speed = 10 //Angle per tick. - var/homing_inaccuracy_min = 0 //in pixels for these. offsets are set once when setting target. + /// Angles per move segment, distance is based on SSprojectiles.pixels_per_decisecond + /// With pixels_per_decisecond set to 16 and homing_turn_speed, the projectile can turn up to 20 pixels per turf passed + var/homing_turn_speed = 10 + // Allowed leeway in pixels + var/homing_inaccuracy_min = 0 var/homing_inaccuracy_max = 0 var/homing_offset_x = 0 var/homing_offset_y = 0 @@ -151,17 +183,20 @@ var/damage = 10 var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY are the only things that should be in here - ///Defines what armor to use when it hits things. Must be set to bullet, laser, energy, or bomb + /// Defines what armor to use when it hits things. Must be set to bullet, laser, energy, or bomb var/armor_flag = BULLET - ///How much armor this projectile pierces. + /// How much armor this projectile pierces. var/armour_penetration = 0 - ///Whether or not our projectile doubles the value of affecting armour + /// Whether or not our projectile doubles the value of affecting armour var/weak_against_armour = FALSE - var/projectile_type = /obj/projectile - var/range = 50 //This will de-increment every step. When 0, it will deletze the projectile. - var/decayedRange //stores original range - var/reflect_range_decrease = 5 //amount of original range that falls off when reflecting, so it doesn't go forever - var/reflectable = NONE // Can it be reflected or not? + /// This will de-increment every step. When 0, it will delete the projectile. + var/range = 50 + /// Original range upon being fired/reflected + var/maximum_range + /// Amount of original range that falls off when reflecting, so it doesn't go forever + var/reflect_range_decrease = 5 + /// If this projectile can be reflected + var/reflectable = FALSE // Status effects applied on hit var/stun = 0 SECONDS @@ -182,59 +217,86 @@ /// Slurring applied on projectile hit var/slur = 0 SECONDS - var/dismemberment = 0 //The higher the number, the greater the bonus to dismembering. 0 will not dismember at all. - var/catastropic_dismemberment = FALSE //If TRUE, this projectile deals its damage to the chest if it dismembers a limb. - var/impact_effect_type //what type of impact effect to show when hitting something - var/log_override = FALSE //is this type spammed enough to not log? (KAs) - /// If true, the projectile won't cause any logging. Used for hallucinations and shit. + /// Damage the limb must have for it to be dismembered upon getting hit. 0 will prevent dismembering altogether + var/dismemberment = 0 + /// If TRUE, this projectile deals its damage to the chest if it dismembers a limb. + var/catastropic_dismemberment = FALSE + /// Impact VFX created upon hitting something + var/impact_effect_type + /// If the act of firing this projectile does not create logs + var/log_override = FALSE + /// If true, the projectile won't cause any logging whatsoever. Used for hallucinations and shit. var/do_not_log = FALSE /// We ignore mobs with these factions. var/list/ignored_factions + /// Turf that we have registered connect_loc signal - this is done for performance, as we're moving ~a dozen turfs per tick + /// and registering and unregistering signal for every single one of them is stupid. Unregistering the signal from the correct turf in case we get moved by smth else is important + var/turf/last_tick_turf + /// Remaining pixel movement last tick - used for precise range calculations + var/pixels_moved_last_tile = 0 + /// In order to preserve animations, projectiles are only deleted the tick *after* they impact something. + /// Same is applied to reaching the range limit + var/deletion_queued = NONE + /// How many ticks should we wait in queued deletion mode before qdeleting? Sometimes increased in animations + var/ticks_to_deletion = 1 - ///If defined, on hit we create an item of this type then call hitby() on the hit target with this, mainly used for embedding items (bullets) in targets + /// If defined, on hit we create an item of this type then call hitby() on the hit target with this, mainly used for embedding items (bullets) in targets var/shrapnel_type - ///If we have a shrapnel_type defined, these embedding stats will be passed to the spawned shrapnel type, which will roll for embedding on the target + /// If we have a shrapnel_type defined, these embedding stats will be passed to the spawned shrapnel type, which will roll for embedding on the target var/embed_type - ///Saves embedding data + /// Saves embedding data var/datum/embed_data/embed_data - ///If TRUE, hit mobs, even if they are lying on the floor and are not our target within MAX_RANGE_HIT_PRONE_TARGETS tiles + /// If TRUE, hit mobs, even if they are lying on the floor and are not our target within MAX_RANGE_HIT_PRONE_TARGETS tiles var/hit_prone_targets = FALSE - ///if TRUE, ignores the range of MAX_RANGE_HIT_PRONE_TARGETS tiles of hit_prone_targets + /// If TRUE, ignores the range of MAX_RANGE_HIT_PRONE_TARGETS tiles of hit_prone_targets var/ignore_range_hit_prone_targets = FALSE - ///For what kind of brute wounds we're rolling for, if we're doing such a thing. Lasers obviously don't care since they do burn instead. + /// For what kind of brute wounds we're rolling for, if we're doing such a thing. Lasers obviously don't care since they do burn instead. var/sharpness = NONE - ///How much we want to drop damage per tile as it travels through the air + /// How much we want to drop damage per tile as it travels through the air var/damage_falloff_tile - ///How much we want to drop stamina damage (defined by the stamina variable) per tile as it travels through the air + /// How much we want to drop stamina damage (defined by the stamina variable) per tile as it travels through the air var/stamina_falloff_tile - ///How much we want to drop both wound_bonus and bare_wound_bonus (to a minimum of 0 for the latter) per tile, for falloff purposes + /// How much we want to drop both wound_bonus and bare_wound_bonus (to a minimum of 0 for the latter) per tile, for falloff purposes var/wound_falloff_tile - ///How much we want to drop the embed_chance value, if we can embed, per tile, for falloff purposes + /// How much we want to drop the embed_chance value, if we can embed, per tile, for falloff purposes var/embed_falloff_tile - ///How much accuracy is lost for each tile travelled + /// How much accuracy is lost for each tile travelled var/accuracy_falloff = 7 - ///How much accuracy before falloff starts to matter. Formula is range - falloff * tiles travelled + /// How much accuracy before falloff starts to matter. Formula is range - falloff * tiles travelled var/accurate_range = 100 - var/static/list/projectile_connections = list(COMSIG_ATOM_ENTERED = PROC_REF(on_entered)) /// If true directly targeted turfs can be hit var/can_hit_turfs = FALSE /obj/projectile/Initialize(mapload) . = ..() - decayedRange = range - if(get_embed()) + maximum_range = range + if (get_embed()) AddElement(/datum/element/embed) - AddElement(/datum/element/connect_loc, projectile_connections) - add_traits(list(TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT), INNATE_TRAIT) -/obj/projectile/proc/Range() +/obj/projectile/Destroy() + if (hitscan) + generate_hitscan_tracers() + STOP_PROCESSING(SSprojectiles, src) + firer = null + original = null + if (movement_vector) + QDEL_NULL(movement_vector) + if (beam_points) + QDEL_LIST(beam_points) + if (last_point) + QDEL_NULL(last_point) + return ..() + +/// Called every time a projectile passes one tile worth of movement +/obj/projectile/proc/reduce_range() range-- - if(wound_bonus != CANT_WOUND) + pixels_moved_last_tile -= ICON_SIZE_ALL + if(wound_falloff_tile && wound_bonus != CANT_WOUND) wound_bonus += wound_falloff_tile bare_wound_bonus = max(0, bare_wound_bonus + wound_falloff_tile) - if(get_embed()) - set_embed(embed_data.generate_with_values(embed_data.embed_chance + embed_falloff_tile)) // Should be rewritten in projecitle refactor + if(embed_falloff_tile && get_embed()) + set_embed(embed_data.generate_with_values(embed_data.embed_chance + embed_falloff_tile)) if(damage_falloff_tile && damage >= 0) damage += damage_falloff_tile if(stamina_falloff_tile && stamina >= 0) @@ -242,26 +304,22 @@ SEND_SIGNAL(src, COMSIG_PROJECTILE_RANGE) if(range <= 0 && loc) - on_range() + if (hitscan) + qdel(src) + return + deletion_queued = PROJECTILE_RANGE_DELETE if(damage_falloff_tile && damage <= 0 || stamina_falloff_tile && stamina <= 0) - on_range() + if (hitscan) + qdel(src) + return + deletion_queued = PROJECTILE_RANGE_DELETE -/obj/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range +/// Called next tick after the projectile reaches its maximum range so the animation has time to fully play out +/obj/projectile/proc/on_range() SEND_SIGNAL(src, COMSIG_PROJECTILE_RANGE_OUT) qdel(src) -/// Returns the string form of the def_zone we have hit. -/mob/living/proc/check_hit_limb_zone_name(hit_zone) - if(has_limbs) - return hit_zone - -/mob/living/carbon/check_hit_limb_zone_name(hit_zone) - if(get_bodypart(hit_zone)) - return hit_zone - else //when a limb is missing the damage is actually passed to the chest - return BODY_ZONE_CHEST - /** * Called when the projectile hits something * @@ -289,21 +347,19 @@ hit_limb_zone = victim.check_hit_limb_zone_name(def_zone) if(fired_from) - SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, angle, hit_limb_zone, blocked) - SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, angle, hit_limb_zone, blocked) + SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, angle, hit_limb_zone, blocked, pierce_hit) + SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, angle, hit_limb_zone, blocked, pierce_hit) - if(QDELETED(src)) // in case one of the above signals deleted the projectile for whatever reason + if(QDELETED(src) || deletion_queued) // in case one of the above signals deleted the projectile for whatever reason return BULLET_ACT_BLOCK var/turf/target_turf = get_turf(target) - var/hitx - var/hity if(target == original) - hitx = target.pixel_x + p_x - 16 - hity = target.pixel_y + p_y - 16 + impact_x = target.pixel_x + p_x - ICON_SIZE_X / 2 + impact_y = target.pixel_y + p_y - ICON_SIZE_Y / 2 else - hitx = target.pixel_x + rand(-8, 8) - hity = target.pixel_y + rand(-8, 8) + impact_x = entry_x + movement_vector?.pixel_x * rand(0, ICON_SIZE_X / 2) + impact_y = entry_y + movement_vector?.pixel_y * rand(0, ICON_SIZE_Y / 2) if(isturf(target) && hitsound_wall) playsound(src, hitsound_wall, clamp(vol_by_damage() + (suppressed ? 0 : 20), 0, 100), TRUE, -1) @@ -311,9 +367,9 @@ if(damage > 0 && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_turf) && prob(75)) var/turf/closed/wall/target_wall = target_turf if(impact_effect_type && !hitscan) - new impact_effect_type(target_wall, hitx, hity) + new impact_effect_type(target_wall, impact_x, impact_y) - target_wall.add_dent(WALL_DENT_SHOT, hitx, hity) + target_wall.add_dent(WALL_DENT_SHOT, impact_x, impact_y) return BULLET_ACT_HIT if (hitsound) @@ -321,11 +377,11 @@ if (!isliving(target)) if(impact_effect_type && !hitscan) - new impact_effect_type(target_turf, hitx, hity) + new impact_effect_type(target_turf, impact_x, impact_y) return BULLET_ACT_HIT if((blocked >= 100 || (damage && damage_type != BRUTE)) && impact_effect_type && !hitscan) - new impact_effect_type(target_turf, hitx, hity) + new impact_effect_type(target_turf, impact_x, impact_y) var/mob/living/living_target = target var/reagent_note @@ -353,9 +409,20 @@ /obj/projectile/proc/vol_by_damage() if (suppressed) return 5 - if(!damage) + if (!damage) return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume - return clamp(damage * 0.67, 30, 100)// Multiply projectile damage by 0.67, then CLAMP the value between 30 and 1 + return clamp(damage * 0.67, 30, 100) // Multiply projectile damage by 0.67, then CLAMP the value between 30 and 1 + +/obj/projectile/proc/firer_deleted(datum/source) + SIGNAL_HANDLER + // Shooting yourself point-blank + if (firer == original) + original = null + firer = null + +/obj/projectile/proc/original_deleted(datum/source) + SIGNAL_HANDLER + original = null /obj/projectile/proc/on_ricochet(atom/target) ricochets++ @@ -378,16 +445,10 @@ set_angle(get_angle(src, unlucky_sob.loc)) original = unlucky_sob -/obj/projectile/proc/store_hitscan_collision(datum/point/point_cache) - beam_segments[beam_index] = point_cache - beam_index = point_cache - beam_segments[beam_index] = null - /obj/projectile/Bump(atom/bumped_atom) SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, bumped_atom) - if(!can_hit_target(bumped_atom, bumped_atom == original, TRUE, TRUE)) - return - impact(bumped_atom) + if (can_hit_target(bumped_atom, bumped_atom == original, TRUE, TRUE)) + impact(bumped_atom) /** * Called when the projectile hits something @@ -401,44 +462,58 @@ * Also, we select_target to find what to process_hit first. */ /obj/projectile/proc/impact(atom/target) - if(impacted[target.weak_reference]) // never doublehit, otherwise someone may end up running into a projectile from the back + // Don't impact anything if we've been queued for deletion + if (deletion_queued) + return + + // never doublehit, otherwise someone may end up running into a projectile from the back + if(impacted[target.weak_reference]) return if(ricochets < ricochets_max && check_ricochet_flag(target) && check_ricochet(target) && target.handle_ricochet(src)) on_ricochet(target) impacted = list() // Shoot a x-ray laser at a pair of mirrors I dare you ignore_source_check = TRUE // Firer is no longer immune - decayedRange = max(0, decayedRange - reflect_range_decrease) + maximum_range = max(0, maximum_range - reflect_range_decrease) ricochet_chance *= ricochet_decay_chance damage *= ricochet_decay_damage stamina *= ricochet_decay_damage - range = decayedRange - if(hitscan && trajectory) - store_hitscan_collision(trajectory.copy_to()) + range = maximum_range return - var/turf/target_turf = get_turf(target) + last_impact_turf = get_turf(target) // Lower accurancy/longer range tradeoff. 7 is a balanced number to use. - def_zone = ran_zone(def_zone, clamp(accurate_range - (accuracy_falloff * get_dist(target_turf, starting)), 5, 100)) - process_hit_loop(select_target(target_turf, target)) + def_zone = ran_zone(def_zone, clamp(accurate_range - (accuracy_falloff * get_dist(last_impact_turf, starting)), 5, 100)) + var/impact_result = process_hit_loop(select_target(last_impact_turf, target)) + if (impact_result == PROJECTILE_IMPACT_PASSED) + return + if (hitscan) + qdel(src) + return + deletion_queued = PROJECTILE_IMPACT_DELETE /* * Main projectile hit loop code * As long as there are valid targets on the hit target's tile, we will loop through all the ones that we have not hit * (and thus invalidated) and try to hit them until either no targets remain or we've been deleted. + * Should *never* be called directly, as impact() is the proc queueing projectiles for deletion + * If you need to call this directly, you should reconsider the choices that led you to this point */ /obj/projectile/proc/process_hit_loop(atom/target) SHOULD_NOT_SLEEP(TRUE) - SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + + // Don't impact anything if we've been queued for deletion + if (deletion_queued) + return PROJECTILE_IMPACT_PASSED var/turf/target_turf = get_turf(target) - while (target && !QDELETED(src)) + while (target && !QDELETED(src) && !deletion_queued) // Doublehitting can be an issue with slow projectiles or when the server is chugging impacted[WEAKREF(target)] = TRUE var/mode = prehit_pierce(target) if(mode == PROJECTILE_DELETE_WITHOUT_HITTING) - qdel(src) - return + return PROJECTILE_IMPACT_INTERRUPTED // If we've phasing through a target, first set ourselves as phasing and then try to locate a new one if(mode == PROJECTILE_PIERCE_PHASE) @@ -449,12 +524,10 @@ continue if (SEND_SIGNAL(target, COMSIG_PROJECTILE_PREHIT, src) & PROJECTILE_INTERRUPT_HIT) - qdel(src) - return + return PROJECTILE_IMPACT_INTERRUPTED if (SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_PREHIT, target) & PROJECTILE_INTERRUPT_HIT) - qdel(src) - return + return PROJECTILE_IMPACT_INTERRUPTED if(mode == PROJECTILE_PIERCE_HIT) pierces += 1 @@ -462,13 +535,11 @@ // Targets should handle their impact logic on our own and if they decide that we hit them, they call our on_hit var/result = target.bullet_act(src, def_zone, mode == PROJECTILE_PIERCE_HIT) if (result != BULLET_ACT_FORCE_PIERCE && max_pierces && pierces >= max_pierces) - qdel(src) - return + return PROJECTILE_IMPACT_SUCCESSFUL // If we're not piercing or phasing, delete ourselves if (result != BULLET_ACT_FORCE_PIERCE && mode != PROJECTILE_PIERCE_HIT && mode != PROJECTILE_PIERCE_PHASE) - qdel(src) - return + return PROJECTILE_IMPACT_SUCCESSFUL // We've piercing though this one, go look for a new target if(!(movement_type & PHASING)) @@ -477,13 +548,14 @@ target = select_target(target_turf, target) + return PROJECTILE_IMPACT_PASSED + /** * Selects a target to hit from a turf * * @params - * T - The turf - * target - The "preferred" atom to hit, usually what we Bumped() first. - * bumped - used to track if something is the reason we impacted in the first place. + * our_turf - Turf on which we hit the target + * bumped - What we've impacted and why this selection was called in the first place. * If set, this atom is always treated as dense by can_hit_target. * * Priority: @@ -523,8 +595,8 @@ // 6. nothing // (returns null) -//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. +/// 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, direct_target = FALSE, ignore_loc = FALSE, cross_failed = FALSE) if(QDELETED(target) || impacted[target.weak_reference]) return FALSE @@ -568,7 +640,7 @@ return FALSE if(hit_prone_targets) var/mob/living/buckled_to = living_target.lowest_buckled_mob() - if((decayedRange - range) <= MAX_RANGE_HIT_PRONE_TARGETS) // after MAX_RANGE_HIT_PRONE_TARGETS tiles, auto-aim hit for mobs on the floor turns off + if((maximum_range - range) <= MAX_RANGE_HIT_PRONE_TARGETS) // after MAX_RANGE_HIT_PRONE_TARGETS tiles, auto-aim hit for mobs on the floor turns off return TRUE if(ignore_range_hit_prone_targets) // doesn't apply to projectiles that must hit the target in combat mode or something else, no matter what return TRUE @@ -579,19 +651,6 @@ return FALSE return TRUE -/** - * Scan if we should hit something and hit it if we need to - * The difference between this and handling in impact is - * In this we strictly check if we need to impact() something in specific - * If we do, we do - * We don't even check if it got hit already - impact() does that - * In impact there's more code for selecting WHAT to hit - * So this proc is more of checking if we should hit something at all BY having an atom cross us. - */ -/obj/projectile/proc/scan_crossed_hit(atom/movable/crossed_atom) - if(can_hit_target(crossed_atom, direct_target = (crossed_atom == original))) - impact(crossed_atom) - /** * Scans if we should hit something on the turf we just moved to if we haven't already * @@ -619,14 +678,15 @@ */ /obj/projectile/proc/on_entered(datum/source, atom/movable/entered_atom) SIGNAL_HANDLER - scan_crossed_hit(entered_atom) + if(can_hit_target(entered_atom, direct_target = (entered_atom == original))) + impact(entered_atom) /** * Projectile can pass through * Used to not even attempt to Bump() or fail to Cross() anything we already hit. */ /obj/projectile/CanPassThrough(atom/blocker, movement_dir, blocker_opinion) - return ..() || impacted[blocker.weak_reference] + return impacted[blocker.weak_reference] || ..() /** * Projectile moved: @@ -644,7 +704,8 @@ if(temporary_unstoppable_movement) temporary_unstoppable_movement = FALSE movement_type &= ~PHASING - scan_moved_turf() //mostly used for making sure we can hit a non-dense object the user directly clicked on, and for penetrating projectiles that don't bump + // Mostly used for making sure we can hit a non-dense object the user directly clicked on, and for penetrating projectiles that don't bump + scan_moved_turf() /** * Checks if we should pierce something. @@ -674,257 +735,64 @@ /obj/projectile/proc/check_ricochet_flag(atom/target) if((armor_flag in list(ENERGY, LASER)) && (target.flags_ricochet & RICOCHET_SHINY)) return TRUE - if((armor_flag in list(BOMB, BULLET)) && (target.flags_ricochet & RICOCHET_HARD)) return TRUE - return FALSE -/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 get_line(current, ending) - /obj/projectile/Process_Spacemove(movement_dir = 0, continuous_move = FALSE) return TRUE //Bullets don't drift in space -/obj/projectile/process() - last_process = world.time - if(!loc || !fired || !trajectory) - fired = FALSE - return PROCESS_KILL - if(paused || !isturf(loc)) - last_projectile_move += world.time - last_process //Compensates for pausing, so it doesn't become a hitscan projectile when unpaused from charged up ticks. - return - var/elapsed_time_deciseconds = (world.time - last_projectile_move) + time_offset - time_offset = 0 - var/required_moves = speed > 0? FLOOR(elapsed_time_deciseconds / speed, 1) : MOVES_HITSCAN //Would be better if a 0 speed made hitscan but everyone hates those so I can't make it a universal system :< - if(required_moves == MOVES_HITSCAN) - required_moves = SSprojectiles.global_max_tick_moves - else - if(required_moves > SSprojectiles.global_max_tick_moves) - var/overrun = required_moves - SSprojectiles.global_max_tick_moves - required_moves = SSprojectiles.global_max_tick_moves - time_offset += overrun * speed - time_offset += MODULUS(elapsed_time_deciseconds, speed) - SEND_SIGNAL(src, COMSIG_PROJECTILE_BEFORE_MOVE) - for(var/i in 1 to required_moves) - pixel_move(pixel_speed_multiplier, FALSE) - /obj/projectile/proc/fire(fire_angle, atom/direct_target) LAZYINITLIST(impacted) - if(fired_from) + if (fired_from) SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original) - if(firer) + if (firer) RegisterSignal(firer, COMSIG_QDELETING, PROC_REF(firer_deleted)) SEND_SIGNAL(firer, COMSIG_PROJECTILE_FIRER_BEFORE_FIRE, src, fired_from, original) if (original) if (firer != original) RegisterSignal(original, COMSIG_QDELETING, PROC_REF(original_deleted)) - if(!log_override && firer && original && !do_not_log) + if (!log_override && firer && original && !do_not_log) log_combat(firer, original, "fired at", src, "from [get_area_name(src, TRUE)]") //note: mecha projectile logging is handled in /obj/item/mecha_parts/mecha_equipment/weapon/action(). try to keep these messages roughly the sameish just for consistency's sake. - if(direct_target && (get_dist(direct_target, get_turf(src)) <= 1)) // point blank shots - process_hit_loop(direct_target) - if(QDELETED(src)) + if (direct_target && (get_dist(direct_target, get_turf(src)) <= 1)) // point blank shots + impact(direct_target) + if (QDELETED(src)) return var/turf/starting = get_turf(src) - if(isnum(fire_angle)) + if (isnum(fire_angle)) set_angle(fire_angle) - else if(isnull(angle)) //Try to resolve through offsets if there's no angle set. - if(isnull(xo) || isnull(yo)) + else 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) set_angle(get_angle(src, target)) - if(spread) + if (spread) set_angle(angle + (rand() - 0.5) * spread) original_angle = angle - trajectory_ignore_forcemove = TRUE + movement_vector = new(speed, angle) + if (hitscan) + beam_points = list() + free_hitscan_forceMove = TRUE forceMove(starting) - trajectory_ignore_forcemove = FALSE - trajectory = new(starting.x, starting.y, starting.z, pixel_x, pixel_y, angle, SSprojectiles.global_pixel_speed) last_projectile_move = world.time fired = TRUE play_fov_effect(starting, 6, "gunfire", dir = NORTH, angle = angle) SEND_SIGNAL(src, COMSIG_PROJECTILE_FIRE) - if(hitscan) + if (hitscan && !deletion_queued) + record_hitscan_start() process_hitscan() - if(QDELETED(src)) + if (QDELETED(src)) return - if(!(datum_flags & DF_ISPROCESSING)) + if (!(datum_flags & DF_ISPROCESSING)) START_PROCESSING(SSprojectiles, src) - pixel_move(pixel_speed_multiplier, FALSE) //move it now! - -/obj/projectile/proc/set_angle(new_angle) //wrapper for overrides. - if(!nondirectional_sprite) - transform = transform.TurnTo(angle, new_angle) - angle = new_angle - if(trajectory) - trajectory.set_angle(new_angle) - if(fired && hitscan && isloc(loc) && (loc != last_angle_set_hitscan_store)) - last_angle_set_hitscan_store = loc - var/datum/point/point_cache = new (src) - point_cache = trajectory.copy_to() - store_hitscan_collision(point_cache) - return TRUE - -/obj/projectile/proc/firer_deleted(datum/source) - SIGNAL_HANDLER - // Shooting yourself point-blank - if (firer == original) - original = null - firer = null - -/obj/projectile/proc/original_deleted(datum/source) - SIGNAL_HANDLER - original = null - -/// Same as set_angle, but the reflection continues from the center of the object that reflects it instead of the side -/obj/projectile/proc/set_angle_centered(new_angle) - if(!nondirectional_sprite) - transform = transform.TurnTo(angle, new_angle) - angle = new_angle - if(trajectory) - trajectory.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(fired && hitscan && isloc(loc) && (loc != last_angle_set_hitscan_store)) // Handles hitscan projectiles - last_angle_set_hitscan_store = loc - var/datum/point/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/projectile/forceMove(atom/target) - if(!isloc(target) || !isloc(loc) || !z) - return ..() - var/zc = target.z != z - var/old = loc - if(zc) - before_z_change(old, target) - . = ..() - if(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)) - if(zc) - after_z_change(old, target) - -/obj/projectile/proc/after_z_change(atom/olcloc, atom/newloc) - return - -/obj/projectile/proc/before_z_change(atom/oldloc, atom/newloc) - return - -/obj/projectile/vv_edit_var(var_name, var_value) - switch(var_name) - if(NAMEOF(src, angle)) - set_angle(var_value) - return TRUE - else - return ..() - -/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/point_cache) - if(point_cache) - beam_segments = list() - beam_index = point_cache - beam_segments[beam_index] = null //record start. - -/obj/projectile/proc/process_hitscan() - 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) - // No kevinz I do not care that this is a hitscan weapon, it is not allowed to travel 100 turfs in a tick - if(CHECK_TICK && QDELETED(src)) - return - -/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/cur_turf = trajectory.return_turf() - if(!istype(cur_turf)) - // step back to the last valid turf before we Destroy - trajectory.increment(-trajectory_multiplier) - qdel(src) - return - if (cur_turf == loc) - continue - if (cur_turf.z == loc.z) - step_towards(src, cur_turf) - hitscan_last = loc - SEND_SIGNAL(src, COMSIG_PROJECTILE_PIXEL_STEP) - continue - var/old = loc - before_z_change(loc, cur_turf) - trajectory_ignore_forcemove = TRUE - forceMove(cur_turf) - 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 - SEND_SIGNAL(src, COMSIG_PROJECTILE_PIXEL_STEP) - if(QDELETED(src)) //deleted on last move - return - if(!hitscanning && !forcemoved) - pixel_x = trajectory.return_px() - trajectory.mpx * trajectory_multiplier * SSprojectiles.global_iterations_per_move - pixel_y = trajectory.return_py() - trajectory.mpy * trajectory_multiplier * SSprojectiles.global_iterations_per_move - animate(src, pixel_x = trajectory.return_px(), pixel_y = trajectory.return_py(), time = 1, flags = ANIMATION_END_NOW) - Range() - -/obj/projectile/proc/process_homing() //may need speeding up in the future performance wise. - if(!homing_target) - return FALSE - var/datum/point/new_point = RETURN_PRECISE_POINT(homing_target) - new_point.x += clamp(homing_offset_x, 1, world.maxx) - new_point.y += clamp(homing_offset_y, 1, world.maxy) - var/new_angle = closer_angle_difference(angle, angle_between_points(RETURN_PRECISE_POINT(src), new_point)) - set_angle(angle + clamp(new_angle, -homing_turn_speed, homing_turn_speed)) + // move it now to avoid potentially hitting yourself with firer-hitting projectiles + if (!deletion_queued && !hitscan) + process_movement(max(FLOOR(speed, 1), 1), tile_limit = TRUE) +/// Makes projectile home onto the passed target with minor inaccuracy /obj/projectile/proc/set_homing_target(atom/target) if(!target || (!isturf(target) && !isturf(target.loc))) return FALSE @@ -937,6 +805,369 @@ if(prob(50)) homing_offset_y = -homing_offset_y +/obj/projectile/proc/set_angle(new_angle) + if (angle == new_angle) + return + if(!nondirectional_sprite) + transform = transform.TurnTo(angle, new_angle) + angle = new_angle + if(movement_vector) + movement_vector.set_angle(new_angle) + if(fired && hitscan && isturf(loc)) + create_hitscan_point() + +/// Same as set_angle, but the reflection continues from the center of the object that reflects it instead of the side +/obj/projectile/proc/set_angle_centered(center_turf, new_angle) + if (angle == new_angle) + return + if(!nondirectional_sprite) + transform = transform.TurnTo(angle, new_angle) + free_hitscan_forceMove = TRUE + forceMove(center_turf) + entry_x = 0 + entry_y = 0 + angle = new_angle + if(movement_vector) + movement_vector.set_angle(new_angle) + if(fired && hitscan && isturf(loc)) + create_hitscan_point(tile_center = TRUE) + +/obj/projectile/vv_edit_var(var_name, var_value) + if(var_name == NAMEOF(src, angle)) + set_angle(var_value) + return TRUE + return ..() + +/* + * Projectile's process calculates the amount of pixels that it needs to move per tick and calls moveloop processing + * There is a strict cap on how many pixels it can move in a tick to prevent them from turning into hitscans during lag + * Path that the projectile could not finish would be stored in the overrun variable to be processed next tick + */ + +/obj/projectile/process() + last_process = world.time + if(!loc || !fired || !movement_vector) + fired = FALSE + return PROCESS_KILL + + // If last tick the projectile impacted something or reached its range, don't process it + if (deletion_queued == PROJECTILE_IMPACT_DELETE) + ticks_to_deletion -= 1 + if (!ticks_to_deletion) + qdel(src) + return + + if (deletion_queued == PROJECTILE_RANGE_DELETE) + on_range() + return + + if(paused || !isturf(loc)) + // Compensates for pausing, so it doesn't become a hitscan projectile when unpaused from charged up ticks. + last_projectile_move = last_process + return + + if (hitscan) + process_hitscan() + return + + // Calculates how many pixels should be moved this tick, including overrun debt from the previous tick + var/elapsed_time = world.time - last_projectile_move + var/pixels_to_move = elapsed_time * SSprojectiles.pixels_per_decisecond * speed + overrun + overrun = 0 + + if (pixels_to_move > SSprojectiles.max_pixels_per_tick) + overrun = pixels_to_move - SSprojectiles.max_pixels_per_tick + pixels_to_move = SSprojectiles.max_pixels_per_tick + + overrun += MODULUS(pixels_to_move, 1) + pixels_to_move = FLOOR(pixels_to_move, 1) + SEND_SIGNAL(src, COMSIG_PROJECTILE_BEFORE_MOVE) + + // Registering turf entries is done here instead of a connect_loc because else it could be called multiple times per tick and waste performance + if (last_tick_turf) + UnregisterSignal(last_tick_turf, COMSIG_ATOM_ENTERED) + + process_movement(pixels_to_move) + + if (!QDELETED(src) && !deletion_queued && isturf(loc)) + RegisterSignal(loc, COMSIG_ATOM_ENTERED, PROC_REF(on_entered)) + last_tick_turf = loc + +/* + * Main projectile movement cycle. + * Normal behavior moves projectiles in a straight line through tiles, but it gets trickier with homing. + * Every pixels_per_decisecond we will stop and call process_homing(), which while a bit rough, does not have a significant performance impact + * This proc needs to be very performant, so do not add overridable logic that can be handled in homing or animations here. + * + * pixels_to_move determines how many pixels the projectile should move + * hitscan prevents animation logic from running + * tile_limit prevents any movements past the first tile change + */ +/obj/projectile/proc/process_movement(pixels_to_move, hitscan = FALSE, tile_limit = FALSE) + if (!isturf(loc) || !movement_vector) + return + var/total_move_distance = pixels_to_move + last_projectile_move = world.time + while (pixels_to_move > 0 && isturf(loc) && !QDELETED(src) && !deletion_queued) + // Because pixel_x/y represents offset and not actual visual position of the projectile, we add 16 pixels to each and cut the excess because projectiles are not meant to be highly offset by default + var/pixel_x_actual = pixel_x + ICON_SIZE_X / 2 + if(pixel_x_actual > ICON_SIZE_X) + pixel_x_actual = pixel_x_actual % ICON_SIZE_X + + var/pixel_y_actual = pixel_y + ICON_SIZE_Y / 2 + if(pixel_y_actual > ICON_SIZE_Y) + pixel_y_actual = pixel_y_actual % ICON_SIZE_Y + + var/distance_to_border = INFINITY + // What distances do we need to move to hit the horizontal/vertical turf border + var/x_to_border = INFINITY + var/y_to_border = INFINITY + // If we're moving strictly up/down/left/right then one of these can be 0 and produce div by zero + if (movement_vector.pixel_x) + var/x_border_dist = -pixel_x_actual + if (movement_vector.pixel_x > 0) + x_border_dist = ICON_SIZE_X - pixel_x_actual + x_to_border = x_border_dist / movement_vector.pixel_x + distance_to_border = x_to_border + + if (movement_vector.pixel_y) + var/y_border_dist = -pixel_y_actual + if (movement_vector.pixel_y > 0) + y_border_dist = ICON_SIZE_Y - pixel_y_actual + y_to_border = y_border_dist / movement_vector.pixel_y + distance_to_border = min(distance_to_border, y_to_border) + + // Something went extremely wrong + if (distance_to_border == INFINITY) + stack_trace("WARNING: Projectile had an empty movement vector and tried to process") + qdel(src) + return + + var/distance_to_move = min(distance_to_border, pixels_to_move) + // For homing we cap the maximum distance to move every loop + if (homing && distance_to_move > SSprojectiles.pixels_per_decisecond) + distance_to_move = SSprojectiles.pixels_per_decisecond + + // Figure out if we move to the next turf and if so, what its positioning relatively to us is + var/x_shift = distance_to_move >= x_to_border ? SIGN(movement_vector.pixel_x) : 0 + var/y_shift = distance_to_move >= y_to_border ? SIGN(movement_vector.pixel_y) : 0 + var/moving_turfs = x_shift || y_shift + // Calculate where in the turf we will be when we cross the edge. + // This is a projectile variable because its also used in hit VFX + entry_x = pixel_x + movement_vector.pixel_x * distance_to_move - x_shift * ICON_SIZE_X + entry_y = pixel_y + movement_vector.pixel_y * distance_to_move - y_shift * ICON_SIZE_Y + var/delete_distance = 0 + + if (moving_turfs) + var/turf/new_turf = locate(x + x_shift, y + y_shift, z) + // We've hit an invalid turf, end of a z level or smth went wrong + if (!istype(new_turf)) + qdel(src) + return + + // Move to the next tile + step_towards(src, new_turf) + SEND_SIGNAL(src, COMSIG_PROJECTILE_MOVE_PROCESS_STEP) + // We hit something and got deleted, stop the loop + if (QDELETED(src)) + return + if (loc != new_turf) + moving_turfs = FALSE + // If we've impacted something, we need to animate our movement until the actual hit + // Otherwise the projectile visually disappears slightly before the actual impact + if (deletion_queued) + // distance_to_move is how much we have to step to get to the next turf, hypotenuse is how much we need + // to move in the next turf to get from entry to impact position + delete_distance = distance_to_move + sqrt((impact_x - entry_x) ** 2 + (impact_y - entry_y) ** 2) + + // We cannot move more than one turf worth of distance per loop, so this is a safe solution + pixels_moved_last_tile += distance_to_move + if (!deletion_queued && pixels_moved_last_tile >= ICON_SIZE_ALL) + reduce_range() + if (QDELETED(src)) + return + // Similarly with range out deletion, need to calculate how many pixels we can actually move before deleting + if (deletion_queued) + delete_distance = distance_to_move - (ICON_SIZE_ALL - pixels_moved_last_tile) + + if (deletion_queued) + // We moved to the next turf first, then impacted something + // This means that we need to offset our visual position back to the previous turf, then figure out + // how much we moved on the next turf (or we didn't move at all in which case we both shifts are 0 anyways) + if (moving_turfs) + pixel_x -= x_shift * ICON_SIZE_X + pixel_y -= y_shift * ICON_SIZE_Y + + // Similarly to normal animate code, but use lowered deletion distance instead. + var/delete_x = pixel_x + movement_vector.pixel_x * delete_distance + var/delete_y = pixel_y + movement_vector.pixel_y * delete_distance + // In order to keep a consistent speed, calculate at what point between ticks we get deleted + var/animate_time = world.tick_lag * delete_distance / total_move_distance + // Sometimes we need to move *just a bit* more than we can afford this tick - in this case, delete a tick after + // so we don't disappear before impact. This shouldn't be more than 1, ever. + if (delete_distance > pixels_to_move) + ticks_to_deletion += 1 + // We can use animation chains to visually disappear between ticks. + if (!move_animate(delete_x, delete_y, animate_time, deleting = TRUE)) + animate(src, pixel_x = delete_x, pixel_y = delete_y, time = animate_time, flags = ANIMATION_PARALLEL | ANIMATION_CONTINUE) + animate(alpha = 0, time = 0, flags = ANIMATION_CONTINUE) + return + + pixels_to_move -= distance_to_move + // animate() instantly changes pixel_x/y values and just interpolates them client-side so next loop processes properly + if (hitscan) + pixel_x = entry_x + pixel_y = entry_y + else + // We need to shift back to the tile we were on before moving + pixel_x -= x_shift * ICON_SIZE_X + pixel_y -= y_shift * ICON_SIZE_Y + if (!move_animate(entry_x, entry_y)) + animate(src, pixel_x = entry_x, pixel_y = entry_y, time = world.tick_lag * distance_to_move / total_move_distance, flags = ANIMATION_PARALLEL | ANIMATION_CONTINUE) + + // Homing caps our movement speed per loop while leaving per tick speed intact, so we can just call process_homing every loop here + if (homing) + process_homing() + + // We've hit a timestop field, abort any remaining movement + if (paused) + return + + // Prevents long-range high-speed projectiles from ruining the server performance by moving 100 tiles per tick when subsystem is set to a high cap + if (TICK_CHECK) + // If we ran out of time, add whatever distance we're yet to pass to overrun debt to be processed next tick and break the loop + overrun += pixels_to_move + return + + if (tile_limit && moving_turfs) + return + +/// Called every time projectile animates its movement, in case child wants to have custom animations. +/// Returning TRUE cancels normal animation +/obj/projectile/proc/move_animate(animate_x, animate_y, animate_time = world.tick_lag, deleting = FALSE) + return FALSE + +/// Called every projectile loop for homing or alternatively, custom trajectory changes. +/obj/projectile/proc/process_homing() + if(!homing_target) + return + var/datum/point/new_point = RETURN_PRECISE_POINT(homing_target) + new_point.x += clamp(homing_offset_x, 1, world.maxx) + new_point.y += clamp(homing_offset_y, 1, world.maxy) + var/new_angle = closer_angle_difference(angle, angle_between_points(RETURN_PRECISE_POINT(src), new_point)) + set_angle(angle + clamp(new_angle, -homing_turn_speed, homing_turn_speed)) + +/// Attempts to force the projectile to move until the subsystem runs out of processing time, the projectile impacts something or gets frozen by timestop +/obj/projectile/proc/process_hitscan() + if (isnull(movement_vector)) + qdel(src) + return + + while (isturf(loc) && !QDELETED(src)) + process_movement(ICON_SIZE_ALL, hitscan = TRUE) + if (TICK_CHECK || paused || QDELETED(src)) + return + +/// Creates (or wipes clean) list of tracer keypoints and creates a first point. +/obj/projectile/proc/record_hitscan_start(offset = TRUE) + if (isnull(beam_points)) + beam_points = list() + else + QDEL_LIST_ASSOC(beam_points) + QDEL_NULL(last_point) + last_point = RETURN_PRECISE_POINT(src) + // If moving, increment its position a bit to prevent it from looking like its coming from firer's ass + if (offset && !isnull(movement_vector)) + last_point.increment(movement_vector.pixel_x * MUZZLE_EFFECT_PIXEL_INCREMENT, movement_vector.pixel_y * MUZZLE_EFFECT_PIXEL_INCREMENT) + beam_points[last_point] = null + +/// Creates a new keypoint in which the tracer will split +/obj/projectile/proc/create_hitscan_point(impact = FALSE, tile_center = FALSE, broken_segment = FALSE) + var/atom/handle_atom = last_impact_turf || src + var/atom/used_point = tile_center ? loc : src + var/datum/point/new_point = impact ? new /datum/point(handle_atom.x, handle_atom.y, handle_atom.z, impact_x, impact_y) : RETURN_PRECISE_POINT(used_point) + if (!broken_segment) + beam_points[last_point] = new_point + beam_points[new_point] = null + last_point = new_point + +/obj/projectile/forceMove(atom/target) + if (!hitscan || isnull(beam_points)) + return ..() + create_hitscan_point() + . = ..() + if(!isturf(loc) || !isturf(target) || !z || QDELETED(src) || deletion_queued) + return + if (isnull(movement_vector) || free_hitscan_forceMove) + return + // Create firing VFX and start a new chain because we most likely got teleported + generate_hitscan_tracers(impact = FALSE) + original_angle = angle + record_hitscan_start(offset = FALSE) + +/obj/projectile/proc/generate_hitscan_tracers(impact = TRUE) + if (!length(beam_points)) + return + + if (impact) + create_hitscan_point(impact = TRUE) + + if (tracer_type) + // Stores all turfs we've created light effects on, in order to not dupe them if we enter a reflector loop + // Uses an assoc list for performance reasons + var/list/passed_turfs = list() + for (var/beam_point in beam_points) + generate_tracer(beam_point, passed_turfs) + + if (muzzle_type) + var/datum/point/start_point = beam_points[1] + var/atom/movable/muzzle_effect = new muzzle_type(loc) + start_point.move_atom_to_src(muzzle_effect) + var/matrix/matrix = new + matrix.Turn(original_angle) + muzzle_effect.transform = matrix + muzzle_effect.color = color + muzzle_effect.set_light(muzzle_flash_range, muzzle_flash_intensity, muzzle_flash_color_override || color) + QDEL_IN(muzzle_effect, PROJECTILE_TRACER_DURATION) + + if (impact_type) + var/atom/movable/impact_effect = new impact_type(loc) + last_point.move_atom_to_src(impact_effect) + var/matrix/matrix = new + matrix.Turn(angle) + impact_effect.transform = matrix + impact_effect.color = color + impact_effect.set_light(impact_light_range, impact_light_intensity, impact_light_color_override || color) + QDEL_IN(impact_effect, PROJECTILE_TRACER_DURATION) + +/obj/projectile/proc/generate_tracer(datum/point/start_point, list/passed_turfs) + if (isnull(beam_points[start_point])) + return + + var/datum/point/end_point = beam_points[start_point] + var/datum/point/midpoint = point_midpoint_points(start_point, end_point) + var/obj/effect/projectile/tracer/tracer_effect = new tracer_type(midpoint.return_turf()) + tracer_effect.apply_vars( + angle_override = angle_between_points(start_point, end_point), + p_x = midpoint.pixel_x, + p_y = midpoint.pixel_y, + color_override = color, + scaling = pixel_length_between_points(start_point, end_point) / ICON_SIZE_ALL + ) + SET_PLANE_EXPLICIT(tracer_effect, GAME_PLANE, src) + + QDEL_IN(tracer_effect, PROJECTILE_TRACER_DURATION) + + if (!hitscan_light_range || !hitscan_light_intensity) + return + + var/list/turf/light_line = get_line(start_point.return_turf(), end_point.return_turf()) + for (var/turf/light_turf as anything in light_line) + if (passed_turfs[light_turf]) + continue + passed_turfs[light_turf] = TRUE + QDEL_IN(new /obj/effect/abstract/projectile_lighting(light_turf, hitscan_light_color_override || color, hitscan_light_range, hitscan_light_intensity), PROJECTILE_TRACER_DURATION) + /** * Aims the projectile at a target. * @@ -950,29 +1181,50 @@ * - deviation: (Optional) How the trajectory should deviate from the target in degrees. * - //Spread is FORCED! */ -/obj/projectile/proc/preparePixelProjectile(atom/target, atom/source, list/modifiers = null, deviation = 0) +/obj/projectile/proc/aim_projectile(atom/target, atom/source, list/modifiers = null, deviation = 0) if(!(isnull(modifiers) || islist(modifiers))) - stack_trace("WARNING: Projectile [type] fired with non-list modifiers, likely was passed click params.") + stack_trace("WARNING: Projectile [type] fired with non-list modifiers, likely was passed click params. Modifiers were the following: [modifiers]") modifiers = null var/turf/source_loc = get_turf(source) var/turf/target_loc = get_turf(target) + if(isnull(source_loc)) stack_trace("WARNING: Projectile [type] fired from nullspace.") qdel(src) return FALSE - trajectory_ignore_forcemove = TRUE - forceMove(source_loc) - trajectory_ignore_forcemove = FALSE + if(fired) + stack_trace("WARNING: Projectile [type] was aimed after already being fired.") + qdel(src) + return FALSE + free_hitscan_forceMove = TRUE + forceMove(source_loc) starting = source_loc pixel_x = source.pixel_x pixel_y = source.pixel_y original = target + + // Trim off excess pixel_x/y by converting them into turf offset + if (abs(pixel_x) > ICON_SIZE_X / 2) + for (var/i in 1 to floor(abs(pixel_x) + ICON_SIZE_X / 2) / ICON_SIZE_X) + var/turf/new_loc = get_step(source_loc, pixel_x > 0 ? EAST : WEST) + if (!istype(new_loc)) + break + source_loc = new_loc + pixel_x = pixel_x % (ICON_SIZE_X / 2) + + if (abs(pixel_y) > ICON_SIZE_Y / 2) + for (var/i in 1 to floor(abs(pixel_y) + ICON_SIZE_Y / 2) / ICON_SIZE_Y) + var/turf/new_loc = get_step(source_loc, pixel_y > 0 ? NORTH : SOUTH) + if (!istype(new_loc)) + break + source_loc = new_loc + pixel_y = pixel_y % (ICON_SIZE_X / 2) + if(length(modifiers)) var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, target_loc && target, modifiers) - p_x = calculated[2] p_y = calculated[3] set_angle(calculated[1] + deviation) @@ -1031,63 +1283,11 @@ //Calculate the "resolution" of screen based on client's view and world's icon size. This will work if the user can view more tiles than average. var/list/screenview = view_to_pixels(user.client.view) - var/ox = round(screenview[1] / 2) - user.client.pixel_x //"origin" x - var/oy = round(screenview[2] / 2) - user.client.pixel_y //"origin" y + var/ox = round(screenview[1] * 0.5) - user.client.pixel_x //"origin" x + var/oy = round(screenview[2] * 0.5) - user.client.pixel_y //"origin" y angle = ATAN2(tx - oy, ty - ox) return list(angle, p_x, p_y) -/obj/projectile/Destroy() - if(hitscan) - finalize_hitscan_and_generate_tracers() - STOP_PROCESSING(SSprojectiles, src) - cleanup_beam_segments() - firer = null - original = null - if(trajectory) - QDEL_NULL(trajectory) - return ..() - -/obj/projectile/proc/cleanup_beam_segments() - QDEL_LIST_ASSOC(beam_segments) - beam_segments = list() - QDEL_NULL(beam_index) - -/obj/projectile/proc/finalize_hitscan_and_generate_tracers(impacting = TRUE) - if(trajectory && beam_index) - var/datum/point/point_cache = trajectory.copy_to() - beam_segments[beam_index] = point_cache - generate_hitscan_tracers(null, null, impacting) - -/obj/projectile/proc/generate_hitscan_tracers(cleanup = TRUE, duration = 3, impacting = TRUE) - if(!length(beam_segments)) - return - if(tracer_type) - var/tempref = REF(src) - for(var/datum/point/beam_point in beam_segments) - generate_tracer_between_points(beam_point, beam_segments[beam_point], tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity, tempref) - if(muzzle_type && duration > 0) - var/datum/point/beam_point = beam_segments[1] - var/atom/movable/thing = new muzzle_type - beam_point.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/beam_point = beam_segments[beam_segments[beam_segments.len]] - var/atom/movable/thing = new impact_type - beam_point.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/projectile/experience_pressure_difference() return @@ -1126,15 +1326,11 @@ firer = hit_atom yo = new_y - current_tile.y xo = new_x - current_tile.x - var/new_angle_s = angle + rand(120,240) + var/new_angle_s = angle + rand(120, 240) while(new_angle_s > 180) // Translate to regular projectile degrees new_angle_s -= 360 set_angle(new_angle_s) -#undef MOVES_HITSCAN -#undef MUZZLE_EFFECT_PIXEL_INCREMENT -#undef MAX_RANGE_HIT_PRONE_TARGETS - /// Fire a projectile from this atom at another atom /atom/proc/fire_projectile(projectile_type, atom/target, sound, firer, list/ignore_targets = list()) if (!isnull(sound)) @@ -1150,7 +1346,7 @@ bullet.yo = target.y - startloc.y bullet.xo = target.x - startloc.x bullet.original = target - bullet.preparePixelProjectile(target, src) + bullet.aim_projectile(target, src) bullet.fire() return bullet @@ -1167,3 +1363,6 @@ if(!isnull(embed_data) && !GLOB.embed_by_type[embed_data.type]) qdel(embed_data) embed_data = ispath(embed) ? get_embed_by_type(armor) : embed + +#undef MOVES_HITSCAN +#undef MUZZLE_EFFECT_PIXEL_INCREMENT diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index 355221eab86..edbb0535ac6 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -15,7 +15,7 @@ light_color = COLOR_SOFT_RED ricochets_max = 50 //Honk! ricochet_chance = 80 - reflectable = REFLECT_NORMAL + reflectable = TRUE wound_bonus = -20 bare_wound_bonus = 10 @@ -45,7 +45,7 @@ impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser damage = 9 wound_bonus = -40 - speed = 1.1 + speed = 0.9 //overclocked laser, does a bit more damage but has much higher wound power (-0 vs -20) /obj/projectile/beam/laser/hellfire @@ -53,7 +53,7 @@ icon_state = "hellfire" wound_bonus = 0 damage = 30 - speed = 0.6 // higher power = faster, that's how light works right + speed = 1.6 light_color = "#FF969D" /obj/projectile/beam/laser/heavylaser diff --git a/code/modules/projectiles/projectile/bullets/_incendiary.dm b/code/modules/projectiles/projectile/bullets/_incendiary.dm index 2e526adb053..d5c4dc93c5b 100644 --- a/code/modules/projectiles/projectile/bullets/_incendiary.dm +++ b/code/modules/projectiles/projectile/bullets/_incendiary.dm @@ -35,7 +35,7 @@ suppressed = SUPPRESSED_VERY damage_type = BURN armor_flag = BOMB - speed = 1.2 + speed = 0.8 wound_bonus = 30 bare_wound_bonus = 30 wound_falloff_tile = -4 diff --git a/code/modules/projectiles/projectile/bullets/pistol.dm b/code/modules/projectiles/projectile/bullets/pistol.dm index 63e491e2f29..bc64363a2d3 100644 --- a/code/modules/projectiles/projectile/bullets/pistol.dm +++ b/code/modules/projectiles/projectile/bullets/pistol.dm @@ -79,7 +79,7 @@ icon_state = "smartgun" damage = 10 embed_type = /datum/embed_data/bullet_c160smart - speed = 2 + speed = 0.5 homing_turn_speed = 5 homing_inaccuracy_min = 4 homing_inaccuracy_max = 10 diff --git a/code/modules/projectiles/projectile/bullets/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm index 4caec924625..1302aea9315 100644 --- a/code/modules/projectiles/projectile/bullets/rifle.dm +++ b/code/modules/projectiles/projectile/bullets/rifle.dm @@ -67,7 +67,7 @@ name = "rebar" icon_state = "rebar" damage = 30 - speed = 0.4 + speed = 2.5 dismemberment = 1 //because a 1 in 100 chance to just blow someones arm off is enough to be cool but also not enough to be reliable armour_penetration = 10 wound_bonus = -20 @@ -93,7 +93,6 @@ name = "rebar" icon_state = "rebar" damage = 45 - speed = 0.4 dismemberment = 2 //It's a budget sniper rifle. armour_penetration = 20 //A bit better versus armor. Gets past anti laser armor or a vest, but doesnt wound proc on sec armor. wound_bonus = 10 @@ -116,7 +115,7 @@ name = "zaukerite shard" icon_state = "rebar_zaukerite" damage = 60 - speed = 0.6 + speed = 1.6 dismemberment = 10 damage_type = TOX eyeblur = 5 @@ -141,7 +140,7 @@ name = "metallic hydrogen bolt" icon_state = "rebar_hydrogen" damage = 35 - speed = 0.6 + speed = 1.6 projectile_piercing = PASSMOB|PASSVEHICLE projectile_phasing = ~(PASSMOB|PASSVEHICLE) max_pierces = 3 @@ -172,7 +171,6 @@ name = "healium bolt" icon_state = "rebar_healium" damage = 0 - speed = 0.4 dismemberment = 0 damage_type = BRUTE armour_penetration = 100 @@ -199,7 +197,6 @@ name = "supermatter bolt" icon_state = "rebar_supermatter" damage = 0 - speed = 0.4 dismemberment = 0 damage_type = TOX embed_type = null diff --git a/code/modules/projectiles/projectile/bullets/shotgun.dm b/code/modules/projectiles/projectile/bullets/shotgun.dm index a2c11f76d47..3576c9e5a12 100644 --- a/code/modules/projectiles/projectile/bullets/shotgun.dm +++ b/code/modules/projectiles/projectile/bullets/shotgun.dm @@ -94,7 +94,7 @@ stamina = 11 sharpness = NONE embed_type = null - speed = 1.2 + speed = 0.8 stamina_falloff_tile = -0.25 ricochets_max = 4 ricochet_chance = 120 @@ -106,7 +106,7 @@ /// Subtracted from the ricochet chance for each tile traveled var/tile_dropoff_ricochet = 4 -/obj/projectile/bullet/pellet/shotgun_rubbershot/Range() +/obj/projectile/bullet/pellet/shotgun_rubbershot/reduce_range() if(ricochet_chance > 0) ricochet_chance -= tile_dropoff_ricochet . = ..() @@ -124,7 +124,7 @@ wound_bonus = -25 bare_wound_bonus = 50 wound_falloff_tile = -10 - speed = 0.8 + speed = 1.2 ricochet_decay_chance = 0.6 ricochet_decay_damage = 0.3 demolition_mod = 10 diff --git a/code/modules/projectiles/projectile/bullets/sniper.dm b/code/modules/projectiles/projectile/bullets/sniper.dm index 6118d90644d..44caa2ebe21 100644 --- a/code/modules/projectiles/projectile/bullets/sniper.dm +++ b/code/modules/projectiles/projectile/bullets/sniper.dm @@ -2,7 +2,7 @@ /obj/projectile/bullet/p50 name =".50 BMG bullet" - speed = 0.4 + speed = 2.5 range = 400 // Enough to travel from one corner of the Z to the opposite corner and then some. damage = 70 paralyze = 100 @@ -87,7 +87,7 @@ name = ".50 BMG aggression dissuasion round" icon_state = "gaussstrong" damage = 25 - speed = 0.3 + speed = 3 range = 16 /obj/projectile/bullet/p50/marksman diff --git a/code/modules/projectiles/projectile/bullets/special.dm b/code/modules/projectiles/projectile/bullets/special.dm index c1299b3bed0..cd1439cbb01 100644 --- a/code/modules/projectiles/projectile/bullets/special.dm +++ b/code/modules/projectiles/projectile/bullets/special.dm @@ -79,8 +79,7 @@ /obj/projectile/bullet/coin name = "marksman coin" icon_state = "coinshot" - pixel_speed_multiplier = 0.333 - speed = 1 + speed = 0.33 damage = 5 color = "#dbdd4c" @@ -100,7 +99,7 @@ /obj/projectile/bullet/coin/Initialize(mapload, turf/the_target, mob/original_firer) src.original_firer = original_firer target_turf = the_target - range = (get_dist(original_firer, target_turf) + 3) * 3 // 3 tiles past the origin (the *3 is because Range() ticks 3 times a tile because of the slower speed) + range = (get_dist(original_firer, target_turf) + 3) * 3 // 3 tiles past the origin (the *3 is because reduce_range() ticks 3 times a tile because of the slower speed) . = ..() @@ -167,7 +166,7 @@ if(Adjacent(current_turf, target_turf)) new_splitshot.fire(get_angle(current_turf, target_turf), direct_target = next_target) else - new_splitshot.preparePixelProjectile(next_target, get_turf(src)) + new_splitshot.aim_projectile(next_target, get_turf(src)) new_splitshot.fire() if(istype(next_target, /obj/projectile/bullet/coin)) // handle further splitshot checks diff --git a/code/modules/projectiles/projectile/energy/_energy.dm b/code/modules/projectiles/projectile/energy/_energy.dm index 8527041e860..0985691a5fb 100644 --- a/code/modules/projectiles/projectile/energy/_energy.dm +++ b/code/modules/projectiles/projectile/energy/_energy.dm @@ -4,5 +4,5 @@ damage = 0 damage_type = BURN armor_flag = ENERGY - reflectable = REFLECT_NORMAL + reflectable = TRUE impact_effect_type = /obj/effect/temp_visual/impact_effect/energy diff --git a/code/modules/projectiles/projectile/energy/nuclear_particle.dm b/code/modules/projectiles/projectile/energy/nuclear_particle.dm index 6626cb9b5ed..08c35690286 100644 --- a/code/modules/projectiles/projectile/energy/nuclear_particle.dm +++ b/code/modules/projectiles/projectile/energy/nuclear_particle.dm @@ -6,7 +6,7 @@ armor_flag = ENERGY damage_type = TOX damage = 10 - speed = 0.4 + speed = 2.5 hitsound = 'sound/items/weapons/emitter2.ogg' impact_type = /obj/effect/projectile/impact/xray var/static/list/particle_colors = list( diff --git a/code/modules/projectiles/projectile/energy/photon.dm b/code/modules/projectiles/projectile/energy/photon.dm index 5210737a198..00ae9281af7 100644 --- a/code/modules/projectiles/projectile/energy/photon.dm +++ b/code/modules/projectiles/projectile/energy/photon.dm @@ -1,4 +1,4 @@ -#define MULTIPLY_PIXELSPEED 0.8 +#define MULTIPLY_SPEED 0.8 /obj/projectile/energy/photon name = "photon bolt" @@ -9,7 +9,6 @@ damage = 5 //It's literally a weaker tesla bolt, which is already weak. Don't worry, we'll fix that. range = 20 speed = 1 - pixel_speed_multiplier = 1 projectile_piercing = PASSMOB light_color = LIGHT_COLOR_DEFAULT light_system = OVERLAY_LIGHT @@ -44,9 +43,9 @@ if(prob(40)) new /obj/effect/hotspot(arrived) -/obj/projectile/energy/photon/Range() +/obj/projectile/energy/photon/reduce_range() . = ..() - pixel_speed_multiplier *= MULTIPLY_PIXELSPEED + speed *= MULTIPLY_SPEED /obj/projectile/energy/photon/on_range() do_sparks(rand(4, 9), FALSE, src) @@ -55,4 +54,4 @@ flashed_mob.flash_act() return ..() -#undef MULTIPLY_PIXELSPEED +#undef MULTIPLY_SPEED diff --git a/code/modules/projectiles/projectile/energy/tesla.dm b/code/modules/projectiles/projectile/energy/tesla.dm index 9dfe043a015..91677dab2fc 100644 --- a/code/modules/projectiles/projectile/energy/tesla.dm +++ b/code/modules/projectiles/projectile/energy/tesla.dm @@ -29,7 +29,7 @@ name = "tesla orb" icon_state = "ice_1" damage = 0 - speed = 1.5 + speed = 0.66 var/shock_damage = 5 /obj/projectile/energy/tesla_cannon/on_hit(atom/target, blocked = 0, pierce_hit) diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index 5c050bdebb6..1755d43de1f 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -238,7 +238,8 @@ /obj/projectile/magic/locker/Destroy() locker_suck = FALSE - RemoveElement(/datum/element/connect_loc, projectile_connections) //We do this manually so the forcemoves don't "hit" us. This behavior is kinda dumb, someone refactor this + if (last_tick_turf) + UnregisterSignal(last_tick_turf, COMSIG_ATOM_ENTERED) for(var/atom/movable/AM in contents) AM.forceMove(get_turf(src)) . = ..() @@ -400,8 +401,10 @@ var/trail_icon = 'icons/effects/magic.dmi' /// The icon state the trail uses. var/trail_icon_state = "arrow" + /// Can we spawn a trail effect again? + COOLDOWN_DECLARE(trail_cooldown) -/obj/projectile/magic/aoe/Range() +/obj/projectile/magic/aoe/reduce_range() if(trigger_range >= 1) for(var/mob/living/nearby_guy in range(trigger_range, get_turf(src))) if(nearby_guy.stat == DEAD) @@ -413,38 +416,31 @@ return ..() -/obj/projectile/magic/aoe/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE) +/obj/projectile/magic/aoe/prehit_pierce(atom/target) if(can_only_hit_target && target != original) - return FALSE + return PROJECTILE_PIERCE_PHASE return ..() -/obj/projectile/magic/aoe/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - . = ..() - if(trail) - create_trail() - -/// Creates and handles the trail that follows the projectile. -/obj/projectile/magic/aoe/proc/create_trail() - if(!trajectory) +/obj/projectile/magic/aoe/move_animate(animate_x, animate_y, animate_time = world.tick_lag, deleting = FALSE) + if(!trail || !movement_vector || deleting || !COOLDOWN_FINISHED(src, trail_cooldown)) return - var/datum/point/vector/previous = trajectory.return_vector_after_increments(1, -1) - var/obj/effect/overlay/trail = new /obj/effect/overlay(previous.return_turf()) - trail.pixel_x = previous.return_px() - trail.pixel_y = previous.return_py() - trail.icon = trail_icon - trail.icon_state = trail_icon_state - //might be changed to temp overlay - trail.set_density(FALSE) - trail.mouse_opacity = MOUSE_OPACITY_TRANSPARENT - QDEL_IN(trail, trail_lifespan) + var/obj/effect/overlay/trail_effect = new /obj/effect/overlay(loc) + trail_effect.pixel_x = pixel_x + trail_effect.pixel_y = pixel_y + trail_effect.icon = trail_icon + trail_effect.icon_state = trail_icon_state + trail_effect.set_density(FALSE) + trail_effect.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + QDEL_IN(trail_effect, trail_lifespan) + COOLDOWN_START(src, trail_cooldown, trail_lifespan) /obj/projectile/magic/aoe/lightning name = "lightning bolt" icon_state = "tesla_projectile" //Better sprites are REALLY needed and appreciated!~ damage = 15 damage_type = BURN - speed = 0.3 + speed = 3.5 /// The power of the zap itself when it electrocutes someone var/zap_power = 2e4 @@ -513,8 +509,7 @@ name = "magic missile" icon_state = "magicm" range = 100 - speed = 1 - pixel_speed_multiplier = 0.2 + speed = 0.2 trigger_range = 0 can_only_hit_target = TRUE paralyze = 6 SECONDS @@ -540,8 +535,7 @@ antimagic_flags = MAGIC_RESISTANCE_HOLY ignored_factions = list(FACTION_CULT) range = 105 - speed = 1 - pixel_speed_multiplier = 1/7 + speed = 0.15 /obj/projectile/magic/aoe/juggernaut/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() diff --git a/code/modules/projectiles/projectile/special/curse.dm b/code/modules/projectiles/projectile/special/curse.dm index 5f1f1017e83..8d1030c8c93 100644 --- a/code/modules/projectiles/projectile/special/curse.dm +++ b/code/modules/projectiles/projectile/special/curse.dm @@ -10,7 +10,7 @@ damage_type = BURN damage = 10 paralyze = 20 - speed = 2 + speed = 0.5 range = 16 var/datum/beam/arm var/handedness = 0 diff --git a/code/modules/reagents/reagent_containers/cups/drinks.dm b/code/modules/reagents/reagent_containers/cups/drinks.dm index 717e2248ddb..41d3161bd68 100644 --- a/code/modules/reagents/reagent_containers/cups/drinks.dm +++ b/code/modules/reagents/reagent_containers/cups/drinks.dm @@ -31,11 +31,11 @@ qdel(src) target.Bumped(B) -/obj/item/reagent_containers/cup/glass/bullet_act(obj/projectile/P) +/obj/item/reagent_containers/cup/glass/bullet_act(obj/projectile/proj) . = ..() if(QDELETED(src)) return - if(P.damage > 0 && P.damage_type == BRUTE) + if(proj.damage > 0 && proj.damage_type == BRUTE) var/atom/T = get_turf(src) smash(T) diff --git a/code/modules/reagents/reagent_containers/cups/soda.dm b/code/modules/reagents/reagent_containers/cups/soda.dm index a23e9c32354..c9380d2b9b9 100644 --- a/code/modules/reagents/reagent_containers/cups/soda.dm +++ b/code/modules/reagents/reagent_containers/cups/soda.dm @@ -76,17 +76,17 @@ return TRUE return ..() -/obj/item/reagent_containers/cup/soda_cans/bullet_act(obj/projectile/P) +/obj/item/reagent_containers/cup/soda_cans/bullet_act(obj/projectile/proj) . = ..() if(QDELETED(src)) return - if(P.damage > 0 && P.damage_type == BRUTE) - var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(src.loc) - crushed_can.icon_state = icon_state - var/atom/throw_target = get_edge_target_turf(crushed_can, pick(GLOB.alldirs)) - crushed_can.throw_at(throw_target, rand(1,2), 7) - qdel(src) + if(!proj.damage || proj.damage_type != BRUTE) return + var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(loc) + crushed_can.icon_state = icon_state + var/atom/throw_target = get_edge_target_turf(crushed_can, pick(GLOB.alldirs)) + crushed_can.throw_at(throw_target, rand(1,2), 7) + qdel(src) /obj/item/reagent_containers/cup/soda_cans/proc/open_soda(mob/user) if(prob(fizziness)) @@ -138,7 +138,7 @@ burst_soda(hit_atom, hide_message = TRUE) visible_message(span_danger("[src]'s impact with [hit_atom] causes it to rupture, spilling everywhere!")) - var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(src.loc) + var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(loc) crushed_can.icon_state = icon_state moveToNullspace() QDEL_IN(src, 1 SECONDS) // give it a second so it can still be logged for the throw impact diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm index 998995436ad..c78ca6601d4 100644 --- a/code/modules/research/experimentor.dm +++ b/code/modules/research/experimentor.dm @@ -363,7 +363,7 @@ visible_message(span_danger("[src] dangerously overheats, launching a flaming fuel orb!")) investigate_log("Experimentor has launched a fireball at [M]!", INVESTIGATE_EXPERIMENTOR) var/obj/projectile/magic/fireball/FB = new /obj/projectile/magic/fireball(start) - FB.preparePixelProjectile(MT, start) + FB.aim_projectile(MT, start) FB.fire() else if(prob(EFFECT_PROB_LOW * (100 - malfunction_probability_coeff) * 0.01)) visible_message(span_danger("[src] malfunctions, melting [exp_on] and releasing a burst of flame!")) diff --git a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm index 02f19f45323..743ea8b23d8 100644 --- a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm +++ b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm @@ -31,7 +31,7 @@ /datum/action/cooldown/spell/aoe/magic_missile/proc/fire_projectile(atom/victim, mob/caster) var/obj/projectile/to_fire = new projectile_type() - to_fire.preparePixelProjectile(victim, caster) + to_fire.aim_projectile(victim, caster) SEND_SIGNAL(caster, COMSIG_MOB_SPELL_PROJECTILE, src, victim, to_fire) to_fire.fire() diff --git a/code/modules/spells/spell_types/pointed/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm index a96e2fe9fd2..39d6fb9d673 100644 --- a/code/modules/spells/spell_types/pointed/_pointed.dm +++ b/code/modules/spells/spell_types/pointed/_pointed.dm @@ -168,7 +168,7 @@ /datum/action/cooldown/spell/pointed/projectile/proc/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) to_fire.firer = owner to_fire.fired_from = src - to_fire.preparePixelProjectile(target, owner) + to_fire.aim_projectile(target, owner) RegisterSignal(to_fire, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(on_cast_hit)) if(istype(to_fire, /obj/projectile/magic)) diff --git a/code/modules/spells/spell_types/pointed/spell_cards.dm b/code/modules/spells/spell_types/pointed/spell_cards.dm index 65f9561658b..bbaed5ad66d 100644 --- a/code/modules/spells/spell_types/pointed/spell_cards.dm +++ b/code/modules/spells/spell_types/pointed/spell_cards.dm @@ -81,4 +81,4 @@ to_fire.pixel_x = rand(-projectile_location_spread_amount, projectile_location_spread_amount) to_fire.pixel_y = rand(-projectile_location_spread_amount, projectile_location_spread_amount) - to_fire.preparePixelProjectile(target, user, null, current_angle) + to_fire.aim_projectile(target, user, null, current_angle) diff --git a/code/modules/spells/spell_types/projectile/_basic_projectile.dm b/code/modules/spells/spell_types/projectile/_basic_projectile.dm index 343de438cd6..bcab5633ecc 100644 --- a/code/modules/spells/spell_types/projectile/_basic_projectile.dm +++ b/code/modules/spells/spell_types/projectile/_basic_projectile.dm @@ -25,6 +25,6 @@ /datum/action/cooldown/spell/basic_projectile/proc/fire_projectile(atom/target, atom/caster) var/obj/projectile/to_fire = new projectile_type() - to_fire.preparePixelProjectile(target, caster) + to_fire.aim_projectile(target, caster) SEND_SIGNAL(caster, COMSIG_MOB_SPELL_PROJECTILE, src, target, to_fire) to_fire.fire() diff --git a/code/modules/unit_tests/embedding.dm b/code/modules/unit_tests/embedding.dm index 05e8cc8b8aa..5e6a8a90647 100644 --- a/code/modules/unit_tests/embedding.dm +++ b/code/modules/unit_tests/embedding.dm @@ -6,7 +6,7 @@ var/obj/projectile/bullet/c38/bullet = new(get_turf(firer)) bullet.set_embed(bullet.get_embed().generate_with_values(embed_chance = 100)) TEST_ASSERT_EQUAL(bullet.get_embed().embed_chance, 100, "embed_chance failed to modify") - bullet.preparePixelProjectile(victim, firer) + bullet.aim_projectile(victim, firer) bullet.fire(get_angle(firer, victim), victim) var/list/components = victim.GetComponents(/datum/component/embedded) TEST_ASSERT_EQUAL(components.len, 1, "Projectile with 100% embed chance didn't embed, or embedded multiple times") diff --git a/code/modules/vehicles/atv.dm b/code/modules/vehicles/atv.dm index 4aa28d6fe5a..6a472ccc31c 100644 --- a/code/modules/vehicles/atv.dm +++ b/code/modules/vehicles/atv.dm @@ -112,12 +112,12 @@ smoke.set_up(0, holder = src, location = src) smoke.start() -/obj/vehicle/ridden/atv/bullet_act(obj/projectile/P) +/obj/vehicle/ridden/atv/bullet_act(obj/projectile/proj) if(prob(50) || !LAZYLEN(buckled_mobs)) return ..() for(var/mob/buckled_mob as anything in buckled_mobs) - buckled_mob.bullet_act(P) - return BULLET_ACT_HIT + return buckled_mob.bullet_act(proj) + return ..() /obj/vehicle/ridden/atv/atom_destruction() explosion(src, devastation_range = -1, light_impact_range = 2, flame_range = 3, flash_range = 4) diff --git a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm index b29c5501a79..cf042033cba 100644 --- a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm +++ b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm @@ -68,7 +68,7 @@ var/obj/projectile/projectile_obj = new projectile(get_turf(src)) projectile_obj.log_override = TRUE //we log being fired ourselves a little further down. projectile_obj.firer = chassis - projectile_obj.preparePixelProjectile(target, source, modifiers, spread) + projectile_obj.aim_projectile(target, source, modifiers, spread) if(isliving(source) && source.client) //dont want it to happen from syndie mecha npc mobs, they do direct fire anyways var/mob/living/shooter = source projectile_obj.hit_prone_targets = shooter.combat_mode diff --git a/code/modules/vehicles/secway.dm b/code/modules/vehicles/secway.dm index ab366da4b28..341167630af 100644 --- a/code/modules/vehicles/secway.dm +++ b/code/modules/vehicles/secway.dm @@ -97,9 +97,9 @@ return ..() //bullets will have a 60% chance to hit any riders -/obj/vehicle/ridden/secway/bullet_act(obj/projectile/P) +/obj/vehicle/ridden/secway/bullet_act(obj/projectile/proj) if(!buckled_mobs || prob(40)) return ..() for(var/mob/rider as anything in buckled_mobs) - return rider.bullet_act(P) + return rider.bullet_act(proj) return ..()