mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-22 20:48:56 +01:00
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:
@@ -94,6 +94,10 @@
|
||||
/code/game/area/ @san7890
|
||||
/icons/area/ @san7890
|
||||
|
||||
# SmArtKar
|
||||
|
||||
/code/modules/projectiles/projectile.dm @SmArtKar
|
||||
|
||||
# stylemistake
|
||||
|
||||
/code/__DEFINES/chat.dm @stylemistake
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
. = ..()
|
||||
|
||||
@@ -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 ..()
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
suppressed = SUPPRESSED_VERY
|
||||
range = 4
|
||||
speed = 4
|
||||
speed = 0.25
|
||||
spread = 40
|
||||
damage_type = BRUTE
|
||||
damage = 0
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user