mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 18:22:39 +00:00
Adds a system for projectiles to inflict additional damage on specific kinds of simple animals. Adds special bullets for certain rifles which are very effective against 'animal' SAs such as giant spiders, bears, carp, and such. The bullets do less damage to other targets such as humanoids. Replaces the southern cross gun locker regular ammo with hunting ammo.
481 lines
17 KiB
Plaintext
481 lines
17 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
|
|
|
|
var/damage = 10
|
|
var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE, HALLOSS are the only things that should be in here
|
|
var/SA_bonus_damage = 0 // Some bullets inflict extra damage on simple animals.
|
|
var/SA_vulnerability = null // What kind of simple animal the above bonus damage should be applied to. Set to null to apply to all SAs.
|
|
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/incendiary = 0 //1 for ignite on hit, 2 for trail of fire. 3 maybe later for burst of fire around the impact point. - Mech
|
|
var/flammability = 0 //Amount of fire stacks to add for the above.
|
|
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/reflected = 0 // This should be set to 1 if reflected by any means, to prevent infinite reflections.
|
|
|
|
embed_chance = 0 //Base chance for a projectile to embed
|
|
|
|
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/vacuum_traversal = 1 //Determines if the projectile can exist in vacuum, if false, the projectile will be deleted if it enters vacuum.
|
|
|
|
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
|
|
L.apply_effects(stun, weaken, paralyze, irradiate, stutter, eyeblur, drowsy, agony, blocked, incendiary, flammability) // add in AGONY!
|
|
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_chance == 0 || 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)
|
|
on_impact(user)
|
|
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 + round(15*target_mob.get_evasion()), 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)
|
|
if(!silenced)
|
|
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
|
|
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
|
|
if(target_mob) // Sometimes the target_mob gets gibbed or something.
|
|
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>)")
|
|
|
|
//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)
|
|
if(G.affecting.stat == DEAD)
|
|
var/shield_chance = min(80, (30 * (M.mob_size / 10))) //Small mobs have a harder time keeping a dead body as a shield than a human-sized one. Unathi would have an easier job, if they are made to be SIZE_LARGE in the future. -Mech
|
|
if(prob(shield_chance))
|
|
visible_message("<span class='danger'>\The [M] uses [G.affecting] as a shield!</span>")
|
|
if(Bump(G.affecting, forced=1))
|
|
return
|
|
else
|
|
visible_message("<span class='danger'>\The [M] tries to use [G.affecting] as a shield, but fails!</span>")
|
|
else
|
|
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
|
|
update_light() //energy projectiles will look glowy and fun
|
|
|
|
if(!location)
|
|
qdel(src) // if it's left the world... kill it
|
|
return
|
|
|
|
if (is_below_sound_pressure(get_turf(src)) && !vacuum_traversal) //Deletes projectiles that aren't supposed to bein vacuum if they leave pressurised areas
|
|
qdel(src)
|
|
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(incendiary >= 2) //This should cover the bases of 'Why is there fuel here?' in a much cleaner way than previous.
|
|
if(src && src.loc) //Safety.
|
|
if(!src.loc.density)
|
|
var/trail_volume = (flammability * 0.20)
|
|
new /obj/effect/decal/cleanable/liquid_fuel/flamethrower_fuel(src.loc, trail_volume, src.dir)
|
|
|
|
if(!hitscan)
|
|
sleep(step_delay) //add delay between movement iterations if it's not a hitscan weapon
|
|
|
|
/obj/item/projectile/proc/before_move()
|
|
return
|
|
|
|
/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.update_light()
|
|
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
|
|
P.update_light()
|
|
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(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.update_light()
|
|
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, /obj/structure/foamedmetal)) //Turrets can detect through foamed metal, but will have to blast through it. Similar to windows, if someone runs behind it, a person should probably just not shoot.
|
|
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)
|
|
while(src) //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
|
|
|
|
//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!
|