Files
CatsinHD a1e55d0958 Right Arms of the Broken World - Tajara Gun Resprites (#21743)
Resprites 3 Tajaran rifles, expanding them to use a 48x32 canvas, and
brings them in-line with lore as written in terms of weapon design
history. Additionally, adds 6.8mm in the form of the Tajaran Z33
Tamparii cartridge to further separate Tajara and Human history and arms
development.

All weapon sprites are made by me, except the ba and ss sling which was
borrowed from the Dominian rifle sprites. Special thanks to Elorg for
help in making these not look awful.
<details><summary>Z33 Tamparii</summary>
<p>
<img width="331" height="332" alt="image"
src="https://github.com/user-attachments/assets/f9892dc8-9c6d-45c6-8779-3ad3e39abaed"
/>

An old caliber that continues to show its usefulness. The compromise
between weight and firepower, as well as room to change the powder load
has seen the cartridge remain in service across Adhomai.
</p>
</details> 
<details><summary>Tsarrayut Rifle (PRA)</summary>
<p>
<img width="572" height="387" alt="image"
src="https://github.com/user-attachments/assets/53327f3b-4b87-415e-8215-b1c2ccc08c9c"
/>

Redesigned to a general magazine-fed rifle. Not meant to be flashy or
cool, simply easy to mass-produce and train conscripts. Additionally,
still takes old bolt action rifle clips.
</p>
</details> 
<details><summary>Hotaki Marksman Rifle (ALA)</summary>
<p>
<img width="586" height="400" alt="image"
src="https://github.com/user-attachments/assets/c08986ec-1134-4869-8dc2-36b981a211f9"
/>

Carries many of the same lines and design from the Tsarrayut, especially
in the receiver. However, it was modified to be a bullpup for a longer
barrel. Higher bullet velocity for a similar overall length; perfect for
a marksman harassing down an enemy.
</p>
</details> 
<details><summary>Mrrazhak Assault Rifle (ALA)</summary>
<p>
<img width="483" height="332" alt="image"
src="https://github.com/user-attachments/assets/3f0a2885-49bb-418b-b956-4d2f89a60b40"
/>

Pretty much a shortened Hotaki. Intended to have a similar barrel length
to the Tsarrayut, but a shorter overall gun.
</p>
</details> 

WIP for the following items:

- [x] Golden Mrrazhak
- [x] Cartridge balancing
- [x] Addition/clean-up with uplink items
- [x] Addition/clean-up with ghost roles

---------

Signed-off-by: CatsinHD <31459154+CatsinHD@users.noreply.github.com>
Signed-off-by: Wildkins <john.wildkins@gmail.com>
Co-authored-by: Wildkins <john.wildkins@gmail.com>
2026-02-17 16:30:22 +00:00

617 lines
15 KiB
Plaintext

/obj/projectile/bullet
name = "bullet"
icon_state = "bullet"
damage = 60
damage_type = DAMAGE_BRUTE
impact_sounds = list(BULLET_IMPACT_MEAT = SOUNDS_BULLET_MEAT, BULLET_IMPACT_METAL = SOUNDS_BULLET_METAL)
check_armor = BULLET
embed = TRUE
sharp = TRUE
shrapnel_type = /obj/item/material/shard/shrapnel
var/mob_passthrough_check = 0
muzzle_type = /obj/effect/projectile/muzzle/bullet
/obj/projectile/bullet/on_hit(atom/target, blocked, def_zone)
if(isliving(target) && (..(target, blocked, def_zone) == BULLET_ACT_HIT))
var/mob/living/L = target
shake_camera(L, 3, 2)
if(penetrating > 0 && damage > 20 && prob(damage))
mob_passthrough_check = 1
else
mob_passthrough_check = 0
return ..()
/obj/projectile/bullet/can_embed()
//prevent embedding if the projectile is passing through the mob
if(mob_passthrough_check)
return 0
return ..()
/**
* # Pellet projectiles
*
* For projectiles that actually represent clouds of projectiles
*/
/obj/projectile/bullet/pellet
name = "shrapnel" //'shrapnel' sounds more dangerous (i.e. cooler) than 'pellet'
icon_state = "pellets"
damage = 20
/// Number of pellets that will be ejected from this bullet
var/pellets = 4
/// The projectile will lose a pellet each time it travels this distance. Can be a non-integer.
var/range_step = 2
/// Lower means the pellets spread more across body parts. If zero then this is considered a shrapnel explosion instead of a shrapnel cone
var/base_spread = 90
/// Higher means the pellets spread more across body parts with distance
var/spread_step = 10
/obj/projectile/bullet/pellet/proc/get_pellets(var/distance)
var/pellet_loss = round((distance - 1)/range_step) //pellets lost due to distance
return max(pellets - pellet_loss, 1)
/obj/projectile/bullet/pellet/on_hit(atom/target, blocked, def_zone)
if(!isliving(target))
return ..()
var/mob/living/living_target = target
if (pellets < 0)
return BULLET_ACT_BLOCK
var/distance = get_dist(src.starting, get_turf(living_target))
var/total_pellets = get_pellets(distance)
var/spread = max(base_spread - (spread_step*distance), 0)
// Shrapnel explosions miss prone mobs with a chance that increases with distance
var/prone_chance = 0
if(!base_spread)
prone_chance = max(spread_step*(distance - 2), 0)
var/hits = 0
for (var/i in 1 to total_pellets)
if(living_target.lying && living_target != original && prob(prone_chance))
continue
// pellet hits spread out across different zones, but 'aim at' the targeted zone with higher probability
// whether the pellet actually hits the def_zone or a different zone should still be determined by the parent using get_zone_with_miss_chance().
var/old_zone = def_zone
def_zone = ran_zone(def_zone, spread)
// relatively hacky way of basing a shotgun pellet's likelihood of hitting on the first pellet of the burst while not affecting shrapnel explosions.
if (base_spread > 0)
if (i == 1)
if (..() == BULLET_ACT_HIT)
hits++
else
return 0
else if (..() == BULLET_ACT_HIT)
hits++
else if (..() == BULLET_ACT_HIT)
hits++
def_zone = old_zone //restore the original zone the projectile was aimed at
pellets -= hits //each hit reduces the number of pellets left
// if (hits >= total_pellets || pellets <= 0)
// return TRUE
if(hits)
damage *= hits
return BULLET_ACT_HIT //Technically not everything, but good enough
return BULLET_ACT_BLOCK //Nothing hit
/obj/projectile/bullet/pellet/get_structure_damage()
var/distance = get_dist(loc, starting)
return ..() * get_pellets(distance)
/obj/projectile/bullet/pellet/Move()
. = ..()
//If this is a shrapnel explosion, allow mobs that are prone to get hit, too
if(. && !base_spread && isturf(loc))
for(var/mob/living/M in loc)
if(M.lying || !M.CanPass(src, loc)) //Bump if lying or if we would normally Bump.
if(Collide(M)) //Bump will make sure we don't hit a mob multiple times
return
/obj/projectile/bullet/rubberball
name = "rubber ball"
icon_state = "pellets"
damage = 2
agony = 50
embed = FALSE
var/balls = 4
/// Projectile will lose a fragment each time it travels this distance. Can be a non-integer.
var/range_step = 3
var/base_spread = 90
var/spread_step = 10
/obj/projectile/bullet/rubberball/proc/get_balls(var/distance)
var/ball_loss = round((distance - 1)/range_step)
return max(balls - ball_loss, 1)
/obj/projectile/bullet/rubberball/on_hit(atom/target, blocked, def_zone)
if(!isliving(target))
return ..()
var/mob/living/target_mob = target
var/distance = get_dist(src.starting, get_turf(target_mob))
if (balls < 0)
return TRUE
var/total_balls = get_balls(distance)
var/spread = max(base_spread - (spread_step*distance), 0)
var/prone_chance = 0
if(!base_spread)
prone_chance = max(spread_step*(distance - 2), 0)
var/hits = 0
for (var/i in 1 to total_balls)
if(target_mob.lying && target_mob != original && prob(prone_chance))
continue
var/old_zone = def_zone
def_zone = ran_zone(def_zone, spread)
if (..())
hits++
def_zone = old_zone
balls -= hits
if (hits >= total_balls || balls <= 0)
return TRUE
return FALSE
/obj/projectile/bullet/rubberball/Move()
. = ..()
if(. && !base_spread && isturf(loc))
for(var/mob/living/M in loc)
if(M.lying || !M.CanPass(src, loc))
if(Collide(M))
return
/* short-casing projectiles, like the kind used in pistols or SMGs */
/obj/projectile/bullet/pistol
damage = 20
armor_penetration = 15
/obj/projectile/bullet/pistol/polymer
damage = 12
armor_penetration = 30
/obj/projectile/bullet/pistol/medium
damage = 30
armor_penetration = 0
/obj/projectile/bullet/pistol/medium/ap
armor_penetration = 15
penetrating = FALSE
/obj/projectile/bullet/pistol/strong
damage = 45
armor_penetration = 20
/obj/projectile/bullet/pistol/revolver
damage = 40
armor_penetration = 15
/obj/projectile/bullet/pistol/rubber //"rubber" bullets
name = "rubber bullet"
check_armor = MELEE
damage = 5
agony = 40
embed = 0
/obj/projectile/bullet/pistol/assassin
damage = 20
armor_penetration = 5
/* shotgun projectiles */
/obj/projectile/bullet/shotgun
name = "slug"
damage = 55
armor_penetration = 5
/obj/projectile/bullet/shotgun/beanbag //because beanbags are not bullets
name = "beanbag"
check_armor = MELEE
damage = 10
agony = 60
embed = 0
sharp = 0
/obj/projectile/bullet/shotgun/incendiary
name = "incendiary"
check_armor = MELEE
damage = 5
agony = 0
embed = 0
sharp = 0
incinerate = 10
/obj/projectile/bullet/tracking
name = "tracking shot"
damage = 20
embed_chance = 60 // this thing was designed to embed, so it has a 80% base chance to embed (damage + this flat increase)
agony = 20
shrapnel_type = /obj/item/implant/tracking
/obj/projectile/bullet/tracking/do_embed(obj/item/organ/external/organ)
. = ..()
if(.)
var/obj/item/implant/tracking/T = .
T.implanted = TRUE
T.imp_in = organ.owner
T.part = organ
LAZYADD(organ.implants, T)
/obj/projectile/bullet/shotgun/moghes
name = "wall shot"
//Should do about 80 damage at 1 tile distance (adjacent), and 50 damage at 3 tiles distance.
//Overall less damage than slugs in exchange for more damage at very close range and more embedding
/obj/projectile/bullet/pellet/shotgun
name = "pellet"
damage = 25
pellets = 3
range_step = 1
spread_step = 10
/obj/projectile/bullet/pellet/shotgun/canister
pellets = 15
range_step = 3
spread_step = 15
/* "Rifle" rounds */
/obj/projectile/bullet/rifle
damage = 40
armor_penetration = 15
penetrating = FALSE
/obj/projectile/bullet/rifle/a762
damage = 35
armor_penetration = 22
penetrating = TRUE
/obj/projectile/bullet/rifle/a556
damage = 30
armor_penetration = 28
penetrating = FALSE
/obj/projectile/bullet/rifle/a556/ap
damage = 25
armor_penetration = 45
penetrating = TRUE
/obj/projectile/bullet/rifle/a556/polymer
damage = 25
armor_penetration = 34
penetrating = FALSE
/obj/projectile/bullet/rifle/a65
damage = 30
armor_penetration = 30
penetrating = FALSE
/obj/projectile/bullet/rifle/a68
name = "R33 Tamparii bullet"
damage = 40
armor_penetration = 17
penetrating = FALSE
/obj/projectile/bullet/rifle/a68/ap
name = "R33s Tamparii bullet"
damage = 30
armor_penetration = 30
penetrating = TRUE
/obj/projectile/bullet/rifle/a145
damage = 80
stun = 3
weaken = 3
penetrating = 5
armor_penetration = 70
hitscan = 1 //so the PTR isn't useless as a sniper weapon
maim_rate = 3
anti_materiel_potential = 2
/obj/projectile/rifle/kumar_super
damage = 40
armor_penetration = 30
penetrating = TRUE
/obj/projectile/bullet/rifle/vintage
name = ".30-06 Govt. bullet"
damage = 50
weaken = 1
penetrating = TRUE
/obj/projectile/bullet/rifle/govt
name = ".40-70 Govt. bullet"
damage = 50
/obj/projectile/bullet/rifle/slugger
name = "slugger round"
damage = 60
weaken = 3
penetrating = 5
armor_penetration = 10
maim_rate = 3
anti_materiel_potential = 2
/obj/projectile/bullet/rifle/slugger/on_hit(atom/target, blocked, def_zone)
var/atom/movable/movable_target = target
if(istype(movable_target))
var/throwdir = get_dir(firer, movable_target)
movable_target.throw_at(get_edge_target_turf(movable_target, throwdir), 3, 3)
. = ..()
/obj/projectile/bullet/rifle/tranq
name = "dart"
icon_state = "dart"
damage = 5
stun = 0
weaken = 0
drowsy = 0
eyeblur = 0
damage_type = DAMAGE_BRUTE
speed = 0.3
/obj/projectile/bullet/rifle/tranq/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null)
var/mob/living/L = target
if(isanimal(target))
target.visible_message("[SPAN_BOLD("[target]")] twitches, foaming at the mouth.")
L.apply_damage(35, DAMAGE_TOXIN) //temporary until simple_animal paralysis actually works.
//Determine in how long the mob will be put to rest, depending on its size
var/sleep_in = 10 SECONDS
switch(L.mob_size)
if(MOB_TINY)
sleep_in = 3 SECOND
if(MOB_SMALL)
sleep_in = 6 SECONDS
if(MOB_MEDIUM)
sleep_in = 10 SECONDS
if(MOB_LARGE)
sleep_in = 40 SECONDS
addtimer(CALLBACK(L, TYPE_PROC_REF(/mob/living, Weaken), (L.weakened + 40)), sleep_in)
else
if(!(isipc(target)) && !isrobot(target))
L.apply_effect(5, DROWSY, 0)
switch(def_zone)
if(BP_CHEST)
if(blocked < 20)
if(L.reagents)
L.reagents.add_reagent(/singleton/reagent/soporific, 10)
else if(blocked < 100)
L.emote("yawns")
if(BP_HEAD)
if(blocked < 100)
if(L.reagents)
L.reagents.add_reagent(/singleton/reagent/soporific, 15)
else
if(blocked < 20)
if(L.reagents)
L.reagents.add_reagent(/singleton/reagent/soporific, 5)
else if(blocked < 100)
L.emote("yawns")
..()
/* Miscellaneous */
/obj/projectile/bullet/blank
invisibility = 101
damage = 1
embed = 0
/obj/projectile/bullet/chameleon
damage = 1 // stop trying to murderbone with a fake gun dumbass!!!
embed = 0 // nope
/* Practice */
/obj/projectile/bullet/pistol/practice
damage = 5
/obj/projectile/bullet/rifle/a556/practice
damage = 5
/obj/projectile/bullet/shotgun/practice
name = "practice"
damage = 5
/obj/projectile/bullet/pistol/cap
name = "cap"
damage_type = DAMAGE_PAIN
damage = 0
embed = 0
sharp = 0
/obj/projectile/bullet/pistol/cap/process()
loc = null
qdel(src)
/obj/projectile/bullet/flechette
name = "flechette"
icon = 'icons/obj/terminator.dmi'
icon_state = "flechette_bullet"
damage = 40
armor_penetration = 15
damage_type = DAMAGE_BRUTE
check_armor = BULLET
embed = 1
sharp = 1
penetrating = 1
muzzle_type = /obj/effect/projectile/muzzle/pulse
/obj/projectile/bullet/flechette/explosive
shrapnel_type = /obj/item/material/shard/shrapnel/flechette
penetrating = 0
damage = 10
armor_penetration = 60
/obj/projectile/bullet/gauss
name = "slug"
icon_state = "heavygauss"
damage = 40
armor_penetration = 20
muzzle_type = /obj/effect/projectile/muzzle/gauss
embed = 0
/obj/projectile/bullet/gauss/carbine
name = "compact slug"
damage = 20
/obj/projectile/bullet/gauss/highex
name = "high-ex shell"
damage = 10
armor_penetration = 30
/obj/projectile/bullet/gauss/highex/on_hit(atom/target, blocked, def_zone)
. = ..()
explosion(target, -1, 0, 2)
if(ismovable(target))
var/atom/movable/T = target
var/throwdir = get_dir(firer,target)
INVOKE_ASYNC(T, TYPE_PROC_REF(/atom/movable, throw_at), get_edge_target_turf(target, throwdir), 3, 3)
/obj/projectile/bullet/cannonball
name = "cannonball"
icon_state = "cannonball"
damage = 60
embed = 0
penetrating = 1
armor_penetration = 25
anti_materiel_potential = 2
/obj/projectile/bullet/cannonball/explosive
damage = 50
penetrating = 0
armor_penetration = 5
/obj/projectile/bullet/cannonball/explosive/on_hit(atom/target, blocked, def_zone)
explosion(target, -1, 1, 2)
. = ..()
/obj/projectile/bullet/nuke
name = "miniaturized nuclear warhead"
icon_state = "nuke"
damage = 25
anti_materiel_potential = 2
/obj/projectile/bullet/nuke/on_hit(atom/target, blocked, def_zone)
for(var/mob/living/carbon/human/mob in GLOB.human_mob_list)
var/turf/T = get_turf(mob)
if(T && (loc.z == T.z))
if(ishuman(mob))
mob.apply_damage(250, DAMAGE_RADIATION, damage_flags = DAMAGE_FLAG_DISPERSED)
new /obj/effect/temp_visual/nuke(target.loc)
explosion(target,2,5,9)
. = ..()
/obj/projectile/bullet/shard
name = "shard"
icon_state = "shard"
damage = 15
muzzle_type = /obj/effect/projectile/muzzle/bolt
/obj/projectile/bullet/shard/heavy
damage = 30
armor_penetration = 15
/obj/projectile/bullet/recoilless_rifle
name = "anti-tank warhead"
icon_state = "missile"
damage = 30
armor_penetration = 30
anti_materiel_potential = 4
embed = FALSE
penetrating = FALSE
var/heavy_impact_range = 1
/obj/projectile/bullet/recoilless_rifle/on_hit(atom/target, blocked, def_zone)
explosion(target, -1, heavy_impact_range, 2)
. = ..()
/obj/projectile/bullet/peac
name = "anti-tank missile"
icon_state = "peac"
damage = 25
armor_penetration = 35
anti_materiel_potential = 6
embed = FALSE
penetrating = 1
var/devastation_range = -1
var/heavy_impact_range = -1
var/light_impact_range = 2
/obj/projectile/bullet/peac/on_hit(atom/target, blocked, def_zone)
. = ..()
explosion(target, devastation_range, heavy_impact_range, light_impact_range)
/obj/projectile/bullet/peac/he
name = "high-explosive missile"
armor_penetration = 0
anti_materiel_potential = 3
devastation_range = 1
heavy_impact_range = 2
light_impact_range = 4
/obj/projectile/bullet/peac/shrapnel
name = "fragmentation missile"
armor_penetration = 0
anti_materiel_potential = 2
light_impact_range = 1
/obj/projectile/bullet/peac/shrapnel/preparePixelProjectile(atom/target, atom/source, list/modifiers, deviation)
. = ..()
range = get_dist(firer, original)
/obj/projectile/bullet/peac/shrapnel/on_hit(atom/target, blocked, def_zone)
. = ..()
spawn_shrapnel(starting ? get_dir(starting, original) : dir)
/obj/projectile/bullet/peac/shrapnel/proc/spawn_shrapnel(var/shrapnel_dir)
set waitfor = FALSE
var/turf/O = get_turf(src)
var/list/target_turfs = list()
target_turfs += get_step(O, shrapnel_dir)
target_turfs += get_step(O, turn(shrapnel_dir, -45))
target_turfs += get_step(O, turn(shrapnel_dir, 45))
for(var/turf/T in target_turfs)
var/obj/projectile/bullet/pellet/fragment/P = new(O)
P.damage = 30
P.pellets = 4
P.range_step = 3
P.range = 15
P.name = "shrapnel"
P.preparePixelProjectile(T, src)
P.firer = src
P.fired_from = src
P.fire()