diff --git a/code/__DEFINES/conflict.dm b/code/__DEFINES/conflict.dm index 241bcb469622..0820c709cdae 100644 --- a/code/__DEFINES/conflict.dm +++ b/code/__DEFINES/conflict.dm @@ -26,7 +26,7 @@ #define AMMO_IGNORE_RESIST (1<<10) #define AMMO_BALLISTIC (1<<11) #define AMMO_IGNORE_COVER (1<<12) -// (1<<13) unused, previously was AMMO_SCANS_NEARBY +#define AMMO_ANTIVEHICLE (1<<13) #define AMMO_STOPPED_BY_COVER (1<<14) #define AMMO_SPECIAL_EMBED (1<<15) /// If the projectile hits a dense turf it'll do on_hit_turf on the turf just in front of the turf instead of on the turf itself diff --git a/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm b/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm index 323e0ee6966c..9eff6fa3ddc8 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm @@ -116,3 +116,9 @@ #define COMSIG_MOB_MOUSEDRAG "mob_mousedrag" //from /client/MouseDrag(): (atom/src_object, atom/over_object, turf/src_location, turf/over_location, src_control, over_control, params) #define COMSIG_MOB_CLICK_CANCELED (1<<0) #define COMSIG_MOB_CLICK_HANDLED (1<<1) + +#define COMSIG_MOB_DEPLOYED_BIPOD "mob_deployed_bipod" +#define COMSIG_MOB_UNDEPLOYED_BIPOD "mob_undeployed_bipod" + +/// From /obj/item/proc/pickup() : (obj/item/picked_up) +#define COMSIG_MOB_PICKUP_ITEM "mob_pickup_item" diff --git a/code/__DEFINES/dcs/signals/atom/signals_item.dm b/code/__DEFINES/dcs/signals/atom/signals_item.dm index b7bbca9f64a3..6c31b77f76a4 100644 --- a/code/__DEFINES/dcs/signals/atom/signals_item.dm +++ b/code/__DEFINES/dcs/signals/atom/signals_item.dm @@ -55,6 +55,11 @@ #define COMSIG_GUN_BURST_SHOTS_TO_FIRE_MODIFIED "gun_burst_shots_to_fire_modified" #define COMSIG_GUN_BURST_SHOT_DELAY_MODIFIED "gun_burst_shot_delay_modified" +#define COMSIG_GUN_VULTURE_FIRED_ONEHAND "gun_vulture_fired_onehand" +#define COMSIG_VULTURE_SCOPE_MOVED "vulture_scope_moved" +#define COMSIG_VULTURE_SCOPE_SCOPED "vulture_scope_scoped" +#define COMSIG_VULTURE_SCOPE_UNSCOPED "vulture_scope_unscoped" + /// from /obj/item/weapon/gun/proc/recalculate_attachment_bonuses() : () #define COMSIG_GUN_RECALCULATE_ATTACHMENT_BONUSES "gun_recalculate_attachment_bonuses" diff --git a/code/__DEFINES/equipment.dm b/code/__DEFINES/equipment.dm index 461eae27a2a3..ccfc9e4773da 100644 --- a/code/__DEFINES/equipment.dm +++ b/code/__DEFINES/equipment.dm @@ -233,6 +233,8 @@ #define SLOT_LEGS (1<<13) #define SLOT_ACCESSORY (1<<14) #define SLOT_SUIT_STORE (1<<15) //this allows items to be stored in the suit slot regardless of suit +/// Anything with this flag cannot be worn in suit storage, period. +#define SLOT_BLOCK_SUIT_STORE (1<<16) //================================================= //slots diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index b55a1b7ce583..8904c0295abf 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -153,6 +153,8 @@ #define FULLSCREEN_BLIND_LAYER 17.15 /// pain flashes #define FULLSCREEN_PAIN_LAYER 17.2 +/// Vulture sniper/spotter scope +#define FULLSCREEN_VULTURE_SCOPE_LAYER 17.21 /// in critical #define FULLSCREEN_CRIT_LAYER 17.25 diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index bc939fc2450b..793e7b6b2f35 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -177,6 +177,8 @@ #define TRAIT_USING_WHEELCHAIR "t_using_wheelchair" /// If the mob will instantly go permadead upon death #define TRAIT_HARDCORE "t_hardcore" +/// If the mob is able to use the vulture rifle or spotting scope +#define TRAIT_VULTURE_USER "t_vulture_user" // -- ability traits -- /// Xenos with this trait cannot have plasma transfered to them @@ -203,6 +205,9 @@ // GUN TRAITS #define TRAIT_GUN_SILENCED "t_gun_silenced" + +#define TRAIT_GUN_BIPODDED "t_gun_bipodded" + #define TRAIT_GUN_LIGHT_DEACTIVATED "t_gun_light_deactivated" // Miscellaneous item traits. @@ -239,7 +244,8 @@ GLOBAL_LIST_INIT(mob_traits, list( TRAIT_LEADERSHIP, TRAIT_DEXTROUS, TRAIT_REAGENT_SCANNER, - TRAIT_ABILITY_BURROWED + TRAIT_ABILITY_BURROWED, + TRAIT_VULTURE_USER, )) /* @@ -271,6 +277,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_EMOTE_CD_EXEMPT" = TRAIT_EMOTE_CD_EXEMPT, "TRAIT_LISPING" = TRAIT_LISPING, "TRAIT_CANNOT_EAT" = TRAIT_CANNOT_EAT, + "TRAIT_VULTURE_USER" = TRAIT_VULTURE_USER, ), /mob/living/carbon/xenomorph = list( "TRAIT_ABILITY_NO_PLASMA_TRANSFER" = TRAIT_ABILITY_NO_PLASMA_TRANSFER, @@ -299,6 +306,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( ), /obj/item/weapon/gun = list( "TRAIT_GUN_SILENCED" = TRAIT_GUN_SILENCED, + "TRAIT_GUN_BIPODDED" = TRAIT_GUN_BIPODDED, ), /obj/structure/surface/table = list( "TRAIT_STRUCTURE_FLIPPING" = TRAIT_TABLE_FLIPPING, diff --git a/code/__DEFINES/weapon_stats.dm b/code/__DEFINES/weapon_stats.dm index 590223426a66..beac54d98892 100644 --- a/code/__DEFINES/weapon_stats.dm +++ b/code/__DEFINES/weapon_stats.dm @@ -136,6 +136,7 @@ As such, don't expect any values assigned to common firearms to even consider ho //How many ticks you have to wait between firing. Burst delay uses the same variable! */ +#define FIRE_DELAY_TIER_VULTURE 20 #define FIRE_DELAY_TIER_1 12 #define FIRE_DELAY_TIER_2 10 #define FIRE_DELAY_TIER_3 9 diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 575071b76833..9a6ee4362088 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -2066,3 +2066,30 @@ GLOBAL_LIST_INIT(duplicate_forbidden_vars,list( if(stop_type && istype(turf_to_check, stop_type)) break return turf_to_check + +/// Given a direction, return the direction and the +-45 degree directions next to it +/proc/get_related_directions(direction = NORTH) + switch(direction) + if(NORTH) + return list(NORTH, NORTHEAST, NORTHWEST) + + if(EAST) + return list(EAST, NORTHEAST, SOUTHEAST) + + if(SOUTH) + return list(SOUTH, SOUTHEAST, SOUTHWEST) + + if(WEST) + return list(WEST, NORTHWEST, SOUTHWEST) + + if(NORTHEAST) + return list(NORTHEAST, NORTH, EAST) + + if(SOUTHEAST) + return list(SOUTHEAST, EAST, SOUTH) + + if(SOUTHWEST) + return list(SOUTHWEST, SOUTH, WEST) + + if(NORTHWEST) + return list(NORTHWEST, NORTH, WEST) diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index a497f4d01dfb..53dd40ff6035 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -249,6 +249,7 @@ DEFINE_BITFIELD(valid_equip_slots, list( "SLOT_LEGS" = SLOT_LEGS, "SLOT_ACCESSORY" = SLOT_ACCESSORY, "SLOT_SUIT_STORE" = SLOT_SUIT_STORE, + "SLOT_BLOCK_SUIT_STORE" = SLOT_BLOCK_SUIT_STORE, )) DEFINE_BITFIELD(flags_alarm_state, list( diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm index ff271b889e6c..fec62c35317f 100644 --- a/code/_onclick/hud/fullscreen.dm +++ b/code/_onclick/hud/fullscreen.dm @@ -72,9 +72,11 @@ var/severity = 0 var/fs_view = 7 var/show_when_dead = FALSE + /// If this should try and resize if the user's view is bigger than the default + var/should_resize = TRUE /atom/movable/screen/fullscreen/proc/update_for_view(client_view) - if (screen_loc == "CENTER-7,CENTER-7" && fs_view != client_view) + if (screen_loc == "CENTER-7,CENTER-7" && fs_view != client_view && should_resize) var/list/actualview = getviewsize(client_view) fs_view = client_view transform = matrix(actualview[1]/FULLSCREEN_OVERLAY_RESOLUTION_X, 0, 0, 0, actualview[2]/FULLSCREEN_OVERLAY_RESOLUTION_Y, 0) @@ -169,6 +171,14 @@ /atom/movable/screen/fullscreen/laser_blind icon_state = "impairedoverlay1" +/atom/movable/screen/fullscreen/vulture + icon_state = "vulture_scope_overlay_sniper" + layer = FULLSCREEN_VULTURE_SCOPE_LAYER + +/atom/movable/screen/fullscreen/vulture/spotter + icon_state = "vulture_scope_overlay_spotter" + should_resize = FALSE + //Weather overlays// /atom/movable/screen/fullscreen/weather diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 9643d0f652ae..206b1dbf9979 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -639,3 +639,7 @@ /atom/movable/screen/rotate/alt dir = WEST rotate_amount = -90 + +/atom/movable/screen/vulture_scope // The part of the vulture's scope that drifts over time + icon_state = "vulture_unsteady" + screen_loc = "CENTER,CENTER" diff --git a/code/datums/elements/bullet_trait/penetrating/heavy.dm b/code/datums/elements/bullet_trait/penetrating/heavy.dm new file mode 100644 index 000000000000..13086d01b212 --- /dev/null +++ b/code/datums/elements/bullet_trait/penetrating/heavy.dm @@ -0,0 +1,47 @@ +/datum/element/bullet_trait_penetrating/heavy + // Generic bullet trait vars + element_flags = ELEMENT_DETACH|ELEMENT_BESPOKE + id_arg_index = 3 + + /// For every turf this pierces, how much damage should this lose? + var/damage_lost_per_pen = 100 + /// Typecache of things to annihilate if the bullet is on a tile with it + var/static/list/bullet_destroy_structures = typecacheof(list( + /obj/structure/surface, + /obj/structure/barricade, + )) + +/datum/element/bullet_trait_penetrating/heavy/Attach(datum/target, distance_loss_per_hit = 3, damage_lost_per_pen = 75) + . = ..() + if(. == ELEMENT_INCOMPATIBLE) + return + + src.damage_lost_per_pen = damage_lost_per_pen + +/datum/element/bullet_trait_penetrating/heavy/handle_passthrough_movables(obj/item/projectile/bullet, atom/movable/hit_movable, did_hit) + if(did_hit) + var/slow_mult = 1 + if(ismob(hit_movable)) + var/mob/mob = hit_movable + if(mob.mob_size >= MOB_SIZE_BIG) + slow_mult = 2 + + bullet.distance_travelled += (distance_loss_per_hit * slow_mult) + + if(is_type_in_typecache(hit_movable, bullet_destroy_structures)) + var/obj/structure/cade = hit_movable + cade.deconstruct() // This bullet just tears through whatever cades you put it up against from either side + bullet.damage -= damage_lost_per_pen + + return COMPONENT_BULLET_PASS_THROUGH + +/datum/element/bullet_trait_penetrating/heavy/handle_passthrough_turf(obj/item/projectile/bullet, turf/closed/wall/hit_wall) + bullet.distance_travelled += distance_loss_per_hit + bullet.damage -= damage_lost_per_pen + + if(!istype(hit_wall)) + return COMPONENT_BULLET_PASS_THROUGH + + if(!hit_wall.hull) + return COMPONENT_BULLET_PASS_THROUGH + diff --git a/code/datums/elements/bullet_trait/penetrating.dm b/code/datums/elements/bullet_trait/penetrating/penetrating.dm similarity index 100% rename from code/datums/elements/bullet_trait/penetrating.dm rename to code/datums/elements/bullet_trait/penetrating/penetrating.dm diff --git a/code/datums/supply_packs/weapons.dm b/code/datums/supply_packs/weapons.dm index a28e0c7191ce..927db853e9fd 100644 --- a/code/datums/supply_packs/weapons.dm +++ b/code/datums/supply_packs/weapons.dm @@ -42,7 +42,7 @@ group = "Weapons" /datum/supply_packs/grenade_launchers - name = "M79 Grenade Launcher Crate (x2 Guncasess)" + name = "M79 Grenade Launcher Crate (x2 Guncases)" contains = list( /obj/item/storage/box/guncase/m79, /obj/item/storage/box/guncase/m79, @@ -120,6 +120,16 @@ containername = "\improper XM88 Heavy Rifle crate" group = "Weapons" +/* Uncomment me if it's decided to let the m707 be purchasable through req +/datum/supply_packs/gun/m707 + name = "M707 Anti-Materiel Rifle crate (M707 x1)" + contains = list() + cost = 120 + containertype = /obj/structure/closet/crate/secure/vulture + containername = "M707 crate" + group = "Weapons" +*/ + /datum/supply_packs/gun/merc contains = list() name = "black market firearms (x1)" diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 80254be4a6d9..f497bc20ba83 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -368,6 +368,7 @@ cases. Override_icon_state should be a list.*/ /obj/item/proc/pickup(mob/user, silent) SHOULD_CALL_PARENT(TRUE) SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user) + SEND_SIGNAL(user, COMSIG_MOB_PICKUP_ITEM, src) setDir(SOUTH)//Always rotate it south. This resets it to default position, so you wouldn't be putting things on backwards if(pickup_sound && !silent && src.loc?.z) playsound(src, pickup_sound, pickupvol, pickup_vary) @@ -635,6 +636,8 @@ cases. Override_icon_state should be a list.*/ return FALSE if(flags_equip_slot & SLOT_SUIT_STORE) return TRUE + if(flags_equip_slot & SLOT_BLOCK_SUIT_STORE) + return FALSE if(!H.wear_suit && (WEAR_JACKET in mob_equip)) if(!disable_warning) to_chat(H, SPAN_WARNING("You need a suit before you can attach this [name].")) diff --git a/code/game/objects/items/devices/device.dm b/code/game/objects/items/devices/device.dm index 148d47249201..d3058960233c 100644 --- a/code/game/objects/items/devices/device.dm +++ b/code/game/objects/items/devices/device.dm @@ -5,7 +5,7 @@ icon = 'icons/obj/items/devices.dmi' var/serial_number -/obj/item/device/Initialize() +/obj/item/device/Initialize(mapload, ...) . = ..() serial_number = "[rand(0,9)][pick(alphabet_uppercase)][rand(0,9)][rand(0,9)][rand(0,9)][rand(0,9)][pick(alphabet_uppercase)]" diff --git a/code/game/objects/items/devices/vulture_spotter.dm b/code/game/objects/items/devices/vulture_spotter.dm new file mode 100644 index 000000000000..b89009efde7e --- /dev/null +++ b/code/game/objects/items/devices/vulture_spotter.dm @@ -0,0 +1,41 @@ +/obj/item/device/vulture_spotter_scope + name = "\improper M707 spotter scope" + desc = "A scope that, when mounted on a tripod, allows a user to assist the M707's firer in target acquisition." + icon_state = "vulture_scope" + item_state = "electronic" + flags_atom = FPRINT|CONDUCT + unacidable = TRUE + indestructible = TRUE + /// A weakref to the corresponding rifle + var/datum/weakref/bound_rifle + +/obj/item/device/vulture_spotter_scope/Initialize(mapload, datum/weakref/rifle) + . = ..() + if(rifle) + bound_rifle = rifle + +/obj/item/device/vulture_spotter_scope/attack_self(mob/user) + . = ..() + to_chat(user, SPAN_WARNING("[src] needs to be mounted on a tripod to use!")) + +/obj/item/device/vulture_spotter_tripod + name = "\improper M707 spotter tripod" + desc = "A tripod, meant for stabilizing a spotting scope for the M707 anti-materiel rifle." + icon_state = "vulture_tripod" + item_state = "electronic" + flags_atom = FPRINT|CONDUCT + unacidable = TRUE + indestructible = TRUE + +/obj/item/device/vulture_spotter_tripod/get_examine_text(mob/user) + . = ..() + . += SPAN_NOTICE("[src] can be set down by using in-hand.") + +/obj/item/device/vulture_spotter_tripod/attack_self(mob/user) + . = ..() + user.balloon_alert(user, "setting up tripod...") + if(!do_after(user, 1.5 SECONDS, target = user)) + return + + new /obj/structure/vulture_spotter_tripod(get_turf(user)) + qdel(src) diff --git a/code/game/objects/items/pamphlets.dm b/code/game/objects/items/pamphlets.dm index dd96f275ef17..682215be67bb 100644 --- a/code/game/objects/items/pamphlets.dm +++ b/code/game/objects/items/pamphlets.dm @@ -186,3 +186,38 @@ desc = "A piece of paper covered in crude depictions of bananas and various types of primates. Probably drawn by a three-year-old child - or an unusually intelligent marine." trait = /datum/character_trait/language/primitive + +/obj/item/pamphlet/trait + bypass_pamphlet_limit = TRUE + /// What trait to give the user + var/trait_to_give + +/obj/item/pamphlet/trait/can_use(mob/living/carbon/human/user) + if(!istype(user)) + return FALSE + + if(HAS_TRAIT(user, trait_to_give)) + to_chat(user, SPAN_WARNING("You know this already!")) + return FALSE + + if(user.job != JOB_SQUAD_MARINE) + to_chat(user, SPAN_WARNING("Only squad riflemen can use this.")) + return FALSE + + if(user.has_used_pamphlet && !bypass_pamphlet_limit) + to_chat(user, SPAN_WARNING("You've already used a pamphlet!")) + return FALSE + + return TRUE + +/obj/item/pamphlet/trait/on_use(mob/living/carbon/human/user) + to_chat(user, SPAN_NOTICE(flavour_text)) + ADD_TRAIT(user, trait_to_give, "pamphlet") + if(!bypass_pamphlet_limit) + user.has_used_pamphlet = TRUE + +/obj/item/pamphlet/trait/vulture + name = "\improper M707 instructional pamphlet" + desc = "A pamphlet used to quickly impart vital knowledge of how to shoot big guns and spot for them." + icon_state = "pamphlet_vulture" + trait_to_give = TRAIT_VULTURE_USER diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index 9ef2f6f716b1..9b0b8cf30aae 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -15,7 +15,7 @@ projectile_coverage = PROJECTILE_COVERAGE_MEDIUM can_block_movement = TRUE -/obj/structure/Initialize() +/obj/structure/Initialize(mapload, ...) . = ..() if(climbable) verbs += /obj/structure/proc/climb_on diff --git a/code/game/objects/structures/crates_lockers/secure_crates.dm b/code/game/objects/structures/crates_lockers/secure_crates.dm index 0bd77d877ee6..a308c4c0a21c 100644 --- a/code/game/objects/structures/crates_lockers/secure_crates.dm +++ b/code/game/objects/structures/crates_lockers/secure_crates.dm @@ -176,6 +176,18 @@ icon_locked = "secure_locked_weyland" icon_unlocked = "secure_unlocked_weyland" +/obj/structure/closet/crate/secure/vulture + name = "secure M707 crate" + desc = "A secure crate, containing an M707 anti-materiel rifle." + icon_state = "secure_locked_vulture" + icon_opened = "secure_open_vulture" + icon_locked = "secure_locked_vulture" + icon_unlocked = "secure_unlocked_vulture" + +/obj/structure/closet/crate/secure/vulture/Initialize() + . = ..() + new /obj/item/storage/box/guncase/vulture(src) + //special version, able to store OB fuel and warheads only /obj/structure/closet/crate/secure/ob name = "secure orbital bombardment ammunition crate" diff --git a/code/game/objects/structures/vulture_spotter.dm b/code/game/objects/structures/vulture_spotter.dm new file mode 100644 index 000000000000..a668cb562028 --- /dev/null +++ b/code/game/objects/structures/vulture_spotter.dm @@ -0,0 +1,305 @@ +/obj/structure/vulture_spotter_tripod + name = "\improper M707 spotting tripod" + desc = "A tripod for an M707 anti-materiel rifle's spotting scope." + icon_state = "vulture_tripod" + density = TRUE + anchored = TRUE + unacidable = TRUE + /// Weakref to the associated rifle + var/datum/weakref/bound_rifle + /// Weakref to the scope user, if any + var/datum/weakref/scope_user + /// If the tripod has an attached spotting scope + var/scope_attached = FALSE + /// If the scope is currently being used + var/scope_using = FALSE + /// Ref to the action to give the user of the scope + var/datum/action/vulture_tripod_unscope/unscope_action + /// How far out the scope zooms + var/scope_zoom = 10 + /// How much to increase the user's dark vision by + var/darkness_view = 12 + /// The maximum distance this can be from the sniper scope + var/max_sniper_distance = 7 + +/obj/structure/vulture_spotter_tripod/Initialize(mapload) + . = ..() + desc = initial(desc) + " Though, it doesn't seem to have one attached yet." + +/obj/structure/vulture_spotter_tripod/Destroy() + if(scope_user) + var/mob/user = scope_user.resolve() + user.unset_interaction() + QDEL_NULL(unscope_action) + return ..() + +/obj/structure/vulture_spotter_tripod/deconstruct(disassembled) + . = ..() + if(scope_attached && bound_rifle) + new /obj/item/device/vulture_spotter_scope(get_turf(src), bound_rifle) + new /obj/item/device/vulture_spotter_tripod(get_turf(src)) + +/obj/structure/vulture_spotter_tripod/get_examine_text(mob/user) + . = ..() + if(scope_attached) + . += SPAN_NOTICE("You can remove the scope from [src] with a screwdriver.") + else + . += SPAN_NOTICE("You can pick up [src] by dragging it to your sprite.") + . += SPAN_NOTICE("You can rotate [src] with Alt-click.") + +/obj/structure/vulture_spotter_tripod/attackby(obj/item/thing, mob/user) + if(istype(thing, /obj/item/device/vulture_spotter_scope)) + on_scope_attach(user, thing) + return + + if(HAS_TRAIT(thing, TRAIT_TOOL_SCREWDRIVER)) + on_screwdriver(user) + return + + return ..() + +/obj/structure/vulture_spotter_tripod/MouseDrop(over_object, src_location, over_location) + if(!ishuman(usr)) + return + var/mob/living/carbon/human/user = usr //this is us + + if(!HAS_TRAIT(user, TRAIT_VULTURE_USER)) + to_chat(user, SPAN_WARNING("You don't know how to use this!")) + return + + if(!scope_attached) + fold_up(user) + return + + try_scope(user) + +/obj/structure/vulture_spotter_tripod/on_set_interaction(mob/user) + var/obj/item/attachable/vulture_scope/scope = get_vulture_scope() + scope.spotter_spotting = TRUE + to_chat(scope.scope_user, SPAN_NOTICE("You notice that [scope] drifts less.")) + RegisterSignal(scope, COMSIG_VULTURE_SCOPE_MOVED, PROC_REF(on_vulture_move)) + RegisterSignal(scope, COMSIG_VULTURE_SCOPE_UNSCOPED, PROC_REF(on_vulture_unscope)) + if(user.client) + RegisterSignal(user.client, COMSIG_PARENT_QDELETING, PROC_REF(do_unscope)) + user.client.change_view(scope_zoom, src) + RegisterSignal(user, list(COMSIG_MOB_PICKUP_ITEM, COMSIG_MOB_RESISTED), PROC_REF(do_unscope)) + user.see_in_dark += darkness_view + user.lighting_alpha = 127 + user.sync_lighting_plane_alpha() + user.overlay_fullscreen("vulture_spotter", /atom/movable/screen/fullscreen/vulture/spotter) + user.freeze() + user.status_flags |= IMMOBILE_ACTION + user.visible_message(SPAN_NOTICE("[user] looks through [src]."),SPAN_NOTICE("You look through [src], ready to go!")) + user.forceMove(loc) + user.setDir(dir) + scope_user = WEAKREF(user) + update_pixels(TRUE) + give_action(user, /datum/action/vulture_tripod_unscope, null, null, src) + set_scope_loc(user, scope) + +/obj/structure/vulture_spotter_tripod/on_unset_interaction(mob/user) + user.status_flags &= ~IMMOBILE_ACTION + user.visible_message(SPAN_NOTICE("[user] looks up from [src]."),SPAN_NOTICE("You look up from [src].")) + user.unfreeze() + user.reset_view(null) + user.Move(get_step(src, reverse_direction(src.dir))) + user.client?.change_view(world_view_size, src) + user.setDir(dir) //set the direction of the player to the direction the gun is facing + update_pixels(FALSE) + remove_action(user, /datum/action/vulture_tripod_unscope) + unscope() + +/obj/structure/vulture_spotter_tripod/clicked(mob/user, list/mods) + if(mods["alt"]) + if(in_range(src, user) && !user.is_mob_incapacitated()) + rotate(user) + return TRUE + return ..() + +/// Rotates the tripod 90* counter-clockwise +/obj/structure/vulture_spotter_tripod/proc/rotate(mob/user) + if(scope_using) + to_chat(user, SPAN_WARNING("You can't rotate [src] while someone is using it!")) + return FALSE + + playsound(src, 'sound/items/Ratchet.ogg', 25, 1) + user.visible_message("[user] rotates [src].","You rotate [src].") + setDir(turn(dir, -90)) + update_pixels(TRUE) + +/// Updates the direction the operator should be facing, and their pixel offset +/obj/structure/vulture_spotter_tripod/proc/update_pixels(mounting = TRUE) + if(!scope_user) + return + + var/mob/user = scope_user.resolve() + if(mounting) + var/diff_x = 0 + var/diff_y = 0 + switch(dir) + if(NORTH) + diff_y = -16 + if(SOUTH) + diff_y = 16 + if(EAST) + diff_x = -16 + if(WEST) + diff_x = 16 + + user.pixel_x = diff_x + user.pixel_y = diff_y + else + user.pixel_x = 0 + user.pixel_y = 0 + +/// Handler for when the scope is being attached to the tripod +/obj/structure/vulture_spotter_tripod/proc/on_scope_attach(mob/user, obj/structure/vulture_spotter_tripod/scope) + if(scope_attached) + return + + user.visible_message(SPAN_NOTICE("[user] attaches [scope] to [src]."), SPAN_NOTICE("You attach [scope] to [src].")) + icon_state = "vulture_scope" + setDir(user.dir) + bound_rifle = scope.bound_rifle + scope_attached = TRUE + desc = initial(desc) + qdel(scope) + +/// Handler for when the scope is being detached from the tripod by screwdriver +/obj/structure/vulture_spotter_tripod/proc/on_screwdriver(mob/user) + if(!scope_attached) + to_chat(user, SPAN_NOTICE("You don't need a screwdriver to pick this up!")) + return + user.visible_message(SPAN_NOTICE("[user] unscrews the scope from [src] before detaching it."), SPAN_NOTICE("You unscrew the scope from [src], detaching it.")) + icon_state = initial(icon_state) + unscope() + scope_attached = FALSE + desc = initial(desc) + " Though, it doesn't seem to have one attached yet." + new /obj/item/device/vulture_spotter_scope(get_turf(src), bound_rifle) + +/// Handler for user folding up the tripod, picking it up +/obj/structure/vulture_spotter_tripod/proc/fold_up(mob/user) + user.visible_message(SPAN_NOTICE("[user] folds up [src]."), SPAN_NOTICE("You fold up [src].")) + var/obj/item/device/vulture_spotter_tripod/tripod = new(get_turf(src)) + user.put_in_hands(tripod, TRUE) + qdel(src) + +/// Checks if the user is able to use the scope, uses it if so +/obj/structure/vulture_spotter_tripod/proc/try_scope(mob/living/carbon/human/user) + if(!user.client) + return + + if(user.l_hand || user.r_hand) + to_chat(user, SPAN_WARNING("Your hands need to be free to use [src]!")) + return + + var/obj/item/attachable/vulture_scope/scope = get_vulture_scope() + if(!scope) + return + + if(get_dist(get_turf(scope), get_turf(src)) > max_sniper_distance) + to_chat(user, SPAN_WARNING("[src] needs to be closer to the M707 to be used!")) + return + + if(!scope.scoping) + to_chat(user, SPAN_WARNING("The M707's sight needs to be in use to be able to look through [src]!")) + return + + user.set_interaction(src) + +/// Handler for when the user should be unscoping +/obj/structure/vulture_spotter_tripod/proc/do_unscope() + SIGNAL_HANDLER + + if(!scope_user) + return + + var/mob/user = scope_user.resolve() + user.unset_interaction() + +/// Unscopes the user, cleaning up everything related +/obj/structure/vulture_spotter_tripod/proc/unscope() + SIGNAL_HANDLER + if(scope_user) + var/mob/living/carbon/human/user = scope_user.resolve() + user.see_in_dark -= darkness_view + user.lighting_alpha = user.default_lighting_alpha + user.sync_lighting_plane_alpha() + user.clear_fullscreen("vulture_spotter") + UnregisterSignal(user, list(COMSIG_MOB_PICKUP_ITEM, COMSIG_MOB_RESISTED)) + user.pixel_x = 0 + user.pixel_y = 0 + if(user.client) + user.client.change_view(world_view_size, src) + user.client.pixel_x = 0 + user.client.pixel_y = 0 + UnregisterSignal(user.client, COMSIG_PARENT_QDELETING) + + var/obj/item/attachable/vulture_scope/scope = get_vulture_scope() + if(scope) + scope.spotter_spotting = FALSE + to_chat(scope.scope_user, SPAN_NOTICE("You notice that [scope] starts drifting more.")) + UnregisterSignal(scope, list(COMSIG_VULTURE_SCOPE_MOVED, COMSIG_VULTURE_SCOPE_UNSCOPED)) + + QDEL_NULL(unscope_action) + +/// Sets the scope's sight location to the same as the sniper's +/obj/structure/vulture_spotter_tripod/proc/set_scope_loc(mob/living/carbon/human/user, obj/item/attachable/vulture_scope/scope) + if(!user.client || !scope) + return + + var/turf/user_turf = get_turf(user) + var/x_off = scope.scope_x - user_turf.x + var/y_off = scope.scope_y - user_turf.y + var/pixels_per_tile = 32 + + user.client.pixel_x = x_off * pixels_per_tile + user.client.pixel_y = y_off * pixels_per_tile + +/// Handler for when the vulture spotter scope moves +/obj/structure/vulture_spotter_tripod/proc/on_vulture_move(datum/source) + SIGNAL_HANDLER + if(!scope_user) + return + + set_scope_loc(scope_user.resolve(), get_vulture_scope()) + +/// Handler for when the sniper unscopes +/obj/structure/vulture_spotter_tripod/proc/on_vulture_unscope(datum/source) + SIGNAL_HANDLER + if(!scope_user) + return + + var/mob/user = scope_user.resolve() + + to_chat(user, SPAN_WARNING("[src]'s sight disengages as the linked rifle unscopes.")) + unscope() + +/// Getter for the vulture scope on the sniper +/obj/structure/vulture_spotter_tripod/proc/get_vulture_scope() + RETURN_TYPE(/obj/item/attachable/vulture_scope) + if(!bound_rifle) + return + + var/obj/item/weapon/gun/boltaction/vulture/rifle = bound_rifle.resolve() + if(!("rail" in rifle.attachments) || !istype(rifle.attachments["rail"], /obj/item/attachable/vulture_scope)) + return + + return rifle.attachments["rail"] + +/datum/action/vulture_tripod_unscope + name = "Stop Using Scope" + action_icon_state = "vulture_tripod_close" + /// Weakref to the tripod that this is linked to + var/datum/weakref/tripod + +/datum/action/vulture_tripod_unscope/New(Target, override_icon_state, obj/structure/vulture_spotter_tripod/spotting_tripod) + . = ..() + tripod = WEAKREF(spotting_tripod) + +/datum/action/vulture_tripod_unscope/action_activate() + if(!tripod) + return + + var/obj/structure/vulture_spotter_tripod/spotting_tripod = tripod.resolve() + spotting_tripod.do_unscope() diff --git a/code/modules/clothing/head/head.dm b/code/modules/clothing/head/head.dm index 0916ecfb34e9..d722c9aa9d58 100644 --- a/code/modules/clothing/head/head.dm +++ b/code/modules/clothing/head/head.dm @@ -537,7 +537,7 @@ //==========================//PROTECTIVE\\===============================\\ //=======================================================================\\ -D + /obj/item/clothing/head/ushanka name = "ushanka" desc = "Perfect for winter in Siberia, da?" diff --git a/code/modules/cm_marines/equipment/guncases.dm b/code/modules/cm_marines/equipment/guncases.dm index aa01535ff888..33684aa29d0a 100644 --- a/code/modules/cm_marines/equipment/guncases.dm +++ b/code/modules/cm_marines/equipment/guncases.dm @@ -296,6 +296,35 @@ new /obj/item/ammo_magazine/handful/revolver/marksman/six_rounds(src) new /obj/item/ammo_magazine/handful/revolver/marksman/six_rounds(src) +/obj/item/storage/box/guncase/vulture + name = "\improper M707 anti-materiel rifle case" + desc = "A gun case containing the M707 \"Vulture\" anti-materiel rifle and its requisite spotting tools." + icon_state = "guncase_blue" + storage_slots = 7 + can_hold = list( + /obj/item/weapon/gun/boltaction/vulture, + /obj/item/ammo_magazine/rifle/boltaction/vulture, + /obj/item/device/vulture_spotter_tripod, + /obj/item/device/vulture_spotter_scope, + /obj/item/tool/screwdriver, + /obj/item/pamphlet/trait/vulture, + ) + +/obj/item/storage/box/guncase/vulture/update_icon() + if(LAZYLEN(contents)) + icon_state = "guncase_blue" + else + icon_state = "guncase_blue_e" + +/obj/item/storage/box/guncase/vulture/fill_preset_inventory() + var/obj/item/weapon/gun/boltaction/vulture/rifle = new(src) + new /obj/item/ammo_magazine/rifle/boltaction/vulture(src) + new /obj/item/device/vulture_spotter_tripod(src) + new /obj/item/device/vulture_spotter_scope(src, WEAKREF(rifle)) + new /obj/item/tool/screwdriver(src) // Spotter scope needs a screwdriver to disassemble + new /obj/item/pamphlet/trait/vulture(src) //both pamphlets give use of the scope and the rifle + new /obj/item/pamphlet/trait/vulture(src) + //Handgun case for Military police vendor three mag , a railflashligh and the handgun. //88 Mod 4 Combat Pistol diff --git a/code/modules/mob/living/silicon/ai/freelook/update_triggers.dm b/code/modules/mob/living/silicon/ai/freelook/update_triggers.dm index 1e1cef2b7a8e..a7109f491740 100644 --- a/code/modules/mob/living/silicon/ai/freelook/update_triggers.dm +++ b/code/modules/mob/living/silicon/ai/freelook/update_triggers.dm @@ -34,7 +34,7 @@ cameranet.updateVisibility(src) . = ..() -/obj/structure/Initialize() +/obj/structure/Initialize(mapload, ...) . = ..() if(z && SSatoms.initialized != INITIALIZATION_INSSATOMS) cameranet.updateVisibility(src) diff --git a/code/modules/projectiles/ammo_datums.dm b/code/modules/projectiles/ammo_datums.dm index 9315a1dd0c68..b5e51db88998 100644 --- a/code/modules/projectiles/ammo_datums.dm +++ b/code/modules/projectiles/ammo_datums.dm @@ -1643,6 +1643,24 @@ // 180% damage to all targets (225), 240% (300) against non-Runner xenos, and 300% against Big xenos (375). -Kaga to_chat(P.firer, SPAN_WARNING("Bullseye!")) +/datum/ammo/bullet/sniper/anti_materiel/vulture + damage = 400 // Fully intended to vaporize anything smaller than a mini cooper + accurate_range_min = 10 + handful_state = "vulture_bullet" + sound_hit = 'sound/bullets/bullet_vulture_impact.ogg' + flags_ammo_behavior = AMMO_BALLISTIC|AMMO_SNIPER|AMMO_IGNORE_COVER|AMMO_ANTIVEHICLE + +/datum/ammo/bullet/sniper/anti_materiel/vulture/on_hit_mob(mob/hit_mob, obj/item/projectile/bullet) + . = ..() + knockback(hit_mob, bullet, 30) + hit_mob.apply_effect(3, SLOW) + +/datum/ammo/bullet/sniper/anti_materiel/vulture/set_bullet_traits() + . = ..() + LAZYADD(traits_to_give, list( + BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_penetrating/heavy) + )) + /datum/ammo/bullet/sniper/elite name = "supersonic sniper bullet" diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 2eddf975c1b9..dd232de259e3 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -229,6 +229,8 @@ VAR_PROTECTED/start_semiauto = TRUE /// If this gun should spawn with automatic fire. Protected due to it never needing to be edited. VAR_PROTECTED/start_automatic = FALSE + /// The type of projectile that this gun should shoot + var/projectile_type = /obj/item/projectile /// The multiplier for how much slower this should fire in automatic mode. 1 is normal, 1.2 is 20% slower, 2 is 100% slower, etc. Protected due to it never needing to be edited. VAR_PROTECTED/autofire_slow_mult = 1 @@ -1019,7 +1021,7 @@ and you're good to go. if(isliving(loc)) var/mob/M = loc weapon_source_mob = M - var/obj/item/projectile/P = new /obj/item/projectile(src, create_cause_data(bullet_source, weapon_source_mob)) + var/obj/item/projectile/P = new projectile_type(src, create_cause_data(bullet_source, weapon_source_mob)) P.generate_bullet(chambered, 0, NO_FLAGS) return P @@ -1923,3 +1925,7 @@ not all weapons use normal magazines etc. load_into_chamber() itself is designed /// Setter proc for fa_firing /obj/item/weapon/gun/proc/set_auto_firing(auto = FALSE) fa_firing = auto + +/// Getter for gun_user +/obj/item/weapon/gun/proc/get_gun_user() + return gun_user diff --git a/code/modules/projectiles/gun_attachables.dm b/code/modules/projectiles/gun_attachables.dm index 19da628c7922..7346c18770c2 100644 --- a/code/modules/projectiles/gun_attachables.dm +++ b/code/modules/projectiles/gun_attachables.dm @@ -451,6 +451,11 @@ Defined in conflicts.dm of the #defines folder. accuracy_mod = HIT_ACCURACY_MULT_TIER_3 scatter_mod = -SCATTER_AMOUNT_TIER_8 +/obj/item/attachable/sniperbarrel/vulture + name = "\improper M707 barrel" + icon_state = "vulture_barrel" + hud_offset_mod = -1 + /obj/item/attachable/m60barrel name = "M60 barrel" icon = 'icons/obj/items/weapons/guns/attachments/barrel.dmi' @@ -1125,7 +1130,474 @@ Defined in conflicts.dm of the #defines folder. attach_icon = "slavicscope" desc = "Oppa! How did you get this off glorious Stalin weapon? Blyat, put back on and do job tovarish. Yankee is not shoot self no?" +/obj/item/attachable/vulture_scope // not a subtype of scope because it uses basically none of the scope's features + name = "\improper M707 \"Vulture\" scope" + icon = 'icons/obj/items/weapons/guns/attachments/rail.dmi' + icon_state = "vulture_scope" + attach_icon = "vulture_scope" + desc = "A powerful yet obtrusive sight for the M707 anti-materiel rifle." // Can't be seen normally, anyway + slot = "rail" + aim_speed_mod = SLOWDOWN_ADS_SCOPE //Extra slowdown when wielded + wield_delay_mod = WIELD_DELAY_FAST + flags_attach_features = ATTACH_REMOVABLE|ATTACH_ACTIVATION + attachment_action_type = /datum/action/item_action/toggle + /// Weakref to the user of the scope + var/datum/weakref/scope_user + /// If the scope is currently in use + var/scoping = FALSE + /// How far out the player should see by default + var/start_scope_range = 12 + /// The bare minimum distance the scope can be from the player + var/min_scope_range = 12 + /// The maximum distance the scope can be from the player + var/max_scope_range = 25 + /// How far in the perpendicular axis the scope can move in either direction + var/perpendicular_scope_range = 7 + /// How far in each direction the scope should see. Default human view size is 7 + var/scope_viewsize = 7 + /// The current X position of the scope within the sniper's view box. 0 is center + var/scope_offset_x = 0 + /// The current Y position of the scope within the sniper's view box. 0 is center + var/scope_offset_y = 0 + /// How far in any given direction the scope can drift + var/scope_drift_max = 2 + /// The current X coord position of the scope camera + var/scope_x = 0 + /// The current Y coord position of the scope camera + var/scope_y = 0 + /// Ref to the scope screen element + var/atom/movable/screen/vulture_scope/scope_element + /// If the gun should experience scope drift + var/scope_drift = TRUE + /// % chance for the scope to drift on process with a spotter using their scope + var/spotted_drift_chance = 33 + /// % chance for the scope to drift on process without a spotter using their scope + var/unspotted_drift_chance = 100 + /// If the scope should use do_afters for adjusting and moving the sight + var/slow_use = TRUE + /// Cooldown for interacting with the scope's adjustment or position + COOLDOWN_DECLARE(scope_interact_cd) + /// If the user is currently holding their breath + var/holding_breath = FALSE + /// Cooldown for after holding your breath + COOLDOWN_DECLARE(hold_breath_cd) + /// How long you can hold your breath for + var/breath_time = 4 SECONDS + /// How long the cooldown for holding your breath is, only starts after breath_time finishes + var/breath_cooldown_time = 12 SECONDS + /// The initial dir of the scope user when scoping in + var/scope_user_initial_dir + /// How much to increase darkness view by + var/darkness_view = 12 + /// If there is currently a spotter using the linked spotting scope + var/spotter_spotting = FALSE + +/obj/item/attachable/vulture_scope/Initialize(mapload, ...) + . = ..() + START_PROCESSING(SSobj, src) + select_gamemode_skin(type) + +/obj/item/attachable/vulture_scope/Destroy() + STOP_PROCESSING(SSobj, src) + on_unscope() + QDEL_NULL(scope_element) + return ..() + +/obj/item/attachable/vulture_scope/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "VultureScope", name) + ui.open() + +/obj/item/attachable/vulture_scope/ui_state(mob/user) + return GLOB.not_incapacitated_state + +/obj/item/attachable/vulture_scope/ui_data(mob/user) + var/list/data = list() + data["offset_x"] = scope_offset_x + data["offset_y"] = scope_offset_y + data["valid_offset_dirs"] = get_offset_dirs() + data["scope_cooldown"] = !COOLDOWN_FINISHED(src, scope_interact_cd) + data["valid_adjust_dirs"] = get_adjust_dirs() + data["breath_cooldown"] = !COOLDOWN_FINISHED(src, hold_breath_cd) + data["breath_recharge"] = get_breath_recharge() + data["spotter_spotting"] = spotter_spotting + data["current_scope_drift"] = get_scope_drift_chance() + data["time_to_fire_remaining"] = 1 - (get_time_to_fire() / FIRE_DELAY_TIER_VULTURE) + return data + +/obj/item/attachable/vulture_scope/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + switch(action) + if("adjust_dir") + var/direction = params["offset_dir"] + if(!(direction in alldirs) || !scoping || !scope_user) + return + + var/mob/scoper = scope_user.resolve() + if(slow_use) + if(!COOLDOWN_FINISHED(src, scope_interact_cd)) + return + to_chat(scoper, SPAN_NOTICE("You begin adjusting [src]...")) + COOLDOWN_START(src, scope_interact_cd, 0.5 SECONDS) + if(!do_after(scoper, 0.5 SECONDS)) + return + + adjust_offset(direction) + . = TRUE + + if("adjust_position") + var/direction = params["position_dir"] + if(!(direction in alldirs) || !scoping || !scope_user) + return + + var/mob/scoper = scope_user.resolve() + if(slow_use) + if(!COOLDOWN_FINISHED(src, scope_interact_cd)) + return + + to_chat(scoper, SPAN_NOTICE("You begin moving [src]...")) + COOLDOWN_START(src, scope_interact_cd, 1 SECONDS) + if(!do_after(scoper, 1 SECONDS)) + return + + adjust_position(direction) + . = TRUE + + if("hold_breath") + if(!COOLDOWN_FINISHED(src, hold_breath_cd) || holding_breath) + return + + hold_breath() + . = TRUE + +/obj/item/attachable/vulture_scope/process() + if(scope_element && prob(get_scope_drift_chance())) //every 6 seconds when unspotted, on average + scope_drift() + +/// Returns a number between 0 and 100 for the chance of the scope drifting on process() +/obj/item/attachable/vulture_scope/proc/get_scope_drift_chance() + if(!scope_drift || holding_breath) + return 0 + + if(spotter_spotting) + return spotted_drift_chance + + else + return unspotted_drift_chance + +/// Returns how many deciseconds until the gun is able to fire again +/obj/item/attachable/vulture_scope/proc/get_time_to_fire() + if(!istype(loc, /obj/item/weapon/gun/boltaction/vulture)) + return 0 + + var/obj/item/weapon/gun/boltaction/vulture/rifle = loc + if(!rifle.last_fired) + return 0 + + return (rifle.last_fired + rifle.get_fire_delay()) - world.time + +/obj/item/attachable/vulture_scope/activate_attachment(obj/item/weapon/gun/gun, mob/living/carbon/user, turn_off) + if(turn_off || scoping) + on_unscope() + return TRUE + + if(!scoping) + if(!(gun.flags_item & WIELDED)) + to_chat(user, SPAN_WARNING("You must hold [gun] with two hands to use [src].")) + return FALSE + + if(!HAS_TRAIT(gun, TRAIT_GUN_BIPODDED)) + to_chat(user, SPAN_WARNING("You must have a deployed bipod to use [src].")) + return FALSE + + on_scope() + return TRUE + +/obj/item/attachable/vulture_scope/proc/get_offset_dirs() + var/list/possible_dirs = alldirs.Copy() + if(scope_offset_x >= scope_drift_max) + possible_dirs -= list(NORTHEAST, EAST, SOUTHEAST) + else if(scope_offset_x <= -scope_drift_max) + possible_dirs -= list(NORTHWEST, WEST, SOUTHWEST) + + if(scope_offset_y >= scope_drift_max) + possible_dirs -= list(NORTHWEST, NORTH, NORTHEAST) + else if(scope_offset_y <= -scope_drift_max) + possible_dirs -= list(SOUTHWEST, SOUTH, SOUTHEAST) + + return possible_dirs + +/// Gets a list of valid directions to be able to adjust the reticle in +/obj/item/attachable/vulture_scope/proc/get_adjust_dirs() + if(!scoping) + return list() + var/list/possible_dirs = alldirs.Copy() + var/turf/current_turf = get_turf(src) + var/turf/scope_tile = locate(scope_x, scope_y, current_turf.z) + var/mob/scoper = scope_user.resolve() + if(!scoper) + return list() + + var/user_dir = scoper.dir + var/distance = get_dist(current_turf, scope_tile) + if(distance >= max_scope_range) + possible_dirs -= get_related_directions(user_dir) + + else if(distance <= min_scope_range) + possible_dirs -= get_related_directions(REVERSE_DIR(user_dir)) + + if((user_dir == EAST) || (user_dir == WEST)) + if(scope_y - current_turf.y >= perpendicular_scope_range) + possible_dirs -= get_related_directions(NORTH) + + else if(current_turf.y - scope_y >= perpendicular_scope_range) + possible_dirs -= get_related_directions(SOUTH) + + else + if(scope_x - current_turf.x >= perpendicular_scope_range) + possible_dirs -= get_related_directions(EAST) + + else if(current_turf.x - scope_x >= perpendicular_scope_range) + possible_dirs -= get_related_directions(WEST) + + return possible_dirs + +/// Adjusts the position of the reticle by a tile in a given direction +/obj/item/attachable/vulture_scope/proc/adjust_offset(direction = NORTH) + var/old_x = scope_offset_x + var/old_y = scope_offset_y + if((direction == NORTHEAST) || (direction == EAST) || (direction == SOUTHEAST)) + scope_offset_x = min(scope_offset_x + 1, scope_drift_max) + else if((direction == NORTHWEST) || (direction == WEST) || (direction == SOUTHWEST)) + scope_offset_x = max(scope_offset_x - 1, -scope_drift_max) + + if((direction == NORTHWEST) || (direction == NORTH) || (direction == NORTHEAST)) + scope_offset_y = min(scope_offset_y + 1, scope_drift_max) + else if((direction == SOUTHWEST) || (direction == SOUTH) || (direction == SOUTHEAST)) + scope_offset_y = max(scope_offset_y - 1, -scope_drift_max) + + recalculate_scope_offset(old_x, old_y) + +/// Adjusts the position of the scope by a tile in a given direction +/obj/item/attachable/vulture_scope/proc/adjust_position(direction = NORTH) + var/perpendicular_axis = "x" + var/mob/user = scope_user.resolve() + var/turf/user_turf = get_turf(user) + if((user.dir == EAST) || (user.dir == WEST)) + perpendicular_axis = "y" + + if((direction == NORTHEAST) || (direction == EAST) || (direction == SOUTHEAST)) + scope_x++ + scope_x = user_turf.x + axis_math(user, perpendicular_axis, "x", direction) + else if((direction == NORTHWEST) || (direction == WEST) || (direction == SOUTHWEST)) + scope_x-- + scope_x = user_turf.x + axis_math(user, perpendicular_axis, "x", direction) + if((direction == NORTHWEST) || (direction == NORTH) || (direction == NORTHEAST)) + scope_y++ + scope_y = user_turf.y + axis_math(user, perpendicular_axis, "y", direction) + else if((direction == SOUTHWEST) || (direction == SOUTH) || (direction == SOUTHEAST)) + scope_y-- + scope_y = user_turf.y + axis_math(user, perpendicular_axis, "y", direction) + + SEND_SIGNAL(src, COMSIG_VULTURE_SCOPE_MOVED) + + recalculate_scope_pos() + +/// Figures out which direction the scope should move based on user direction and their input +/obj/item/attachable/vulture_scope/proc/axis_math(mob/user, perpendicular_axis = "x", modifying_axis = "x", direction = NORTH) + var/turf/user_turf = get_turf(user) + var/inverse = FALSE + if((user.dir == SOUTH) || (user.dir == WEST)) + inverse = TRUE + var/user_offset + if(modifying_axis == "x") + user_offset = scope_x - user_turf.x + + else + user_offset = scope_y - user_turf.y + + if(perpendicular_axis == modifying_axis) + return clamp(user_offset, -perpendicular_scope_range, perpendicular_scope_range) + + else + return clamp(abs(user_offset), min_scope_range, max_scope_range) * (inverse ? -1 : 1) + +/// Recalculates where the reticle should be inside the scope +/obj/item/attachable/vulture_scope/proc/recalculate_scope_offset(old_x = 0, old_y = 0) + var/mob/scoper = scope_user.resolve() + if(!scoper.client) + return + + var/x_to_set = (scope_offset_x >= 0 ? "+" : "") + "[scope_offset_x]" + var/y_to_set = (scope_offset_y >= 0 ? "+" : "") + "[scope_offset_y]" + scope_element.screen_loc = "CENTER[x_to_set],CENTER[y_to_set]" + +/// Recalculates where the scope should be in relation to the user +/obj/item/attachable/vulture_scope/proc/recalculate_scope_pos() + if(!scope_user) + return + var/turf/current_turf = get_turf(src) + var/x_off = scope_x - current_turf.x + var/y_off = scope_y - current_turf.y + var/pixels_per_tile = 32 + var/mob/scoper = scope_user.resolve() + if(!scoper.client) + return + + if(scoping) + scoper.client.pixel_x = x_off * pixels_per_tile + scoper.client.pixel_y = y_off * pixels_per_tile + else + scoper.client.pixel_x = 0 + scoper.client.pixel_y = 0 + +/// Handler for when the user begins scoping +/obj/item/attachable/vulture_scope/proc/on_scope() + var/turf/gun_turf = get_turf(src) + scope_x = gun_turf.x + scope_y = gun_turf.y + scope_offset_x = 0 + scope_offset_y = 0 + holding_breath = FALSE + + if(!isgun(loc)) + return + + var/obj/item/weapon/gun/gun = loc + var/mob/living/gun_user = gun.get_gun_user() + if(!gun_user) + return + + switch(gun_user.dir) + if(NORTH) + scope_y += start_scope_range + if(EAST) + scope_x += start_scope_range + if(SOUTH) + scope_y -= start_scope_range + if(WEST) + scope_x -= start_scope_range + + scope_user = WEAKREF(gun_user) + scope_user_initial_dir = gun_user.dir + scoping = TRUE + recalculate_scope_pos() + gun_user.overlay_fullscreen("vulture", /atom/movable/screen/fullscreen/vulture) + scope_element = new(src) + gun_user.client.screen += scope_element + gun_user.see_in_dark += darkness_view + gun_user.lighting_alpha = 127 + gun_user.sync_lighting_plane_alpha() + RegisterSignal(gun, list( + COMSIG_ITEM_DROPPED, + COMSIG_ITEM_UNWIELD, + ), PROC_REF(on_unscope)) + RegisterSignal(gun_user, COMSIG_MOB_UNDEPLOYED_BIPOD, PROC_REF(on_unscope)) + RegisterSignal(gun_user, COMSIG_MOB_MOVE_OR_LOOK, PROC_REF(on_mob_move_look)) + RegisterSignal(gun_user.client, COMSIG_PARENT_QDELETING, PROC_REF(on_unscope)) + +/// Handler for when the scope is deleted, dropped, etc. +/obj/item/attachable/vulture_scope/proc/on_unscope() + SIGNAL_HANDLER + if(!scope_user) + return + + var/mob/scoper = scope_user.resolve() + if(isgun(loc)) + UnregisterSignal(loc, list( + COMSIG_ITEM_DROPPED, + COMSIG_ITEM_UNWIELD, + )) + UnregisterSignal(scoper, list(COMSIG_MOB_UNDEPLOYED_BIPOD, COMSIG_MOB_MOVE_OR_LOOK)) + UnregisterSignal(scoper.client, COMSIG_PARENT_QDELETING) + stop_holding_breath() + scope_user_initial_dir = null + scoper.clear_fullscreen("vulture") + scoper.client.screen -= scope_element + scoper.see_in_dark -= darkness_view + scoper.lighting_alpha = 127 + scoper.sync_lighting_plane_alpha() + QDEL_NULL(scope_element) + recalculate_scope_pos() + scope_user = null + scoping = FALSE + if(scoper.client) + scoper.client.pixel_x = 0 + scoper.client.pixel_y = 0 + +/// Handler for if the mob moves or changes look direction +/obj/item/attachable/vulture_scope/proc/on_mob_move_look(mob/living/mover, actually_moving, direction, specific_direction) + SIGNAL_HANDLER + + if(actually_moving || (mover.dir != scope_user_initial_dir)) + on_unscope() + +/// Causes the scope to drift in a random direction by 1 tile +/obj/item/attachable/vulture_scope/proc/scope_drift(forced_dir) + var/dir_picked + if(!forced_dir) + dir_picked = pick(get_offset_dirs()) + else + dir_picked = forced_dir + + adjust_offset(dir_picked) + +/// Returns the turf that the sniper scope + reticle is currently focused on +/obj/item/attachable/vulture_scope/proc/get_viewed_turf() + RETURN_TYPE(/turf) + if(!scoping) + return null + var/turf/gun_turf = get_turf(src) + return locate(scope_x + scope_offset_x, scope_y + scope_offset_y, gun_turf.z) + +/// Lets the user start holding their breath, stopping gun sway for a short time +/obj/item/attachable/vulture_scope/proc/hold_breath() + if(!scope_user) + return + + var/mob/scoper = scope_user.resolve() + to_chat(scoper, SPAN_NOTICE("You hold your breath, steadying your scope...")) + holding_breath = TRUE + INVOKE_ASYNC(src, PROC_REF(tick_down_breath_scope)) + addtimer(CALLBACK(src, PROC_REF(stop_holding_breath)), breath_time) + +/// Slowly empties out the crosshair as the user's breath runs out +/obj/item/attachable/vulture_scope/proc/tick_down_breath_scope() + scope_element.icon_state = "vulture_steady_4" + sleep(breath_time * 0.25) + scope_element.icon_state = "vulture_steady_3" + sleep(breath_time * 0.25) + scope_element.icon_state = "vulture_steady_2" + sleep(breath_time * 0.25) + scope_element.icon_state = "vulture_steady_1" + +/// Stops the user from holding their breath, starting the cooldown +/obj/item/attachable/vulture_scope/proc/stop_holding_breath() + if(!scope_user || !holding_breath) + return + var/mob/scoper = scope_user.resolve() + to_chat(scoper, SPAN_NOTICE("You breathe out, letting your scope sway.")) + holding_breath = FALSE + scope_element.icon_state = "vulture_unsteady" + COOLDOWN_START(src, hold_breath_cd, breath_cooldown_time) + +/// Returns a % of how much time until the user can still their breath again +/obj/item/attachable/vulture_scope/proc/get_breath_recharge() + return 1 - (COOLDOWN_TIMELEFT(src, hold_breath_cd) / breath_cooldown_time) + +/datum/action/item_action/vulture + +/datum/action/item_action/vulture/action_activate() + var/obj/item/weapon/gun/gun_holder = holder_item + var/obj/item/attachable/vulture_scope/scope = gun_holder.attachments["rail"] + if(!istype(scope)) + return + scope.tgui_interact(owner) // ======== Stock attachments ======== // @@ -1271,6 +1743,16 @@ Defined in conflicts.dm of the #defines folder. recoil_unwielded_mod = RECOIL_AMOUNT_TIER_5 scatter_unwielded_mod = SCATTER_AMOUNT_TIER_4 +/obj/item/attachable/stock/vulture + name = "\improper M707 heavy stock" + icon_state = "vulture_stock" + hud_offset_mod = 3 + +/obj/item/attachable/stock/vulture/Initialize(mapload, ...) + . = ..() + select_gamemode_skin(type) + // Doesn't give any stat additions due to the gun already having really good ones, and this is unremovable from the gun itself + /obj/item/attachable/stock/tactical name = "\improper MK221 tactical stock" desc = "A metal stock made for the MK221 tactical shotgun." @@ -2762,6 +3244,7 @@ Defined in conflicts.dm of the #defines folder. user.apply_effect(2, SLOW) /obj/item/attachable/bipod/proc/undeploy_bipod(obj/item/weapon/gun/G) + REMOVE_TRAIT(G, TRAIT_GUN_BIPODDED, "attached_bipod") bipod_deployed = FALSE accuracy_mod = -HIT_ACCURACY_MULT_TIER_5 scatter_mod = SCATTER_AMOUNT_TIER_9 @@ -2773,6 +3256,7 @@ Defined in conflicts.dm of the #defines folder. var/mob/living/user if(isliving(G.loc)) user = G.loc + SEND_SIGNAL(user, COMSIG_MOB_UNDEPLOYED_BIPOD) UnregisterSignal(user, COMSIG_MOB_MOVE_OR_LOOK) if(G.flags_gun_features & GUN_SUPPORT_PLATFORM) @@ -2796,7 +3280,9 @@ Defined in conflicts.dm of the #defines folder. bipod_deployed = !bipod_deployed if(user) if(bipod_deployed) + ADD_TRAIT(G, TRAIT_GUN_BIPODDED, "attached_bipod") to_chat(user, SPAN_NOTICE("You deploy [src] [support ? "on [support]" : "on the ground"].")) + SEND_SIGNAL(user, COMSIG_MOB_DEPLOYED_BIPOD) playsound(user,'sound/items/m56dauto_rotate.ogg', 55, 1) accuracy_mod = HIT_ACCURACY_MULT_TIER_5 scatter_mod = -SCATTER_AMOUNT_TIER_10 @@ -2858,6 +3344,11 @@ Defined in conflicts.dm of the #defines folder. flags_attach_features = ATTACH_ACTIVATION +/obj/item/attachable/bipod/vulture + name = "heavy bipod" + desc = "A set of rugged telescopic poles to keep a weapon stabilized during firing." + icon_state = "bipod_m60" + attach_icon = "vulture_bipod" /obj/item/attachable/burstfire_assembly name = "burst fire assembly" diff --git a/code/modules/projectiles/guns/boltaction.dm b/code/modules/projectiles/guns/boltaction.dm index a558d3dd7969..9639e0c75a20 100644 --- a/code/modules/projectiles/guns/boltaction.dm +++ b/code/modules/projectiles/guns/boltaction.dm @@ -33,9 +33,13 @@ aim_slowdown = SLOWDOWN_ADS_RIFLE wield_delay = WIELD_DELAY_NORMAL civilian_usable_override = TRUE + unacidable = TRUE // Like other 1-of-a-kind weapons, it can't be gotten rid of that fast + indestructible = TRUE var/bolted = TRUE // FALSE IS OPEN, TRUE IS CLOSE var/bolt_delay var/recent_cycle //world.time to see when they last bolted it. + /// If this gun should change icon states when the bolt is open + var/has_openbolt_icon = TRUE /obj/item/weapon/gun/boltaction/set_gun_attachment_offsets() attachable_offset = list("muzzle_x" = 32, "muzzle_y" = 17,"rail_x" = 5, "rail_y" = 18, "under_x" = 25, "under_y" = 14, "stock_x" = 18, "stock_y" = 10) @@ -49,7 +53,7 @@ ..() var/new_icon_state = icon_state - if(!bolted) + if(!bolted && has_openbolt_icon) new_icon_state += "_o" icon_state = new_icon_state @@ -111,3 +115,163 @@ SPAN_NOTICE("You load [magazine] into [src]!"), null, 3, CHAT_TYPE_COMBAT_ACTION) if(reload_sound) playsound(user, reload_sound, 25, 1, 5) + + +/obj/item/weapon/gun/boltaction/vulture + name = "\improper M707 \"Vulture\" anti-materiel rifle" + desc = "The M707 is a crude but highly powerful rifle, designed for disabling lightly armored vehicles and hitting targets inside buildings. Its unwieldy scope and caliber necessitates a spotter to be fully effective, suffering severe scope drift without one." + desc_lore = {" + Put into production in 2175 as an economical answer to rising militancy in the Outer Rim, the M707 was derived from jury-rigged anti-materiel rifles that were captured during the Linna 349 campaign. + + The rebels (colloquially known among the USCMC as bug-boys and beebops) had achieved extensive success at Neusheune using the aforementioned rifles to pick off incinerator-wielding marines by detonating their napthal fuel tank in the midst of squad formations, subsequently leading to the USCMC designating users of those rifles as high-priority targets, as well as changes in USCMC patrol tactics. + + Some of the failings and quirks of the beebops' jury-rigged rifle were quickly noticed by vehicle crews early on in the campaign, as in multiple memoirs the crews mention that: "Once the rain starts, that's when you know you've got an ambush." + + The 'pitter-patter' of 'rain' that the crews heard was in fact multiple rifles failing to penetrate through the vehicle's external armor. Once a number of the anti-materiel rifles were examined, it was deemed a high priority to produce a Corps version. In the process, the rifles were designed for a higher calibre then that of the rebel versions, so the M707 would be capable of penetrating the light vehicle armor of their UPP peers in the event of another Dog War or Tientsin."} + + icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi' // overriden with camos + icon_state = "vulture" + item_state = "vulture" + cocked_sound = 'sound/weapons/gun_cocked2.ogg' + fire_sound = 'sound/weapons/gun_vulture_fire.ogg' + open_bolt_sound ='sound/weapons/handling/gun_vulture_bolt_eject.ogg' + close_bolt_sound ='sound/weapons/handling/gun_vulture_bolt_close.ogg' + flags_equip_slot = SLOT_BACK|SLOT_BLOCK_SUIT_STORE + w_class = SIZE_LARGE + force = 5 + flags_gun_features = NONE + gun_category = GUN_CATEGORY_HEAVY + aim_slowdown = SLOWDOWN_ADS_SPECIALIST // Consider SUPERWEAPON, but it's not like you can fire this without being bipodded + wield_delay = WIELD_DELAY_VERY_SLOW + map_specific_decoration = TRUE + current_mag = /obj/item/ammo_magazine/rifle/boltaction/vulture + attachable_allowed = list( + /obj/item/attachable/sniperbarrel/vulture, + /obj/item/attachable/vulture_scope, + /obj/item/attachable/bipod/vulture, + /obj/item/attachable/stock/vulture, + ) + starting_attachment_types = list( + /obj/item/attachable/sniperbarrel/vulture, + /obj/item/attachable/vulture_scope, + /obj/item/attachable/bipod/vulture, + /obj/item/attachable/stock/vulture, + ) + civilian_usable_override = FALSE + projectile_type = /obj/item/projectile/vulture + actions_types = list( + /datum/action/item_action/vulture, + ) + has_openbolt_icon = FALSE + bolt_delay = 1 SECONDS + /// How far out people can tell the direction of the shot + var/fire_message_range = 25 + /// If the gun should bypass the trait requirement + var/bypass_trait = FALSE + +/obj/item/weapon/gun/boltaction/vulture/update_icon() + ..() + if(!bolted) + overlays += "vulture_bolt_open" + + +/obj/item/weapon/gun/boltaction/vulture/set_gun_config_values() //check that these work + ..() + set_fire_delay(FIRE_DELAY_TIER_VULTURE) + accuracy_mult = BASE_ACCURACY_MULT + HIT_ACCURACY_MULT_TIER_7 + accuracy_mult_unwielded = BASE_ACCURACY_MULT - HIT_ACCURACY_MULT_TIER_10 + scatter = SCATTER_AMOUNT_TIER_10 + burst_scatter_mult = SCATTER_AMOUNT_TIER_6 + scatter_unwielded = SCATTER_AMOUNT_TIER_2 + damage_mult = BASE_BULLET_DAMAGE_MULT + recoil = RECOIL_AMOUNT_TIER_4 + recoil_unwielded = RECOIL_AMOUNT_TIER_2 + damage_falloff_mult = 0 + +/obj/item/weapon/gun/boltaction/vulture/set_gun_attachment_offsets() + attachable_offset = list("muzzle_x" = 33, "muzzle_y" = 19, "rail_x" = 11, "rail_y" = 24, "under_x" = 25, "under_y" = 14, "stock_x" = 11, "stock_y" = 15) + +/obj/item/weapon/gun/boltaction/vulture/able_to_fire(mob/user) + . = ..() + if(!.) + return + + if(!bypass_trait && !HAS_TRAIT(user, TRAIT_VULTURE_USER)) + to_chat(user, SPAN_WARNING("You don't know how to use this!")) + return + +/obj/item/weapon/gun/boltaction/vulture/Fire(atom/target, mob/living/user, params, reflex, dual_wield) + var/obj/item/attachable/vulture_scope/scope = attachments["rail"] + if(istype(scope) && scope.scoping) + var/turf/viewed_turf = scope.get_viewed_turf() + target = viewed_turf + var/mob/living/living_mob = locate(/mob/living) in viewed_turf + if(living_mob) + target = living_mob + + . = ..() + if(!.) + return . + + for(var/mob/current_mob as anything in get_mobs_in_z_level_range(get_turf(user), fire_message_range) - user) + var/relative_dir = get_dir(current_mob, user) + var/final_dir = dir2text(relative_dir) + to_chat(current_mob, SPAN_HIGHDANGER("You hear a massive boom coming from [final_dir ? "the [final_dir]" : "nearby"]!")) + if(current_mob.client) + playsound_client(current_mob.client, 'sound/weapons/gun_vulture_report.ogg', src, 25) + + if(!HAS_TRAIT(src, TRAIT_GUN_BIPODDED)) + fired_without_bipod(user) + else + shake_camera(user, 3, 4) // equivalent to getting hit with a heavy round + + return . + +/// Someone tried to fire this without using a bipod, so we break their arm along with sending them flying backwards +/obj/item/weapon/gun/boltaction/vulture/proc/fired_without_bipod(mob/living/user) + SEND_SIGNAL(src, COMSIG_GUN_VULTURE_FIRED_ONEHAND) + to_chat(user, SPAN_HIGHDANGER("You get flung backwards as you fire [src], breaking your firing arm in the process!")) + user.apply_effect(0.7, WEAKEN) + user.apply_effect(1, SUPERSLOW) + user.apply_effect(2, SLOW) + + if(ishuman(user)) + if(user.hand) + break_arm(user, RIGHT) + else + break_arm(user, LEFT) + + //Either knockback or slam them into an obstacle. + var/direction = REVERSE_DIR(user.dir) + if(direction && !step(user, direction)) + user.animation_attack_on(get_step(user, direction)) + user.visible_message(SPAN_DANGER("[user] slams into an obstacle!"), SPAN_HIGHDANGER("You slam into an obstacle!"), null, 4, CHAT_TYPE_TAKING_HIT) + user.apply_damage(MELEE_FORCE_TIER_2) + + shake_camera(user, 7, 6) // Around 2x worse than getting hit with a heavy round + +/// The code that takes care of breaking a person's firing arm +/obj/item/weapon/gun/boltaction/vulture/proc/break_arm(mob/living/carbon/human/user, arm = LEFT) + var/obj/limb/arm/found_limb + var/obj/limb/hand/found_hand + if(arm == LEFT) + found_limb = locate(/obj/limb/arm/l_arm) in user.limbs + found_hand = locate(/obj/limb/hand/l_hand) in user.limbs + else + found_limb = locate(/obj/limb/arm/r_arm) in user.limbs + found_hand = locate(/obj/limb/hand/r_hand) in user.limbs + + if(!found_limb || !found_hand) + return + found_limb.take_damage((found_limb.status & LIMB_BROKEN) ? rand(25, 30) : rand(10, 15)) + found_hand.take_damage((found_hand.status & LIMB_BROKEN) ? rand(25, 30) : rand(10, 15)) + found_limb.fracture(100) + found_hand.fracture(100) + + for(var/obj/limb/limb as anything in list(found_limb, found_hand)) + if(!(limb.status & LIMB_SPLINTED_INDESTRUCTIBLE) && (limb.status & LIMB_SPLINTED)) //If they have it splinted, the splint won't hold. + limb.status &= ~LIMB_SPLINTED + playsound(user, 'sound/items/splintbreaks.ogg', 20) + to_chat(user, SPAN_DANGER("The splint on your [limb.display_name] comes apart under the recoil!")) + user.pain.apply_pain(PAIN_BONE_BREAK_SPLINTED) + user.update_med_icon() diff --git a/code/modules/projectiles/magazines/rifles.dm b/code/modules/projectiles/magazines/rifles.dm index f960cc82535b..c12d2bd1bf43 100644 --- a/code/modules/projectiles/magazines/rifles.dm +++ b/code/modules/projectiles/magazines/rifles.dm @@ -445,3 +445,15 @@ max_rounds = 10 gun_type = /obj/item/weapon/gun/boltaction w_class = SIZE_SMALL + +/obj/item/ammo_magazine/rifle/boltaction/vulture + name = "\improper M707 \"Vulture\" magazine (20x102mm)" + desc = "A magazine for the M707 \"Vulture\" anti-matieriel rifle. Contains up to 4 massively oversized rounds." + caliber = "20x102mm" + icon = 'icons/obj/items/weapons/guns/ammo_by_faction/uscm.dmi' + icon_state = "vulture" + handful_state = "vulture_bullet" + default_ammo = /datum/ammo/bullet/sniper/anti_materiel/vulture + max_rounds = 4 + gun_type = /obj/item/weapon/gun/boltaction/vulture + w_class = SIZE_MEDIUM // maybe small? This shit's >4 inches long mind you diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index eccba14a442a..de48d8c79be2 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -69,6 +69,8 @@ /// The flicker that plays when a bullet hits a target. Usually red. Can be nulled so it doesn't show up at all. var/hit_effect_color = "#FF0000" + /// How much to make the bullet fall off by accuracy-wise when closer than the ideal range + var/accuracy_range_falloff = 10 /obj/item/projectile/Initialize(mapload, datum/cause_data/cause_data) . = ..() @@ -534,7 +536,7 @@ var/ammo_flags = ammo.flags_ammo_behavior | projectile_override_flags if(distance_travelled <= ammo.accurate_range) if(distance_travelled <= ammo.accurate_range_min) // If bullet stays within max accurate range + random variance - effective_accuracy -= (ammo.accurate_range_min - distance_travelled) * 10 // Snipers have accuracy falloff at closer range before point blank + effective_accuracy -= (ammo.accurate_range_min - distance_travelled) * accuracy_range_falloff // Snipers have accuracy falloff at closer range before point blank else effective_accuracy -= (distance_travelled - ammo.accurate_range) * ((ammo_flags & AMMO_SNIPER) ? 1.5 : 10) // Snipers have a smaller falloff constant due to longer max range @@ -1192,6 +1194,31 @@ if(dx == -1 || dx == 1) return TRUE +/obj/item/projectile/vulture + accuracy_range_falloff = 10 + /// The odds of hitting a xeno in less than your gun's range. Doesn't apply to humans. + var/xeno_shortrange_chance = 10 + +/obj/item/projectile/vulture/Initialize(mapload, datum/cause_data/cause_data) + . = ..() + RegisterSignal(src, COMSIG_GUN_VULTURE_FIRED_ONEHAND, PROC_REF(on_onehand)) + +/obj/item/projectile/vulture/handle_mob(mob/living/hit_mob) + if((ammo.accurate_range_min > distance_travelled) && isxeno(hit_mob)) + if(prob(xeno_shortrange_chance)) + return ..() + + permutated |= hit_mob + return + + return ..() + +/// Handler for when the user one-hands the firing gun +/obj/item/projectile/vulture/proc/on_onehand(datum/source) + SIGNAL_HANDLER + + accuracy = HIT_ACCURACY_TIER_2 // flat 10% chance if you're desperate and try to fire this thing without a bipod + #undef DEBUG_HIT_CHANCE #undef DEBUG_HUMAN_DEFENSE #undef DEBUG_XENO_DEFENSE diff --git a/code/modules/vehicles/multitile/multitile_interaction.dm b/code/modules/vehicles/multitile/multitile_interaction.dm index f956d64ebf12..e3c69d10e17c 100644 --- a/code/modules/vehicles/multitile/multitile_interaction.dm +++ b/code/modules/vehicles/multitile/multitile_interaction.dm @@ -295,7 +295,7 @@ if(P.runtime_iff_group && get_target_lock(P.runtime_iff_group)) return - if(ammo_flags & AMMO_ANTISTRUCT) + if(ammo_flags & AMMO_ANTISTRUCT|AMMO_ANTIVEHICLE) // Multiplier based on tank railgun relationship, so might have to reconsider multiplier for AMMO_SIEGE in general damage = round(damage*ANTISTRUCT_DMG_MULT_TANK) if(ammo_flags & AMMO_ACIDIC) diff --git a/colonialmarines.dme b/colonialmarines.dme index 721b77d56d9c..20a7e0e752c5 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -450,7 +450,8 @@ s// DM Environment file for colonialmarines.dme. #include "code\datums\elements\bullet_trait\iff.dm" #include "code\datums\elements\bullet_trait\ignored_range.dm" #include "code\datums\elements\bullet_trait\incendiary.dm" -#include "code\datums\elements\bullet_trait\penetrating.dm" +#include "code\datums\elements\bullet_trait\penetrating\heavy.dm" +#include "code\datums\elements\bullet_trait\penetrating\penetrating.dm" #include "code\datums\elements\traitbound\_traitbound.dm" #include "code\datums\elements\traitbound\crawler.dm" #include "code\datums\elements\traitbound\gun_silenced.dm" @@ -1037,6 +1038,7 @@ s// DM Environment file for colonialmarines.dme. #include "code\game\objects\items\devices\taperecorder.dm" #include "code\game\objects\items\devices\teleportation.dm" #include "code\game\objects\items\devices\transfer_valve.dm" +#include "code\game\objects\items\devices\vulture_spotter.dm" #include "code\game\objects\items\devices\walkman.dm" #include "code\game\objects\items\devices\whistle.dm" #include "code\game\objects\items\devices\radio\beacon.dm" @@ -1208,6 +1210,7 @@ s// DM Environment file for colonialmarines.dme. #include "code\game\objects\structures\surface.dm" #include "code\game\objects\structures\tables_racks.dm" #include "code\game\objects\structures\tank_dispenser.dm" +#include "code\game\objects\structures\vulture_spotter.dm" #include "code\game\objects\structures\watercloset.dm" #include "code\game\objects\structures\windoor_assembly.dm" #include "code\game\objects\structures\window.dm" diff --git a/icons/mob/hud/actions.dmi b/icons/mob/hud/actions.dmi index 9f885c44f50f..9021db895eb7 100644 Binary files a/icons/mob/hud/actions.dmi and b/icons/mob/hud/actions.dmi differ diff --git a/icons/mob/hud/screen1.dmi b/icons/mob/hud/screen1.dmi index fd4cf8188579..c79f6321a083 100644 Binary files a/icons/mob/hud/screen1.dmi and b/icons/mob/hud/screen1.dmi differ diff --git a/icons/mob/hud/screen1_full.dmi b/icons/mob/hud/screen1_full.dmi index 10eabc7a1659..c61aeaad55ee 100644 Binary files a/icons/mob/hud/screen1_full.dmi and b/icons/mob/hud/screen1_full.dmi differ diff --git a/icons/mob/humans/onmob/back.dmi b/icons/mob/humans/onmob/back.dmi index e4f228109127..33310fe93bdc 100644 Binary files a/icons/mob/humans/onmob/back.dmi and b/icons/mob/humans/onmob/back.dmi differ diff --git a/icons/mob/humans/onmob/items_lefthand_1.dmi b/icons/mob/humans/onmob/items_lefthand_1.dmi index 75c578316c83..2947ee0b237a 100644 Binary files a/icons/mob/humans/onmob/items_lefthand_1.dmi and b/icons/mob/humans/onmob/items_lefthand_1.dmi differ diff --git a/icons/mob/humans/onmob/items_righthand_1.dmi b/icons/mob/humans/onmob/items_righthand_1.dmi index 4f98571ff715..f1703be661c3 100644 Binary files a/icons/mob/humans/onmob/items_righthand_1.dmi and b/icons/mob/humans/onmob/items_righthand_1.dmi differ diff --git a/icons/obj/items/devices.dmi b/icons/obj/items/devices.dmi index de9e6fb01145..df3a53339dc4 100644 Binary files a/icons/obj/items/devices.dmi and b/icons/obj/items/devices.dmi differ diff --git a/icons/obj/items/pamphlets.dmi b/icons/obj/items/pamphlets.dmi index 021adfcdee52..b178b6389802 100644 Binary files a/icons/obj/items/pamphlets.dmi and b/icons/obj/items/pamphlets.dmi differ diff --git a/icons/obj/items/storage.dmi b/icons/obj/items/storage.dmi index 310bee26b9e9..6edbf5b6c7d3 100644 Binary files a/icons/obj/items/storage.dmi and b/icons/obj/items/storage.dmi differ diff --git a/icons/obj/items/weapons/guns/ammo_by_faction/uscm.dmi b/icons/obj/items/weapons/guns/ammo_by_faction/uscm.dmi index 3d2f1377db6f..51bc441aefaa 100644 Binary files a/icons/obj/items/weapons/guns/ammo_by_faction/uscm.dmi and b/icons/obj/items/weapons/guns/ammo_by_faction/uscm.dmi differ diff --git a/icons/obj/items/weapons/guns/attachments/barrel.dmi b/icons/obj/items/weapons/guns/attachments/barrel.dmi index aaa591f64a88..1ffb40420286 100644 Binary files a/icons/obj/items/weapons/guns/attachments/barrel.dmi and b/icons/obj/items/weapons/guns/attachments/barrel.dmi differ diff --git a/icons/obj/items/weapons/guns/attachments/rail.dmi b/icons/obj/items/weapons/guns/attachments/rail.dmi index 315005696ba8..68cb2648ebd1 100644 Binary files a/icons/obj/items/weapons/guns/attachments/rail.dmi and b/icons/obj/items/weapons/guns/attachments/rail.dmi differ diff --git a/icons/obj/items/weapons/guns/attachments/stock.dmi b/icons/obj/items/weapons/guns/attachments/stock.dmi index 330102213689..a15409b808ca 100644 Binary files a/icons/obj/items/weapons/guns/attachments/stock.dmi and b/icons/obj/items/weapons/guns/attachments/stock.dmi differ diff --git a/icons/obj/items/weapons/guns/attachments/under.dmi b/icons/obj/items/weapons/guns/attachments/under.dmi index 1a55ea7e215f..5a9be7542348 100644 Binary files a/icons/obj/items/weapons/guns/attachments/under.dmi and b/icons/obj/items/weapons/guns/attachments/under.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi b/icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi index 8576612c13b2..49e06f5ee547 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi and b/icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/classic/back.dmi b/icons/obj/items/weapons/guns/guns_by_map/classic/back.dmi index 9ca0704b6f46..63b197dd36c4 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/classic/back.dmi and b/icons/obj/items/weapons/guns/guns_by_map/classic/back.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/classic/guns_lefthand.dmi b/icons/obj/items/weapons/guns/guns_by_map/classic/guns_lefthand.dmi index d89a862cb7c3..22eae6bd0ba4 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/classic/guns_lefthand.dmi and b/icons/obj/items/weapons/guns/guns_by_map/classic/guns_lefthand.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/classic/guns_obj.dmi b/icons/obj/items/weapons/guns/guns_by_map/classic/guns_obj.dmi index 3f1b8385390e..b9aa8907a806 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/classic/guns_obj.dmi and b/icons/obj/items/weapons/guns/guns_by_map/classic/guns_obj.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/classic/guns_righthand.dmi b/icons/obj/items/weapons/guns/guns_by_map/classic/guns_righthand.dmi index b5aaba3e9431..dd432a1fa2c1 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/classic/guns_righthand.dmi and b/icons/obj/items/weapons/guns/guns_by_map/classic/guns_righthand.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/desert/back.dmi b/icons/obj/items/weapons/guns/guns_by_map/desert/back.dmi index b06e4bcd63e9..63771f7ff133 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/desert/back.dmi and b/icons/obj/items/weapons/guns/guns_by_map/desert/back.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/desert/guns_lefthand.dmi b/icons/obj/items/weapons/guns/guns_by_map/desert/guns_lefthand.dmi index b498fba1e8c1..6530e1d6967d 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/desert/guns_lefthand.dmi and b/icons/obj/items/weapons/guns/guns_by_map/desert/guns_lefthand.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/desert/guns_obj.dmi b/icons/obj/items/weapons/guns/guns_by_map/desert/guns_obj.dmi index f9e1d720098c..cae152f237ef 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/desert/guns_obj.dmi and b/icons/obj/items/weapons/guns/guns_by_map/desert/guns_obj.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/desert/guns_righthand.dmi b/icons/obj/items/weapons/guns/guns_by_map/desert/guns_righthand.dmi index 4fe8f9cd3939..9df4a0d86ccc 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/desert/guns_righthand.dmi and b/icons/obj/items/weapons/guns/guns_by_map/desert/guns_righthand.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/jungle/back.dmi b/icons/obj/items/weapons/guns/guns_by_map/jungle/back.dmi index 7d4f0a5db658..04718caddcd1 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/jungle/back.dmi and b/icons/obj/items/weapons/guns/guns_by_map/jungle/back.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/jungle/guns_lefthand.dmi b/icons/obj/items/weapons/guns/guns_by_map/jungle/guns_lefthand.dmi index 486d5a3d73b0..717a1182824e 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/jungle/guns_lefthand.dmi and b/icons/obj/items/weapons/guns/guns_by_map/jungle/guns_lefthand.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/jungle/guns_obj.dmi b/icons/obj/items/weapons/guns/guns_by_map/jungle/guns_obj.dmi index 479b0b9d39f2..9ce7e553ba45 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/jungle/guns_obj.dmi and b/icons/obj/items/weapons/guns/guns_by_map/jungle/guns_obj.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/jungle/guns_righthand.dmi b/icons/obj/items/weapons/guns/guns_by_map/jungle/guns_righthand.dmi index c30fb58093bb..9f69b4ac6815 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/jungle/guns_righthand.dmi and b/icons/obj/items/weapons/guns/guns_by_map/jungle/guns_righthand.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/snow/back.dmi b/icons/obj/items/weapons/guns/guns_by_map/snow/back.dmi index a9a1f0c3b1b7..b72963ff7917 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/snow/back.dmi and b/icons/obj/items/weapons/guns/guns_by_map/snow/back.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/snow/guns_lefthand.dmi b/icons/obj/items/weapons/guns/guns_by_map/snow/guns_lefthand.dmi index ee677a66bc20..2c8dedb6cf35 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/snow/guns_lefthand.dmi and b/icons/obj/items/weapons/guns/guns_by_map/snow/guns_lefthand.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/snow/guns_obj.dmi b/icons/obj/items/weapons/guns/guns_by_map/snow/guns_obj.dmi index 801a03dbcfcd..fbc98874a4f6 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/snow/guns_obj.dmi and b/icons/obj/items/weapons/guns/guns_by_map/snow/guns_obj.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/snow/guns_righthand.dmi b/icons/obj/items/weapons/guns/guns_by_map/snow/guns_righthand.dmi index 0af69fa8a9d6..6e7b9cb8c9b5 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/snow/guns_righthand.dmi and b/icons/obj/items/weapons/guns/guns_by_map/snow/guns_righthand.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/urban/back.dmi b/icons/obj/items/weapons/guns/guns_by_map/urban/back.dmi index 0a9f43029891..e849b8134004 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/urban/back.dmi and b/icons/obj/items/weapons/guns/guns_by_map/urban/back.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/urban/guns_lefthand.dmi b/icons/obj/items/weapons/guns/guns_by_map/urban/guns_lefthand.dmi index 2eece9a9ffc4..c844b637ab7e 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/urban/guns_lefthand.dmi and b/icons/obj/items/weapons/guns/guns_by_map/urban/guns_lefthand.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/urban/guns_obj.dmi b/icons/obj/items/weapons/guns/guns_by_map/urban/guns_obj.dmi index 2899c72fa9a8..09029fb61f2b 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/urban/guns_obj.dmi and b/icons/obj/items/weapons/guns/guns_by_map/urban/guns_obj.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_map/urban/guns_righthand.dmi b/icons/obj/items/weapons/guns/guns_by_map/urban/guns_righthand.dmi index 1950b50114db..78bebfdd4bd9 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_map/urban/guns_righthand.dmi and b/icons/obj/items/weapons/guns/guns_by_map/urban/guns_righthand.dmi differ diff --git a/icons/obj/items/weapons/guns/handful.dmi b/icons/obj/items/weapons/guns/handful.dmi index 7d717db608c4..bbea110531ea 100644 Binary files a/icons/obj/items/weapons/guns/handful.dmi and b/icons/obj/items/weapons/guns/handful.dmi differ diff --git a/icons/obj/structures/crates.dmi b/icons/obj/structures/crates.dmi index 5fab82b5185d..a1a494d90da2 100644 Binary files a/icons/obj/structures/crates.dmi and b/icons/obj/structures/crates.dmi differ diff --git a/icons/obj/structures/structures.dmi b/icons/obj/structures/structures.dmi index ef63dee56e13..d96329839a7b 100644 Binary files a/icons/obj/structures/structures.dmi and b/icons/obj/structures/structures.dmi differ diff --git a/sound/bullets/bullet_vulture_impact.ogg b/sound/bullets/bullet_vulture_impact.ogg new file mode 100644 index 000000000000..8ccf6c77deac Binary files /dev/null and b/sound/bullets/bullet_vulture_impact.ogg differ diff --git a/sound/weapons/gun_vulture_fire.ogg b/sound/weapons/gun_vulture_fire.ogg new file mode 100644 index 000000000000..156127cb5a14 Binary files /dev/null and b/sound/weapons/gun_vulture_fire.ogg differ diff --git a/sound/weapons/gun_vulture_report.ogg b/sound/weapons/gun_vulture_report.ogg new file mode 100644 index 000000000000..16cb5eae9e32 Binary files /dev/null and b/sound/weapons/gun_vulture_report.ogg differ diff --git a/sound/weapons/handling/gun_vulture_bolt_close.ogg b/sound/weapons/handling/gun_vulture_bolt_close.ogg new file mode 100644 index 000000000000..1417782295d5 Binary files /dev/null and b/sound/weapons/handling/gun_vulture_bolt_close.ogg differ diff --git a/sound/weapons/handling/gun_vulture_bolt_eject.ogg b/sound/weapons/handling/gun_vulture_bolt_eject.ogg new file mode 100644 index 000000000000..a0a1dc7d0904 Binary files /dev/null and b/sound/weapons/handling/gun_vulture_bolt_eject.ogg differ diff --git a/tgui/packages/tgui/interfaces/VultureScope.tsx b/tgui/packages/tgui/interfaces/VultureScope.tsx new file mode 100644 index 000000000000..10c73df2ba65 --- /dev/null +++ b/tgui/packages/tgui/interfaces/VultureScope.tsx @@ -0,0 +1,244 @@ +import { BooleanLike } from '../../common/react'; +import { useBackend } from '../backend'; +import { Box, Button, Flex, Stack, Icon, Section, ProgressBar } from '../components'; +import { Window } from '../layouts'; + +type ScopeData = { + offset_x: Number; + offset_y: Number; + valid_offset_dirs: Array; + valid_adjust_dirs: Array; + scope_cooldown: BooleanLike; + breath_cooldown: BooleanLike; + breath_recharge: Number; + current_scope_drift: Number; + time_to_fire_remaining: Number; +}; + +enum Direction { + North = 1, + NorthEast = 5, + East = 4, + SouthEast = 6, + South = 2, + SouthWest = 10, + West = 8, + NorthWest = 9, +} + +const DirectionAbbreviation: Record = { + [Direction.North]: 'N', + [Direction.NorthEast]: 'NE', + [Direction.East]: 'E', + [Direction.SouthEast]: 'SE', + [Direction.South]: 'S', + [Direction.SouthWest]: 'SW', + [Direction.West]: 'W', + [Direction.NorthWest]: 'NW', +}; + +const OffsetAdjuster = (props, context) => { + const { act, data } = useBackend(context); + return ( +
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+ ); +}; + +const OffsetDirection = (props, context) => { + const { dir } = props; + const { data, act } = useBackend(context); + return ( + + + + + + + + + + + + + + + ); +}; + +const ScopePosition = (props, context) => { + const { dir } = props; + const { data, act } = useBackend(context); + return ( + +