This commit is contained in:
Ghommie
2020-06-23 03:12:49 +02:00
736 changed files with 240884 additions and 242590 deletions
+12 -8
View File
@@ -1,17 +1,21 @@
/obj/item/ammo_casing/proc/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from)
distro += variance
for (var/i = max(1, pellets), i > 0, i--)
var/targloc = get_turf(target)
ready_proj(target, user, quiet, zone_override, fired_from)
var/targloc = get_turf(target)
ready_proj(target, user, quiet, zone_override, fired_from)
if(pellets == 1)
if(distro) //We have to spread a pixel-precision bullet. throw_proj was called before so angles should exist by now...
if(randomspread)
spread = round((rand() - 0.5) * distro)
else //Smart spread
spread = round((i / pellets - 0.5) * distro)
spread = round(1 - 0.5) * distro
if(!throw_proj(target, targloc, user, params, spread))
return 0
if(i > 1)
newshot()
return FALSE
else
if(isnull(BB))
return FALSE
AddComponent(/datum/component/pellet_cloud, projectile_type, pellets)
SEND_SIGNAL(src, COMSIG_PELLET_CLOUD_INIT, target, user, fired_from, randomspread, spread, zone_override, params, distro)
if(click_cooldown_override)
user.changeNext_move(click_cooldown_override)
else
@@ -31,7 +35,7 @@
else
BB.def_zone = user.zone_selected
BB.suppressed = quiet
if(isgun(fired_from))
var/obj/item/gun/G = fired_from
BB.damage *= G.projectile_damage_multiplier
@@ -21,3 +21,16 @@
name = "1.95x129mm incendiary bullet casing"
desc = "A 1.95x129mm bullet casing designed with a chemical-filled capsule on the tip that when bursted, reacts with the atmosphere to produce a fireball, engulfing the target in flames."
projectile_type = /obj/item/projectile/bullet/incendiary/mm195x129
/obj/item/ammo_casing/mm712x82/match
name = "7.12x82mm match bullet casing"
desc = "A 7.12x82mm bullet casing manufactured to unfailingly high standards, you could pull off some cool trickshots with this."
projectile_type = /obj/item/projectile/bullet/mm712x82_match
/obj/item/projectile/bullet/mm712x82_match
name = "7.12x82mm match bullet"
damage = 40
ricochets_max = 2
ricochet_chance = 60
ricochet_auto_aim_range = 4
ricochet_incidence_leeway = 35
@@ -44,7 +44,6 @@
desc = "A 9mm incendiary bullet casing."
projectile_type = /obj/item/projectile/bullet/incendiary/c9mm
// .50AE (Desert Eagle)
/obj/item/ammo_casing/a50AE
@@ -53,3 +52,16 @@
caliber = ".50"
projectile_type = /obj/item/projectile/bullet/a50AE
// .32 ACP (Improvised Pistol)
/obj/item/ammo_casing/c32acp
name = ".32 bullet casing"
desc = "A .32 bullet casing."
caliber = "c32acp"
projectile_type = /obj/item/projectile/bullet/c32acp
/obj/item/ammo_casing/r32acp
name = ".32 rubber bullet casing"
desc = "A .32 rubber bullet casing."
caliber = "c32acp"
projectile_type = /obj/item/projectile/bullet/r32acp
@@ -11,6 +11,12 @@
desc = "A .357 armor-piercing bullet casing."
projectile_type = /obj/item/projectile/bullet/a357/ap
/obj/item/ammo_casing/a357/match
name = ".357 match bullet casing"
desc = "A .357 bullet casing, manufactured to exceedingly high standards."
caliber = "357"
projectile_type = /obj/item/projectile/bullet/a357/match
// 7.62x38mmR (Nagant Revolver)
/obj/item/ammo_casing/n762
@@ -47,4 +53,19 @@
name = ".38 Iceblox bullet casing"
desc = "A .38 Iceblox bullet casing."
caliber = "38"
projectile_type = /obj/item/projectile/bullet/c38/iceblox
projectile_type = /obj/item/projectile/bullet/c38/iceblox
/obj/item/ammo_casing/c38/match
name = ".38 Match bullet casing"
desc = "A .38 bullet casing, manufactured to exceedingly high standards."
projectile_type = /obj/item/projectile/bullet/c38/match
/obj/item/ammo_casing/c38/match/bouncy
name = ".38 Rubber bullet casing"
desc = "A .38 rubber bullet casing, manufactured to exceedingly high standards."
projectile_type = /obj/item/projectile/bullet/c38/match/bouncy
/obj/item/ammo_casing/c38/dumdum
name = ".38 DumDum bullet casing"
desc = "A .38 DumDum bullet casing."
projectile_type = /obj/item/projectile/bullet/c38/dumdum
@@ -143,3 +143,12 @@
reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 5) //this and the acid equal about 25ish burn, not counting the minute toxin damage dealt by their metabolism, this makes each dart about as lethal as a stechkin shot in upfront damage
reagents.add_reagent(/datum/reagent/toxin/acid, 5)
reagents.add_reagent(/datum/reagent/consumable/frostoil, 10) //tempgun slowdown goes both ways and adds to the burn
/obj/item/ammo_casing/shotgun/incapacitate
name = "custom incapacitating shot"
desc = "A shotgun casing filled with... something. used to incapacitate targets."
icon_state = "bountyshell"
projectile_type = /obj/item/projectile/bullet/pellet/shotgun_incapacitate
pellets = 12//double the pellets, but half the stun power of each, which makes this best for just dumping right in someone's face.
variance = 25
custom_materials = list(/datum/material/iron=4000)
@@ -12,6 +12,15 @@
e_cost = 200
select_name = "kill"
/obj/item/ammo_casing/energy/lasergun/improvised
projectile_type = /obj/item/projectile/beam/weak/improvised
e_cost = 200
select_name = "kill"
/obj/item/ammo_casing/energy/lasergun/improvised/upgraded
projectile_type = /obj/item/projectile/beam/weak
e_cost = 100
/obj/item/ammo_casing/energy/laser/hos
e_cost = 100
@@ -7,6 +7,11 @@
max_ammo = 7
multiple_sprites = 1
/obj/item/ammo_box/a357/match
name = "speed loader (.357 Match)"
desc = "Designed to quickly reload revolvers. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with."
ammo_type = /obj/item/ammo_casing/a357/match
/obj/item/ammo_box/a357/ap
name = "speed loader (.357 AP)"
ammo_type = /obj/item/ammo_casing/a357/ap
@@ -40,6 +45,28 @@
desc = "Designed to quickly reload revolvers. Iceblox bullets contain a cryogenic payload."
ammo_type = /obj/item/ammo_casing/c38/iceblox
/obj/item/ammo_box/c38/dumdum
name = "speed loader (.38 DumDum)"
desc = "Designed to quickly reload revolvers. DumDum bullets shatter on impact and shred the target's innards, likely getting caught inside."
ammo_type = /obj/item/ammo_casing/c38/dumdum
/obj/item/ammo_box/c38/match
name = "speed loader (.38 Match)"
desc = "Designed to quickly reload revolvers. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with."
ammo_type = /obj/item/ammo_casing/c38/match
/obj/item/ammo_box/c32mm
name = "ammo box (.32 acp)"
desc = "Lethal .32 acp bullets, there's forty in the box."
ammo_type = /obj/item/ammo_casing/c32acp
max_ammo = 40
/obj/item/ammo_box/r32mm
name = "ammo box (rubber .32 acp)"
desc = "Non-lethal .32 acp bullets, there's forty in the box."
ammo_type = /obj/item/ammo_casing/r32acp
max_ammo = 40
/obj/item/ammo_box/c9mm
name = "ammo box (9mm)"
icon_state = "9mmbox"
@@ -20,3 +20,7 @@
/obj/item/ammo_box/magazine/mm195x129/update_icon()
..()
icon_state = "a762-[round(ammo_count(),10)]"
/obj/item/ammo_box/magazine/mm712x82/match
name = "box magazine (Match 7.12x82mm)"
ammo_type = /obj/item/ammo_casing/mm712x82/match
@@ -66,3 +66,15 @@
caliber = ".50"
max_ammo = 7
multiple_sprites = 1
/obj/item/ammo_box/magazine/m32acp
name = "pistol magazine (.32)"
desc = "A crudely construction pistol magazine that holds .32 ACP rounds. It looks like it can only fit eight bullets."
icon_state = "32acp"
ammo_type = /obj/item/ammo_casing/c32acp
caliber = "c32acp"
max_ammo = 8
multiple_sprites = 2
/obj/item/ammo_box/magazine/m32acp/empty
start_empty = 1
@@ -12,3 +12,105 @@
/obj/item/ammo_box/magazine/recharge/attack_self() //No popping out the "bullets"
return
// MWS Magazine //
/obj/item/ammo_box/magazine/mws_mag
name = "microbattery magazine"
desc = "A microbattery holder for the 'Big Iron'"
icon = 'icons/obj/ammo.dmi'
icon_state = "mws_mag"
caliber = "mws"
ammo_type = /obj/item/ammo_casing/mws_batt
start_empty = TRUE
max_ammo = 3
var/list/modes = list()
/obj/item/ammo_box/magazine/mws_mag/update_overlays()
.=..()
if(!stored_ammo.len)
return //Why bother
var/x_offset = 5
var/current = 0
for(var/B in stored_ammo)
var/obj/item/ammo_casing/mws_batt/batt = B
var/mutable_appearance/cap = mutable_appearance(icon, "[initial(icon_state)]_cap", color = batt.type_color)
cap.pixel_x = current * x_offset //Caps don't need a pixel_y offset
. += cap
if(batt.cell.charge > 0)
var/ratio = CEILING(clamp(batt.cell.charge / batt.cell.maxcharge, 0, 1) * 4, 1) //4 is how many lights we have a sprite for
var/mutable_appearance/charge = mutable_appearance(icon, "[initial(icon_state)]_charge-[ratio]", color = "#29EAF4") //Could use battery color but eh.
charge.pixel_x = current * x_offset
. += charge
current++ //Increment for offsets
// MWS Batteries //
/obj/item/ammo_casing/mws_batt
name = "\'MWS\' microbattery - UNKNOWN"
desc = "A miniature battery for an energy weapon."
icon = 'icons/obj/ammo.dmi'
icon_state = "mws_batt"
slot_flags = SLOT_BELT | SLOT_EARS
throwforce = 1
caliber = "mws"
var/type_color = null
var/type_name = null
var/obj/item/stock_parts/cell/cell
var/cell_type = /obj/item/stock_parts/cell{charge = 600; maxcharge = 600}
var/e_cost = 100
projectile_type = /obj/item/projectile/beam
/obj/item/ammo_casing/mws_batt/Initialize()
. = ..()
pixel_x = rand(-10, 10)
pixel_y = rand(-10, 10)
cell = new cell_type(src)
cell.give(cell.maxcharge)
update_icon()
/obj/item/ammo_casing/mws_batt/update_overlays()
.=..()
var/mutable_appearance/ends = mutable_appearance(icon, "[initial(icon_state)]_ends", color = type_color)
. += ends
/obj/item/ammo_casing/mws_batt/get_cell()
return cell
/obj/item/ammo_casing/mws_batt/proc/chargeshot()
if(cell.charge >= e_cost)
cell.use(e_cost)
newshot()
return
// Specific batteries //
/obj/item/ammo_casing/mws_batt/lethal
name = "'MWS' microbattery - LETHAL"
type_color = "#bf3d3d"
type_name = "<span class='lethal'>LETHAL</span>"
projectile_type = /obj/item/projectile/beam
/obj/item/ammo_casing/mws_batt/stun
name = "'MWS' microbattery - STUN"
type_color = "#0f81bc"
type_name = "<span class='stun'>STUN</span>"
projectile_type = /obj/item/projectile/beam/disabler
/obj/item/ammo_casing/mws_batt/xray
name = "'MWS' microbattery - XRAY"
type_color = "#32c025"
type_name = "<span class='xray'>XRAY</span>"
projectile_type = /obj/item/projectile/beam/xray
/obj/item/ammo_casing/mws_batt/ion
name = "'MWS' microbattery - ION"
type_color = "#d084d6"
type_name = "<span class='ion'>ION</span>"
projectile_type = /obj/item/projectile/ion
@@ -50,3 +50,8 @@
name = "riot shotgun internal magazine"
ammo_type = /obj/item/ammo_casing/shotgun/rubbershot
max_ammo = 6
/obj/item/ammo_box/magazine/internal/shot/bounty
name = "triple-barrel shotgun internal magazine"
ammo_type = /obj/item/ammo_casing/shotgun/incapacitate
max_ammo = 3
+54 -31
View File
@@ -15,7 +15,7 @@
throw_speed = 3
throw_range = 5
force = 5
item_flags = NEEDS_PERMIT | NO_ATTACK_CHAIN_SOFT_STAMCRIT
item_flags = NEEDS_PERMIT
attack_verb = list("struck", "hit", "bashed")
var/fire_sound = "gunshot"
@@ -128,7 +128,7 @@
zoom(user, FALSE) //we can only stay zoomed in if it's in our hands //yeah and we only unzoom if we're actually zoomed using the gun!!
//called after the gun has successfully fired its chambered ammo.
/obj/item/gun/proc/process_chamber()
/obj/item/gun/proc/process_chamber(mob/living/user)
return FALSE
//check if there's enough ammo/energy/whatever to shoot one time
@@ -140,12 +140,13 @@
to_chat(user, "<span class='danger'>*click*</span>")
playsound(src, "gun_dry_fire", 30, 1)
/obj/item/gun/proc/shoot_live_shot(mob/living/user as mob|obj, pointblank = 0, mob/pbtarget = null, message = 1)
/obj/item/gun/proc/shoot_live_shot(mob/living/user, pointblank = FALSE, mob/pbtarget, message = 1, stam_cost = 0)
if(recoil)
shake_camera(user, recoil + 1, recoil)
if(isliving(user)) //CIT CHANGE - makes gun recoil cause staminaloss
user.adjustStaminaLossBuffered(getstamcost(user)*(firing && burst_size >= 2 ? 1/burst_size : 1)) //CIT CHANGE - ditto
if(stam_cost) //CIT CHANGE - makes gun recoil cause staminaloss
var/safe_cost = clamp(stam_cost, 0, STAMINA_NEAR_CRIT - user.getStaminaLoss())*(firing && burst_size >= 2 ? 1/burst_size : 1)
user.adjustStaminaLossBuffered(safe_cost) //CIT CHANGE - ditto
if(suppressed)
playsound(user, fire_sound, 10, 1)
@@ -172,9 +173,10 @@
return
if(firing)
return
if(IS_STAMCRIT(user)) //respect stamina softcrit
to_chat(user, "<span class='warning'>You are too exhausted to fire [src]!</span>")
return
var/stamloss = user.getStaminaLoss()
if(stamloss >= STAMINA_NEAR_SOFTCRIT) //The more tired you are, the less damage you do.
var/penalty = (stamloss - STAMINA_NEAR_SOFTCRIT)/(STAMINA_NEAR_CRIT - STAMINA_NEAR_SOFTCRIT)*STAM_CRIT_GUN_DELAY
user.changeNext_move(CLICK_CD_RANGE+(CLICK_CD_RANGE*penalty))
if(flag) //It's adjacent, is the user, or is on the user's person
if(target in user.contents) //can't shoot stuff inside us.
return
@@ -216,7 +218,7 @@
var/loop_counter = 0
if(user)
bonus_spread += getinaccuracy(user) //CIT CHANGE - adds bonus spread while not aiming
bonus_spread = getinaccuracy(user, bonus_spread, stamloss) //CIT CHANGE - adds bonus spread while not aiming
if(ishuman(user) && user.a_intent == INTENT_HARM && weapon_weight <= WEAPON_LIGHT)
var/mob/living/carbon/human/H = user
for(var/obj/item/gun/G in H.held_items)
@@ -225,9 +227,11 @@
else if(G.can_trigger_gun(user))
bonus_spread += 24 * G.weapon_weight * G.dualwield_spread_mult
loop_counter++
addtimer(CALLBACK(G, /obj/item/gun.proc/process_fire, target, user, TRUE, params, null, bonus_spread), loop_counter)
var/stam_cost = G.getstamcost(user)
addtimer(CALLBACK(G, /obj/item/gun.proc/process_fire, target, user, TRUE, params, null, bonus_spread, stam_cost), loop_counter)
process_fire(target, user, TRUE, params, null, bonus_spread)
var/stam_cost = getstamcost(user)
process_fire(target, user, TRUE, params, null, bonus_spread, stam_cost)
/obj/item/gun/can_trigger_gun(mob/living/user)
. = ..()
@@ -258,21 +262,21 @@
/obj/item/gun/proc/on_cooldown()
return busy_action || firing || ((last_fire + fire_delay) > world.time)
/obj/item/gun/proc/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
/obj/item/gun/proc/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0, stam_cost = 0)
add_fingerprint(user)
if(on_cooldown())
return
firing = TRUE
. = do_fire(target, user, message, params, zone_override, bonus_spread)
. = do_fire(target, user, message, params, zone_override, bonus_spread, stam_cost)
firing = FALSE
last_fire = world.time
if(user)
user.update_inv_hands()
SEND_SIGNAL(user, COMSIG_LIVING_GUN_PROCESS_FIRE, target, params, zone_override)
SEND_SIGNAL(user, COMSIG_LIVING_GUN_PROCESS_FIRE, target, params, zone_override, bonus_spread, stam_cost)
/obj/item/gun/proc/do_fire(atom/target, mob/living/user, message = TRUE, params, zone_override = "", bonus_spread = 0)
/obj/item/gun/proc/do_fire(atom/target, mob/living/user, message = TRUE, params, zone_override = "", bonus_spread = 0, stam_cost = 0)
var/sprd = 0
var/randomized_gun_spread = 0
var/rand_spr = rand()
@@ -290,7 +294,7 @@
sleep(burst_shot_delay)
if(QDELETED(src))
break
do_burst_shot(user, target, message, params, zone_override, sprd, randomized_gun_spread, randomized_bonus_spread, rand_spr, i)
do_burst_shot(user, target, message, params, zone_override, sprd, randomized_gun_spread, randomized_bonus_spread, rand_spr, i, stam_cost)
else
if(chambered)
sprd = round((rand() - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread))
@@ -300,19 +304,19 @@
return
else
if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot
shoot_live_shot(user, 1, target, message)
shoot_live_shot(user, 1, target, message, stam_cost)
else
shoot_live_shot(user, 0, target, message)
shoot_live_shot(user, 0, target, message, stam_cost)
else
shoot_with_empty_chamber(user)
return
process_chamber()
process_chamber(user)
update_icon()
SSblackbox.record_feedback("tally", "gun_fired", 1, type)
return TRUE
/obj/item/gun/proc/do_burst_shot(mob/living/user, atom/target, message = TRUE, params=null, zone_override = "", sprd = 0, randomized_gun_spread = 0, randomized_bonus_spread = 0, rand_spr = 0, iteration = 0)
/obj/item/gun/proc/do_burst_shot(mob/living/user, atom/target, message = TRUE, params=null, zone_override = "", sprd = 0, randomized_gun_spread = 0, randomized_bonus_spread = 0, rand_spr = 0, iteration = 0, stam_cost = 0)
if(!user || !firing)
firing = FALSE
return FALSE
@@ -336,33 +340,34 @@
return FALSE
else
if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot
shoot_live_shot(user, 1, target, message)
shoot_live_shot(user, 1, target, message, stam_cost)
else
shoot_live_shot(user, 0, target, message)
shoot_live_shot(user, 0, target, message, stam_cost)
if (iteration >= burst_size)
firing = FALSE
else
shoot_with_empty_chamber(user)
firing = FALSE
return FALSE
process_chamber()
process_chamber(user)
update_icon()
return TRUE
/obj/item/gun/attack(mob/M as mob, mob/user)
/obj/item/gun/attack(mob/living/M, mob/user)
if(user.a_intent == INTENT_HARM) //Flogging
if(bayonet)
M.attackby(bayonet, user)
attack_delay_done = TRUE
return
else
return ..()
return
attack_delay_done = TRUE //we are firing the gun, not bashing people with its butt.
/obj/item/gun/attack_obj(obj/O, mob/user)
if(user.a_intent == INTENT_HARM)
if(bayonet)
O.attackby(bayonet, user)
return
return TRUE
return ..()
/obj/item/gun/attackby(obj/item/I, mob/user, params)
@@ -498,7 +503,7 @@
if(chambered && chambered.BB)
chambered.BB.damage *= 5
process_fire(target, user, TRUE, params)
process_fire(target, user, TRUE, params, stam_cost = getstamcost(user))
/obj/item/gun/proc/unlock() //used in summon guns and as a convience for admins
if(pin)
@@ -566,7 +571,25 @@
chambered = null
update_icon()
/obj/item/gun/proc/getinaccuracy(mob/user)
if(SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
return ((weapon_weight * 25) * inaccuracy_modifier)
return 0
/obj/item/gun/proc/getinaccuracy(mob/living/user, bonus_spread, stamloss)
if(inaccuracy_modifier == 0)
return bonus_spread
var/base_inaccuracy = weapon_weight * 25 * inaccuracy_modifier
var/aiming_delay = 0 //Otherwise aiming would be meaningless for slower guns such as sniper rifles and launchers.
if(fire_delay)
var/penalty = (last_fire + GUN_AIMING_TIME + fire_delay) - world.time
if(penalty > 0) //Yet we only penalize users firing it multiple times in a haste. fire_delay isn't necessarily cumbersomeness.
aiming_delay = penalty
if(SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE)) //To be removed in favor of something less tactless later.
base_inaccuracy /= 1.5
if(stamloss > STAMINA_NEAR_SOFTCRIT) //This can null out the above bonus.
base_inaccuracy *= 1 + (stamloss - STAMINA_NEAR_SOFTCRIT)/(STAMINA_NEAR_CRIT - STAMINA_NEAR_SOFTCRIT)*0.5
var/mult = max((GUN_AIMING_TIME + aiming_delay + user.last_click_move - world.time)/GUN_AIMING_TIME, -0.5) //Yes, there is a bonus for taking time aiming.
if(mult < 0) //accurate weapons should provide a proper bonus with negative inaccuracy. the opposite is true too.
mult *= 1/inaccuracy_modifier
return max(bonus_spread + (base_inaccuracy * mult), 0) //no negative spread.
/obj/item/gun/proc/getstamcost(mob/living/carbon/user)
. = recoil
if(user && !user.has_gravity())
. = recoil*5
+1 -1
View File
@@ -25,7 +25,7 @@
else
icon_state = "[initial(icon_state)][suppressed ? "-suppressed" : ""][sawn_off ? "-sawn" : ""]"
/obj/item/gun/ballistic/process_chamber(empty_chamber = 1)
/obj/item/gun/ballistic/process_chamber(mob/living/user, empty_chamber = 1)
var/obj/item/ammo_casing/AC = chambered //Find chambered round
if(istype(AC)) //there's a chambered round
if(casing_ejector)
@@ -365,7 +365,7 @@
can_unsuppress = TRUE
can_suppress = TRUE
w_class = WEIGHT_CLASS_NORMAL
inaccuracy_modifier = 0
inaccuracy_modifier = 0.5
zoomable = TRUE
zoom_amt = 10 //Long range, enough to see in front of you, but no tiles behind you.
zoom_out_amt = 13
@@ -53,6 +53,7 @@
..()
/obj/item/minigunpack/dropped(mob/user)
. = ..()
if(armed)
user.dropItemToGround(gun, TRUE)
@@ -125,12 +126,13 @@
return
/obj/item/gun/ballistic/minigun/dropped(mob/user)
. = ..()
if(ammo_pack)
ammo_pack.attach_gun(user)
else
qdel(src)
/obj/item/gun/ballistic/minigun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
/obj/item/gun/ballistic/minigun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0, stam_cost = 0)
if(ammo_pack)
if(ammo_pack.overheat < ammo_pack.overheat_max)
ammo_pack.overheat += burst_size
@@ -144,4 +146,5 @@
. = ..()
/obj/item/gun/ballistic/minigun/dropped(mob/living/user)
. = ..()
ammo_pack.attach_gun(user)
@@ -6,7 +6,7 @@
name = "grenade launcher"
icon_state = "dshotgun-sawn"
item_state = "gun"
inaccuracy_modifier = 0
inaccuracy_modifier = 0.5
mag_type = /obj/item/ammo_box/magazine/internal/grenadelauncher
fire_sound = 'sound/weapons/grenadelaunch.ogg'
w_class = WEIGHT_CLASS_NORMAL
@@ -87,7 +87,7 @@
pin = /obj/item/firing_pin/implant/pindicate
burst_size = 1
fire_delay = 0
inaccuracy_modifier = 0
inaccuracy_modifier = 0.25
casing_ejector = FALSE
weapon_weight = WEAPON_HEAVY
magazine_wording = "rocket"
@@ -144,13 +144,13 @@
user.visible_message("<span class='warning'>[user] aims [src] at the ground! It looks like [user.p_theyre()] performing a sick rocket jump!</span>", \
"<span class='userdanger'>You aim [src] at the ground to perform a bisnasty rocket jump...</span>")
if(can_shoot())
user.notransform = TRUE
user.mob_transforming = TRUE
playsound(src, 'sound/vehicles/rocketlaunch.ogg', 80, 1, 5)
animate(user, pixel_z = 300, time = 30, easing = LINEAR_EASING)
sleep(70)
animate(user, pixel_z = 0, time = 5, easing = LINEAR_EASING)
sleep(5)
user.notransform = FALSE
user.mob_transforming = FALSE
process_fire(user, user, TRUE)
if(!QDELETED(user)) //if they weren't gibbed by the explosion, take care of them for good.
user.gib()
@@ -14,7 +14,7 @@
spread = 0
recoil = 0.1
casing_ejector = FALSE
inaccuracy_modifier = 0
inaccuracy_modifier = 0.15
dualwield_spread_mult = 1.4
weapon_weight = WEAPON_MEDIUM
w_class = WEIGHT_CLASS_BULKY
@@ -46,7 +46,7 @@
return 0
. = ..()
/obj/item/gun/ballistic/automatic/magrifle/shoot_live_shot()
/obj/item/gun/ballistic/automatic/magrifle/shoot_live_shot(mob/living/user, pointblank = FALSE, mob/pbtarget, message = 1, stam_cost = 0)
var/obj/item/ammo_casing/caseless/magnetic/shot = chambered
cell.use(shot.energy_cost)
. = ..()
@@ -115,6 +115,7 @@
icon_state = "flatgun"
/obj/item/gun/ballistic/automatic/pistol/stickman/pickup(mob/living/user)
. = ..()
to_chat(user, "<span class='notice'>As you try to pick up [src], it slips out of your grip..</span>")
if(prob(50))
to_chat(user, "<span class='notice'>..and vanishes from your vision! Where the hell did it go?</span>")
@@ -155,3 +156,19 @@
name = "Syndicate Anti Tank Pistol"
desc = "A massively impractical and silly monstrosity of a pistol that fires .50 calliber rounds. The recoil is likely to dislocate a variety of joints without proper bracing."
pin = /obj/item/firing_pin/implant/pindicate
////////////Improvised Pistol////////////
/obj/item/gun/ballistic/automatic/pistol/improvised
name = "Improvised Pistol"
desc = "An improvised pocket-sized pistol that fires .32 calibre rounds. It looks incredibly flimsy."
icon_state = "ipistol"
item_state = "pistol"
mag_type = /obj/item/ammo_box/magazine/m32acp
fire_delay = 7.5
can_suppress = FALSE
w_class = WEIGHT_CLASS_SMALL
spread = 15 // Keep the spread between 15 and 20. This hardlocks it into being a mid-range pistol, the magazine size means you're allowed to miss. Fills the mid-range niche that slugs/rifle and buckshot doesn't fill.
/obj/item/gun/ballistic/automatic/pistol/improvised/nomag
spawnwithmagazine = FALSE // For crafting as you shouldn't get eight bullets for free otherwise people will reaper reload.
@@ -102,7 +102,7 @@
. = ..()
safe_calibers = magazine.caliber
/obj/item/gun/ballistic/revolver/detective/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
/obj/item/gun/ballistic/revolver/detective/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0, stam_cost = 0)
if(chambered && !(chambered.caliber in safe_calibers))
if(prob(70 - (magazine.ammo_count() * 10))) //minimum probability of 10, maximum of 60
playsound(user, fire_sound, 50, 1)
@@ -242,7 +242,7 @@
user.visible_message("<span class='danger'>*click*</span>")
playsound(src, "gun_dry_fire", 30, 1)
/obj/item/gun/ballistic/revolver/russian/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
/obj/item/gun/ballistic/revolver/russian/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0, stam_cost = 0)
add_fingerprint(user)
playsound(src, "gun_dry_fire", 30, TRUE)
user.visible_message("<span class='danger'>[user.name] tries to fire \the [src] at the same time, but only succeeds at looking like an idiot.</span>", "<span class='danger'>\The [src]'s anti-combat mechanism prevents you from firing it at the same time!</span>")
@@ -319,7 +319,6 @@
/obj/item/gun/ballistic/revolver/doublebarrel/improvised
name = "improvised shotgun"
desc = "Essentially a tube that aims shotgun shells."
desc = "A shoddy break-action breechloaded shotgun. Its lacklustre construction will probably result in it hurting people less than a normal shotgun."
icon_state = "ishotgun"
item_state = "shotgun"
@@ -330,8 +329,9 @@
mag_type = /obj/item/ammo_box/magazine/internal/shot/improvised
sawn_desc = "I'm just here for the gasoline."
unique_reskin = null
projectile_damage_multiplier = 0.8
projectile_damage_multiplier = 0.9
var/slung = FALSE
weapon_weight = WEAPON_HEAVY
/obj/item/gun/ballistic/revolver/doublebarrel/improvised/attackby(obj/item/A, mob/user, params)
..()
@@ -358,7 +358,7 @@
/obj/item/gun/ballistic/revolver/doublebarrel/improvised/sawn
name = "sawn-off improvised shotgun"
desc = "A single-shot shotgun. Better not miss."
desc = "The barrel and stock have been sawn and filed down; it can fit in backpacks. You still need two hands to fire this, if you value unbroken wrists."
icon_state = "ishotgun"
item_state = "gun"
w_class = WEIGHT_CLASS_NORMAL
@@ -377,3 +377,114 @@
user.emote("scream")
user.drop_all_held_items()
user.DefaultCombatKnockdown(80)
// -------------- HoS Modular Weapon System -------------
// ---------- Code originally from VoreStation ----------
/obj/item/gun/ballistic/revolver/mws
name = "MWS-01 'Big Iron'"
desc = "Modular Weapons System"
icon = 'icons/obj/guns/projectile.dmi'
icon_state = "mws"
fire_sound = 'sound/weapons/Taser.ogg'
mag_type = /obj/item/ammo_box/magazine/mws_mag
spawnwithmagazine = FALSE
recoil = 0
var/charge_sections = 6
/obj/item/gun/ballistic/revolver/mws/examine(mob/user)
. = ..()
. += "<span class='notice'>Alt-click to remove the magazine.</span>"
/obj/item/gun/ballistic/revolver/mws/shoot_with_empty_chamber(mob/living/user as mob|obj)
process_chamber(user)
if(!chambered || !chambered.BB)
to_chat(user, "<span class='danger'>*click*</span>")
playsound(src, "gun_dry_fire", 30, 1)
/obj/item/gun/ballistic/revolver/mws/process_chamber(mob/living/user)
if(chambered && !chambered.BB) //if BB is null, i.e the shot has been fired...
var/obj/item/ammo_casing/mws_batt/shot = chambered
if(shot.cell.charge >= shot.e_cost)
shot.chargeshot()
else
for(var/B in magazine.stored_ammo)
var/obj/item/ammo_casing/mws_batt/other_batt = B
if(istype(other_batt,shot) && other_batt.cell.charge >= other_batt.e_cost)
switch_to(other_batt, user)
break
update_icon()
/obj/item/gun/ballistic/revolver/mws/proc/switch_to(obj/item/ammo_casing/mws_batt/new_batt, mob/living/user)
if(ishuman(user))
if(chambered && new_batt.type == chambered.type)
to_chat(user,"<span class='warning'>[src] is now using the next [new_batt.type_name] power cell.</span>")
else
to_chat(user,"<span class='warning'>[src] is now firing [new_batt.type_name].</span>")
chambered = new_batt
update_icon()
/obj/item/gun/ballistic/revolver/mws/attack_self(mob/living/user)
if(!chambered)
return
var/list/stored_ammo = magazine.stored_ammo
if(stored_ammo.len == 1)
return //silly you.
//Find an ammotype that ISN'T the same, or exhaust the list and don't change.
var/our_slot = stored_ammo.Find(chambered)
for(var/index in 1 to stored_ammo.len)
var/true_index = ((our_slot + index - 1) % stored_ammo.len) + 1 // Stupid ONE BASED lists!
var/obj/item/ammo_casing/mws_batt/next_batt = stored_ammo[true_index]
if(chambered != next_batt && !istype(next_batt, chambered.type) && next_batt.cell.charge >= next_batt.e_cost)
switch_to(next_batt, user)
break
/obj/item/gun/ballistic/revolver/mws/AltClick(mob/living/user)
.=..()
if(magazine)
user.put_in_hands(magazine)
magazine.update_icon()
if(magazine.ammo_count())
playsound(src, 'sound/weapons/gun_magazine_remove_full.ogg', 70, 1)
else
playsound(src, "gun_remove_empty_magazine", 70, 1)
magazine = null
to_chat(user, "<span class='notice'>You pull the magazine out of [src].</span>")
if(chambered)
chambered = null
update_icon()
/obj/item/gun/ballistic/revolver/mws/update_overlays()
.=..()
if(!chambered)
return
var/obj/item/ammo_casing/mws_batt/batt = chambered
var/batt_color = batt.type_color //Used many times
//Mode bar
var/image/mode_bar = image(icon, icon_state = "[initial(icon_state)]_type")
mode_bar.color = batt_color
. += mode_bar
//Barrel color
var/mutable_appearance/barrel_color = mutable_appearance(icon, "[initial(icon_state)]_barrel", color = batt_color)
barrel_color.alpha = 150
. += barrel_color
//Charge bar
var/ratio = can_shoot() ? CEILING(clamp(batt.cell.charge / batt.cell.maxcharge, 0, 1) * charge_sections, 1) : 0
for(var/i = 0, i < ratio, i++)
var/mutable_appearance/charge_bar = mutable_appearance(icon, "[initial(icon_state)]_charge", color = batt_color)
charge_bar.pixel_x = i
. += charge_bar
@@ -23,7 +23,7 @@
A.update_icon()
update_icon()
/obj/item/gun/ballistic/shotgun/process_chamber(empty_chamber = 0)
/obj/item/gun/ballistic/shotgun/process_chamber(mob/living/user, empty_chamber = 0)
return ..() //changed argument value
/obj/item/gun/ballistic/shotgun/chamber_round()
@@ -116,7 +116,7 @@
icon_state = "moistnugget"
item_state = "moistnugget"
slot_flags = 0 //no ITEM_SLOT_BACK sprite, alas
inaccuracy_modifier = 0
inaccuracy_modifier = 0.5
mag_type = /obj/item/ammo_box/magazine/internal/boltaction
var/bolt_open = FALSE
can_bayonet = TRUE
@@ -131,6 +131,7 @@
desc = "A bolt-action breechloaded rifle that takes 7.62mm bullets."
mag_type = /obj/item/ammo_box/magazine/internal/boltaction/improvised
can_bayonet = FALSE
var/slung = FALSE
/obj/item/gun/ballistic/shotgun/boltaction/pump(mob/M)
playsound(M, 'sound/weapons/shotgunpump.ogg', 60, 1)
@@ -152,6 +153,22 @@
. = ..()
. += "The bolt is [bolt_open ? "open" : "closed"]."
/obj/item/gun/ballistic/shotgun/boltaction/improvised/attackby(obj/item/A, mob/user, params)
..()
if(istype(A, /obj/item/stack/cable_coil) && !sawn_off)
if(A.use_tool(src, user, 0, 10, max_level = JOB_SKILL_BASIC))
slot_flags = ITEM_SLOT_BACK
to_chat(user, "<span class='notice'>You tie the lengths of cable to the rifle, making a sling.</span>")
slung = TRUE
update_icon()
else
to_chat(user, "<span class='warning'>You need at least ten lengths of cable if you want to make a sling!</span>")
/obj/item/gun/ballistic/shotgun/boltaction/improvised/update_icon()
..()
if(slung)
icon_state += "sling"
/obj/item/gun/ballistic/shotgun/boltaction/enchanted
name = "enchanted bolt action rifle"
desc = "Careful not to lose your head."
@@ -191,7 +208,7 @@
/obj/item/gun/ballistic/shotgun/boltaction/enchanted/attack_self()
return
/obj/item/gun/ballistic/shotgun/boltaction/enchanted/shoot_live_shot(mob/living/user as mob|obj, pointblank = 0, mob/pbtarget = null, message = 1)
/obj/item/gun/ballistic/shotgun/boltaction/enchanted/shoot_live_shot(mob/living/user, pointblank = FALSE, mob/pbtarget, message = 1, stam_cost = 0)
..()
if(guns_left)
var/obj/item/gun/ballistic/shotgun/boltaction/enchanted/GUN = new gun_type
@@ -205,7 +222,7 @@
// Automatic Shotguns//
/obj/item/gun/ballistic/shotgun/automatic/shoot_live_shot(mob/living/user as mob|obj)
/obj/item/gun/ballistic/shotgun/automatic/shoot_live_shot(mob/living/user, pointblank = FALSE, mob/pbtarget, message = 1, stam_cost = 0)
..()
src.pump(user)
@@ -303,3 +320,18 @@
return TRUE
// DOUBLE BARRELED SHOTGUN and IMPROVISED SHOTGUN are in revolver.dm
/obj/item/gun/ballistic/shotgun/doublebarrel/hook
name = "hook modified sawn-off shotgun"
desc = "Range isn't an issue when you can bring your victim to you."
icon_state = "hookshotgun"
item_state = "shotgun"
mag_type = /obj/item/ammo_box/magazine/internal/shot/bounty
w_class = WEIGHT_CLASS_BULKY
weapon_weight = WEAPON_MEDIUM
force = 16 //it has a hook on it
attack_verb = list("slashed", "hooked", "stabbed")
hitsound = 'sound/weapons/bladeslice.ogg'
//our hook gun!
var/obj/item/gun/magic/hook/bounty/hook
var/toggled = FALSE
@@ -57,7 +57,7 @@
casing_ejector = FALSE
can_suppress = FALSE
/obj/item/gun/ballistic/shotgun/toy/process_chamber(empty_chamber = 0)
/obj/item/gun/ballistic/shotgun/toy/process_chamber(mob/living/user, empty_chamber = 0)
..()
if(chambered && !chambered.BB)
qdel(chambered)
+2 -2
View File
@@ -127,12 +127,12 @@
chambered = null //either way, released the prepared shot
recharge_newshot() //try to charge a new shot
/obj/item/gun/energy/do_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
/obj/item/gun/energy/do_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0, stam_cost = 0)
if(!chambered && can_shoot())
process_chamber() // If the gun was drained and then recharged, load a new shot.
return ..()
/obj/item/gun/energy/do_burst_shot(mob/living/user, atom/target, message = TRUE, params = null, zone_override="", sprd = 0, randomized_gun_spread = 0, randomized_bonus_spread = 0, rand_spr = 0, iteration = 0)
/obj/item/gun/energy/do_burst_shot(mob/living/user, atom/target, message = TRUE, params = null, zone_override="", sprd = 0, randomized_gun_spread = 0, randomized_bonus_spread = 0, rand_spr = 0, iteration = 0, stam_cost = 0)
if(!chambered && can_shoot())
process_chamber() // Ditto.
return ..()
@@ -237,7 +237,7 @@
return FALSE
return TRUE
/obj/item/gun/energy/dueling/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread)
/obj/item/gun/energy/dueling/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread = 0, stam_cost = 0)
if(duel.state == DUEL_READY)
duel.confirmations[src] = TRUE
to_chat(user,"<span class='notice'>You confirm your readiness.</span>")
@@ -110,7 +110,7 @@
fail_tick--
..()
/obj/item/gun/energy/e_gun/nuclear/shoot_live_shot()
/obj/item/gun/energy/e_gun/nuclear/shoot_live_shot(mob/living/user, pointblank = FALSE, mob/pbtarget, message = 1, stam_cost = 0)
failcheck()
update_icon()
..()
@@ -60,6 +60,12 @@
else
cut_overlays()
/obj/item/gun/energy/kinetic_accelerator/getinaccuracy(mob/living/user, bonus_spread, stamloss)
var/old_fire_delay = fire_delay //It's pretty irrelevant tbh but whatever.
fire_delay = overheat_time
. = ..()
fire_delay = old_fire_delay
/obj/item/gun/energy/kinetic_accelerator/examine(mob/user)
. = ..()
if(max_mod_capacity)
@@ -128,7 +134,7 @@
if(!holds_charge)
empty()
/obj/item/gun/energy/kinetic_accelerator/shoot_live_shot()
/obj/item/gun/energy/kinetic_accelerator/shoot_live_shot(mob/living/user, pointblank = FALSE, mob/pbtarget, message = 1, stam_cost = 0)
. = ..()
attempt_reload()
@@ -207,6 +213,12 @@
var/obj/item/gun/energy/kinetic_accelerator/KA = loc
KA.modify_projectile(BB)
/obj/item/gun/energy/kinetic_accelerator/getstamcost(mob/living/carbon/user)
if(user && !lavaland_equipment_pressure_check(get_turf(user)))
return 0
else
return ..()
//Projectiles
/obj/item/projectile/kinetic
name = "kinetic force"
+18 -1
View File
@@ -62,7 +62,7 @@
slot_flags = ITEM_SLOT_BACK
w_class = WEIGHT_CLASS_BULKY
weapon_weight = WEAPON_MEDIUM
inaccuracy_modifier = 0.5
inaccuracy_modifier = 0.7
force = 10
throwforce = 10
cell_type = /obj/item/stock_parts/cell/lascarbine
@@ -240,3 +240,20 @@
chambered.BB.damage *= 5
process_fire(target, user, TRUE, params)
////////////////
// IMPROVISED //
////////////////
/obj/item/gun/energy/e_gun/old/improvised
name = "improvised energy rifle"
desc = "A crude imitation of an energy gun. It works, however the beams are poorly focused and most of the energy is wasted before it reaches the target. Welp, it still burns things."
icon_state = "improvised"
ammo_x_offset = 1
shaded_charge = 1
ammo_type = list(/obj/item/ammo_casing/energy/lasergun/improvised)
/obj/item/gun/energy/e_gun/old/improvised/upgraded
name = "makeshift energy rifle"
desc = "The new lens and upgraded parts gives this a higher capacity and more energy output, however, the shoddy construction still leaves it inferior to Nanotrasen's own energy weapons."
ammo_type = list(/obj/item/ammo_casing/energy/lasergun/improvised/upgraded)
@@ -89,7 +89,7 @@
suppressed = TRUE
ammo_type = list(/obj/item/ammo_casing/energy/bolt)
weapon_weight = WEAPON_LIGHT
inaccuracy_modifier = 0
inaccuracy_modifier = 0.25
obj_flags = 0
overheat_time = 20
holds_charge = TRUE
@@ -126,7 +126,7 @@
attack_verb = list("attacked", "slashed", "cut", "sliced")
force = 12
sharpness = IS_SHARP
inaccuracy_modifier = 0
inaccuracy_modifier = 0.25
can_charge = 0
heat = 3800
@@ -182,7 +182,7 @@
item_state = null
icon_state = "wormhole_projector"
pin = null
inaccuracy_modifier = 0
inaccuracy_modifier = 0.25
var/obj/effect/portal/p_blue
var/obj/effect/portal/p_orange
var/atmos_link = FALSE
@@ -318,7 +318,7 @@
icon_state = "emitter_carbine"
force = 12
w_class = WEIGHT_CLASS_SMALL
inaccuracy_modifier = 0
inaccuracy_modifier = 0.25
cell_type = /obj/item/stock_parts/cell/super
ammo_type = list(/obj/item/ammo_casing/energy/emitter)
+1 -1
View File
@@ -45,7 +45,7 @@
/obj/item/projectile/magic/death, /obj/item/projectile/magic/teleport, /obj/item/projectile/magic/door, /obj/item/projectile/magic/aoe/fireball,
/obj/item/projectile/magic/spellblade, /obj/item/projectile/magic/arcane_barrage, /obj/item/projectile/magic/locker)
/obj/item/gun/magic/staff/chaos/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
/obj/item/gun/magic/staff/chaos/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0, stam_cost = 0)
chambered.projectile_type = pick(allowed_projectile_types)
. = ..()
@@ -9,7 +9,7 @@
throw_speed = 3
throw_range = 7
force = 4
inaccuracy_modifier = 0
inaccuracy_modifier = 0.25
custom_materials = list(/datum/material/iron=2000)
clumsy_check = FALSE
fire_sound = 'sound/items/syringeproj.ogg'
@@ -31,7 +31,7 @@
/obj/item/gun/grenadelauncher/can_shoot()
return grenades.len
/obj/item/gun/grenadelauncher/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
/obj/item/gun/grenadelauncher/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0, stam_cost = 0)
user.visible_message("<span class='danger'>[user] fired a grenade!</span>", \
"<span class='danger'>You fire the grenade launcher!</span>")
var/obj/item/grenade/F = grenades[1] //Now with less copypasta!
@@ -41,7 +41,7 @@
on_beam_release(current_target)
current_target = null
/obj/item/gun/medbeam/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
/obj/item/gun/medbeam/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0, stam_cost = 0)
if(isliving(user))
add_fingerprint(user)
@@ -7,7 +7,7 @@
throw_speed = 3
throw_range = 7
force = 4
inaccuracy_modifier = 0
inaccuracy_modifier = 0.25
custom_materials = list(/datum/material/iron=2000)
clumsy_check = 0
fire_sound = 'sound/items/syringeproj.ogg'
@@ -160,7 +160,7 @@
item_state = "blowgun"
fire_sound = 'sound/items/syringeproj.ogg'
/obj/item/gun/syringe/blowgun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
/obj/item/gun/syringe/blowgun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0, stam_cost = 0)
visible_message("<span class='danger'>[user] starts aiming with a blowgun!</span>")
if(do_after(user, 25, target = src))
user.adjustStaminaLoss(20)
+100 -24
View File
@@ -45,6 +45,8 @@
var/pixels_range_leftover = 0
/// "leftover" tick pixels and stuff yeah, so we don't round off things and introducing tracing inaccuracy.
var/pixels_tick_leftover = 0
/// Used to detect jumps in the middle of a pixel_move. Yes, this is ugly as sin code-wise but it works.
var/pixel_move_interrupted = FALSE
/// Pixels moved per second.
var/pixels_per_second = TILES_TO_PIXELS(12.5)
@@ -56,9 +58,25 @@
var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle
var/spread = 0 //amount (in degrees) of projectile spread
animate_movement = 0 //Use SLIDE_STEPS in conjunction with legacy
/// how many times we've ricochet'd so far (instance variable, not a stat)
var/ricochets = 0
var/ricochets_max = 2
var/ricochet_chance = 30
/// how many times we can ricochet max
var/ricochets_max = 0
/// 0-100, the base chance of ricocheting, before being modified by the atom we shoot and our chance decay
var/ricochet_chance = 0
/// 0-1 (or more, I guess) multiplier, the ricochet_chance is modified by multiplying this after each ricochet
var/ricochet_decay_chance = 0.7
/// 0-1 (or more, I guess) multiplier, the projectile's damage is modified by multiplying this after each ricochet
var/ricochet_decay_damage = 0.7
/// On ricochet, if nonzero, we consider all mobs within this range of our projectile at the time of ricochet to home in on like Revolver Ocelot, as governed by ricochet_auto_aim_angle
var/ricochet_auto_aim_range = 0
/// On ricochet, if ricochet_auto_aim_range is nonzero, we'll consider any mobs within this range of the normal angle of incidence to home in on, higher = more auto aim
var/ricochet_auto_aim_angle = 30
/// the angle of impact must be within this many degrees of the struck surface, set to 0 to allow any angle
var/ricochet_incidence_leeway = 40
///If the object being hit can pass ths damage on to something else, it should not do it for this bullet
var/force_hit = FALSE
//Hitscan
var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored.
@@ -131,6 +149,11 @@
var/temporary_unstoppable_movement = FALSE
///If defined, on hit we create an item of this type then call hitby() on the hit target with this
var/shrapnel_type
///If TRUE, hit mobs even if they're on the floor and not our target
var/hit_stunned_targets = FALSE
/obj/item/projectile/Initialize()
. = ..()
permutated = list()
@@ -146,6 +169,7 @@
on_range()
/obj/item/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range
SEND_SIGNAL(src, COMSIG_PROJECTILE_RANGE_OUT)
qdel(src)
//to get the correct limb (if any) for the projectile hit message
@@ -165,6 +189,14 @@
/obj/item/projectile/proc/on_hit(atom/target, blocked = FALSE)
if(fired_from)
SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle)
// i know that this is probably more with wands and gun mods in mind, but it's a bit silly that the projectile on_hit signal doesn't ping the projectile itself.
// maybe we care what the projectile thinks! See about combining these via args some time when it's not 5AM
var/obj/item/bodypart/hit_limb
if(isliving(target))
var/mob/living/L = target
hit_limb = L.check_limb_hit(def_zone)
SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle, hit_limb)
var/turf/target_loca = get_turf(target)
var/hitx
@@ -216,10 +248,13 @@
new impact_effect_type(target_loca, hitx, hity)
var/organ_hit_text = ""
var/limb_hit = L.check_limb_hit(def_zone)//to get the correct message info.
var/limb_hit = hit_limb
if(limb_hit)
organ_hit_text = " in \the [parse_zone(limb_hit)]"
if(suppressed)
if(suppressed==SUPPRESSED_VERY)
playsound(loc, hitsound, 5, TRUE, -1)
else if(suppressed)
playsound(loc, hitsound, 5, 1, -1)
to_chat(L, "<span class='userdanger'>You're shot by \a [src][organ_hit_text]!</span>")
else
@@ -250,7 +285,23 @@
return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume
/obj/item/projectile/proc/on_ricochet(atom/A)
return
if(!ricochet_auto_aim_angle || !ricochet_auto_aim_range)
return
var/mob/living/unlucky_sob
var/best_angle = ricochet_auto_aim_angle
if(firer && HAS_TRAIT(firer, TRAIT_NICE_SHOT))
best_angle += NICE_SHOT_RICOCHET_BONUS
for(var/mob/living/L in range(ricochet_auto_aim_range, src.loc))
if(L.stat == DEAD || !isInSight(src, L))
continue
var/our_angle = abs(closer_angle_difference(Angle, get_projectile_angle(src.loc, L.loc)))
if(our_angle < best_angle)
best_angle = our_angle
unlucky_sob = L
if(unlucky_sob)
setAngle(get_projectile_angle(src, unlucky_sob.loc))
/obj/item/projectile/proc/store_hitscan_collision(datum/point/pcache)
beam_segments[beam_index] = pcache
@@ -258,21 +309,22 @@
beam_segments[beam_index] = null
/obj/item/projectile/Bump(atom/A)
if(!trajectory)
return
var/turf/T = get_turf(A)
if(trajectory && check_ricochet(A) && check_ricochet_flag(A) && ricochets < ricochets_max)
if(check_ricochet(A) && A.handle_ricochet(src)) //if you can ricochet, attempt to ricochet off the object
on_ricochet(A) //if allowed, use autoaim to ricochet into someone, otherwise default to ricocheting off the object from above
var/datum/point/pcache = trajectory.copy_to()
ricochets++
if(A.handle_ricochet(src))
on_ricochet(A)
ignore_source_check = TRUE
decayedRange = max(0, decayedRange - reflect_range_decrease)
range = decayedRange
if(hitscan)
store_hitscan_collision(pcache)
return TRUE
if(hitscan)
store_hitscan_collision(pcache)
decayedRange = max(0, decayedRange - reflect_range_decrease)
ricochet_chance *= ricochet_decay_chance
damage *= ricochet_decay_damage
range = decayedRange
return TRUE
var/distance = get_dist(T, starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations.
if(check_zone(def_zone) != BODY_ZONE_CHEST)
if(def_zone && check_zone(def_zone) != BODY_ZONE_CHEST)
def_zone = ran_zone(def_zone, max(100-(7*distance), 5) * zone_accuracy_factor) //Lower accurancy/longer range tradeoff. 7 is a balanced number to use.
if(isturf(A) && hitsound_wall)
@@ -344,13 +396,30 @@
return T
//Returns null if nothing at all was found.
/obj/item/projectile/proc/check_ricochet()
if(prob(ricochet_chance))
return TRUE
return FALSE
/obj/item/projectile/proc/check_ricochet(atom/A)
if(ricochets > ricochets_max) //safety thing, we don't care about what the other thing says about this.
return FALSE
var/them = A.check_projectile_ricochet(src)
switch(them)
if(PROJECTILE_RICOCHET_PREVENT)
return FALSE
if(PROJECTILE_RICOCHET_FORCE)
return TRUE
if(PROJECTILE_RICOCHET_NO)
return FALSE
if(PROJECTILE_RICOCHET_YES)
var/chance = ricochet_chance * A.ricochet_chance_mod
if(firer && HAS_TRAIT(firer, TRAIT_NICE_SHOT))
chance += NICE_SHOT_RICOCHET_BONUS
if(prob(chance))
return TRUE
else
CRASH("Invalid return value for projectile ricochet check from [A].")
/obj/item/projectile/proc/check_ricochet_flag(atom/A)
if(A.flags_1 & CHECK_RICOCHET_1)
if((flag in list("energy", "laser")) && (A.flags_ricochet & RICOCHET_SHINY))
return TRUE
if((flag in list("bomb", "bullet")) && (A.flags_ricochet & RICOCHET_HARD))
return TRUE
return FALSE
@@ -391,6 +460,8 @@
/obj/item/projectile/proc/fire(angle, atom/direct_target)
if(fired_from)
SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original) //If no angle needs to resolve it from xo/yo!
if(shrapnel_type)
AddElement(/datum/element/embed, projectile_payload = shrapnel_type)
if(!log_override && firer && original)
log_combat(firer, original, "fired at", src, "from [get_area_name(src, TRUE)]")
if(direct_target)
@@ -431,6 +502,7 @@
/obj/item/projectile/proc/setAngle(new_angle, hitscan_store_segment = TRUE) //wrapper for overrides.
Angle = new_angle
pixel_move_interrupted = TRUE
if(!nondirectional_sprite)
var/matrix/M = new
M.Turn(Angle)
@@ -457,6 +529,7 @@
trajectory.initialize_location(target.x, target.y, target.z, 0, 0)
if(hitscan)
record_hitscan_start(RETURN_PRECISE_POINT(src))
pixel_move_interrupted = TRUE
if(zc)
after_z_change(old, target)
@@ -505,7 +578,7 @@
* The proc to make the projectile go, using a simulated pixel movement line trace.
* Note: deciseconds_equivalent is currently only used for homing, times is the number of times to move pixel_increment_amount.
* Trajectory multiplier directly modifies the factor of pixel_increment_amount to go per time.
* It's complicated, so probably just don'ot mess with this unless you know what you're doing.
* It's complicated, so probably just don't mess with this unless you know what you're doing.
*/
/obj/item/projectile/proc/pixel_move(times, hitscanning = FALSE, deciseconds_equivalent = world.tick_lag, trajectory_multiplier = 1, allow_animation = TRUE)
if(!loc || !trajectory)
@@ -515,6 +588,7 @@
M.Turn(Angle)
transform = M
var/forcemoved = FALSE
pixel_move_interrupted = FALSE // reset that
var/turf/oldloc = loc
var/old_px = pixel_x
var/old_py = pixel_y
@@ -548,9 +622,11 @@
var/safety = CEILING(pixel_increment_amount / world.icon_size, 1) * 2 + 1
while(T != loc)
if(!--safety)
CRASH("Projectile took more than pixel incrememnt speed times 2 to get to its location, this is probably something seriously scuffed going on.")
CRASH("[type] took too long (allowed: [CEILING(pixel_increment_amount/world.icon_size,1)*2] moves) to get to its location.")
step_towards(src, T)
if(QDELETED(src))
if(QDELETED(src) || pixel_move_interrupted) // this doesn't take into account with pixel_move_interrupted the portion of the move cut off by any forcemoves, but we're opting to ignore that for now
// the reason is the entire point of moving to pixel speed rather than tile speed is smoothness, which will be crucial when pixel movement is done in the future
// reverting back to tile is more or less the only way of fixing this issue.
return
pixels_range_leftover += pixel_increment_amount
if(pixels_range_leftover > world.icon_size)
@@ -39,6 +39,9 @@
/obj/item/projectile/beam/weak
damage = 15
/obj/item/projectile/beam/weak/improvised
damage = 10
/obj/item/projectile/beam/weak/penetrator
armour_penetration = 50
@@ -47,4 +47,16 @@
if(L.getStaminaLoss() >= 60)
L.Sleeping(300)
else
L.adjustStaminaLoss(25)
L.adjustStaminaLoss(25)
// .32 ACP (Improvised Pistol)
/obj/item/projectile/bullet/c32acp
name = ".32 bullet"
damage = 13
/obj/item/projectile/bullet/r32acp
name = ".32 rubber bullet"
damage = 3
eyeblur = 1
stamina = 20
@@ -15,6 +15,38 @@
/obj/item/projectile/bullet/c38
name = ".38 bullet"
damage = 25
ricochets_max = 2
ricochet_chance = 50
ricochet_auto_aim_angle = 10
ricochet_auto_aim_range = 3
/obj/item/projectile/bullet/c38/match
name = ".38 Match bullet"
ricochets_max = 4
ricochet_chance = 100
ricochet_auto_aim_angle = 40
ricochet_auto_aim_range = 5
ricochet_incidence_leeway = 50
ricochet_decay_chance = 1
ricochet_decay_damage = 1
/obj/item/projectile/bullet/c38/match/bouncy
name = ".38 Rubber bullet"
damage = 10
stamina = 30
armour_penetration = -30
ricochets_max = 6
ricochet_incidence_leeway = 70
ricochet_chance = 130
ricochet_decay_damage = 0.8
shrapnel_type = NONE
/obj/item/projectile/bullet/c38/dumdum
name = ".38 DumDum bullet"
damage = 15
armour_penetration = -30
ricochets_max = 0
shrapnel_type = /obj/item/shrapnel/bullet/c38/dumdum
/obj/item/projectile/bullet/c38/rubber
name = ".38 rubber bullet"
@@ -24,6 +56,7 @@
/obj/item/projectile/bullet/c38/trac
name = ".38 TRAC bullet"
damage = 10
ricochets_max = 0
/obj/item/projectile/bullet/c38/trac/on_hit(atom/target, blocked = FALSE)
. = ..()
@@ -39,6 +72,7 @@
/obj/item/projectile/bullet/c38/hotshot //similar to incendiary bullets, but do not leave a flaming trail
name = ".38 Hot Shot bullet"
damage = 20
ricochets_max = 0
/obj/item/projectile/bullet/c38/hotshot/on_hit(atom/target, blocked = FALSE)
. = ..()
@@ -51,6 +85,7 @@
name = ".38 Iceblox bullet"
damage = 20
var/temperature = 100
ricochets_max = 0
/obj/item/projectile/bullet/c38/iceblox/on_hit(atom/target, blocked = FALSE)
. = ..()
@@ -68,4 +103,14 @@
/obj/item/projectile/bullet/a357/ap
name = ".357 armor-piercing bullet"
damage = 45
armour_penetration = 45
armour_penetration = 45
// admin only really, for ocelot memes
/obj/item/projectile/bullet/a357/match
name = ".357 match bullet"
ricochets_max = 5
ricochet_chance = 140
ricochet_auto_aim_angle = 50
ricochet_auto_aim_range = 6
ricochet_incidence_leeway = 80
ricochet_decay_chance = 1
@@ -112,3 +112,8 @@
/obj/item/projectile/bullet/seed
damage = 4
stamina = 1
/obj/item/projectile/bullet/pellet/shotgun_incapacitate
name = "incapacitating pellet"
damage = 1
stamina = 6
+2 -2
View File
@@ -121,10 +121,10 @@
qdel(src)
/proc/wabbajack(mob/living/M)
if(!istype(M) || M.stat == DEAD || M.notransform || (GODMODE & M.status_flags))
if(!istype(M) || M.stat == DEAD || M.mob_transforming || (GODMODE & M.status_flags))
return
M.notransform = TRUE
M.mob_transforming = TRUE
M.Paralyze(INFINITY)
M.icon = null
M.cut_overlays()