From 39b467c9da3b3fc286dcfb69e2de7c5f5ac67978 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 4 Feb 2015 17:56:43 -0500 Subject: [PATCH] New bullet types, projectile rewrite * Refactors projectile Bump() * Converts projectile_type var strings to paths * Reorganizes bullet projectile paths * Made a pass through all the bullet_act() definitions. Mainly ensured that damage_type is checked when dealing damage to certain objects. Removed stupid /turf bullet_act() override, replaced with on_hit() overrides on the relevant projectiles. * Adds shotgun pellets projectile. Adds Raptor's shotgun slug sprite. * Gives stunshots more of their own identity, refluffs them as taser cartridges for shotguns. They still aren't obtainable anywhere unless spawned. * Makes projectiles pass through girders and cultgirders with a certain probability, unless the girder itself was clicked. * Projectiles are also able to pass through grilles. Low damage projectiles have a chance to be blocked by grilles. High damage projectiles have a chance to have some damage absorbed by the grille. * Makes projectiles for blanks invisible. * Adds flash bullet types * Adds support for 'penetrating' projectiles * Swaps .45 and 9mm projectile types. .45s hit slightly harder, 9mils have more ammo capacity. --- code/game/machinery/atmoalter/canister.dm | 3 + code/game/machinery/bots/bots.dm | 2 + code/game/machinery/turrets.dm | 4 +- code/game/mecha/equipment/tools/tools.dm | 1 + code/game/mecha/equipment/weapons/weapons.dm | 4 +- code/game/mecha/mecha.dm | 19 +- code/game/objects/effects/effect_system.dm | 15 ++ .../items/weapons/grenades/flashbang.dm | 14 +- .../structures/crates_lockers/closets.dm | 3 + code/game/objects/structures/girders.dm | 34 +++- code/game/objects/structures/grille.dm | 27 ++- code/game/objects/structures/inflatable.dm | 3 + code/game/objects/structures/mirror.dm | 7 +- code/game/objects/structures/window.dm | 2 +- code/game/turfs/turf.dm | 12 -- .../spacesuits/rig/modules/rig_weapons.dm | 4 +- .../mob/living/carbon/human/human_defense.dm | 15 +- code/modules/mob/living/living_defense.dm | 2 +- .../mob/living/simple_animal/constructs.dm | 11 +- .../living/simple_animal/hostile/syndicate.dm | 2 +- code/modules/projectiles/ammunition.dm | 8 +- code/modules/projectiles/ammunition/boxes.dm | 2 +- .../modules/projectiles/ammunition/bullets.dm | 98 +++++---- code/modules/projectiles/gun.dm | 65 +++--- code/modules/projectiles/guns/alien.dm | 2 +- code/modules/projectiles/guns/energy.dm | 4 +- .../projectiles/guns/energy/nuclear.dm | 6 +- code/modules/projectiles/guns/energy/pulse.dm | 8 +- code/modules/projectiles/guns/energy/stun.dm | 10 +- code/modules/projectiles/guns/projectile.dm | 6 +- .../projectiles/guns/projectile/automatic.dm | 18 +- .../projectiles/guns/projectile/pistol.dm | 28 +-- .../projectiles/guns/projectile/revolver.dm | 11 +- code/modules/projectiles/projectile.dm | 190 +++++++++++++----- code/modules/projectiles/projectile/beams.dm | 5 + .../modules/projectiles/projectile/bullets.dm | 137 ++++++++++--- code/modules/projectiles/projectile/energy.dm | 36 +++- code/modules/reagents/reagent_dispenser.dm | 2 +- .../xenoarchaeology/machinery/coolant.dm | 2 +- code/modules/vehicles/vehicle.dm | 3 +- icons/obj/ammo.dmi | Bin 4493 -> 4588 bytes 41 files changed, 542 insertions(+), 283 deletions(-) diff --git a/code/game/machinery/atmoalter/canister.dm b/code/game/machinery/atmoalter/canister.dm index 4d96320518..0ab210b526 100644 --- a/code/game/machinery/atmoalter/canister.dm +++ b/code/game/machinery/atmoalter/canister.dm @@ -221,6 +221,9 @@ update_flag return /obj/machinery/portable_atmospherics/canister/bullet_act(var/obj/item/projectile/Proj) + if(!(Proj.damage_type == BRUTE || Proj.damage_type == BURN)) + return + if(Proj.damage) src.health -= round(Proj.damage / 2) healthcheck() diff --git a/code/game/machinery/bots/bots.dm b/code/game/machinery/bots/bots.dm index c4007c6994..f87ae33ee0 100644 --- a/code/game/machinery/bots/bots.dm +++ b/code/game/machinery/bots/bots.dm @@ -81,6 +81,8 @@ ..() /obj/machinery/bot/bullet_act(var/obj/item/projectile/Proj) + if(!(Proj.damage_type == BRUTE || Proj.damage_type == BURN)) + return health -= Proj.damage ..() healthcheck() diff --git a/code/game/machinery/turrets.dm b/code/game/machinery/turrets.dm index cb76c7921a..8f81c414de 100644 --- a/code/game/machinery/turrets.dm +++ b/code/game/machinery/turrets.dm @@ -89,7 +89,7 @@ return /obj/machinery/turret/bullet_act(var/obj/item/projectile/Proj) - if(Proj.damage_type == HALLOSS) + if(!(Proj.damage_type == BRUTE || Proj.damage_type == BURN)) return take_damage(Proj.damage) ..() @@ -299,7 +299,7 @@ popping = 0 /obj/machinery/turret/bullet_act(var/obj/item/projectile/Proj) - if(Proj.damage_type == HALLOSS) + if(!(Proj.damage_type == BRUTE || Proj.damage_type == BURN)) return src.health -= Proj.damage ..() diff --git a/code/game/mecha/equipment/tools/tools.dm b/code/game/mecha/equipment/tools/tools.dm index e66796fa9c..5b71a34ec6 100644 --- a/code/game/mecha/equipment/tools/tools.dm +++ b/code/game/mecha/equipment/tools/tools.dm @@ -1083,6 +1083,7 @@ /obj/item/mecha_parts/mecha_equipment/tool/passenger/destroy() for(var/atom/movable/AM in src) AM.forceMove(get_turf(src)) + AM << "You tumble out of the destroyed [src.name]!" return ..() /obj/item/mecha_parts/mecha_equipment/tool/passenger/Exit(atom/movable/O) diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm index bcb907a040..324637cdd3 100644 --- a/code/game/mecha/equipment/weapons/weapons.dm +++ b/code/game/mecha/equipment/weapons/weapons.dm @@ -199,7 +199,7 @@ name = "\improper LBX AC 10 \"Scattershot\"" icon_state = "mecha_scatter" equip_cooldown = 20 - projectile = /obj/item/projectile/bullet/midbullet + projectile = /obj/item/projectile/bullet/pistol/medium fire_sound = 'sound/weapons/Gunshot.ogg' fire_volume = 80 projectiles = 40 @@ -211,7 +211,7 @@ name = "\improper Ultra AC 2" icon_state = "mecha_uac2" equip_cooldown = 10 - projectile = /obj/item/projectile/bullet/weakbullet + projectile = /obj/item/projectile/bullet/pistol/medium fire_sound = 'sound/weapons/Gunshot.ogg' projectiles = 300 projectiles_per_shot = 3 diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 3285218baf..4790550700 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -506,8 +506,25 @@ var/ignore_threshold if(istype(Proj, /obj/item/projectile/beam/pulse)) ignore_threshold = 1 - src.take_damage(Proj.damage,Proj.flag) + src.take_damage(Proj.damage, Proj.flag) + if(prob(25)) spark_system.start() src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),ignore_threshold) + + //AP projectiles have a chance to cause additional damage + if(Proj.penetrating) + var/distance = get_dist(Proj.starting, get_turf(loc)) + var/hit_occupant = 1 //only allow the occupant to be hit once + for(var/i in 1 to min(Proj.penetrating, round(Proj.damage/15))) + if(src.occupant && hit_occupant && prob(20)) + Proj.attack_mob(src.occupant, distance) + hit_occupant = 0 + else + src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT), 1) + + Proj.penetrating-- + + if(prob(15)) + break //give a chance to exit early Proj.on_hit(src) return diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm index 82d27968db..ea9af2f70f 100644 --- a/code/game/objects/effects/effect_system.dm +++ b/code/game/objects/effects/effect_system.dm @@ -233,6 +233,21 @@ steam.start() -- spawns the effect return 0 return 1 +///////////////////////////////////////////// +// Illumination +///////////////////////////////////////////// + +/obj/effect/effect/smoke/illumination + name = "illumination" + opacity = 0 + icon = 'icons/effects/effects.dmi' + icon_state = "sparks" + +/obj/effect/effect/smoke/illumination/New(var/newloc, var/brightness=15, var/lifetime=10) + time_to_live=lifetime + ..() + SetLuminosity(brightness) + ///////////////////////////////////////////// // Bad smoke ///////////////////////////////////////////// diff --git a/code/game/objects/items/weapons/grenades/flashbang.dm b/code/game/objects/items/weapons/grenades/flashbang.dm index f9120d5491..a332f6fa92 100644 --- a/code/game/objects/items/weapons/grenades/flashbang.dm +++ b/code/game/objects/items/weapons/grenades/flashbang.dm @@ -21,11 +21,11 @@ B.health -= damage B.update_icon() - new/obj/effect/effect/smoke/flashbang(src.loc) + new/obj/effect/effect/smoke/illumination(src.loc, brightness=15) del(src) return - proc/bang(var/turf/T , var/mob/living/carbon/M) // Added a new proc called 'bang' that takes a location and a person to be banged. + proc/bang(var/turf/T , var/mob/living/carbon/M) // Added a new proc called 'bang' that takes a location and a person to be banged. if (locate(/obj/item/weapon/cloaking_device, M)) // Called during the loop that bangs people in lockers/containers and when banging for(var/obj/item/weapon/cloaking_device/S in M) // people in normal view. Could theroetically be called during other explosions. S.active = 0 // -- Polymorph @@ -100,16 +100,6 @@ M << "\red Your ears start to ring!" M.update_icons() -/obj/effect/effect/smoke/flashbang - name = "illumination" - time_to_live = 10 - opacity = 0 - icon_state = "sparks" - -/obj/effect/effect/smoke/flashbang/New() - ..() - SetLuminosity(15) - /obj/item/weapon/grenade/flashbang/clusterbang//Created by Polymorph, fixed by Sieve desc = "Use of this weapon may constiute a war crime in your area, consult your local captain." name = "clusterbang" diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index d9abac8979..40163e7c78 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -161,6 +161,9 @@ del(src) /obj/structure/closet/bullet_act(var/obj/item/projectile/Proj) + if(!(Proj.damage_type == BRUTE || Proj.damage_type == BURN)) + return + health -= Proj.damage ..() if(health <= 0) diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm index db0a5acc91..0512b814b2 100644 --- a/code/game/objects/structures/girders.dm +++ b/code/game/objects/structures/girders.dm @@ -5,6 +5,7 @@ layer = 2 var/state = 0 var/health = 200 + var/cover = 50 //how much cover the girder provides against projectiles. /obj/structure/girder/attack_generic(var/mob/user, var/damage, var/attack_message = "smashes apart", var/wallbreaker) if(!damage || !wallbreaker) @@ -14,19 +15,25 @@ return 1 /obj/structure/girder/bullet_act(var/obj/item/projectile/Proj) + //Girders only provide partial cover. There's a chance that the projectiles will just pass through. (unless you are trying to shoot the girder) + if(Proj.original != src && !prob(cover)) + return -1 //pass through //Tasers and the like should not damage girders. - if(Proj.damage_type == HALLOSS || Proj.damage_type == TOX || Proj.damage_type == CLONE) + if(!(Proj.damage_type == BRUTE || Proj.damage_type == BURN)) return - if(istype(Proj, /obj/item/projectile/beam)) - health -= Proj.damage - ..() - if(health <= 0) - new /obj/item/stack/sheet/metal(get_turf(src)) - del(src) + var/damage = Proj.damage + if(!istype(Proj, /obj/item/projectile/beam)) + damage *= 0.4 //non beams do reduced damage + + health -= damage + ..() + if(health <= 0) + new /obj/item/stack/sheet/metal(get_turf(src)) + del(src) - return + return /obj/structure/girder/attackby(obj/item/W as obj, mob/user as mob) if(istype(W, /obj/item/weapon/wrench) && state == 0) @@ -209,11 +216,13 @@ icon_state = "displaced" anchored = 0 health = 50 + cover = 25 /obj/structure/girder/reinforced icon_state = "reinforced" state = 2 health = 500 + cover = 80 /obj/structure/cultgirder icon= 'icons/obj/cult.dmi' @@ -222,6 +231,7 @@ density = 1 layer = 2 var/health = 250 + var/cover = 70 /obj/structure/cultgirder/attack_generic(var/mob/user, var/damage, var/attack_message = "smashes apart", var/wallbreaker) if(!damage || !wallbreaker) @@ -258,6 +268,14 @@ dismantle() /obj/structure/cultgirder/bullet_act(var/obj/item/projectile/Proj) //No beam check- How else will you destroy the cult girder with silver bullets????? + //Girders only provide partial cover. There's a chance that the projectiles will just pass through. (unless you are trying to shoot the girder) + if(Proj.original != src && !prob(cover)) + return -1 //pass through + + //Tasers and the like should not damage cultgirders. + if(!(Proj.damage_type == BRUTE || Proj.damage_type == BURN)) + return + health -= Proj.damage ..() if(health <= 0) diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index 2b06bcb534..76eac1b6b4 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -59,16 +59,33 @@ return !density /obj/structure/grille/bullet_act(var/obj/item/projectile/Proj) - if(!Proj) return //Tasers and the like should not damage grilles. - if(Proj.damage_type == HALLOSS) + if(!(Proj.damage_type == BRUTE || Proj.damage_type == BURN)) return - src.health -= Proj.damage*0.2 - healthcheck() - return 0 + //Flimsy grilles aren't so great at stopping projectiles. However they can absorb some of the impact + var/damage = Proj.damage + var/passthrough + if(damage > 30) + passthrough = 1 + if(prob(20)) + Proj.damage *= 0.5 //weaken the projectile + else + //weaker bullets are affected to a greater extent + if(prob(20)) + passthrough = 0 + else + Proj.damage *= 0.5 //weaken the projectile + passthrough = 1 + + if(passthrough) + . = -1 + damage *= 0.1 //if the bullet passes through then the grille avoids most of the damage + + src.health -= damage*0.2 + spawn(0) healthcheck() //spawn to make sure we return properly if the grille is deleted /obj/structure/grille/attackby(obj/item/weapon/W as obj, mob/user as mob) if(iswirecutter(W)) diff --git a/code/game/objects/structures/inflatable.dm b/code/game/objects/structures/inflatable.dm index 9c5182382b..5df70a54bb 100644 --- a/code/game/objects/structures/inflatable.dm +++ b/code/game/objects/structures/inflatable.dm @@ -38,6 +38,9 @@ return 0 /obj/structure/inflatable/bullet_act(var/obj/item/projectile/Proj) + if(!(Proj.damage_type == BRUTE || Proj.damage_type == BURN)) + return + health -= Proj.damage ..() if(health <= 0) diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 58e1a81b97..93868d720d 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -1,7 +1,7 @@ //wip wip wup /obj/structure/mirror - name = "\improper SalonPro Nano-Mirror(TM)" - desc = "The leading technology in hair salon products, utilizing nano-machinery to style your hair just right." + name = "mirror" + desc = "A SalonPro Nano-Mirror(TM) brand mirror! The leading technology in hair salon products, utilizing nano-machinery to style your hair just right." icon = 'icons/obj/watercloset.dmi' icon_state = "mirror" density = 0 @@ -70,6 +70,9 @@ /obj/structure/mirror/bullet_act(var/obj/item/projectile/Proj) + if(!(Proj.damage_type == BRUTE || Proj.damage_type == BURN)) + return + if(prob(Proj.damage * 2)) if(!shattered) shatter() diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 458c5626bc..e0d96bf821 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -100,7 +100,7 @@ /obj/structure/window/bullet_act(var/obj/item/projectile/Proj) //Tasers and the like should not damage windows. - if(Proj.damage_type == HALLOSS) + if(!(Proj.damage_type == BRUTE || Proj.damage_type == BURN)) return ..() diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 1f86703322..dfd2b480d6 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -54,18 +54,6 @@ step(user.pulling, get_dir(user.pulling.loc, src)) return 1 -/turf/bullet_act(var/obj/item/projectile/Proj) - if(istype(Proj ,/obj/item/projectile/beam/pulse)) - src.ex_act(2) - ..() - return 0 - -/turf/bullet_act(var/obj/item/projectile/Proj) - if(istype(Proj ,/obj/item/projectile/bullet/gyro)) - explosion(src, -1, 0, 2) - ..() - return 0 - /turf/Enter(atom/movable/mover as mob|obj, atom/forget as mob|obj|turf|area) if(movement_disabled && usr.ckey != movement_disabled_exception) usr << "\red Movement is admin-disabled." //This is to identify lag problems diff --git a/code/modules/clothing/spacesuits/rig/modules/rig_weapons.dm b/code/modules/clothing/spacesuits/rig/modules/rig_weapons.dm index d8373e8bbe..792a6ed0d0 100644 --- a/code/modules/clothing/spacesuits/rig/modules/rig_weapons.dm +++ b/code/modules/clothing/spacesuits/rig/modules/rig_weapons.dm @@ -28,7 +28,7 @@ var/obj/item/weapon/rig/suit = H.back if(istype(suit) && suit.cell && suit.cell.charge >= 250) suit.cell.use(250) - var/prog_path = text2path(projectile_type) + var/prog_path = projectile_type in_chamber = new prog_path(src) return 1 return 0 @@ -45,7 +45,7 @@ var/obj/item/weapon/rig/suit = H.back if(istype(suit) && suit.cell && suit.cell.charge >= 250) suit.cell.use(250) - var/prog_path = text2path(projectile_type) + var/prog_path = projectile_type in_chamber = new prog_path(src) return 1 return 0 \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 414f7e2bfb..a45a11f453 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -24,7 +24,7 @@ emp_act if(!(def_zone in list("chest", "groin"))) reflectchance /= 2 if(prob(reflectchance)) - visible_message("\red The [P.name] gets reflected by [src]'s [wear_suit.name]!") + visible_message("\red \The [P] gets reflected by \the [src]'s [wear_suit.name]!") // Find a turf near or on the original location to bounce to if(P.starting) @@ -33,12 +33,7 @@ emp_act var/turf/curloc = get_turf(src) // redirect the projectile - P.original = locate(new_x, new_y, P.z) - P.starting = curloc - P.current = curloc - P.firer = src - P.yo = new_y - curloc.y - P.xo = new_x - curloc.x + P.redirect(new_x, new_y, curloc, src) return -1 // complete projectile permutation @@ -47,9 +42,9 @@ emp_act var/armor = getarmor_organ(organ, "bullet") if((P.embed && prob(20 + max(P.damage - armor, -10)))) var/obj/item/weapon/shard/shrapnel/SP = new() - (SP.name) = "[P.name] shrapnel" - (SP.desc) = "[SP.desc] It looks like it was fired from [P.shot_from]." - (SP.loc) = organ + SP.name = (P.name != "shrapnel")? "[P.name] shrapnel" : "shrapnel" + SP.desc = "[SP.desc] It looks like it was fired from [P.shot_from]." + SP.loc = organ organ.embed(SP) return (..(P , def_zone)) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 1be8ea4ac9..b33c182b28 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -56,7 +56,7 @@ signaler.signal() //Stun Beams - if(istype(P, /obj/item/projectile/beam/stun) || istype(P, /obj/item/projectile/bullet/stunshot)) + if(P.taser_effect) stun_effect_act(0, P.agony, def_zone, P) src <<"\red You have been hit by [P]!" del P diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index 5e3e91b90f..eae7f3e063 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -166,8 +166,8 @@ var/reflectchance = 80 - round(P.damage/3) if(prob(reflectchance)) adjustBruteLoss(P.damage * 0.5) - visible_message("The [P.name] gets reflected by [src]'s shell!", \ - "The [P.name] gets reflected by [src]'s shell!") + visible_message("\The [P] was reflected by \the [src]'s shell!", \ + "\The [P] was reflected by \the [src]'s shell!") // Find a turf near or on the original location to bounce to if(P.starting) @@ -176,12 +176,7 @@ var/turf/curloc = get_turf(src) // redirect the projectile - P.original = locate(new_x, new_y, P.z) - P.starting = curloc - P.current = curloc - P.firer = src - P.yo = new_y - curloc.y - P.xo = new_x - curloc.x + P.redirect(new_x, new_y, curloc, src) return -1 // complete projectile permutation diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate.dm b/code/modules/mob/living/simple_animal/hostile/syndicate.dm index ebcdfc1194..c5c87014ec 100644 --- a/code/modules/mob/living/simple_animal/hostile/syndicate.dm +++ b/code/modules/mob/living/simple_animal/hostile/syndicate.dm @@ -108,7 +108,7 @@ icon_living = "syndicateranged" casingtype = /obj/item/ammo_casing/a12mm projectilesound = 'sound/weapons/Gunshot_smg.ogg' - projectiletype = /obj/item/projectile/bullet/midbullet2 + projectiletype = /obj/item/projectile/bullet/pistol/medium weapon1 = /obj/item/weapon/gun/projectile/automatic/c20r diff --git a/code/modules/projectiles/ammunition.dm b/code/modules/projectiles/ammunition.dm index 9220f847b9..b4b213972f 100644 --- a/code/modules/projectiles/ammunition.dm +++ b/code/modules/projectiles/ammunition.dm @@ -7,14 +7,14 @@ slot_flags = SLOT_BELT throwforce = 1 w_class = 1.0 - var/caliber = "" //Which kind of guns it can be loaded into - var/projectile_type = ""//The bullet type to create when New() is called - var/obj/item/projectile/BB = null //The loaded bullet + var/caliber = "" //Which kind of guns it can be loaded into + var/projectile_type //The bullet type to create when New() is called + var/obj/item/projectile/BB = null //The loaded bullet New() ..() - if(projectile_type) + if(ispath(projectile_type)) BB = new projectile_type(src) pixel_x = rand(-10.0, 10) pixel_y = rand(-10.0, 10) diff --git a/code/modules/projectiles/ammunition/boxes.dm b/code/modules/projectiles/ammunition/boxes.dm index f0daf2eebb..bd165bb698 100644 --- a/code/modules/projectiles/ammunition/boxes.dm +++ b/code/modules/projectiles/ammunition/boxes.dm @@ -56,7 +56,7 @@ icon_state = "9x19p" origin_tech = "combat=2" ammo_type = "/obj/item/ammo_casing/c9mm" - max_ammo = 8 + max_ammo = 10 multiple_sprites = 1 /obj/item/ammo_magazine/mc9mm/empty diff --git a/code/modules/projectiles/ammunition/bullets.dm b/code/modules/projectiles/ammunition/bullets.dm index 134b9a6ca3..d0cb2915dc 100644 --- a/code/modules/projectiles/ammunition/bullets.dm +++ b/code/modules/projectiles/ammunition/bullets.dm @@ -1,113 +1,129 @@ /obj/item/ammo_casing/a357 desc = "A .357 bullet casing." caliber = "357" - projectile_type = "/obj/item/projectile/bullet" + projectile_type = /obj/item/projectile/bullet/pistol/strong /obj/item/ammo_casing/a50 desc = "A .50AE bullet casing." caliber = ".50" - projectile_type = "/obj/item/projectile/bullet" - -/obj/item/ammo_casing/a418 - desc = "A .418 bullet casing." - caliber = "357" - projectile_type = "/obj/item/projectile/bullet/suffocationbullet" - + projectile_type = /obj/item/projectile/bullet/pistol/strong /obj/item/ammo_casing/a75 - desc = "A .75 bullet casing." + desc = "A 20mm bullet casing." caliber = "75" - projectile_type = "/obj/item/projectile/bullet/gyro" - - -/obj/item/ammo_casing/a666 - desc = "A .666 bullet casing." - caliber = "357" - projectile_type = "/obj/item/projectile/bullet/cyanideround" - + projectile_type = /obj/item/projectile/bullet/gyro /obj/item/ammo_casing/c38 desc = "A .38 bullet casing." caliber = "38" - projectile_type = "/obj/item/projectile/bullet/weakbullet" - + projectile_type = /obj/item/projectile/bullet/pistol/rubber /obj/item/ammo_casing/c9mm desc = "A 9mm bullet casing." caliber = "9mm" - projectile_type = "/obj/item/projectile/bullet/midbullet2" - + projectile_type = /obj/item/projectile/bullet/pistol /obj/item/ammo_casing/c45 desc = "A .45 bullet casing." caliber = ".45" - projectile_type = "/obj/item/projectile/bullet/midbullet" + projectile_type = /obj/item/projectile/bullet/pistol/medium /obj/item/ammo_casing/c45r desc = "A .45 rubber bullet casing." caliber = ".45" - projectile_type = "/obj/item/projectile/bullet/weakbullet/rubber" + projectile_type = /obj/item/projectile/bullet/pistol/rubber + +/obj/item/ammo_casing/c45f + desc = "A .45 flash shell casing." + caliber = ".45" + projectile_type = /obj/item/projectile/energy/flash /obj/item/ammo_casing/a12mm desc = "A 12mm bullet casing." caliber = "12mm" - projectile_type = "/obj/item/projectile/bullet/midbullet2" + projectile_type = /obj/item/projectile/bullet/pistol/medium /obj/item/ammo_casing/shotgun + name = "shotgun slug" + desc = "A 12 gauge slug." + icon_state = "slshell" + caliber = "shotgun" + projectile_type = /obj/item/projectile/bullet/shotgun + matter = list("metal" = 12500) + +/obj/item/ammo_casing/shotgun/pellet name = "shotgun shell" desc = "A 12 gauge shell." icon_state = "gshell" - caliber = "shotgun" - projectile_type = "/obj/item/projectile/bullet" + projectile_type = /obj/item/projectile/bullet/pellet/shotgun matter = list("metal" = 12500) - /obj/item/ammo_casing/shotgun/blank name = "shotgun shell" desc = "A blank shell." icon_state = "blshell" - projectile_type = "/obj/item/projectile/bullet/chameleon" + projectile_type = /obj/item/projectile/bullet/blank matter = list("metal" = 250) - /obj/item/ammo_casing/shotgun/beanbag name = "beanbag shell" - desc = "A weak beanbag shell." + desc = "A beanbag shell." icon_state = "bshell" - projectile_type = "/obj/item/projectile/bullet/weakbullet/beanbag" + projectile_type = /obj/item/projectile/bullet/shotgun/beanbag matter = list("metal" = 500) - /obj/item/ammo_casing/shotgun/stunshell name = "stun shell" - desc = "A stunning shell." + desc = "A 12 gauge taser cartridge." icon_state = "stunshell" - projectile_type = "/obj/item/projectile/bullet/stunshot" - matter = list("metal" = 2500) + projectile_type = /obj/item/projectile/energy/electrode/stunshot + matter = list("metal" = 1250, "glass" = 1250) +/obj/item/ammo_casing/shotgun/flash + name = "flash shell" + desc = "A flash shell used to provide illumination." + icon_state = "fshell" + projectile_type = /obj/item/projectile/energy/flash/flare + matter = list("metal" = 250, "glass" = 250) /obj/item/ammo_casing/shotgun/dart - name = "shotgun darts" + name = "shotgun dart" desc = "A dart for use in shotguns." icon_state = "dart" - projectile_type = "/obj/item/projectile/energy/dart" + projectile_type = /obj/item/projectile/energy/dart matter = list("metal" = 12500) /obj/item/ammo_casing/a762 - desc = "A 7.62 bullet casing." + desc = "A 7.62mm bullet casing." caliber = "a762" - projectile_type = "/obj/item/projectile/bullet/a762" + projectile_type = /obj/item/projectile/bullet/rifle/a762 + +/obj/item/ammo_casing/a145 + name = "\improper AP shell casing" + desc = "A 14.5mm AP shell." + icon_state = "slshell" + projectile_type = /obj/item/projectile/bullet/rifle/a145 /obj/item/ammo_casing/rocket name = "rocket shell" desc = "A high explosive designed to be fired from a launcher." icon_state = "rocketshell" - projectile_type = "/obj/item/missile" + projectile_type = /obj/item/missile caliber = "rocket" /obj/item/ammo_casing/chameleon name = "chameleon bullets" desc = "A set of bullets for the Chameleon Gun." - projectile_type = "/obj/item/projectile/bullet/chameleon" + projectile_type = /obj/item/projectile/bullet/chameleon caliber = ".45" + +/obj/item/ammo_casing/a418 + desc = "A .418 bullet casing." + caliber = "357" + projectile_type = /obj/item/projectile/bullet/suffocationbullet + +/obj/item/ammo_casing/a666 + desc = "A .666 bullet casing." + caliber = "357" + projectile_type = /obj/item/projectile/bullet/cyanideround \ No newline at end of file diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index fc44ed09fc..f4bc1175d5 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -77,6 +77,8 @@ /obj/item/weapon/gun/proc/Fire(atom/target as mob|obj|turf|area, mob/living/user as mob|obj, params, reflex = 0)//TODO: go over this //Exclude lasertag guns from the CLUMSY check. + if(!user) return + if(clumsy_check) if(istype(user, /mob/living)) var/mob/living/M = user @@ -97,11 +99,6 @@ add_fingerprint(user) - var/turf/curloc = get_turf(user) - var/turf/targloc = get_turf(target) - if (!istype(targloc) || !istype(curloc)) - return - if(!special_check(user)) return @@ -116,18 +113,6 @@ if(!in_chamber) return - in_chamber.firer = user - in_chamber.def_zone = user.zone_sel.selecting - if(targloc == curloc) - user.bullet_act(in_chamber) - del(in_chamber) - update_icon() - return - - if(recoil) - spawn() - shake_camera(user, recoil + 1, recoil) - if(silenced) playsound(user, fire_sound, 10, 1) else @@ -136,34 +121,46 @@ "You fire [src][reflex ? "by reflex":""]!", \ "You hear a [istype(in_chamber, /obj/item/projectile/beam) ? "laser blast" : "gunshot"]!") - in_chamber.original = target - in_chamber.loc = get_turf(user) - in_chamber.starting = get_turf(user) - in_chamber.shot_from = src user.next_move = world.time + 4 - in_chamber.silenced = silenced - in_chamber.current = curloc - in_chamber.yo = targloc.y - curloc.y - in_chamber.xo = targloc.x - curloc.x + + var/x_offset = 0 + var/y_offset = 0 if(istype(user, /mob/living/carbon)) var/mob/living/carbon/mob = user if(mob.shock_stage > 120) - in_chamber.yo += rand(-2,2) - in_chamber.xo += rand(-2,2) + y_offset = rand(-2,2) + x_offset = rand(-2,2) else if(mob.shock_stage > 70) - in_chamber.yo += rand(-1,1) - in_chamber.xo += rand(-1,1) + y_offset = rand(-1,1) + x_offset = rand(-1,1) + var/p_x + var/p_y if(params) var/list/mouse_control = params2list(params) if(mouse_control["icon-x"]) - in_chamber.p_x = text2num(mouse_control["icon-x"]) + p_x = text2num(mouse_control["icon-x"]) if(mouse_control["icon-y"]) - in_chamber.p_y = text2num(mouse_control["icon-y"]) + p_y = text2num(mouse_control["icon-y"]) + + if(in_chamber) + var/fail = in_chamber.launch( + target = target, + user = user, + launcher = src, + target_zone = user.zone_sel.selecting, + x_offset = x_offset, + y_offset = y_offset, + px = p_x, + py = p_y + ) + + if(fail) return + + if(recoil) + spawn() + shake_camera(user, recoil + 1, recoil) - spawn() - if(in_chamber) - in_chamber.process() sleep(1) in_chamber = null diff --git a/code/modules/projectiles/guns/alien.dm b/code/modules/projectiles/guns/alien.dm index fb678d9b4d..f9af71acc8 100644 --- a/code/modules/projectiles/guns/alien.dm +++ b/code/modules/projectiles/guns/alien.dm @@ -73,7 +73,7 @@ recoil = 1 force = 10 - projectile_type = "/obj/item/projectile/energy/sonic" + projectile_type = /obj/item/projectile/energy/sonic cell_type = "/obj/item/weapon/cell/super" fire_delay = 40 fire_sound = 'sound/effects/basscannon.ogg' diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm index 8a38a0325d..0ed82e9f4a 100644 --- a/code/modules/projectiles/guns/energy.dm +++ b/code/modules/projectiles/guns/energy.dm @@ -7,7 +7,7 @@ var/obj/item/weapon/cell/power_supply //What type of power cell this uses var/charge_cost = 100 //How much energy is needed to fire. var/cell_type = "/obj/item/weapon/cell" - var/projectile_type = "/obj/item/projectile/beam/practice" + var/projectile_type = /obj/item/projectile/beam/practice var/modifystate emp_act(severity) @@ -30,7 +30,7 @@ if(in_chamber) return 1 if(!power_supply) return 0 if(!power_supply.use(charge_cost)) return 0 - if(!projectile_type) return 0 + if(!ispath(projectile_type)) return 0 in_chamber = new projectile_type(src) return 1 diff --git a/code/modules/projectiles/guns/energy/nuclear.dm b/code/modules/projectiles/guns/energy/nuclear.dm index e2a55a5ed4..abfd7510a4 100644 --- a/code/modules/projectiles/guns/energy/nuclear.dm +++ b/code/modules/projectiles/guns/energy/nuclear.dm @@ -6,7 +6,7 @@ fire_sound = 'sound/weapons/Taser.ogg' charge_cost = 100 //How much energy is needed to fire. - projectile_type = "/obj/item/projectile/beam/stun" + projectile_type = /obj/item/projectile/beam/stun origin_tech = "combat=3;magnets=2" modifystate = "energystun" @@ -20,14 +20,14 @@ charge_cost = 100 fire_sound = 'sound/weapons/Laser.ogg' user << "\red [src.name] is now set to kill." - projectile_type = "/obj/item/projectile/beam" + projectile_type = /obj/item/projectile/beam modifystate = "energykill" if(1) mode = 0 charge_cost = 100 fire_sound = 'sound/weapons/Taser.ogg' user << "\red [src.name] is now set to stun." - projectile_type = "/obj/item/projectile/beam/stun" + projectile_type = /obj/item/projectile/beam/stun modifystate = "energystun" update_icon() if(user.l_hand == src) diff --git a/code/modules/projectiles/guns/energy/pulse.dm b/code/modules/projectiles/guns/energy/pulse.dm index 74b6af43c3..9b0a22e06c 100644 --- a/code/modules/projectiles/guns/energy/pulse.dm +++ b/code/modules/projectiles/guns/energy/pulse.dm @@ -6,7 +6,7 @@ force = 10 fire_sound = 'sound/weapons/pulse.ogg' charge_cost = 200 - projectile_type = "/obj/item/projectile/beam/pulse" + projectile_type = /obj/item/projectile/beam/pulse cell_type = "/obj/item/weapon/cell/super" var/mode = 2 fire_delay = 25 @@ -18,19 +18,19 @@ charge_cost = 100 fire_sound = 'sound/weapons/Taser.ogg' user << "\red [src.name] is now set to stun." - projectile_type = "/obj/item/projectile/beam/stun" + projectile_type = /obj/item/projectile/beam/stun if(0) mode = 1 charge_cost = 100 fire_sound = 'sound/weapons/Laser.ogg' user << "\red [src.name] is now set to kill." - projectile_type = "/obj/item/projectile/beam" + projectile_type = /obj/item/projectile/beam if(1) mode = 2 charge_cost = 200 fire_sound = 'sound/weapons/pulse.ogg' user << "\red [src.name] is now set to DESTROY." - projectile_type = "/obj/item/projectile/beam/pulse" + projectile_type = /obj/item/projectile/beam/pulse return isHandgun() diff --git a/code/modules/projectiles/guns/energy/stun.dm b/code/modules/projectiles/guns/energy/stun.dm index 91b64a1de4..c3d6529dcb 100644 --- a/code/modules/projectiles/guns/energy/stun.dm +++ b/code/modules/projectiles/guns/energy/stun.dm @@ -5,7 +5,7 @@ item_state = null //so the human update icon uses the icon_state instead. fire_sound = 'sound/weapons/Taser.ogg' charge_cost = 100 - projectile_type = "/obj/item/projectile/beam/stun" + projectile_type = /obj/item/projectile/beam/stun cell_type = "/obj/item/weapon/cell/crap" /obj/item/weapon/gun/energy/taser/cyborg @@ -47,7 +47,7 @@ fire_sound = 'sound/weapons/Taser.ogg' origin_tech = "combat=3;materials=3;powerstorage=2" charge_cost = 125 - projectile_type = "/obj/item/projectile/beam/stun" + projectile_type = /obj/item/projectile/beam/stun cell_type = "/obj/item/weapon/cell" @@ -62,7 +62,7 @@ origin_tech = "combat=2;magnets=2;syndicate=5" silenced = 1 fire_sound = 'sound/weapons/Genhit.ogg' - projectile_type = "/obj/item/projectile/energy/bolt" + projectile_type = /obj/item/projectile/energy/bolt cell_type = "/obj/item/weapon/cell/crap" var/charge_tick = 0 @@ -91,7 +91,7 @@ /obj/item/weapon/gun/energy/crossbow/ninja name = "energy dart thrower" - projectile_type = "/obj/item/projectile/energy/dart" + projectile_type = /obj/item/projectile/energy/dart /obj/item/weapon/gun/energy/crossbow/largecrossbow name = "Energy Crossbow" @@ -99,4 +99,4 @@ w_class = 4.0 force = 10 matter = list("metal" = 200000) - projectile_type = "/obj/item/projectile/energy/bolt/large" + projectile_type = /obj/item/projectile/energy/bolt/large diff --git a/code/modules/projectiles/guns/projectile.dm b/code/modules/projectiles/guns/projectile.dm index f096b819c8..bea28e9c37 100644 --- a/code/modules/projectiles/guns/projectile.dm +++ b/code/modules/projectiles/guns/projectile.dm @@ -4,7 +4,7 @@ /obj/item/weapon/gun/projectile name = "revolver" - desc = "A classic revolver. Uses 357 ammo" + desc = "A classic revolver. Uses .357 ammo" icon_state = "revolver" caliber = "357" origin_tech = "combat=2;materials=2" @@ -16,12 +16,14 @@ var/max_shells = 7 var/load_method = SPEEDLOADER //0 = Single shells or quick loader, 1 = box, 2 = magazine var/obj/item/ammo_magazine/empty_mag = null - + var/mag_type = null /obj/item/weapon/gun/projectile/New() ..() for(var/i = 1, i <= max_shells, i++) loaded += new ammo_type(src) + if(load_method == MAGAZINE) + empty_mag = new mag_type(src) update_icon() return diff --git a/code/modules/projectiles/guns/projectile/automatic.dm b/code/modules/projectiles/guns/projectile/automatic.dm index ee0d8a6427..0e5f0d68cd 100644 --- a/code/modules/projectiles/guns/projectile/automatic.dm +++ b/code/modules/projectiles/guns/projectile/automatic.dm @@ -3,7 +3,7 @@ desc = "A lightweight, fast firing gun. Uses 9mm rounds." icon_state = "saber" //ugly w_class = 3.0 - max_shells = 18 + max_shells = 22 caliber = "9mm" origin_tech = "combat=4;materials=2" ammo_type = "/obj/item/ammo_casing/c9mm" @@ -14,6 +14,9 @@ isHandgun() return 0 +/obj/item/weapon/gun/projectile/automatic/test + name = "test gun" + ammo_type = "/obj/item/ammo_casing/a145" /obj/item/weapon/gun/projectile/automatic/mini_uzi name = "\improper Uzi" @@ -40,15 +43,8 @@ origin_tech = "combat=5;materials=2;syndicate=8" ammo_type = "/obj/item/ammo_casing/a12mm" fire_sound = 'sound/weapons/Gunshot_smg.ogg' - load_method = 2 - - - New() - ..() - empty_mag = new /obj/item/ammo_magazine/a12mm/empty(src) - update_icon() - return - + load_method = MAGAZINE + mag_type = /obj/item/ammo_magazine/a12mm/empty afterattack(atom/target as mob|obj|turf|area, mob/living/user as mob|obj, flag) ..() @@ -80,7 +76,7 @@ origin_tech = "combat=5;materials=1;syndicate=2" ammo_type = "/obj/item/ammo_casing/a762" fire_sound = 'sound/weapons/Gunshot_smg.ogg' - load_method = 2 + load_method = MAGAZINE var/cover_open = 0 var/mag_inserted = 1 diff --git a/code/modules/projectiles/guns/projectile/pistol.dm b/code/modules/projectiles/guns/projectile/pistol.dm index b963924614..f12420f7dc 100644 --- a/code/modules/projectiles/guns/projectile/pistol.dm +++ b/code/modules/projectiles/guns/projectile/pistol.dm @@ -19,13 +19,8 @@ max_shells = 7 caliber = ".50" ammo_type ="/obj/item/ammo_casing/a50" - load_method = 2 - New() - ..() - empty_mag = new /obj/item/ammo_magazine/a50/empty(src) - update_icon() - return - + load_method = MAGAZINE + mag_type = /obj/item/ammo_magazine/a50/empty afterattack(atom/target as mob|obj|turf|area, mob/living/user as mob|obj, flag) ..() @@ -59,13 +54,8 @@ fire_sound = 'sound/effects/Explosion1.ogg' origin_tech = "combat=3" ammo_type = "/obj/item/ammo_casing/a75" - load_method = 2 - New() - ..() - empty_mag = new /obj/item/ammo_magazine/a75/empty(src) - update_icon() - return - + load_method = MAGAZINE + mag_type = /obj/item/ammo_magazine/a75/empty afterattack(atom/target as mob|obj|turf|area, mob/living/user as mob|obj, flag) ..() @@ -89,17 +79,13 @@ desc = "A small, easily concealable gun. Uses 9mm rounds." icon_state = "pistol" w_class = 2 - max_shells = 8 + max_shells = 10 caliber = "9mm" silenced = 0 origin_tech = "combat=2;materials=2;syndicate=2" ammo_type = "/obj/item/ammo_casing/c9mm" - load_method = 2 - -/obj/item/weapon/gun/projectile/pistol/New() - ..() - empty_mag = new /obj/item/ammo_magazine/mc9mm/empty(src) - return + load_method = MAGAZINE + mag_type = /obj/item/ammo_magazine/mc9mm/empty /obj/item/weapon/gun/projectile/pistol/afterattack(atom/target as mob|obj|turf|area, mob/living/user as mob|obj, flag) ..() diff --git a/code/modules/projectiles/guns/projectile/revolver.dm b/code/modules/projectiles/guns/projectile/revolver.dm index f1eac3e858..8335bd55ae 100644 --- a/code/modules/projectiles/guns/projectile/revolver.dm +++ b/code/modules/projectiles/guns/projectile/revolver.dm @@ -7,7 +7,6 @@ origin_tech = "combat=2;materials=2" ammo_type = "/obj/item/ammo_casing/c38" - special_check(var/mob/living/carbon/human/M) if(caliber == initial(caliber)) return 1 @@ -77,12 +76,8 @@ max_shells = 7 caliber = ".45" ammo_type = "/obj/item/ammo_casing/c45r" - load_method = 2 - -/obj/item/weapon/gun/projectile/detective/semiauto/New() - ..() - empty_mag = new /obj/item/ammo_magazine/c45r/empty(src) - return + load_method = MAGAZINE + mag_type = /obj/item/ammo_magazine/c45r/empty /obj/item/weapon/gun/projectile/detective/semiauto/afterattack(atom/target as mob|obj|turf|area, mob/living/user as mob|obj, flag) ..() @@ -130,7 +125,7 @@ var/num_loaded = 0 if(istype(A, /obj/item/ammo_magazine)) - if((load_method == 2) && loaded.len) return + if((load_method == MAGAZINE) && loaded.len) return var/obj/item/ammo_magazine/AM = A for(var/obj/item/ammo_casing/AC in AM.stored_ammo) if(getAmmo() > 0 || loaded.len >= max_shells) diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 3b5ed8bd53..375d45fd54 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -36,8 +36,10 @@ var/damage = 10 var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE 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/flag = "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/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 @@ -50,7 +52,8 @@ var/agony = 0 var/embed = 0 // whether or not the projectile can embed itself in the mob - proc/on_hit(var/atom/target, var/blocked = 0) + //TODO: make it so this is called more reliably, instead of sometimes by bullet_act() and sometimes not + proc/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null) if(blocked >= 2) return 0//Full block if(!isliving(target)) return 0 if(isanimal(target)) return 0 @@ -58,6 +61,14 @@ L.apply_effects(stun, weaken, paralyze, irradiate, stutter, eyeblur, drowsy, agony, blocked) // add in AGONY! return 1 + //called when the projectile stops flying because it collided with something + proc/on_impact(var/atom/A) + return + + //return 1 if the projectile should be allowed to pass through after all, 0 if not. + proc/on_penetrate(var/atom/A) + return 1 + proc/check_fire(var/mob/living/target as mob, var/mob/living/user as mob) //Checks if you can hit them or not. if(!istype(target) || !istype(user)) return 0 @@ -70,77 +81,152 @@ del(in_chamber) //No need for it anymore return output //Send it back to the gun! + //called to launch a projectile from a gun + proc/launch(atom/target, mob/user, obj/item/weapon/gun/launcher, var/target_zone, var/x_offset=0, var/y_offset=0, var/px=null, var/py=null) + var/turf/curloc = get_turf(user) + var/turf/targloc = get_turf(target) + if (!istype(targloc) || !istype(curloc)) + return 1 + + firer = user + def_zone = user.zone_sel.selecting + + if(user == target) //Shooting yourself + user.bullet_act(src, target_zone) + del(src) + return 0 + if(targloc == curloc) //Shooting the ground + targloc.bullet_act(src, target_zone) + del(src) + return 0 + + original = target + loc = curloc + starting = curloc + current = curloc + yo = targloc.y - curloc.y + y_offset + xo = targloc.x - curloc.x + x_offset + if(!isnull(py)) p_y = py + if(!isnull(px)) p_x = px + + shot_from = launcher + silenced = launcher.silenced + + spawn() + process() + + return 0 + + //Used to change the direction of the projectile in flight. + proc/redirect(var/new_x, var/new_y, var/atom/starting_loc, var/mob/new_firer=null) + original = locate(new_x, new_y, src.z) + starting = starting_loc + current = starting_loc + if(new_firer) + firer = src + + yo = new_y - starting_loc.y + xo = new_x - starting_loc.x + + //Called when the projectile intercepts a mob. Returns 1 if the projectile hit the mob, 0 if it missed and should keep flying. + proc/attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier = -30) + //accuracy bonus from aiming + if (istype(shot_from, /obj/item/weapon/gun)) //If you aim at someone beforehead, it'll hit more often. + var/obj/item/weapon/gun/daddy = shot_from //Kinda balanced by fact you need like 2 seconds to aim + if (daddy.target && original in daddy.target) //As opposed to no-delay pew pew + miss_modifier += -30 + + //roll to-hit + var/hit_zone = get_zone_with_miss_chance(def_zone, target_mob, max(miss_modifier + 15*distance, 0)) + if(!hit_zone) + visible_message("\The [src] misses [target_mob] narrowly!") + return 0 + + //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 + def_zone = hit_zone + + //hit messages + if(silenced) + target_mob << "You've been hit in the [parse_zone(def_zone)] by \the [src]!" + else + visible_message("\The [target_mob] is hit by \the [src] in the [parse_zone(def_zone)]!")//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(istype(firer, /mob)) + target_mob.attack_log += "\[[time_stamp()]\] [firer]/[firer.ckey] shot [target_mob]/[target_mob.ckey] with a [src.type]" + firer.attack_log += "\[[time_stamp()]\] [firer]/[firer.ckey] shot [target_mob]/[target_mob.ckey] with a [src.type]" + msg_admin_attack("[firer] ([firer.ckey]) shot [target_mob] ([target_mob.ckey]) with a [src] (JMP)") //BS12 EDIT ALG + else + target_mob.attack_log += "\[[time_stamp()]\] UNKNOWN SUBJECT (No longer exists) shot [target_mob]/[target_mob.ckey] with a [src]" + msg_admin_attack("UNKNOWN shot [target_mob] ([target_mob.ckey]) with a [src] (JMP)") //BS12 EDIT ALG + + //sometimes bullet_act() will want the projectile to continue flying + if (target_mob.bullet_act(src, def_zone) == -1) + return 0 + + return 1 + Bump(atom/A as mob|obj|turf|area) + if(A == src) + return 0 //no + if(A == firer) loc = A.loc return 0 //cannot shoot yourself - if(bumped) return 0 - var/forcedodge = 0 // force the projectile to pass + if(bumped) + return 0 + + var/passthrough = 0 //if the projectile should continue flying + var/distance = get_dist(starting,loc) bumped = 1 - if(firer && istype(A, /mob)) + if(ismob(A)) var/mob/M = A - if(!istype(A, /mob/living)) - loc = A.loc - return 0// nope.avi - - var/distance = get_dist(starting,loc) - var/miss_modifier = -30 - - if (istype(shot_from,/obj/item/weapon/gun)) //If you aim at someone beforehead, it'll hit more often. - var/obj/item/weapon/gun/daddy = shot_from //Kinda balanced by fact you need like 2 seconds to aim - if (daddy.target && original in daddy.target) //As opposed to no-delay pew pew - miss_modifier += -30 - def_zone = get_zone_with_miss_chance(def_zone, M, miss_modifier + 15*distance) - - if(!def_zone) - visible_message("\blue \The [src] misses [M] narrowly!") - forcedodge = -1 + if(istype(A, /mob/living)) + passthrough = !attack_mob(M, distance) else - if(silenced) - M << "\red You've been shot in the [parse_zone(def_zone)] by the [src.name]!" - else - visible_message("\red [A.name] is hit by the [src.name] in the [parse_zone(def_zone)]!")//X has fired Y is now given by the guns so you cant tell who shot you if you could not see the shooter - if(istype(firer, /mob)) - M.attack_log += "\[[time_stamp()]\] [firer]/[firer.ckey] shot [M]/[M.ckey] with a [src.type]" - firer.attack_log += "\[[time_stamp()]\] [firer]/[firer.ckey] shot [M]/[M.ckey] with a [src.type]" - msg_admin_attack("[firer] ([firer.ckey]) shot [M] ([M.ckey]) with a [src] (JMP)") //BS12 EDIT ALG - else - M.attack_log += "\[[time_stamp()]\] UNKNOWN SUBJECT (No longer exists) shot [M]/[M.ckey] with a [src]" - msg_admin_attack("UNKNOWN shot [M] ([M.ckey]) with a [src] (JMP)") //BS12 EDIT ALG - - if(A) - if (!forcedodge) - forcedodge = A.bullet_act(src, def_zone) // searches for return value - if(forcedodge == -1) // the bullet passes through a dense object! - bumped = 0 // reset bumped variable! - if(istype(A, /turf)) - loc = A - else - loc = A.loc - permutated.Add(A) - return 0 - if(istype(A,/turf)) + passthrough = 1 //so ghosts don't stop bullets + else + passthrough = (A.bullet_act(src, def_zone) == -1) //backwards compatibility + if(isturf(A)) for(var/obj/O in A) O.bullet_act(src) for(var/mob/M in A) - M.bullet_act(src, def_zone) - density = 0 - invisibility = 101 - del(src) - return 1 + attack_mob(M, distance) + //penetrating projectiles can pass through things that otherwise would not let them + if(penetrating > 0) + if(on_penetrate(A)) + passthrough = 1 + penetrating-- + + //the bullet passes through a dense object! + if(passthrough) + bumped = 0 //reset bumped variable! + if(istype(A, /turf)) + loc = A + else + loc = A.loc + permutated.Add(A) + return 0 + + //stop flying + on_impact(A) + + density = 0 + invisibility = 101 + del(src) + return 1 CanPass(atom/movable/mover, turf/target, height=0, air_group=0) if(air_group || (height==0)) return 1 if(istype(mover, /obj/item/projectile)) - return prob(95) + return prob(95) //ha else return 1 - process() if(kill_count < 1) del(src) diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index b811eb9281..4ad1a3fab2 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -105,6 +105,10 @@ var/list/beam_master = list() icon_state = "u_laser" damage = 50 +/obj/item/projectile/beam/pulse/on_hit(var/atom/target, var/blocked = 0) + if(isturf(target)) + target.ex_act(2) + ..() /obj/item/projectile/beam/emitter name = "emitter beam" @@ -169,5 +173,6 @@ var/list/beam_master = list() name = "stun beam" icon_state = "stun" nodamage = 1 + taser_effect = 1 agony = 40 damage_type = HALLOSS diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm index d1216d1559..5da27e9ef0 100644 --- a/code/modules/projectiles/projectile/bullets.dm +++ b/code/modules/projectiles/projectile/bullets.dm @@ -8,62 +8,153 @@ embed = 1 sharp = 1 - on_hit(var/atom/target, var/blocked = 0) - if (..(target, blocked)) - var/mob/living/L = target - shake_camera(L, 3, 2) +/obj/item/projectile/bullet/on_hit(var/atom/target, var/blocked = 0) + if (..(target, blocked)) + var/mob/living/L = target + shake_camera(L, 3, 2) -/obj/item/projectile/bullet/weakbullet // "rubber" bullets +/obj/item/projectile/bullet/on_penetrate(var/atom/A) + if(!A) return 1 //if whatever it was got destroyed when we hit it, then I guess we can just keep going + + if(istype(A, /obj/mecha)) + return 1 //mecha have their own penetration handling + + if(ismob(A)) + if(iscarbon(A)) + //squishy mobs absorb KE + if (damage <= 20) return 0 + damage *= 0.7 + return 1 + + if(istype(A, /obj/machinery) || istype(A, /obj/structure)) + var/chance = 15 + if(istype(A, /turf/simulated/wall)) + var/turf/simulated/wall/W = A + chance = round(damage/W.damage_cap*100) + else if(istype(A, /obj/machinery/door)) + var/obj/machinery/door/D = A + chance = round(damage/D.maxhealth*100) + else if(istype(A, /obj/structure/girder) || istype(A, /obj/structure/cultgirder)) + chance = 100 + + if(prob(chance)) + if(A.opacity) + //display a message so that people on the other side aren't so confused + A.visible_message("\The [src] pierces through \the [A]!") + return 1 + + return 0 + +//For projectiles that actually represent clouds of projectiles +/obj/item/projectile/bullet/pellet + name = "shrapnel" //'shrapnel' sounds more dangerous (i.e. cooler) than 'pellet' + damage = 20 + //icon_state = "bullet" //TODO: would be nice to have it's own icon state + var/pellets = 4 //number of pellets + var/range_step = 2 //effective pellet count decreases every few tiles + var/base_spread = 90 //lower means the pellets spread more across body parts + var/spread_step = 10 //higher means the pellets spread more across body parts with distance + +/obj/item/projectile/bullet/pellet/Bumped() + . = ..() + bumped = 0 //can hit all mobs in a tile. pellets is decremented inside attack_mob so this should be fine. + +/obj/item/projectile/bullet/pellet/attack_mob(var/mob/living/target_mob, var/distance) + if (pellets < 0) return 1 + + var/pellet_loss = round((distance - 1)/range_step) //pellets lost due to distance + var/total_pellets = max(pellets - pellet_loss, 1) + var/spread = max(base_spread - (spread_step*distance), 0) + var/hits = 0 + for (var/i in 1 to total_pellets) + //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) + if (..()) 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 1 + return 0 + +/* short-casing projectiles, like the kind used in pistols or SMGs */ + +/obj/item/projectile/bullet/pistol + damage = 20 + +/obj/item/projectile/bullet/pistol/medium + damage = 25 + +/obj/item/projectile/bullet/pistol/strong //revolvers and matebas + damage = 60 + +/obj/item/projectile/bullet/pistol/rubber //"rubber" bullets + name = "rubber bullet" damage = 10 agony = 40 embed = 0 sharp = 0 -/obj/item/projectile/bullet/weakbullet/beanbag //because beanbags are not bullets +/* shotgun projectiles */ + +/obj/item/projectile/bullet/shotgun + name = "slug" + damage = 60 + +/obj/item/projectile/bullet/shotgun/beanbag //because beanbags are not bullets name = "beanbag" damage = 20 agony = 60 embed = 0 sharp = 0 -/obj/item/projectile/bullet/weakbullet/rubber - name = "rubber bullet" +//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/item/projectile/bullet/pellet/shotgun + name = "shrapnel" + damage = 13 + pellets = 6 + range_step = 1 + spread_step = 10 -/obj/item/projectile/bullet/midbullet - damage = 20 +/* "Rifle" rounds */ -/obj/item/projectile/bullet/midbullet2 +/obj/item/projectile/bullet/rifle/a762 damage = 25 +/obj/item/projectile/bullet/rifle/a145 + damage = 90 + penetrating = 5 + +/* Miscellaneous */ + /obj/item/projectile/bullet/suffocationbullet//How does this even work? name = "co bullet" damage = 20 damage_type = OXY - /obj/item/projectile/bullet/cyanideround name = "poison bullet" damage = 40 damage_type = TOX - -/obj/item/projectile/bullet/burstbullet//I think this one needs something for the on hit +/obj/item/projectile/bullet/burstbullet name = "exploding bullet" damage = 20 embed = 0 edge = 1 +/obj/item/projectile/bullet/gyro/on_hit(var/atom/target, var/blocked = 0) + if(isturf(target)) + explosion(target, -1, 0, 2) + ..() -/obj/item/projectile/bullet/stunshot - name = "stunshot" - damage = 5 - agony = 80 - stutter = 10 +/obj/item/projectile/bullet/blank + invisibility = 101 + damage = 1 embed = 0 - sharp = 0 - -/obj/item/projectile/bullet/a762 - damage = 25 /obj/item/projectile/bullet/chameleon damage = 1 // stop trying to murderbone with a fake gun dumbass!!! diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/energy.dm index b94bda70ad..851f691b41 100644 --- a/code/modules/projectiles/projectile/energy.dm +++ b/code/modules/projectiles/projectile/energy.dm @@ -6,6 +6,35 @@ flag = "energy" +//releases a very short burst of light on impact, mainly used to blind people +/obj/item/projectile/energy/flash + name = "shell" //a chemical filled shell or something + icon_state = "bullet" + damage = 5 + var/flash_range = 1 + var/brightness = 5 + var/light_duration = 10 + +/obj/item/projectile/energy/flash/on_impact() + var/turf/T = get_turf(src) + + if(!istype(T)) return + + src.visible_message("\The [src] explodes in a bright flash!") + for (var/mob/living/carbon/M in viewers(T, flash_range)) + if(M.eyecheck() < 1) + flick("e_flash", M.flash) + + playsound(src, 'sound/effects/snap.ogg', 50, 1) + new/obj/effect/effect/smoke/illumination(src.loc, brightness=max(flash_range*2, brightness), lifetime=light_duration) + +//blinds people like the flash round, but can also be used for temporary illumination +/obj/item/projectile/energy/flash/flare + damage = 10 + flash_range = 1 + brightness = 7 //similar to a flare + light_duration = 150 + /obj/item/projectile/energy/electrode name = "electrode" icon_state = "spark" @@ -15,11 +44,16 @@ weaken = 10 stutter = 10 */ + taser_effect = 1 agony = 40 damage_type = HALLOSS //Damage will be handled on the MOB side, to prevent window shattering. - +/obj/item/projectile/energy/electrode/stunshot + name = "stunshot" + damage = 5 + taser_effect = 1 + agony = 80 /obj/item/projectile/energy/declone name = "declone" diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index 0156625886..9ed7a9ecad 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -146,7 +146,7 @@ /obj/structure/reagent_dispensers/fueltank/bullet_act(var/obj/item/projectile/Proj) - if(istype(Proj ,/obj/item/projectile/beam)||istype(Proj,/obj/item/projectile/bullet)) + if(Proj.damage_type == BRUTE || Proj.damage_type == BURN) if(istype(Proj.firer)) message_admins("[key_name_admin(Proj.firer)] shot fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]) (JMP).") log_game("[key_name(Proj.firer)] shot fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]).") diff --git a/code/modules/research/xenoarchaeology/machinery/coolant.dm b/code/modules/research/xenoarchaeology/machinery/coolant.dm index 6c0652f408..b2f2cd3d67 100644 --- a/code/modules/research/xenoarchaeology/machinery/coolant.dm +++ b/code/modules/research/xenoarchaeology/machinery/coolant.dm @@ -26,7 +26,7 @@ datum/chemical_reaction/coolant reagents.add_reagent("coolant",1000) /obj/structure/reagent_dispensers/coolanttank/bullet_act(var/obj/item/projectile/Proj) - if(istype(Proj ,/obj/item/projectile/beam)||istype(Proj,/obj/item/projectile/bullet)) + if(Proj.damage_type == BRUTE || Proj.damage_type == BURN) if(!istype(Proj ,/obj/item/projectile/beam/lastertag) && !istype(Proj ,/obj/item/projectile/beam/practice) ) explode() diff --git a/code/modules/vehicles/vehicle.dm b/code/modules/vehicles/vehicle.dm index 24f1542219..c39c8ec047 100644 --- a/code/modules/vehicles/vehicle.dm +++ b/code/modules/vehicles/vehicle.dm @@ -114,7 +114,8 @@ ..() /obj/vehicle/bullet_act(var/obj/item/projectile/Proj) - health -= Proj.damage + if (Proj.damage_type == BRUTE || Proj.damage_type == BURN) + health -= Proj.damage ..() healthcheck() diff --git a/icons/obj/ammo.dmi b/icons/obj/ammo.dmi index 10fae561b764edd7c00d2d8079f8921e248f3769..b6780ecec3e2d702223f1080b824ce08fcf177c5 100644 GIT binary patch delta 4283 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 LyP>O{u*ClXF7QFb delta 4212 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 g|1J!T+q*mkEnK%n;0Z07ml*3IG5A