Guncode Agony 4: The Great Projectile Purge (#87740)

## About The Pull Request
~~Kept you waitin huh!~~
The projectile refactor is finally here, 4 years later. This PR (almost)
completely rewrites projectile logic to be more maintainable and
performant.

### Key changes:
* Instead of moving by a fixed amount of pixels, potentially skipping
tile corners and being performance-heavy, projectiles now use
raymarching in order to teleport through tiles and only visually animate
themselves. This allows us to do custom per-projectile animations and
makes the code much more reliable, sane and maintainable. You (did not)
serve us well, pixel_move.
* Speed variable now measures how many tiles (if SSprojectiles has
default values) a projectile passes in a tick instead of being a magical
Kevinz Unit™️ coefficient. pixel_speed_multiplier has been retired
because it never had a right to exist in the first place. __This means
that downstreams will need to set all of their custom projectiles' speed
values to ``pixel_speed_multiplier / speed``__ in order to prevent
projectiles from inverting their speed.
* Hitscans no longer operate with spartial vectors and instead only
store key points in which the projectile impacted something or changed
its angle. This should similarly make the code much easier to work with,
as well as fixing some visual jank due to incorrect calculations.
* Projectiles only delete themselves the ***next*** tick after impacting
something or reaching their maximum range. Doing so allows them to
finish their impact animation and hide themselves between ticks via
animation chains. This means that projectiles no longer disappear ~a
tile before hitting their target, and that we can finally make impact
markers be consistent with where the projectile actually landed instead
of being entirely random.

<details>

<summary>Here is an example of how this affects our slowest-moving
projectile: Magic Missiles.</summary>


Before:


https://github.com/user-attachments/assets/06b3a980-4701-4aeb-aa3e-e21cd056020e

After:


https://github.com/user-attachments/assets/abe8ed5c-4b81-4120-8d2f-cf16ff5be915

</details>


<details>

<summary>And here is a much faster, and currently jankier, disabler
SMG.</summary>


Before:


https://github.com/user-attachments/assets/2d84aef1-0c83-44ef-a698-8ec716587348

After:


https://github.com/user-attachments/assets/2e7c1336-f611-404f-b3ff-87433398d238

</details>

### But how will this affect the ~~trout population~~ gameplay?

Beyond improved visuals, smoother movement and a few minor bugfixes,
this should not have a major gameplay impact. If something changed its
behavior in an unexpected way or started looking odd, please make an
issue report.
Projectile impacts should now be consistent with their visual position,
so hitting and dodging shots should be slightly easier and more
intuitive.

This PR should be testmerged extensively due to the amount of changes it
brings and considerable difficulty in reviewing them. Please contact me
to ensure its good to merge.

Closes #71822
Closes #78547
Closes #78871
Closes #83901
Closes #87802
Closes #88073

## Why It's Good For The Game

Our core projectile code is an ungodly abomination that nobody except
me, Kapu and Potato dared to poke in the past months (potentially
longer). It is laggy, overcomplicated and absolutely unmaintaineable -
while a lot of decisions made sense 4 years ago when we were attempting
to introduce pixel movement, nowadays they are only acting as major
roadblocks for any contributor who is attempting to make projectile
behavior that differs from normal in any way.

Huge thanks to Kapu and Potato (Lemon) on the discord for providing
insights, ideas and advice throughout the past months regarding
potential improvements to projectile code, almost all of which made it
in.

## Changelog
🆑
qol: Projectiles now visually impact their targets instead of
disappearing about a tile short of it.
fix: Fixed multiple minor issues with projectile behavior
refactor: Completely rewrote almost all of our projectile code - if
anything broke or started looking/behaving oddly, make an issue report!
/🆑
This commit is contained in:
SmArtKar
2024-11-23 04:02:35 -08:00
committed by GitHub
parent 2e9932a889
commit bbb7a41743
113 changed files with 1031 additions and 923 deletions
+4
View File
@@ -94,6 +94,10 @@
/code/game/area/ @san7890
/icons/area/ @san7890
# SmArtKar
/code/modules/projectiles/projectile.dm @SmArtKar
# stylemistake
/code/__DEFINES/chat.dm @stylemistake
-4
View File
@@ -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.
+5 -5
View File
@@ -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"
+3
View File
@@ -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)))
+18 -3
View File
@@ -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
+1 -1
View File
@@ -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)
@@ -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
@@ -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'
+3 -3
View File
@@ -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
-2
View File
@@ -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"]
+7 -7
View File
@@ -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()
+7 -4
View File
@@ -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
+22 -22
View File
@@ -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)
+2 -2
View File
@@ -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)
+5 -1
View File
@@ -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())
+3 -1
View File
@@ -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
+5 -5
View File
@@ -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)
+1 -1
View File
@@ -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)
+56 -113
View File
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -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)
+3 -2
View File
@@ -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)
@@ -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
@@ -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'
@@ -530,4 +530,4 @@ effective or pretty fucking useless.
/obj/projectile/bullet/toolbox_turret
damage = 10
speed = 0.6
speed = 1.6
+3 -3
View File
@@ -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
+2 -2
View File
@@ -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
+4 -4
View File
@@ -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
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -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)
@@ -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()
+3 -3
View File
@@ -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)
+7 -6
View File
@@ -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()
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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(.)
@@ -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()
@@ -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
@@ -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
@@ -119,4 +119,4 @@
/obj/projectile/magic/aoe/rust_wave/short
range = 7
speed = 2
speed = 0.5
@@ -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.
+1 -1
View File
@@ -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()
@@ -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)
+2 -2
View File
@@ -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)
+3 -4
View File
@@ -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)
@@ -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)
. = ..()
+1 -1
View File
@@ -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'
@@ -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)
@@ -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]!"))
@@ -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 ..()
+2 -2
View File
@@ -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()
@@ -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
@@ -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
@@ -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
@@ -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))
+6
View File
@@ -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
@@ -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)
. = ..()
@@ -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))
+5
View File
@@ -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
+4 -5
View File
@@ -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)
@@ -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)
@@ -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)
@@ -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))
@@ -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////////////
@@ -106,7 +106,7 @@ Difficulty: Medium
/obj/projectile/kinetic/miner
damage = 20
speed = 0.9
speed = 1.1
icon_state = "ka_tracer"
range = 4
@@ -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 ..()
@@ -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."
@@ -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
@@ -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
@@ -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"
@@ -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 ..()
@@ -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)
@@ -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()
+1 -1
View File
@@ -53,7 +53,7 @@
suppressed = SUPPRESSED_VERY
range = 4
speed = 4
speed = 0.25
spread = 40
damage_type = BRUTE
damage = 0
+1 -1
View File
@@ -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))
@@ -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))
+1 -1
View File
@@ -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))
+1 -1
View File
@@ -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))
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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))
+1 -1
View File
@@ -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))
+3 -3
View File
@@ -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)
@@ -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)
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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(
@@ -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
@@ -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)

Some files were not shown because too many files have changed in this diff Show More