mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2025-12-21 15:42:35 +00:00
Ports Baystation12/Baystation12#12658 Changing how the calculation of armor works , instead of just being a check if it is protected fully, half or nothing. Making armor more reliable and less rng dependant. Also, uranium meteors will irradiate nearby people when they explode.
469 lines
16 KiB
Plaintext
469 lines
16 KiB
Plaintext
/*
|
|
#define BRUTE "brute"
|
|
#define BURN "burn"
|
|
#define TOX "tox"
|
|
#define OXY "oxy"
|
|
#define CLONE "clone"
|
|
|
|
#define ADD "add"
|
|
#define SET "set"
|
|
*/
|
|
|
|
/obj/item/projectile
|
|
name = "projectile"
|
|
icon = 'icons/obj/projectiles.dmi'
|
|
icon_state = "bullet"
|
|
density = 1
|
|
unacidable = 1
|
|
anchored = 1 //There's a reason this is here, Mport. God fucking damn it -Agouri. Find&Fix by Pete. The reason this is here is to stop the curving of emitter shots.
|
|
pass_flags = PASSTABLE
|
|
mouse_opacity = 0
|
|
var/bumped = 0 //Prevents it from hitting more than one guy at once
|
|
var/def_zone = "" //Aiming at
|
|
var/mob/firer = null//Who shot it
|
|
var/silenced = 0 //Attack message
|
|
var/yo = null
|
|
var/xo = null
|
|
var/current = null
|
|
var/shot_from = "" // name of the object which shot us
|
|
var/atom/original = null // the target clicked (not necessarily where the projectile is headed). Should probably be renamed to 'target' or something.
|
|
var/turf/starting = null // the projectile's starting turf
|
|
var/list/permutated = list() // we've passed through these atoms, don't try to hit them again
|
|
|
|
var/p_x = 16
|
|
var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center
|
|
|
|
var/accuracy = 0
|
|
var/dispersion = 0.0
|
|
|
|
//used for shooting at blank range, you shouldn't be able to miss
|
|
var/can_miss = 0
|
|
|
|
var/damage = 10
|
|
var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE, HALLOSS are the only things that should be in here
|
|
var/nodamage = 0 //Determines if the projectile will skip any damage inflictions
|
|
var/taser_effect = 0 //If set then the projectile will apply it's agony damage using stun_effect_act() to mobs it hits, and other damage will be ignored
|
|
var/check_armour = "bullet" //Defines what armor to use when it hits things. Must be set to bullet, laser, energy,or bomb //Cael - bio and rad are also valid
|
|
var/projectile_type = /obj/item/projectile
|
|
var/penetrating = 0 //If greater than zero, the projectile will pass through dense objects as specified by on_penetrate()
|
|
var/kill_count = 50 //This will de-increment every process(). When 0, it will delete the projectile.
|
|
//Effects
|
|
var/stun = 0
|
|
var/weaken = 0
|
|
var/paralyze = 0
|
|
var/irradiate = 0
|
|
var/stutter = 0
|
|
var/eyeblur = 0
|
|
var/drowsy = 0
|
|
var/agony = 0
|
|
var/incinerate = 0
|
|
var/embed = 0 // whether or not the projectile can embed itself in the mob
|
|
var/shrapnel_type //type of shrapnel the projectile leaves in its target.
|
|
|
|
var/hitscan = 0 // whether the projectile should be hitscan
|
|
var/step_delay = 1 // the delay between iterations if not a hitscan projectile
|
|
|
|
// effect types to be used
|
|
var/muzzle_type
|
|
var/tracer_type
|
|
var/impact_type
|
|
|
|
var/datum/plot_vector/trajectory // used to plot the path of the projectile
|
|
var/datum/vector_loc/location // current location of the projectile in pixel space
|
|
var/matrix/effect_transform // matrix to rotate and scale projectile effects - putting it here so it doesn't
|
|
// have to be recreated multiple times
|
|
|
|
//TODO: make it so this is called more reliably, instead of sometimes by bullet_act() and sometimes not
|
|
/obj/item/projectile/proc/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null)
|
|
if(blocked >= 100) return 0//Full block
|
|
if(!isliving(target)) return 0
|
|
if(isanimal(target)) return 0
|
|
var/mob/living/L = target
|
|
if (agony && ishuman(target))
|
|
var/mob/living/carbon/human/H = target
|
|
var/obj/item/organ/external/organ = H.get_organ(def_zone)
|
|
var/armor = H.getarmor_organ(organ, check_armour)
|
|
agony = max(0, agony - armor)
|
|
|
|
L.apply_effects(stun, weaken, paralyze, 0, stutter, eyeblur, drowsy, agony, incinerate, blocked) // add in AGONY!
|
|
//radiation protection is handled separately from other armour types.
|
|
L.apply_effect(irradiate, IRRADIATE, L.getarmor(null, "rad"))
|
|
return 1
|
|
|
|
//called when the projectile stops flying because it collided with something
|
|
/obj/item/projectile/proc/on_impact(var/atom/A)
|
|
impact_effect(effect_transform) // generate impact effect
|
|
return
|
|
|
|
//Checks if the projectile is eligible for embedding. Not that it necessarily will.
|
|
/obj/item/projectile/proc/can_embed()
|
|
//embed must be enabled and damage type must be brute
|
|
if(!embed || damage_type != BRUTE)
|
|
return 0
|
|
return 1
|
|
|
|
/obj/item/projectile/proc/get_structure_damage()
|
|
if(damage_type == BRUTE || damage_type == BURN)
|
|
return damage
|
|
return 0
|
|
|
|
//return 1 if the projectile should be allowed to pass through after all, 0 if not.
|
|
/obj/item/projectile/proc/check_penetrate(var/atom/A)
|
|
return 1
|
|
|
|
/obj/item/projectile/proc/check_fire(atom/target as mob, var/mob/living/user as mob) //Checks if you can hit them or not.
|
|
check_trajectory(target, user, pass_flags, flags)
|
|
|
|
//sets the click point of the projectile using mouse input params
|
|
/obj/item/projectile/proc/set_clickpoint(var/params)
|
|
var/list/mouse_control = params2list(params)
|
|
if(mouse_control["icon-x"])
|
|
p_x = text2num(mouse_control["icon-x"])
|
|
if(mouse_control["icon-y"])
|
|
p_y = text2num(mouse_control["icon-y"])
|
|
|
|
//randomize clickpoint a bit based on dispersion
|
|
if(dispersion)
|
|
var/radius = round((dispersion*0.443)*world.icon_size*0.8) //0.443 = sqrt(pi)/4 = 2a, where a is the side length of a square that shares the same area as a circle with diameter = dispersion
|
|
p_x = between(0, p_x + rand(-radius, radius), world.icon_size)
|
|
p_y = between(0, p_y + rand(-radius, radius), world.icon_size)
|
|
|
|
//called to launch a projectile
|
|
/obj/item/projectile/proc/launch(atom/target, var/target_zone, var/x_offset=0, var/y_offset=0, var/angle_offset=0)
|
|
var/turf/curloc = get_turf(src)
|
|
var/turf/targloc = get_turf(target)
|
|
if (!istype(targloc) || !istype(curloc))
|
|
return 1
|
|
|
|
if(targloc == curloc) //Shooting something in the same turf
|
|
target.bullet_act(src, target_zone)
|
|
on_impact(target)
|
|
qdel(src)
|
|
return 0
|
|
|
|
original = target
|
|
def_zone = target_zone
|
|
|
|
spawn()
|
|
setup_trajectory(curloc, targloc, x_offset, y_offset, angle_offset) //plot the initial trajectory
|
|
process()
|
|
|
|
return 0
|
|
|
|
//called to launch a projectile from a gun
|
|
/obj/item/projectile/proc/launch_from_gun(atom/target, mob/user, obj/item/weapon/gun/launcher, var/target_zone, var/x_offset=0, var/y_offset=0)
|
|
if(user == target) //Shooting yourself
|
|
user.bullet_act(src, target_zone)
|
|
qdel(src)
|
|
return 0
|
|
|
|
loc = get_turf(user) //move the projectile out into the world
|
|
|
|
firer = user
|
|
shot_from = launcher.name
|
|
silenced = launcher.silenced
|
|
|
|
return launch(target, target_zone, x_offset, y_offset)
|
|
|
|
//Used to change the direction of the projectile in flight.
|
|
/obj/item/projectile/proc/redirect(var/new_x, var/new_y, var/atom/starting_loc, var/mob/new_firer=null)
|
|
var/turf/new_target = locate(new_x, new_y, src.z)
|
|
|
|
original = new_target
|
|
if(new_firer)
|
|
firer = src
|
|
|
|
setup_trajectory(starting_loc, new_target)
|
|
|
|
//Called when the projectile intercepts a mob. Returns 1 if the projectile hit the mob, 0 if it missed and should keep flying.
|
|
/obj/item/projectile/proc/attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier=0)
|
|
if(!istype(target_mob))
|
|
return
|
|
|
|
//roll to-hit
|
|
miss_modifier = max(15*(distance-2) - round(15*accuracy) + miss_modifier, 0)
|
|
var/hit_zone = get_zone_with_miss_chance(def_zone, target_mob, miss_modifier, ranged_attack=(distance > 1 || original != target_mob)) //if the projectile hits a target we weren't originally aiming at then retain the chance to miss
|
|
|
|
var/result = PROJECTILE_FORCE_MISS
|
|
if(hit_zone)
|
|
def_zone = hit_zone //set def_zone, so if the projectile ends up hitting someone else later (to be implemented), it is more likely to hit the same part
|
|
result = target_mob.bullet_act(src, def_zone)
|
|
|
|
if(result == PROJECTILE_FORCE_MISS && (can_miss == 0)) //if you're shooting at point blank you can't miss.
|
|
if(!silenced)
|
|
target_mob.visible_message("<span class='notice'>\The [src] misses [target_mob] narrowly!</span>")
|
|
return 0
|
|
|
|
//hit messages
|
|
if(silenced)
|
|
target_mob << "<span class='danger'>You've been hit in the [parse_zone(def_zone)] by \the [src]!</span>"
|
|
else
|
|
target_mob.visible_message("<span class='danger'>\The [target_mob] is hit by \the [src] in the [parse_zone(def_zone)]!</span>")//X has fired Y is now given by the guns so you cant tell who shot you if you could not see the shooter
|
|
|
|
//admin logs
|
|
if(!no_attack_log)
|
|
if(istype(firer, /mob))
|
|
|
|
var/attacker_message = "shot with \a [src.type]"
|
|
var/victim_message = "shot with \a [src.type]"
|
|
var/admin_message = "shot (\a [src.type])"
|
|
|
|
admin_attack_log(firer, target_mob, attacker_message, victim_message, admin_message)
|
|
else
|
|
target_mob.attack_log += "\[[time_stamp()]\] <b>UNKNOWN SUBJECT (No longer exists)</b> shot <b>[target_mob]/[target_mob.ckey]</b> with <b>\a [src]</b>"
|
|
msg_admin_attack("UNKNOWN shot [target_mob] ([target_mob.ckey]) with \a [src] (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[target_mob.x];Y=[target_mob.y];Z=[target_mob.z]'>JMP</a>)",ckey=key_name(target_mob))
|
|
|
|
//sometimes bullet_act() will want the projectile to continue flying
|
|
if (result == PROJECTILE_CONTINUE)
|
|
return 0
|
|
|
|
return 1
|
|
|
|
/obj/item/projectile/Bump(atom/A as mob|obj|turf|area, forced=0)
|
|
if(A == src)
|
|
return 0 //no
|
|
|
|
if(A == firer)
|
|
loc = A.loc
|
|
return 0 //cannot shoot yourself
|
|
|
|
if((bumped && !forced) || (A in permutated))
|
|
return 0
|
|
|
|
var/passthrough = 0 //if the projectile should continue flying
|
|
var/distance = get_dist(starting,loc)
|
|
|
|
bumped = 1
|
|
if(ismob(A))
|
|
var/mob/M = A
|
|
if(istype(A, /mob/living))
|
|
//if they have a neck grab on someone, that person gets hit instead
|
|
var/obj/item/weapon/grab/G = locate() in M
|
|
if(G && G.state >= GRAB_NECK)
|
|
visible_message("<span class='danger'>\The [M] uses [G.affecting] as a shield!</span>")
|
|
if(Bump(G.affecting, forced=1))
|
|
return //If Bump() returns 0 (keep going) then we continue on to attack M.
|
|
|
|
passthrough = !attack_mob(M, distance)
|
|
else
|
|
passthrough = 1 //so ghosts don't stop bullets
|
|
else
|
|
passthrough = (A.bullet_act(src, def_zone) == PROJECTILE_CONTINUE) //backwards compatibility
|
|
if(isturf(A))
|
|
for(var/obj/O in A)
|
|
O.bullet_act(src)
|
|
for(var/mob/living/M in A)
|
|
attack_mob(M, distance)
|
|
|
|
//penetrating projectiles can pass through things that otherwise would not let them
|
|
if(!passthrough && penetrating > 0)
|
|
if(check_penetrate(A))
|
|
passthrough = 1
|
|
penetrating--
|
|
|
|
//the bullet passes through a dense object!
|
|
if(passthrough)
|
|
//move ourselves onto A so we can continue on our way.
|
|
if(A)
|
|
if(istype(A, /turf))
|
|
loc = A
|
|
else
|
|
loc = A.loc
|
|
permutated.Add(A)
|
|
bumped = 0 //reset bumped variable!
|
|
return 0
|
|
|
|
//stop flying
|
|
on_impact(A)
|
|
|
|
density = 0
|
|
invisibility = 101
|
|
|
|
qdel(src)
|
|
return 1
|
|
|
|
/obj/item/projectile/ex_act()
|
|
return //explosions probably shouldn't delete projectiles
|
|
|
|
/obj/item/projectile/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
|
|
return 1
|
|
|
|
/obj/item/projectile/process()
|
|
var/first_step = 1
|
|
|
|
spawn while(src && src.loc)
|
|
if(kill_count-- < 1)
|
|
on_impact(src.loc) //for any final impact behaviours
|
|
qdel(src)
|
|
return
|
|
if((!( current ) || loc == current))
|
|
current = locate(min(max(x + xo, 1), world.maxx), min(max(y + yo, 1), world.maxy), z)
|
|
if((x == 1 || x == world.maxx || y == 1 || y == world.maxy))
|
|
qdel(src)
|
|
return
|
|
|
|
trajectory.increment() // increment the current location
|
|
location = trajectory.return_location(location) // update the locally stored location data
|
|
|
|
if(!location)
|
|
qdel(src) // if it's left the world... kill it
|
|
return
|
|
|
|
before_move()
|
|
Move(location.return_turf())
|
|
|
|
if(!bumped && !isturf(original))
|
|
if(loc == get_turf(original))
|
|
if(!(original in permutated))
|
|
if(Bump(original))
|
|
return
|
|
|
|
if(first_step)
|
|
muzzle_effect(effect_transform)
|
|
first_step = 0
|
|
else if(!bumped)
|
|
tracer_effect(effect_transform)
|
|
|
|
if(!hitscan)
|
|
sleep(step_delay) //add delay between movement iterations if it's not a hitscan weapon
|
|
|
|
/obj/item/projectile/proc/before_move()
|
|
return 0
|
|
|
|
/obj/item/projectile/proc/setup_trajectory(turf/startloc, turf/targloc, var/x_offset = 0, var/y_offset = 0)
|
|
// setup projectile state
|
|
starting = startloc
|
|
current = startloc
|
|
yo = targloc.y - startloc.y + y_offset
|
|
xo = targloc.x - startloc.x + x_offset
|
|
|
|
// trajectory dispersion
|
|
var/offset = 0
|
|
if(dispersion)
|
|
var/radius = round(dispersion*9, 1)
|
|
offset = rand(-radius, radius)
|
|
|
|
// plot the initial trajectory
|
|
trajectory = new()
|
|
trajectory.setup(starting, original, pixel_x, pixel_y, angle_offset=offset)
|
|
|
|
// generate this now since all visual effects the projectile makes can use it
|
|
effect_transform = new()
|
|
effect_transform.Scale(trajectory.return_hypotenuse(), 1)
|
|
effect_transform.Turn(-trajectory.return_angle()) //no idea why this has to be inverted, but it works
|
|
|
|
transform = turn(transform, -(trajectory.return_angle() + 90)) //no idea why 90 needs to be added, but it works
|
|
|
|
/obj/item/projectile/proc/muzzle_effect(var/matrix/T)
|
|
if(silenced)
|
|
return
|
|
|
|
if(ispath(muzzle_type))
|
|
var/obj/effect/projectile/M = new muzzle_type(get_turf(src))
|
|
|
|
if(istype(M))
|
|
M.set_transform(T)
|
|
M.pixel_x = location.pixel_x
|
|
M.pixel_y = location.pixel_y
|
|
M.activate()
|
|
|
|
/obj/item/projectile/proc/tracer_effect(var/matrix/M)
|
|
|
|
if(ispath(tracer_type))
|
|
var/obj/effect/projectile/P = new tracer_type(location.loc)
|
|
|
|
if(istype(P))
|
|
P.set_transform(M)
|
|
P.pixel_x = location.pixel_x
|
|
P.pixel_y = location.pixel_y
|
|
if(!hitscan)
|
|
P.activate(step_delay) //if not a hitscan projectile, remove after a single delay
|
|
else
|
|
P.activate()
|
|
|
|
/obj/item/projectile/proc/impact_effect(var/matrix/M)
|
|
if (!location)
|
|
return//Combat drones sometimes cause a runtime error with null location. Impact effect isnt important
|
|
if(ispath(tracer_type))
|
|
|
|
var/obj/effect/projectile/P = new impact_type(location.loc)
|
|
|
|
if(istype(P))
|
|
P.set_transform(M)
|
|
P.pixel_x = location.pixel_x
|
|
P.pixel_y = location.pixel_y
|
|
P.activate()
|
|
|
|
//"Tracing" projectile
|
|
/obj/item/projectile/test //Used to see if you can hit them.
|
|
invisibility = 101 //Nope! Can't see me!
|
|
yo = null
|
|
xo = null
|
|
var/result = 0 //To pass the message back to the gun.
|
|
|
|
/obj/item/projectile/test/Bump(atom/A as mob|obj|turf|area)
|
|
if(A == firer)
|
|
loc = A.loc
|
|
return //cannot shoot yourself
|
|
if(istype(A, /obj/item/projectile))
|
|
return
|
|
if(istype(A, /mob/living) || istype(A, /obj/mecha) || istype(A, /obj/vehicle))
|
|
result = 2 //We hit someone, return 1!
|
|
return
|
|
result = 1
|
|
return
|
|
|
|
/obj/item/projectile/test/launch(atom/target)
|
|
var/turf/curloc = get_turf(src)
|
|
var/turf/targloc = get_turf(target)
|
|
if(!curloc || !targloc)
|
|
return 0
|
|
|
|
original = target
|
|
|
|
//plot the initial trajectory
|
|
setup_trajectory(curloc, targloc)
|
|
return process(targloc)
|
|
|
|
/obj/item/projectile/test/process(var/turf/targloc)
|
|
var/safety = 100 // We really never should need this to last longer than this number of iterations.
|
|
while(!QDELING(src) && safety > 0) //Loop on through!
|
|
if(result)
|
|
return (result - 1)
|
|
if((!( targloc ) || loc == targloc))
|
|
targloc = locate(min(max(x + xo, 1), world.maxx), min(max(y + yo, 1), world.maxy), z) //Finding the target turf at map edge
|
|
|
|
trajectory.increment() // increment the current location
|
|
location = trajectory.return_location(location) // update the locally stored location data
|
|
|
|
Move(location.return_turf())
|
|
|
|
var/mob/living/M = locate() in get_turf(src)
|
|
if(istype(M)) //If there is someting living...
|
|
return 1 //Return 1
|
|
else
|
|
M = locate() in get_step(src,targloc)
|
|
if(istype(M))
|
|
return 1
|
|
|
|
safety--
|
|
|
|
if (safety < 0)
|
|
crash_with("test projectile process() maximum iterations exceeded, aborting!")
|
|
|
|
//Helper proc to check if you can hit them or not.
|
|
/proc/check_trajectory(atom/target as mob|obj, atom/firer as mob|obj, var/pass_flags=PASSTABLE|PASSGLASS|PASSGRILLE, flags=null)
|
|
if(!istype(target) || !istype(firer))
|
|
return 0
|
|
|
|
var/obj/item/projectile/test/trace = new /obj/item/projectile/test(get_turf(firer)) //Making the test....
|
|
|
|
//Set the flags and pass flags to that of the real projectile...
|
|
if(!isnull(flags))
|
|
trace.flags = flags
|
|
trace.pass_flags = pass_flags
|
|
|
|
var/output = trace.launch(target) //Test it!
|
|
qdel(trace) //No need for it anymore
|
|
return output //Send it back to the gun!
|