From c54b2b768d4787d6dc49c4fe76ed92e3edaf021c Mon Sep 17 00:00:00 2001 From: wraith-54321 <69217972+wraith-54321@users.noreply.github.com> Date: Sun, 14 Jan 2024 21:36:05 -0800 Subject: [PATCH 01/49] Microfusion Energy Guns (#9638) * RELOADING! * Update skillchip.dm * a * Update master.dm * Update master.dm * Part one * E * E * EOR * Oops * EEEH * Fixes. * begone oldcode from old times * fixes * cell attatchments * haha u cant spell * Gun attachments! * Fastest gun in the west * Update multi_cell_charger.dm * ESA * Stability * phase emitters * foixes * More fixes * Update microfusion_gun_attachments.dm * Update microfusion_gun_attachments.dm * eee * Intermediate interactions. * Update microfusion_energy_master.dm * wooh * EEE * Update microfusion_cell.dm * Map edits * Merge branch 'master' into gun_reloads * E * attachment removal * Update _basemap.dm * proper parent gun interactions * Update code/game/machinery/recharger.dm Co-authored-by: Tom <8881105+tf-4@users.noreply.github.com> * E * Update modular_skyrat/modules/gunsgalore/code/guns/ballistic_master.dm Co-authored-by: Tom <8881105+tf-4@users.noreply.github.com> * Update modular_skyrat/modules/microfusion/code/microfusion_cell.dm Co-authored-by: Tom <8881105+tf-4@users.noreply.github.com> * Update modular_skyrat/modules/microfusion/code/microfusion_cell.dm Co-authored-by: Tom <8881105+tf-4@users.noreply.github.com> * Review comments pt2 * review comments pt2 * review part 3 * review part 4 * review 4.1 * review fixes * Update microfusion_designs.dm * ffs * Update code/__DEFINES/~skyrat_defines/traits.dm Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> * Nearly there * final review commit * Update multi_cell_charger.dm * Update microfusion_energy_master.dm * E * insight * Fixes, rgb, remove instability on cells * de epic * Update _basemap.dm * TGUI in the houise * Update MicrofusionGunControl.js * FULL STEAM AHEAD CHOO CHOO (Adds zooming) * FUU bayonets and flights * e * Nearly * microCHANGES * Update gun.dm * Minidate. - Shooting the gun while it's overheating can cause it to [REDACTED]. - Shooting the gun while it's overheating will burn your hand! - Some cells will now notify you when they're empty using an alarm sound. - Gun barrel attachments now change the sound of the laser! - An overheating emitter slows down! - Emitters now control fire time more! * 0 * owo * FFF * Update code/game/machinery/recharger.dm Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> * Update modular_skyrat/modules/gunhud/code/gun_hud_component.dm Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> * no spolsion * Generally we want the cell in our hands * 0 * Update microfusion_gun_attachments.dm * E * E * FINAL * superheater * Update projectiles.dm * Update microfusion_energy_master.dm * Update projectiles.dm * Update microfusion_gun40x32.dmi * Update laser_1.ogg * E * whoop * Cargo crates! * Review plus material updates * Update microfusion_designs.dm * E * E * E * E * EEEE * Update gun_spawns.dm * Some fine tuining * Update gun_spawns.dm * Update recharger.dm * E * Update microfusion_gun_attachments.dm * E * E * Update microfusion_gun_attachments.dmi * Update code/game/machinery/recharger.dm Co-authored-by: Tom <8881105+tf-4@users.noreply.github.com> * E * You can now actually insert different cells into advanced guns! * 0 * E * Update microfusion_gun_attachments.dm * Update microfusion_energy_master.dm * Update modular_skyrat/modules/microfusion/code/microfusion_cell.dm Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> * Update modular_skyrat/modules/microfusion/code/microfusion_cell.dm Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> * E * Update microfusion_energy_master.dm * Update microfusion_gun_attachments.dm Co-Authored-By: Tom <8881105+tf-4@users.noreply.github.com> Co-Authored-By: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> --- code/__DEFINES/inventory.dm | 29 +- code/__DEFINES/~skyrat_defines/traits.dm | 63 ++ code/game/machinery/cell_charger.dm | 7 +- code/game/machinery/recharger.dm | 15 + code/game/objects/items/storage/belt.dm | 3 +- code/modules/power/cell.dm | 64 +- code/modules/projectiles/guns/energy.dm | 23 +- .../code/modules/projectiles/guns/gun.dm | 862 ++++++++++++++++++ .../modules/aesthetics/cells/cell.dm | 7 + .../modules/goofsec/code/projectiles.dm | 203 +++++ .../modules/gunhud/code/gun_hud_component.dm | 203 +++++ .../modules/gunhud/icons/gun_hud.dmi | Bin 0 -> 11023 bytes .../modules/modular_items/code/bags.dm | 42 + .../code/multi_cell_charger.dm | 197 ++++ .../modules/robohand/code/robohand.dm | 112 +++ .../modules/sec_haul/code/guns/gun_spawns.dm | 95 ++ .../gunsgalore/code/guns/ballistic_master.dm | 272 ++++++ .../modules/gunsgalore/code/guns/skillchip.dm | 9 + .../microfusion/code/_microfusion_defines.dm | 52 ++ .../modules/microfusion/code/cargo_stuff.dm | 51 ++ .../modules/microfusion/code/gun_types.dm | 41 + .../microfusion/code/microfusion_cell.dm | 189 ++++ .../code/microfusion_cell_attachments.dm | 132 +++ .../microfusion/code/microfusion_designs.dm | 218 +++++ .../code/microfusion_energy_master.dm | 806 ++++++++++++++++ .../code/microfusion_gun_attachments.dm | 447 +++++++++ .../microfusion/code/microfusion_techweb.dm | 87 ++ .../modules/microfusion/code/phase_emitter.dm | 211 +++++ .../modules/microfusion/code/projectiles.dm | 44 + .../microfusion/icons/guns_lefthand.dmi | Bin 0 -> 840 bytes .../microfusion/icons/guns_righthand.dmi | Bin 0 -> 814 bytes .../microfusion/icons/microfusion_cells.dmi | Bin 0 -> 1724 bytes .../icons/microfusion_gun40x32.dmi | Bin 0 -> 4051 bytes .../icons/microfusion_gun_attachments.dmi | Bin 0 -> 3393 bytes .../modules/microfusion/icons/projectiles.dmi | Bin 0 -> 416 bytes .../code/modules/microfusion/sound/burn.ogg | Bin 0 -> 34599 bytes .../modules/microfusion/sound/incinerate.ogg | Bin 0 -> 29379 bytes .../modules/microfusion/sound/laser_1.ogg | Bin 0 -> 14749 bytes .../modules/microfusion/sound/mag_insert.ogg | Bin 0 -> 9407 bytes .../code/modules/microfusion/sound/melt.ogg | Bin 0 -> 26683 bytes .../modules/microfusion/sound/overheat.ogg | Bin 0 -> 64084 bytes .../modules/microfusion/sound/vaporize.ogg | Bin 0 -> 35624 bytes .../tgui/interfaces/MicrofusionGunControl.js | 269 ++++++ 43 files changed, 4678 insertions(+), 75 deletions(-) create mode 100644 code/__DEFINES/~skyrat_defines/traits.dm create mode 100644 modular_skyrat/master_files/code/modules/projectiles/guns/gun.dm create mode 100644 modular_skyrat/modules/aesthetics/cells/cell.dm create mode 100644 modular_skyrat/modules/goofsec/code/projectiles.dm create mode 100644 modular_skyrat/modules/gunhud/code/gun_hud_component.dm create mode 100644 modular_skyrat/modules/gunhud/icons/gun_hud.dmi create mode 100644 modular_skyrat/modules/modular_items/code/bags.dm create mode 100644 modular_skyrat/modules/multicellcharger/code/multi_cell_charger.dm create mode 100644 modular_skyrat/modules/robohand/code/robohand.dm create mode 100644 modular_skyrat/modules/sec_haul/code/guns/gun_spawns.dm create mode 100644 monkestation/code/modules/gunsgalore/code/guns/ballistic_master.dm create mode 100644 monkestation/code/modules/gunsgalore/code/guns/skillchip.dm create mode 100644 monkestation/code/modules/microfusion/code/_microfusion_defines.dm create mode 100644 monkestation/code/modules/microfusion/code/cargo_stuff.dm create mode 100644 monkestation/code/modules/microfusion/code/gun_types.dm create mode 100644 monkestation/code/modules/microfusion/code/microfusion_cell.dm create mode 100644 monkestation/code/modules/microfusion/code/microfusion_cell_attachments.dm create mode 100644 monkestation/code/modules/microfusion/code/microfusion_designs.dm create mode 100644 monkestation/code/modules/microfusion/code/microfusion_energy_master.dm create mode 100644 monkestation/code/modules/microfusion/code/microfusion_gun_attachments.dm create mode 100644 monkestation/code/modules/microfusion/code/microfusion_techweb.dm create mode 100644 monkestation/code/modules/microfusion/code/phase_emitter.dm create mode 100644 monkestation/code/modules/microfusion/code/projectiles.dm create mode 100644 monkestation/code/modules/microfusion/icons/guns_lefthand.dmi create mode 100644 monkestation/code/modules/microfusion/icons/guns_righthand.dmi create mode 100644 monkestation/code/modules/microfusion/icons/microfusion_cells.dmi create mode 100644 monkestation/code/modules/microfusion/icons/microfusion_gun40x32.dmi create mode 100644 monkestation/code/modules/microfusion/icons/microfusion_gun_attachments.dmi create mode 100644 monkestation/code/modules/microfusion/icons/projectiles.dmi create mode 100644 monkestation/code/modules/microfusion/sound/burn.ogg create mode 100644 monkestation/code/modules/microfusion/sound/incinerate.ogg create mode 100644 monkestation/code/modules/microfusion/sound/laser_1.ogg create mode 100644 monkestation/code/modules/microfusion/sound/mag_insert.ogg create mode 100644 monkestation/code/modules/microfusion/sound/melt.ogg create mode 100644 monkestation/code/modules/microfusion/sound/overheat.ogg create mode 100644 monkestation/code/modules/microfusion/sound/vaporize.ogg create mode 100644 tgui/packages/tgui/interfaces/MicrofusionGunControl.js diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm index b34fc1e5b16d..0646ca918571 100644 --- a/code/__DEFINES/inventory.dm +++ b/code/__DEFINES/inventory.dm @@ -165,7 +165,31 @@ //Allowed equipment lists for security vests. -GLOBAL_LIST_INIT(detective_vest_allowed, list( +GLOBAL_LIST_INIT(advanced_hardsuit_allowed, typecacheof(list( + /obj/item/ammo_box, + /obj/item/ammo_casing, + /obj/item/flashlight, + /obj/item/gun, + /obj/item/melee/baton, + /obj/item/reagent_containers/spray/pepper, + /obj/item/restraints/handcuffs, + /obj/item/tank/internals, + ))) + +GLOBAL_LIST_INIT(security_hardsuit_allowed, typecacheof(list( + /obj/item/ammo_box, + /obj/item/ammo_casing, + /obj/item/flashlight, + /obj/item/gun/ballistic, + /obj/item/gun/energy, + /obj/item/melee/baton, + /obj/item/reagent_containers/spray/pepper, + /obj/item/restraints/handcuffs, + /obj/item/tank/internals, + /obj/item/gun/microfusion, //SKYRAT EDIT ADDITION + ))) + +GLOBAL_LIST_INIT(detective_vest_allowed, typecacheof(list( /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/detective_scanner, @@ -182,6 +206,7 @@ GLOBAL_LIST_INIT(detective_vest_allowed, list( /obj/item/tank/internals/plasmaman, /obj/item/storage/belt/holster/detective, /obj/item/storage/belt/holster/nukie, + /obj/item/gun/microfusion, //SKYRAT EDIT ADDITION /obj/item/storage/belt/holster/energy, )) @@ -199,6 +224,7 @@ GLOBAL_LIST_INIT(security_vest_allowed, list( /obj/item/tank/internals/plasmaman, /obj/item/storage/belt/holster/detective, /obj/item/storage/belt/holster/nukie, + /obj/item/gun/microfusion, //SKYRAT EDIT ADDITION /obj/item/storage/belt/holster/energy, /obj/item/clothing/mask/breath/sec_bandana )) @@ -213,6 +239,7 @@ GLOBAL_LIST_INIT(security_wintercoat_allowed, list( /obj/item/restraints/handcuffs, /obj/item/storage/belt/holster/detective, /obj/item/storage/belt/holster/nukie, + /obj/item/gun/microfusion, //SKYRAT EDIT ADDITION /obj/item/storage/belt/holster/energy, )) diff --git a/code/__DEFINES/~skyrat_defines/traits.dm b/code/__DEFINES/~skyrat_defines/traits.dm new file mode 100644 index 000000000000..74b1f61949a8 --- /dev/null +++ b/code/__DEFINES/~skyrat_defines/traits.dm @@ -0,0 +1,63 @@ +// Defines for some extra traits +#define TRAIT_NO_HUSK "no_husk" +#define TRAIT_NORUNNING "norunning" // You walk! +#define TRAIT_EXCITABLE "wagwag" //Will wag when patted! +#define TRAIT_OXYIMMUNE "oxyimmune" // Immune to oxygen damage, ideally give this to all non-breathing species or bad stuff will happen +#define TRAIT_IRONASS "ironass" +#define TRAIT_MOOD_NOEXAMINE "mood_noexamine" // Can't assess your own mood +#define TRAIT_DNR "cant_revive" //You just can't be revived without supernatural means +#define TRAIT_HARD_SOLES "hard_soles" // No step on glass +#define TRAIT_SENSITIVESNOUT "sensitive_snout" // Snout hurts when booped +#define TRAIT_DETECTIVE "detective_ability" //Given to the detective, if they have this, they can see syndicate special descriptions. +#define TRAIT_FREE_GHOST "free_ghost" // Can ghost and return freely with this trait +#define QUIRK_LINGUIST "Linguist" // Extra language point. +#define GLUED_ITEM_TRAIT "glued-item" // This is for glued items, undroppable. Syndie glue applies this. +#define TRAIT_STICKY_FINGERS "sticky_fingers" //This is so a mob can strip items faster and picks them up after +/// This makes trait makes it so that the person cannot be infected by the zombie virus. +#define TRAIT_MUTANT_IMMUNE "mutant_immune" + +//AdditionalEmotes *turf traits +#define TRAIT_WATER_ASPECT "water_aspect" +#define TRAIT_WEBBING_ASPECT "webbing_aspect" +#define TRAIT_FLORAL_ASPECT "floral_aspect" +#define TRAIT_ASH_ASPECT "ash_aspect" +#define TRAIT_SPARKLE_ASPECT "sparkle_aspect" + +/// Allows the user to instantly reload. +#define TRAIT_INSTANT_RELOAD "instant_reload" + +// Trait sources +#define GHOSTROLE_TRAIT "ghostrole" // SKYRAT EDIT ADDITION -- Ghost Cafe Traits + +/// One can breath under water, you get me? +#define TRAIT_WATER_BREATHING "water_breathing" + +/// Under the effect of a numbling agent, such as morphine, for surgery. +#define TRAIT_NUMBED "numbed" + +// felinid traits +#define TRAIT_FELINE "feline_aspect" + +// chameleon mutation +#define TRAIT_CHAMELEON_SKIN "chameleon_skin" + +//Makes sure that people cant be cult sacrificed twice. +#define TRAIT_SACRIFICED "sacrificed" + +//Adds 2 seconds to the Goliath tentacle stun timer. +#define TRAIT_GOLIATH_STUN "goliath_stun" + +//to determine if someone is a fishing master, or not +#define TRAIT_FISHING_MASTER "fishing_master" + +//to determine if someone is a ceramics master, or not +#define TRAIT_CERAMIC_MASTER "ceramic_master" + +//to determine if someone is a glassblowing master, or not +#define TRAIT_GLASSBLOWING_MASTER "glassblowing_master" + +/// The trait that determines if someone has the oversized quirk. +#define TRAIT_OVERSIZED "trait_oversized" + +/// Caninid trait +#define TRAIT_CANINE "trait_canine" diff --git a/code/game/machinery/cell_charger.dm b/code/game/machinery/cell_charger.dm index 7773b3c1b587..c9b41d58cf06 100644 --- a/code/game/machinery/cell_charger.dm +++ b/code/game/machinery/cell_charger.dm @@ -52,6 +52,12 @@ if(charging) to_chat(user, span_warning("There is already a cell in the charger!")) return + //SKYRAT EDIT ADDITION + var/obj/item/stock_parts/cell/inserting_cell = W + if(inserting_cell.chargerate <= 0) + to_chat(user, span_warning("[inserting_cell] cannot be recharged!")) + return + //SKYRAT EDIT END else var/area/a = loc.loc // Gets our locations location, like a dream within a dream if(!isarea(a)) @@ -133,7 +139,6 @@ /obj/machinery/cell_charger/process(seconds_per_tick) if(!charging || !anchored || (machine_stat & (BROKEN|NOPOWER))) return - if(charging.percent() >= 100) return diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm index 1beee62d473a..14db68c70633 100755 --- a/code/game/machinery/recharger.dm +++ b/code/game/machinery/recharger.dm @@ -13,6 +13,8 @@ var/finished_recharging = FALSE var/static/list/allowed_devices = typecacheof(list( + /obj/item/stock_parts/cell/microfusion, //SKYRAT EDIT ADDITION + /obj/item/gun/microfusion, // SKYRAT EDIT ADDITION /obj/item/gun/energy, /obj/item/melee/baton/security, /obj/item/ammo_box/magazine/recharge, @@ -85,6 +87,19 @@ to_chat(user, span_notice("Your gun has no external power connector.")) return 1 + //SKYRAT EDIT ADDITION + if (istype(G, /obj/item/gun/microfusion)) + var/obj/item/gun/microfusion/microfusion_gun = G + if(microfusion_gun.cell?.chargerate <= 0) + to_chat(user, span_notice("[microfusion_gun] cannot be recharged!")) + return TRUE + + if(istype(G, /obj/item/stock_parts/cell/microfusion)) + var/obj/item/stock_parts/cell/microfusion/inserting_cell = G + if(inserting_cell.chargerate <= 0) + to_chat(user, span_notice("[inserting_cell] cannot be recharged!")) + return TRUE + //SKYRAT EDIT END if(!user.transferItemToLoc(G, src)) return 1 setCharging(G) diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index 21fc18326239..d1770b4aafae 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -343,7 +343,8 @@ /obj/item/reagent_containers/spray/pepper, /obj/item/restraints/handcuffs, /obj/item/restraints/legcuffs/bola, - /obj/item/food/spaghetti/security, //monkestation change: tactical belt spaghetti + /obj/item/food/spaghetti/security, //monkestation change: tactical belt spaghetti, + /obj/item/stock_parts/cell/microfusion, //SKYRAT EDIT ADDITION )) /obj/item/storage/belt/security/full/PopulateContents() diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index 21e16d955382..270675bc91a4 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -51,44 +51,11 @@ create_reagents(5, INJECTABLE | DRAINABLE) if (override_maxcharge) maxcharge = override_maxcharge - rating = max(round(maxcharge / 10000, 1), 1) - if(!charge) - charge = maxcharge - if(empty) - charge = 0 - if(ratingdesc) - desc += " This one has a rating of [display_energy(maxcharge)][prob(10) ? ", and you should not swallow it" : ""]." //joke works better if it's not on every cell - update_appearance() - - RegisterSignal(src, COMSIG_ITEM_MAGICALLY_CHARGED, PROC_REF(on_magic_charge)) - var/static/list/loc_connections = list( - COMSIG_ITEM_MAGICALLY_CHARGED = PROC_REF(on_magic_charge), - ) - AddElement(/datum/element/connect_loc, loc_connections) - -/** - * Signal proc for [COMSIG_ITEM_MAGICALLY_CHARGED] - * - * If we, or the item we're located in, is subject to the charge spell, gain some charge back - */ -/obj/item/stock_parts/cell/proc/on_magic_charge(datum/source, datum/action/cooldown/spell/charge/spell, mob/living/caster) - SIGNAL_HANDLER - - // This shouldn't be running if we're not being held by a mob, - // or if we're not within an object being held by a mob, but just in case... - if(!ismovable(loc)) - return - - . = COMPONENT_ITEM_CHARGED - - if(prob(80)) - maxcharge -= 200 - - if(maxcharge <= 1) // Div by 0 protection - maxcharge = 1 - . |= COMPONENT_ITEM_BURNT_OUT - charge = maxcharge + /* SKYRAT EDIT REMOVAL + if(ratingdesc) + desc += " This one has a rating of [display_energy(maxcharge)], and you should not swallow it." + */ // SKYRAT EDIT END update_appearance() // Guns need to process their chamber when we've been charged @@ -119,20 +86,7 @@ . += mutable_appearance('icons/obj/power.dmi', "grown_wires") if((charge < 0.01) || !charge_light_type) return - . += mutable_appearance('icons/obj/power.dmi', "cell-[charge_light_type]-o[(percent() >= 99.5) ? 2 : 1]") - -/obj/item/stock_parts/cell/vv_edit_var(vname, vval) - if(vname == NAMEOF(src, charge)) - charge = clamp(vval, 0, maxcharge) - return TRUE - if(vname == NAMEOF(src, maxcharge)) - if(charge > vval) - charge = vval - if(vname == NAMEOF(src, corrupted) && vval && !corrupted) - corrupt(TRUE) - return TRUE - return ..() - + . += mutable_appearance(charge_overlay_icon, "cell-o[((charge / maxcharge) >= 0.995) ? 2 : 1]") //SKYRAT EDIT CHANGE /obj/item/stock_parts/cell/proc/percent() // return % charge of cell return 100 * charge / maxcharge @@ -162,8 +116,16 @@ /obj/item/stock_parts/cell/examine(mob/user) . = ..() + // SKYRAT EDIT ADDITION + if(ratingdesc && !microfusion_readout) + . += "This one has a rating of [display_energy(maxcharge)], and you should not swallow it." + // SKYRAT EDIT END if(rigged) . += span_danger("This power cell seems to be faulty!") + // SKYRAT EDIT ADDITION + else if(microfusion_readout) + . += "The charge meter reads [charge]/[maxcharge] MF." + // SKYRAT EDIT END else . += "The charge meter reads [CEILING(percent(), 0.1)]%." //so it doesn't say 0% charge when the overlay indicates it still has charge diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm index 0b1d70941b8b..bb02b130c6ac 100644 --- a/code/modules/projectiles/guns/energy.dm +++ b/code/modules/projectiles/guns/energy.dm @@ -34,26 +34,6 @@ ///set to true so the gun is given an empty cell var/dead_cell = FALSE -/obj/item/gun/energy/fire_sounds() - // What frequency the energy gun's sound will make - var/frequency_to_use - - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - // What percentage of the full battery a shot will expend - var/shot_cost_percent = round(clamp(shot.e_cost / cell.maxcharge, 0, 1) * 100) - // Ignore this on oversized/infinite cells or ammo without cost - if(shot_cost_percent > 0) - // The total amount of shots the fully charged energy gun can fire before running out - var/max_shots = round(100/shot_cost_percent) - // How many shots left before the energy gun's current battery runs out of energy - var/shots_left = round((round(clamp(cell.charge / cell.maxcharge, 0, 1) * 100))/shot_cost_percent) - frequency_to_use = sin((90/max_shots) * shots_left) - - if(suppressed) - playsound(src, suppressed_sound, suppressed_volume, vary_fire_sound, ignore_walls = FALSE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0, frequency = frequency_to_use) - else - playsound(src, fire_sound, fire_sound_volume, vary_fire_sound, frequency = frequency_to_use) - /obj/item/gun/energy/emp_act(severity) . = ..() if(!(. & EMP_PROTECT_CONTENTS)) @@ -121,6 +101,7 @@ ammo_type[i] = shot shot = ammo_type[select] fire_sound = shot.fire_sound + fire_sound_volume = shot.fire_sound_volume //SKYRAT EDIT ADDITION fire_delay = shot.delay /obj/item/gun/energy/Destroy() @@ -202,6 +183,7 @@ select = 1 var/obj/item/ammo_casing/energy/shot = ammo_type[select] fire_sound = shot.fire_sound + fire_sound_volume = shot.fire_sound_volume //SKYRAT EDIT ADDITION fire_delay = shot.delay if (shot.select_name && user) balloon_alert(user, "set to [shot.select_name]") @@ -229,7 +211,6 @@ worn_icon_state = temp_icon_to_use return ..() - /obj/item/gun/energy/update_overlays() . = ..() if(!automatic_charge_overlays) diff --git a/modular_skyrat/master_files/code/modules/projectiles/guns/gun.dm b/modular_skyrat/master_files/code/modules/projectiles/guns/gun.dm new file mode 100644 index 000000000000..0623ae88fcfd --- /dev/null +++ b/modular_skyrat/master_files/code/modules/projectiles/guns/gun.dm @@ -0,0 +1,862 @@ + +#define DUALWIELD_PENALTY_EXTRA_MULTIPLIER 1.4 +#define FIRING_PIN_REMOVAL_DELAY 50 + +/obj/item/gun + name = "gun" + desc = "It's a gun. It's pretty terrible, though." + icon = 'modular_skyrat/modules/fixing_missing_icons/ballistic.dmi' //skyrat edit + icon_state = "detective" + inhand_icon_state = "gun" + worn_icon_state = "gun" + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + custom_materials = list(/datum/material/iron = 2000) + w_class = WEIGHT_CLASS_NORMAL + throwforce = 5 + throw_speed = 3 + throw_range = 5 + force = 5 + item_flags = NEEDS_PERMIT + attack_verb_continuous = list("strikes", "hits", "bashes") + attack_verb_simple = list("strike", "hit", "bash") + + var/gun_flags = NONE + var/fire_sound = 'sound/weapons/gun/pistol/shot.ogg' + var/vary_fire_sound = TRUE + var/fire_sound_volume = 50 + var/dry_fire_sound = 'sound/weapons/gun/general/dry_fire.ogg' + var/suppressed = null //whether or not a message is displayed when fired + var/can_suppress = FALSE + var/suppressed_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' + var/suppressed_volume = 60 + var/can_unsuppress = TRUE + var/recoil = 0 //boom boom shake the room + var/clumsy_check = TRUE + var/obj/item/ammo_casing/chambered = null + trigger_guard = TRIGGER_GUARD_NORMAL //trigger guard on the weapon, hulks can't fire them with their big meaty fingers + var/sawn_desc = null //description change if weapon is sawn-off + var/sawn_off = FALSE + var/burst_size = 1 //how large a burst is + var/fire_delay = 0 //rate of fire for burst firing and semi auto + var/firing_burst = 0 //Prevent the weapon from firing again while already firing + var/semicd = 0 //cooldown handler + var/weapon_weight = WEAPON_LIGHT + var/dual_wield_spread = 24 //additional spread when dual wielding + + /// Just 'slightly' snowflakey way to modify projectile damage for projectiles fired from this gun. + var/projectile_damage_multiplier = 1 + + var/spread = 0 //Spread induced by the gun itself. + var/randomspread = 1 //Set to 0 for shotguns. This is used for weapons that don't fire all their bullets at once. + + lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' + + var/obj/item/firing_pin/pin = /obj/item/firing_pin //standard firing pin for most guns + + var/can_flashlight = FALSE //if a flashlight can be added or removed if it already has one. + /// True if a gun dosen't need a pin, mostly used for abstract guns like tentacles and meathooks + var/pinless = FALSE + var/obj/item/flashlight/seclite/gun_light + var/datum/action/item_action/toggle_gunlight/alight + var/gunlight_state = "flight" + var/gunlight_icon = 'icons/obj/guns/flashlights.dmi' + + var/can_bayonet = FALSE //if a bayonet can be added or removed if it already has one. + var/bayonet_state = "bayonet" + var/bayonet_icon = 'icons/obj/guns/bayonets.dmi' + var/obj/item/knife/bayonet + var/knife_x_offset = 0 + var/knife_y_offset = 0 + + var/ammo_x_offset = 0 //used for positioning ammo count overlay on sprite + var/ammo_y_offset = 0 + var/flight_x_offset = 0 + var/flight_y_offset = 0 + + //Zooming + var/zoomable = FALSE //whether the gun generates a Zoom action on creation + var/zoomed = FALSE //Zoom toggle + var/zoom_amt = 3 //Distance in TURFs to move the user's screen forward (the "zoom" effect) + var/zoom_out_amt = 0 + var/datum/action/toggle_scope_zoom/azoom + var/pb_knockback = 0 + + var/safety = FALSE ///Internal variable for keeping track whether the safety is on or off + var/has_gun_safety = FALSE ///Whether the gun actually has a gun safety + var/datum/action/item_action/toggle_safety/tsafety + + var/datum/action/item_action/toggle_firemode/firemode_action + ///Current fire selection, can choose between burst, single, and full auto. + var/fire_select = SELECT_SEMI_AUTOMATIC + var/fire_select_index = 1 + ///What modes does this weapon have? Put SELECT_FULLY_AUTOMATIC in here to enable fully automatic behaviours. + var/list/fire_select_modes = list(SELECT_SEMI_AUTOMATIC) + ///if i`1t has an icon for a selector switch indicating current firemode. + var/selector_switch_icon = FALSE + +/datum/action/item_action/toggle_safety + name = "Toggle Safety" + icon_icon = 'modular_skyrat/modules/gunsafety/icons/actions.dmi' + button_icon_state = "safety_on" + +/obj/item/gun/ui_action_click(mob/user, actiontype) + if(istype(actiontype, /datum/action/item_action/toggle_firemode)) + fire_select() + else if(istype(actiontype, tsafety)) + toggle_safety(user) + else + ..() + +/obj/item/gun/Initialize() + . = ..() + if(pin && !pinless) + pin = new pin(src) + + if(gun_light) + alight = new(src) + + build_zooming() + + if(has_gun_safety) + safety = TRUE + tsafety = new(src) + + if(burst_size > 1 && !(SELECT_BURST_SHOT in fire_select_modes)) + fire_select_modes.Add(SELECT_BURST_SHOT) + else if(burst_size <= 1 && (SELECT_BURST_SHOT in fire_select_modes)) + fire_select_modes.Remove(SELECT_BURST_SHOT) + + burst_size = 1 + + sort_list(fire_select_modes, /proc/cmp_numeric_asc) + + if(fire_select_modes.len > 1) + firemode_action = new(src) + firemode_action.button_icon_state = "fireselect_[fire_select]" + firemode_action.UpdateButtonIcon() + +/obj/item/gun/ComponentInitialize() + . = ..() + if(SELECT_FULLY_AUTOMATIC in fire_select_modes) + AddComponent(/datum/component/automatic_fire, fire_delay) + +/obj/item/gun/Destroy() + if(isobj(pin)) //Can still be the initial path, then we skip + QDEL_NULL(pin) + if(gun_light) + QDEL_NULL(gun_light) + if(bayonet) + QDEL_NULL(bayonet) + if(chambered) //Not all guns are chambered (EMP'ed energy guns etc) + QDEL_NULL(chambered) + if(azoom) + QDEL_NULL(azoom) + if(isatom(suppressed)) + QDEL_NULL(suppressed) + if(tsafety) + QDEL_NULL(tsafety) + if(firemode_action) + QDEL_NULL(firemode_action) + . = ..() + +/obj/item/gun/handle_atom_del(atom/gun_atom) + if(gun_atom == pin) + pin = null + if(gun_atom == chambered) + chambered = null + update_appearance() + if(gun_atom == bayonet) + clear_bayonet() + if(gun_atom == gun_light) + clear_gunlight() + if(gun_atom == suppressed) + clear_suppressor() + . = ..() + +///Clears var and updates icon. In the case of ballistic weapons, also updates the gun's weight. +/obj/item/gun/proc/clear_suppressor() + if(!can_unsuppress) + return + suppressed = null + update_appearance() + +/obj/item/gun/examine(mob/user) + . = ..() + if(!pinless) + if(pin) + . += "It has \a [pin] installed." + . += span_info("[pin] looks like it could be removed with some tools.") + else + . += "It doesn't have a firing pin installed, and won't fire." + + if(gun_light) + . += "It has \a [gun_light] [can_flashlight ? "" : "permanently "]mounted on it." + if(can_flashlight) //if it has a light and this is false, the light is permanent. + . += span_info("[gun_light] looks like it can be unscrewed from [src].") + else if(can_flashlight) + . += "It has a mounting point for a seclite." + + if(bayonet) + . += "It has \a [bayonet] [can_bayonet ? "" : "permanently "]affixed to it." + if(can_bayonet) //if it has a bayonet and this is false, the bayonet is permanent. + . += span_info("[bayonet] looks like it can be unscrewed from [src].") + if(can_bayonet) + . += "It has a bayonet lug on it." + if(has_gun_safety) + . += "The safety is [safety ? "ON" : "OFF"]." + +/obj/item/gun/equipped(mob/living/user, slot) + . = ..() + if(zoomed && user.get_active_held_item() != src) + zoom(user, user.dir, FALSE) //we can only stay zoomed in if it's in our hands //yeah and we only unzoom if we're actually zoomed using the gun!! + +/obj/item/gun/proc/fire_select() + var/mob/living/carbon/human/user = usr + + var/max_mode = fire_select_modes.len + + if(max_mode <= 1) + to_chat(user, span_warning("[src] is not capable of switching firemodes!")) + return + + fire_select_index = 1 + fire_select_index % max_mode //Magic math to cycle through this shit! + + fire_select = fire_select_modes[fire_select_index] + + switch(fire_select) + if(SELECT_SEMI_AUTOMATIC) + burst_size = 1 + fire_delay = 0 + SEND_SIGNAL(src, COMSIG_GUN_AUTOFIRE_DESELECTED, user) + to_chat(user, span_notice("You switch [src] to semi-automatic.")) + if(SELECT_BURST_SHOT) + burst_size = initial(burst_size) + fire_delay = initial(fire_delay) + SEND_SIGNAL(src, COMSIG_GUN_AUTOFIRE_DESELECTED, user) + to_chat(user, span_notice("You switch [src] to [burst_size]-round burst.")) + if(SELECT_FULLY_AUTOMATIC) + burst_size = 1 + SEND_SIGNAL(src, COMSIG_GUN_AUTOFIRE_SELECTED, user) + to_chat(user, span_notice("You switch [src] to automatic.")) + + playsound(user, 'sound/weapons/empty.ogg', 100, TRUE) + update_appearance() + firemode_action.button_icon_state = "fireselect_[fire_select]" + firemode_action.UpdateButtonIcon() + SEND_SIGNAL(src, COMSIG_UPDATE_AMMO_HUD) + return TRUE + +//called after the gun has successfully fired its chambered ammo. +/obj/item/gun/proc/process_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE) + handle_chamber(empty_chamber, from_firing, chamber_next_round) + SEND_SIGNAL(src, COMSIG_GUN_CHAMBER_PROCESSED) + +/obj/item/gun/proc/handle_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE) + return + + +//check if there's enough ammo/energy/whatever to shoot one time +//i.e if clicking would make it shoot +/obj/item/gun/proc/can_shoot() + return TRUE + +/obj/item/gun/proc/shoot_with_empty_chamber(mob/living/user as mob|obj) + to_chat(user, span_danger("*click*")) + playsound(src, dry_fire_sound, 30, TRUE) + + +/obj/item/gun/proc/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1) + if(recoil) + shake_camera(user, recoil + 1, recoil) + + if(suppressed) + playsound(user, suppressed_sound, suppressed_volume, vary_fire_sound, ignore_walls = FALSE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0) + else + playsound(user, fire_sound, fire_sound_volume, vary_fire_sound) + if(message) + if(pointblank) + user.visible_message(span_danger("[user] fires [src] point blank at [pbtarget]!"), \ + span_danger("You fire [src] point blank at [pbtarget]!"), \ + span_hear("You hear a gunshot!"), COMBAT_MESSAGE_RANGE, pbtarget) + to_chat(pbtarget, span_userdanger("[user] fires [src] point blank at you!")) + if(pb_knockback > 0 && ismob(pbtarget)) + var/mob/PBT = pbtarget + var/atom/throw_target = get_edge_target_turf(PBT, user.dir) + PBT.throw_at(throw_target, pb_knockback, 2) + else + user.visible_message(span_danger("[user] fires [src]!"), \ + span_danger("You fire [src]!"), \ + span_hear("You hear a gunshot!"), COMBAT_MESSAGE_RANGE) + if(user.resting) // SKYRAT EDIT ADD - no crawlshooting + user.Immobilize(20, TRUE) // SKYRAT EDIT END + +/obj/item/gun/emp_act(severity) + . = ..() + if(!(. & EMP_PROTECT_CONTENTS)) + for(var/obj/iterated_object in contents) + iterated_object.emp_act(severity) + +/obj/item/gun/attack_secondary(mob/living/victim, mob/living/user, params) + if(user.GetComponent(/datum/component/gunpoint)) + to_chat(user, span_warning("You are already holding someone up!")) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + if(user == victim) + to_chat(user, span_warning("You can't hold yourself up!")) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + user.AddComponent(/datum/component/gunpoint, victim, src) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/item/gun/afterattack(atom/target, mob/living/user, flag, params) + . = ..() + return fire_gun(target, user, flag, params) + +/obj/item/gun/proc/fire_gun(atom/target, mob/living/user, flag, params) + if(QDELETED(target)) + return + if(firing_burst) + return + if(flag) //It's adjacent, is the user, or is on the user's person + if(target in user.contents) //can't shoot stuff inside us. + return + if(!ismob(target) || user.combat_mode) //melee attack + return + if(target == user && user.zone_selected != BODY_ZONE_PRECISE_MOUTH) //so we can't shoot ourselves (unless mouth selected) + return + if(iscarbon(target)) + var/mob/living/carbon/carbon_target = target + for(var/i in carbon_target.all_wounds) + var/datum/wound/target_wound = i + if(target_wound.try_treating(src, user)) + return // another coward cured! + + if(istype(user))//Check if the user can use the gun, if the user isn't alive(turrets) assume it can. + var/mob/living/living_user = user + if(!can_trigger_gun(living_user)) + return + if(flag) + if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH) + handle_suicide(user, target, params) + return + + if(!can_shoot()) //Just because you can pull the trigger doesn't mean it can shoot. + shoot_with_empty_chamber(user) + return + + if(check_botched(user)) + return + + var/obj/item/bodypart/other_hand = user.has_hand_for_held_index(user.get_inactive_hand_index()) //returns non-disabled inactive hands + if(weapon_weight == WEAPON_HEAVY && (user.get_inactive_held_item() || !other_hand)) + to_chat(user, span_warning("You need two hands to fire [src]!")) + return + //DUAL (or more!) WIELDING + var/bonus_spread = 0 + var/loop_counter = 0 + if(ishuman(user) && user.combat_mode) + var/mob/living/carbon/human/human_user = user + for(var/obj/item/gun/held_gun in human_user.held_items) + if(held_gun == src || held_gun.weapon_weight >= WEAPON_MEDIUM) + continue + else if(held_gun.can_trigger_gun(user)) + bonus_spread += dual_wield_spread + loop_counter++ + addtimer(CALLBACK(held_gun, /obj/item/gun.proc/process_fire, target, user, TRUE, params, null, bonus_spread), loop_counter) + + return process_fire(target, user, TRUE, params, null, bonus_spread) + +/obj/item/gun/proc/check_botched(mob/living/user, params) + if(clumsy_check) + if(istype(user)) + if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(40)) + to_chat(user, span_userdanger("You shoot yourself in the foot with [src]!")) + var/shot_leg = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + process_fire(user, user, FALSE, params, shot_leg) + SEND_SIGNAL(user, COMSIG_MOB_CLUMSY_SHOOT_FOOT) + user.dropItemToGround(src, TRUE) + return TRUE + +/obj/item/gun/can_trigger_gun(mob/living/user) + . = ..() + if(!handle_pins(user)) + return FALSE + if(has_gun_safety && safety) + to_chat(user, span_warning("The safety is on!")) + return FALSE + +/obj/item/gun/proc/toggle_safety(mob/user, override) + if(!has_gun_safety) + return + if(override) + if(override == "off") + safety = FALSE + else + safety = TRUE + else + safety = !safety + tsafety.button_icon_state = "safety_[safety ? "on" : "off"]" + tsafety.UpdateButtonIcon() + playsound(src, 'sound/weapons/empty.ogg', 100, TRUE) + user.visible_message(span_notice("[user] toggles [src]'s safety [safety ? "ON" : "OFF"]."), + span_notice("You toggle [src]'s safety [safety ? "ON" : "OFF"].")) + SEND_SIGNAL(src, COMSIG_UPDATE_AMMO_HUD) + +/obj/item/gun/proc/handle_pins(mob/living/user) + if(pinless) + return TRUE + if(pin) + if(pin.pin_auth(user) || (pin.obj_flags & EMAGGED)) + return TRUE + else + pin.auth_fail(user) + return FALSE + else + to_chat(user, span_warning("[src]'s trigger is locked. This weapon doesn't have a firing pin installed!")) + return FALSE + +/obj/item/gun/proc/recharge_newshot() + return + +/obj/item/gun/proc/process_burst(mob/living/user, atom/target, message = TRUE, params = null, zone_override = "", sprd = 0, randomized_gun_spread = 0, randomized_bonus_spread = 0, rand_spr = 0, iteration = 0) + if(!user || !firing_burst) + firing_burst = FALSE + return FALSE + if(!issilicon(user)) + if(iteration > 1 && !(user.is_holding(src))) //for burst firing + firing_burst = FALSE + return FALSE + if(chambered?.loaded_projectile) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal. + if(chambered.harmful) // Is the bullet chambered harmful? + to_chat(user, span_warning("[src] is lethally chambered! You don't want to risk harming anyone...")) + return + if(randomspread) + sprd = round((rand(0, 1) - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread)) + else //Smart spread + sprd = round((((rand_spr/burst_size) * iteration) - (0.5 + (rand_spr * 0.25))) * (randomized_gun_spread + randomized_bonus_spread)) + before_firing(target, user) + if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, sprd, src)) + shoot_with_empty_chamber(user) + firing_burst = FALSE + return FALSE + else + if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot + shoot_live_shot(user, 1, target, message) + else + shoot_live_shot(user, 0, target, message) + if (iteration >= burst_size) + firing_burst = FALSE + else + shoot_with_empty_chamber(user) + firing_burst = FALSE + return FALSE + process_chamber() + update_appearance() + SEND_SIGNAL(src, COMSIG_UPDATE_AMMO_HUD) + return TRUE + +/obj/item/gun/proc/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) + if(user) + SEND_SIGNAL(user, COMSIG_MOB_FIRED_GUN, user, target, params, zone_override) + + SEND_SIGNAL(src, COMSIG_GUN_FIRED, user, target, params, zone_override) + + add_fingerprint(user) + + if(semicd) + return + + //Vary by at least this much + var/base_bonus_spread = 0 + var/sprd = 0 + var/randomized_gun_spread = 0 + var/rand_spr = rand() + if(user && HAS_TRAIT(user, TRAIT_POOR_AIM)) //Nice job hotshot + bonus_spread += 35 + base_bonus_spread += 10 + + if(spread) + randomized_gun_spread = rand(0, spread) + var/randomized_bonus_spread = rand(base_bonus_spread, bonus_spread) + + if(burst_size > 1) + firing_burst = TRUE + for(var/i = 1 to burst_size) + addtimer(CALLBACK(src, .proc/process_burst, user, target, message, params, zone_override, sprd, randomized_gun_spread, randomized_bonus_spread, rand_spr, i), fire_delay * (i - 1)) + else + if(chambered) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal. + if(chambered.harmful) // Is the bullet chambered harmful? + to_chat(user, span_warning("[src] is lethally chambered! You don't want to risk harming anyone...")) + return + sprd = round((rand(0, 1) - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread)) + before_firing(target, user) + if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, sprd, src)) + shoot_with_empty_chamber(user) + return + else + if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot + shoot_live_shot(user, 1, target, message) + else + shoot_live_shot(user, 0, target, message) + else + shoot_with_empty_chamber(user) + return + process_chamber() + update_appearance() + semicd = TRUE + addtimer(CALLBACK(src, .proc/reset_semicd), fire_delay) + + if(user) + user.update_inv_hands() + SSblackbox.record_feedback("tally", "gun_fired", 1, type) + + SEND_SIGNAL(src, COMSIG_UPDATE_AMMO_HUD) + + return TRUE + +/obj/item/gun/proc/reset_semicd() + semicd = FALSE + +/obj/item/gun/attack(mob/target, mob/living/user) + if(user.combat_mode) //Flogging + if(bayonet) + target.attackby(bayonet, user) + return + else + return ..() + return + +/obj/item/gun/attack_atom(obj/target, mob/living/user, params) + if(user.combat_mode) + if(bayonet) + target.attackby(bayonet, user) + return + return ..() + +/obj/item/gun/attackby(obj/item/attacking_item, mob/living/user, params) + if(user.combat_mode) + return ..() + else if(istype(attacking_item, /obj/item/flashlight/seclite)) + if(!can_flashlight) + return ..() + var/obj/item/flashlight/seclite/attaching_seclite = attacking_item + if(!gun_light) + if(!user.transferItemToLoc(attacking_item, src)) + return + to_chat(user, span_notice("You click [attaching_seclite] into place on [src].")) + set_gun_light(attaching_seclite) + update_gunlight() + alight = new(src) + if(loc == user) + alight.Grant(user) + else if(istype(attacking_item, /obj/item/knife)) + var/obj/item/knife/attaching_knife = attacking_item + if(!can_bayonet || !attaching_knife.bayonet || bayonet) //ensure the gun has an attachment point available, and that the knife is compatible with it. + return ..() + if(!user.transferItemToLoc(attacking_item, src)) + return + to_chat(user, span_notice("You attach [attaching_knife] to [src]'s bayonet lug.")) + bayonet = attaching_knife + update_appearance() + + else + return ..() + +/obj/item/gun/screwdriver_act(mob/living/user, obj/item/tool) + . = ..() + if(.) + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if((can_flashlight && gun_light) && (can_bayonet && bayonet)) //give them a choice instead of removing both + var/list/possible_items = list(gun_light, bayonet) + var/obj/item/item_to_remove = input(user, "Select an attachment to remove", "Attachment Removal") as null|obj in sort_names(possible_items) + if(!item_to_remove || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + return remove_gun_attachment(user, tool, item_to_remove) + + else if(gun_light && can_flashlight) //if it has a gun_light and can_flashlight is false, the flashlight is permanently attached. + return remove_gun_attachment(user, tool, gun_light, "unscrewed") + + else if(bayonet && can_bayonet) //if it has a bayonet, and the bayonet can be removed + return remove_gun_attachment(user, tool, bayonet, "unfix") + + else if(pin && user.is_holding(src)) + user.visible_message(span_warning("[user] attempts to remove [pin] from [src] with [tool]."), + span_notice("You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)"), null, 3) + if(tool.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, volume = 50)) + if(!pin) //check to see if the pin is still there, or we can spam messages by clicking multiple times during the tool delay + return + user.visible_message(span_notice("[pin] is pried out of [src] by [user], destroying the pin in the process."), + span_warning("You pry [pin] out with [tool], destroying the pin in the process."), null, 3) + QDEL_NULL(pin) + return TRUE + +/obj/item/gun/welder_act(mob/living/user, obj/item/tool) + . = ..() + if(.) + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(pin && user.is_holding(src)) + user.visible_message(span_warning("[user] attempts to remove [pin] from [src] with [tool]."), + span_notice("You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)"), null, 3) + if(tool.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, 5, volume = 50)) + if(!pin) //check to see if the pin is still there, or we can spam messages by clicking multiple times during the tool delay + return + user.visible_message(span_notice("[pin] is spliced out of [src] by [user], melting part of the pin in the process."), + span_warning("You splice [pin] out of [src] with [tool], melting part of the pin in the process."), null, 3) + QDEL_NULL(pin) + return TRUE + +/obj/item/gun/wirecutter_act(mob/living/user, obj/item/tool) + . = ..() + if(.) + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(pin && user.is_holding(src)) + user.visible_message(span_warning("[user] attempts to remove [pin] from [src] with [tool]."), + span_notice("You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)"), null, 3) + if(tool.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, volume = 50)) + if(!pin) //check to see if the pin is still there, or we can spam messages by clicking multiple times during the tool delay + return + user.visible_message(span_notice("[pin] is ripped out of [src] by [user], mangling the pin in the process."), + span_warning("You rip [pin] out of [src] with [tool], mangling the pin in the process."), null, 3) + QDEL_NULL(pin) + return TRUE + +/obj/item/gun/proc/remove_gun_attachment(mob/living/user, obj/item/tool_item, obj/item/item_to_remove, removal_verb) + if(tool_item) + tool_item.play_tool_sound(src) + to_chat(user, span_notice("You [removal_verb ? removal_verb : "remove"] [item_to_remove] from [src].")) + item_to_remove.forceMove(drop_location()) + + if(Adjacent(user) && !issilicon(user)) + user.put_in_hands(item_to_remove) + + if(item_to_remove == bayonet) + return clear_bayonet() + else if(item_to_remove == gun_light) + return clear_gunlight() + +/obj/item/gun/proc/clear_bayonet() + if(!bayonet) + return + bayonet = null + update_appearance() + return TRUE + +/obj/item/gun/proc/clear_gunlight() + if(!gun_light) + return + var/obj/item/flashlight/seclite/removed_light = gun_light + set_gun_light(null) + update_gunlight() + removed_light.update_brightness() + QDEL_NULL(alight) + return TRUE + + +/** + * Swaps the gun's seclight, dropping the old seclight if it has not been qdel'd. + * + * Returns the former gun_light that has now been replaced by this proc. + * Arguments: + * * new_light - The new light to attach to the weapon. Can be null, which will mean the old light is removed with no replacement. + */ +/obj/item/gun/proc/set_gun_light(obj/item/flashlight/seclite/new_light) + // Doesn't look like this should ever happen? We're replacing our old light with our old light? + if(gun_light == new_light) + CRASH("Tried to set a new gun light when the old gun light was also the new gun light.") + + . = gun_light + + // If there's an old gun light that isn't being QDELETED, detatch and drop it to the floor. + if(!QDELETED(gun_light)) + gun_light.set_light_flags(gun_light.light_flags & ~LIGHT_ATTACHED) + if(gun_light.loc == src) + gun_light.forceMove(get_turf(src)) + + // If there's a new gun light to be added, attach and move it to the gun. + if(new_light) + new_light.set_light_flags(new_light.light_flags | LIGHT_ATTACHED) + if(new_light.loc != src) + new_light.forceMove(src) + + gun_light = new_light + +/obj/item/gun/ui_action_click(mob/user, actiontype) + if(istype(actiontype, alight)) + toggle_gunlight() + else + ..() + +/obj/item/gun/proc/toggle_gunlight() + if(!gun_light) + return + + var/mob/living/carbon/human/user = usr + gun_light.on = !gun_light.on + gun_light.update_brightness() + to_chat(user, span_notice("You toggle the gunlight [gun_light.on ? "on":"off"].")) + + playsound(user, 'sound/weapons/empty.ogg', 100, TRUE) + update_gunlight() + +/obj/item/gun/proc/update_gunlight() + update_appearance() + update_action_buttons() + +/obj/item/gun/pickup(mob/user) + . = ..() + if(azoom) + azoom.Grant(user) + if(w_class > WEIGHT_CLASS_SMALL && !suppressed) + user.visible_message(span_warning("[user] grabs [src]!"), + span_warning("You grab [src]!")) + +/obj/item/gun/dropped(mob/user) + . = ..() + if(azoom) + azoom.Remove(user) + if(zoomed) + zoom(user, user.dir, FALSE) + +/obj/item/gun/update_overlays() + . = ..() + if(gun_light) + var/mutable_appearance/flashlight_overlay + var/state = "[gunlight_state][gun_light.on? "_on":""]" //Generic state. + if(gun_light.icon_state in icon_states(gunlight_icon)) //Snowflake state? + state = gun_light.icon_state + flashlight_overlay = mutable_appearance(gunlight_icon, state) + flashlight_overlay.pixel_x = flight_x_offset + flashlight_overlay.pixel_y = flight_y_offset + . += flashlight_overlay + + if(bayonet) + var/mutable_appearance/knife_overlay + var/state = bayonet_state + if(bayonet.icon_state in icon_states(bayonet_icon)) //Snowflake state? + state = bayonet.icon_state + knife_overlay = mutable_appearance(bayonet_icon, state) + knife_overlay.pixel_x = knife_x_offset + knife_overlay.pixel_y = knife_y_offset + . += knife_overlay + +/obj/item/gun/proc/handle_suicide(mob/living/carbon/human/user, mob/living/carbon/human/target, params, bypass_timer) + if(!ishuman(user) || !ishuman(target)) + return + + if(semicd) + return + + if(user == target) + target.visible_message(span_warning("[user] sticks [src] in [user.p_their()] mouth, ready to pull the trigger..."), \ + span_userdanger("You stick [src] in your mouth, ready to pull the trigger...")) + else + target.visible_message(span_warning("[user] points [src] at [target]'s head, ready to pull the trigger..."), \ + span_userdanger("[user] points [src] at your head, ready to pull the trigger...")) + + semicd = TRUE + + if(!bypass_timer && (!do_mob(user, target, 120) || user.zone_selected != BODY_ZONE_PRECISE_MOUTH)) + if(user) + if(user == target) + user.visible_message(span_notice("[user] decided not to shoot.")) + else if(target?.Adjacent(user)) + target.visible_message(span_notice("[user] has decided to spare [target]"), span_notice("[user] has decided to spare your life!")) + semicd = FALSE + return + + semicd = FALSE + + target.visible_message(span_warning("[user] pulls the trigger!"), span_userdanger("[(user == target) ? "You pull" : "[user] pulls"] the trigger!")) + + if(chambered?.loaded_projectile) + chambered.loaded_projectile.damage *= 5 + if(chambered.loaded_projectile.wound_bonus != CANT_WOUND) + chambered.loaded_projectile.wound_bonus += 5 // much more dramatic on multiple pellet'd projectiles really + + var/fired = process_fire(target, user, TRUE, params, BODY_ZONE_HEAD) + if(!fired && chambered?.loaded_projectile) + chambered.loaded_projectile.damage /= 5 + if(chambered.loaded_projectile.wound_bonus != CANT_WOUND) + chambered.loaded_projectile.wound_bonus -= 5 + +/obj/item/gun/proc/unlock() //used in summon guns and as a convience for admins + if(pin) + qdel(pin) + pin = new /obj/item/firing_pin + +//Happens before the actual projectile creation +/obj/item/gun/proc/before_firing(atom/target, mob/user) + return + +///////////// +// ZOOMING // +///////////// + +/datum/action/toggle_scope_zoom + name = "Toggle Scope" + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED|AB_CHECK_IMMOBILE|AB_CHECK_LYING + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + var/obj/item/gun/gun = null + +/datum/action/toggle_scope_zoom/Trigger() + . = ..() + if(!.) + return + gun.zoom(owner, owner.dir) + +/datum/action/toggle_scope_zoom/IsAvailable() + . = ..() + if(owner.get_active_held_item() != gun) + . = FALSE + if(!. && gun) + gun.zoom(owner, owner.dir, FALSE) + +/datum/action/toggle_scope_zoom/Remove(mob/living/user) + gun.zoom(user, user.dir, FALSE) + ..() + +/obj/item/gun/proc/rotate(atom/thing, old_dir, new_dir) + SIGNAL_HANDLER + + if(ismob(thing)) + var/mob/lad = thing + lad.client?.view_size.zoomOut(zoom_out_amt, zoom_amt, new_dir) + +/obj/item/gun/proc/zoom(mob/living/user, direc, forced_zoom) + if(!user || !user.client) + return + + if(isnull(forced_zoom)) + zoomed = !zoomed + else + zoomed = forced_zoom + + if(zoomed) + RegisterSignal(user, COMSIG_ATOM_DIR_CHANGE, .proc/rotate) + user.client?.view_size.zoomOut(zoom_out_amt, zoom_amt, direc) + else + UnregisterSignal(user, COMSIG_ATOM_DIR_CHANGE) + user.client?.view_size.zoomIn() + return zoomed + +//Proc, so that gun accessories/scopes/etc. can easily add zooming. +/obj/item/gun/proc/build_zooming() + if(azoom) + return + + if(zoomable) + azoom = new() + azoom.gun = src + +#undef FIRING_PIN_REMOVAL_DELAY +#undef DUALWIELD_PENALTY_EXTRA_MULTIPLIER diff --git a/modular_skyrat/modules/aesthetics/cells/cell.dm b/modular_skyrat/modules/aesthetics/cells/cell.dm new file mode 100644 index 000000000000..bdc9214d8109 --- /dev/null +++ b/modular_skyrat/modules/aesthetics/cells/cell.dm @@ -0,0 +1,7 @@ +/obj/item/stock_parts/cell + icon = 'modular_skyrat/modules/aesthetics/cells/cell.dmi' + /// The charge overlay icon file for the cell charge lights + var/charge_overlay_icon = 'modular_skyrat/modules/aesthetics/cells/cell.dmi' + +/obj/machinery/cell_charger + icon = 'modular_skyrat/modules/aesthetics/cells/cell.dmi' diff --git a/modular_skyrat/modules/goofsec/code/projectiles.dm b/modular_skyrat/modules/goofsec/code/projectiles.dm new file mode 100644 index 000000000000..5c6ff37f26d8 --- /dev/null +++ b/modular_skyrat/modules/goofsec/code/projectiles.dm @@ -0,0 +1,203 @@ +/obj/item/ammo_casing/energy/laser/hardlight_bullet + name = "hardlight bullet casing" + projectile_type = /obj/projectile/beam/laser/hardlight_bullet + e_cost = 83 // 12 shots with a normal cell. + select_name = "hardlight bullet" + fire_sound = 'sound/weapons/gun/pistol/shot.ogg' + + // Not a real bullet, but visually looks like one. For the aesthetic of bullets, while keeping the balance intact. + // Every piece of armor in the game is currently balanced around "sec has lasers, syndies have bullets". This allows us to keep that balance + // without sacrificing the bullet aesthetic. +/obj/projectile/beam/laser/hardlight_bullet + icon = 'modular_skyrat/modules/sec_haul/icons/guns/projectiles.dmi' + icon_state = "bullet" + name = "hardlight bullet" + pass_flags = PASSTABLE // All of the below is to not break kayfabe about it not being a bullet. + hitsound ='sound/weapons/pierce.ogg' + hitsound_wall = "ricochet" + light_system = NO_LIGHT_SUPPORT + light_range = 0 + light_power = 0 + damage_type = BRUTE // So they do brute damage but still hit laser armor! + sharpness = SHARP_POINTY + impact_effect_type = /obj/effect/temp_visual/impact_effect + shrapnel_type = /obj/item/shrapnel/bullet + embedding = list(embed_chance=20, fall_chance=2, jostle_chance=0, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.5, pain_mult=3, rip_time=10) + +// For calculating ammo, remember that these guns have 1000 unit power cells. + +/// Vintorez +/obj/item/gun/energy/vintorez + name = "\improper VKC 'Vintorez'" + desc = "The VKC Vintorez is a lightweight integrally-suppressed scoped carbine usually employed in stealth operations from the long since past 20th century." + icon = 'modular_skyrat/modules/goofsec/icons/gun_sprites.dmi' + righthand_file = 'modular_skyrat/modules/sec_haul/icons/guns/inhands/righthand.dmi' + lefthand_file = 'modular_skyrat/modules/sec_haul/icons/guns/inhands/lefthand.dmi' + icon_state = "vintorez" + worn_icon = 'modular_skyrat/modules/sec_haul/icons/guns/norwind.dmi' + worn_icon_state = "norwind_worn" + w_class = WEIGHT_CLASS_BULKY + slot_flags = ITEM_SLOT_BACK | ITEM_SLOT_OCLOTHING + inhand_icon_state = "vintorez" + burst_size = 2 + fire_delay = 4 + zoomable = TRUE + zoom_amt = 7 + zoom_out_amt = 5 + fire_sound = null + ammo_type = list(/obj/item/ammo_casing/energy/laser/hardlight_bullet/vintorez) + shaded_charge = TRUE + cell_type = /obj/item/stock_parts/cell/super + +/obj/item/ammo_casing/energy/laser/hardlight_bullet/vintorez + name = "hardlight bullet vintorez casing" + projectile_type = /obj/projectile/beam/laser/hardlight_bullet/vintorez + e_cost = 1666 // 27 damage so 12 shots. + fire_sound = 'sound/weapons/gun/smg/shot_suppressed.ogg' + +/obj/projectile/beam/laser/hardlight_bullet/vintorez + damage = 27 // All security armory firearms need to do, at minimum, laser gun damage. + +/obj/item/gun/energy/norwind + name = "\improper M112 'Norwind'" + desc = "A rare M112 DMR rechambered to 12.7x30mm for peacekeeping work, it comes with a scope for medium-long range engagements. A bayonet lug is visible." + icon = 'modular_skyrat/modules/goofsec/icons/gun_sprites.dmi' + righthand_file = 'modular_skyrat/modules/sec_haul/icons/guns/inhands/righthand.dmi' + lefthand_file = 'modular_skyrat/modules/sec_haul/icons/guns/inhands/lefthand.dmi' + worn_icon = 'modular_skyrat/modules/sec_haul/icons/guns/norwind.dmi' + worn_icon_state = "norwind_worn" + icon_state = "norwind" + w_class = WEIGHT_CLASS_BULKY + slot_flags = ITEM_SLOT_BACK | ITEM_SLOT_OCLOTHING + inhand_icon_state = "norwind" + worn_icon = 'modular_skyrat/modules/sec_haul/icons/guns/norwind.dmi' + worn_icon_state = "norwind_worn" + can_bayonet = TRUE + can_flashlight = TRUE + zoomable = TRUE + zoom_amt = 7 + zoom_out_amt = 5 + fire_sound = null + fire_select_modes = list(SELECT_SEMI_AUTOMATIC) + ammo_type = list(/obj/item/ammo_casing/energy/laser/hardlight_bullet/norwind) + burst_size = 1 + fire_delay = 10 + shaded_charge = TRUE + cell_type = /obj/item/stock_parts/cell/super + +/obj/item/ammo_casing/energy/laser/hardlight_bullet/norwind + name = "hardlight bullet norwind casing" + projectile_type = /obj/projectile/beam/laser/hardlight_bullet/norwind + e_cost = 2857 // 7 shots, does 1.6x damage normal laser so fire cost increased by 1.6x + fire_sound = 'modular_skyrat/modules/sec_haul/sound/ltrifle_fire.ogg' + +/obj/projectile/beam/laser/hardlight_bullet/norwind + damage = 45 + +/obj/item/gun/energy/ostwind + name = "\improper DTR-6 rifle" + desc = "A 6.3mm special-purpose rifle designed for specific situations." + icon = 'modular_skyrat/modules/goofsec/icons/gun_sprites.dmi' + righthand_file = 'modular_skyrat/modules/sec_haul/icons/guns/inhands/righthand.dmi' + lefthand_file = 'modular_skyrat/modules/sec_haul/icons/guns/inhands/lefthand.dmi' + inhand_icon_state = "ostwind" + icon_state = "ostwind" + worn_icon = 'modular_skyrat/modules/sec_haul/icons/guns/ostwind.dmi' + worn_icon_state = "ostwind_worn" + w_class = WEIGHT_CLASS_BULKY + slot_flags = ITEM_SLOT_BACK | ITEM_SLOT_OCLOTHING + fire_delay = 2 + burst_size = 2 + fire_sound = null + can_bayonet = TRUE + ammo_type = list(/obj/item/ammo_casing/energy/laser/hardlight_bullet/ostwind) + cell_type = /obj/item/stock_parts/cell/super +/obj/item/ammo_casing/energy/laser/hardlight_bullet/ostwind + name = "hardlight bullet norostwindwind casing" + projectile_type = /obj/projectile/beam/laser/hardlight_bullet/ostwind + e_cost = 1666 // 27 damage so 12 shots. + fire_sound = 'sound/weapons/gun/smg/shot.ogg' + +/obj/projectile/beam/laser/hardlight_bullet/ostwind + damage = 27 // Minimum damage increase to at least base /tg/ laser gun damage. + embedding = list(embed_chance=10, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10) + +/obj/item/gun/energy/pitbull + name = "\improper Pitbull PDW" + desc = "A sturdy personal defense weapon designed to fire 10mm Auto rounds." + icon = 'modular_skyrat/modules/goofsec/icons/gun_sprites.dmi' + righthand_file = 'modular_skyrat/modules/sec_haul/icons/guns/inhands/righthand.dmi' + lefthand_file = 'modular_skyrat/modules/sec_haul/icons/guns/inhands/lefthand.dmi' + inhand_icon_state = "pitbull" + icon_state = "pitbull" + worn_icon = 'modular_skyrat/modules/sec_haul/icons/guns/ostwind.dmi' + worn_icon_state = "ostwind_worn" + w_class = WEIGHT_CLASS_BULKY + slot_flags = ITEM_SLOT_BACK | ITEM_SLOT_OCLOTHING + fire_delay = 4.20 + burst_size = 3 + fire_sound = null + can_bayonet = TRUE + can_flashlight = TRUE + ammo_type = list(/obj/item/ammo_casing/energy/laser/hardlight_bullet/pitbull) + shaded_charge = TRUE + cell_type = /obj/item/stock_parts/cell/super + +/obj/item/ammo_casing/energy/laser/hardlight_bullet/pitbull + name = "hardlight bullet pitbull casing" + projectile_type = /obj/projectile/beam/laser/hardlight_bullet/pitbull + e_cost = 1666 // 27 damage so 12 shots. + fire_sound = 'modular_skyrat/modules/sec_haul/sound/sfrifle_fire.ogg' + +/obj/projectile/beam/laser/hardlight_bullet/pitbull + damage = 27 + +/obj/item/gun/energy/pcr + name = "\improper PCR-9 SMG" + desc = "An accurate, fast-firing SMG chambered in 9x19mm." + icon = 'modular_skyrat/modules/goofsec/icons/gun_sprites.dmi' + righthand_file = 'modular_skyrat/modules/sec_haul/icons/guns/inhands/righthand.dmi' + lefthand_file = 'modular_skyrat/modules/sec_haul/icons/guns/inhands/lefthand.dmi' + worn_icon = 'modular_skyrat/modules/sec_haul/icons/guns/ostwind.dmi' + worn_icon_state = "ostwind_worn" + inhand_icon_state = "pcr" + icon_state = "pcr" + w_class = WEIGHT_CLASS_BULKY + slot_flags = ITEM_SLOT_BACK | ITEM_SLOT_OCLOTHING + fire_delay = 1.80 + burst_size = 5 + can_flashlight = TRUE + ammo_type = list(/obj/item/ammo_casing/energy/laser/hardlight_bullet/pcr) + shaded_charge = TRUE + cell_type = /obj/item/stock_parts/cell/super + +/obj/item/ammo_casing/energy/laser/hardlight_bullet/pcr + name = "hardlight bullet pcr casing" + projectile_type = /obj/projectile/beam/laser/hardlight_bullet/pcr + e_cost = 1666 // 27 damage so 12 shots. + fire_sound = 'modular_skyrat/modules/sec_haul/sound/smg_fire.ogg' + +/obj/projectile/beam/laser/hardlight_bullet/pcr + damage = 27 // Minimum damage increase to at least base /tg/ laser gun damage. + +/* TODO: Use for SolFed. +/obj/item/gun/energy/peacemaker + name = "\improper Peacemaker" + desc = "The gun that won the space frontier. Four shots, and with a long firing delay, but packs an extreme punch." + icon = 'modular_skyrat/modules/goofsec/icons/gun_sprites.dmi' + icon_state = "peacemaker" + slot_flags = ITEM_SLOT_BACK | ITEM_SLOT_BELT | ITEM_SLOT_OCLOTHING + fire_delay = 2 SECONDS + ammo_type = list(/obj/item/ammo_casing/energy/laser/hardlight_bullet/peacemaker) + shaded_charge = TRUE + +/obj/item/ammo_casing/energy/laser/hardlight_bullet/peacemaker + name = "hardlight bullet peacemaker casing" + projectile_type = /obj/projectile/beam/laser/hardlight_bullet/peacemaker + e_cost = 250 // 4 shots. + fire_sound = 'sound/weapons/gun/revolver/shot_alt.ogg' + +/obj/projectile/beam/laser/hardlight_bullet/peacemaker + damage = 60 +*/ + diff --git a/modular_skyrat/modules/gunhud/code/gun_hud_component.dm b/modular_skyrat/modules/gunhud/code/gun_hud_component.dm new file mode 100644 index 000000000000..4c6a0e87b7ec --- /dev/null +++ b/modular_skyrat/modules/gunhud/code/gun_hud_component.dm @@ -0,0 +1,203 @@ +/datum/component/ammo_hud + var/atom/movable/screen/ammo_counter/hud + +/datum/component/ammo_hud/Initialize() + . = ..() + if(!istype(parent, /obj/item/gun) && !istype(parent, /obj/item/weldingtool)) + return COMPONENT_INCOMPATIBLE + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/wake_up) + +/datum/component/ammo_hud/Destroy() + turn_off() + return ..() + +/datum/component/ammo_hud/proc/wake_up(datum/source, mob/user, slot) + SIGNAL_HANDLER + + RegisterSignal(parent, list(COMSIG_PARENT_PREQDELETED, COMSIG_ITEM_DROPPED), .proc/turn_off) + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.is_holding(parent)) + if(H.hud_used) + hud = H.hud_used.ammo_counter + turn_on() + else + turn_off() + +/datum/component/ammo_hud/proc/turn_on() + SIGNAL_HANDLER + + RegisterSignal(parent, COMSIG_UPDATE_AMMO_HUD, .proc/update_hud) + + hud.turn_on() + update_hud() + +/datum/component/ammo_hud/proc/turn_off() + SIGNAL_HANDLER + + UnregisterSignal(parent, list(COMSIG_PARENT_PREQDELETED, COMSIG_ITEM_DROPPED, COMSIG_UPDATE_AMMO_HUD)) + + if(hud) + hud.turn_off() + hud = null + +/datum/component/ammo_hud/proc/update_hud() + SIGNAL_HANDLER + if(istype(parent, /obj/item/gun/ballistic)) + var/obj/item/gun/ballistic/pew = parent + hud.maptext = null + hud.icon_state = "backing" + var/backing_color = COLOR_CYAN + if(!pew.magazine) + hud.set_hud(backing_color, "oe", "te", "he", "no_mag") + return + if(!pew.get_ammo()) + hud.set_hud(backing_color, "oe", "te", "he", "empty_flash") + return + + var/indicator + var/rounds = num2text(pew.get_ammo(TRUE)) + var/oth_o + var/oth_t + var/oth_h + + switch(pew.fire_select) + if(SELECT_SEMI_AUTOMATIC) + indicator = "semi" + if(SELECT_BURST_SHOT) + indicator = "burst" + if(SELECT_FULLY_AUTOMATIC) + indicator = "auto" + + if(pew.safety) + indicator = "safe" + + if(pew.jammed) + indicator = "jam" + + switch(length(rounds)) + if(1) + oth_o = "o[rounds[1]]" + if(2) + oth_o = "o[rounds[2]]" + oth_t = "t[rounds[1]]" + if(3) + oth_o = "o[rounds[3]]" + oth_t = "t[rounds[2]]" + oth_h = "h[rounds[1]]" + else + oth_o = "o9" + oth_t = "t9" + oth_h = "h9" + hud.set_hud(backing_color, oth_o, oth_t, oth_h, indicator) + + else if(istype(parent, /obj/item/gun/energy)) + var/obj/item/gun/energy/pew = parent + hud.icon_state = "eammo_counter" + hud.cut_overlays() + hud.maptext_x = -12 + var/obj/item/ammo_casing/energy/shot = pew.ammo_type[pew.select] + var/batt_percent = FLOOR(clamp(pew.cell.charge / pew.cell.maxcharge, 0, 1) * 100, 1) + var/shot_cost_percent = FLOOR(clamp(shot.e_cost / pew.cell.maxcharge, 0, 1) * 100, 1) + if(batt_percent > 99 || shot_cost_percent > 99) + hud.maptext_x = -12 + else + hud.maptext_x = -8 + if(!pew.can_shoot()) + hud.icon_state = "eammo_counter_empty" + hud.maptext = span_maptext("
;K*kn9ANVqjPrL)v7FD9|7}XozuV!`o?I;sF)Y=O72U@ zac=#CZPV?Ts;Psf%lR$%ZE!(frS{TpYJgELW?o50O1!{faIw*H){PMzn z;lhAezR0-LXNl})RwU!vA6e-FP`L~ZpA*NUEf;kW2c=z4%eu%ValD8;Uc1WWrZyr$ z^}(7JI$z&7+o9m|+-j?n>rcNMP=|$mSZ)iy8x1Q66z-#QrQVf!DJ$;KkLOK^$(hM) zJc2(v>=AP126dtf=lWb0_EWjh{-3`_qpP~F{8~`iM`K1Ob@ ZD!M! znurc194+drZZg^>W+r&ZqH4{QbOO+yEh?b;#|~syEft{yNAC~3fkc)Y>j02DDc{oB zXK>oGI?xfK%zqK5>V!z)nY`l$7IUlXE2~!x%FpzCdclXj{xPJ6D$l0QiW%4>Tht~& z{J!_>3%aWJO0u-a*$n@v`ki#LL+kBsvj6}k>(#pe3R 9J-5d +d=s+GHDgQ>#YtBCFADyUSEi+f+Gn^ZbLH(06yNg9KRcA*6+1H-0h)$v}Mbw ztqQ-DiDwxDuObUEL)q`F#Ov9HCCRgL|6`$()}W!G!G2Fmt6IY3%Sh!_s&^TGYih(l zw8g^WLL}vCYVt;+?ZW{H@3V&|^G|92E)p_)$CQ&~km}mDA#aWKk^n-bNT8?V>ARuV z${a >roHzsxB+S?+V8U@N0@0%W6!wM>oWiToagxz^E z1^LAUt$$@fTaH>|+OCf`6jLVSaOrX{xK@&>Go7Z)Q2h7hNe^%0m%0k=?13*V=uguI z0Q8RE@tD9o128+mTg8!7UDKVBg|o~{#W9+&%r>+rr9VSyjK38yPd+jaSMbKU19hSc zdsu?|O%KY9&Pz0MmFp^$r{g5IP*Rn%jXvvs8GQ;ccO2JD8_O%Zq#S%USt-77=xs!2 zioU0nz0x?;cX-aatbk|NqU 2kF%6OL46#bI#2)) zF&j_9ke7nUXu|H<)Bf-=VegUZowO2D9x#Rp7*I9ATBhSTjHDWTgsA69`AWaOdA?p= zJagOE-GyMbP!Y2K9Se3aM@1JK4Bx4k(M8)yhN5Q7LFfa}LAddp{XO=?6>JPg1QNF} z*rGb6N5cE^XYdcNX78KZO{E79_?GHmXlRZ3bm3g(#EoV9FWs}$f?E%+;#HI=6VIp5~Px?yiHG+6BwObS?U24n4=Sn7FFO)j9w;q&q1qIfHl}Q%V2(U zhhL~I^|fFSN7KD8`LA7e%zGOZdHkx}f$hP2$ILFKgKIE5F-N`jloa}#k3z+n6K}h_ zl}Fxi=X|P?t*1vK=eq)QAWFi7buv-KSkN2tQ9M?aMD Tc@e=y5o(m7GLLwFZ zIZEt7$%4^c>hc_gaunqoonn2rU};8`N$wP)i3kcF_b%)E1RYjtZu}c2|3%P=4yPNf zjSRtWA`cGEHt;d5b`Ci6WSIM}b@0McJbj4!{DuY?46nhjD%WM{Goo@ToUP#Op0EBb zocllTknr{e3W}o^D8K7(u@ml`@*j_nM@lt)R{T#k`m~FN0NzDQFHyR)PhH5=_+&FL zQR-1x%GFb9{A0B9f0Y>iiwxqQcy2URO>781LYJSo{VhTPOI2OGV;I{!x2_m<$Inx} z&HEnNm54_*rRe{psD`SUhV4DhXWIAkz*A@uqN#z<)-9Pb#%#{OVG+CtPf|@WGxL rX{ys(v# x%F8ELBt=bsF_&-tsv9mLRiGtG*U9rUHVeVE_XrOb9B = 1, i--) + var/obj/item/stock_parts/cell/charging = charging_batteries[i] + var/newlevel = round(charging.percent() * 4 / 100) + var/mutable_appearance/charge_overlay = mutable_appearance(icon, "cchargermulti-o[newlevel]") + var/mutable_appearance/cell_overlay = mutable_appearance(icon, "cchargermulti-cell") + charge_overlay.pixel_x = 5 * (i - 1) + cell_overlay.pixel_x = 5 * (i - 1) + . += new /mutable_appearance(charge_overlay) + . += new /mutable_appearance(cell_overlay) + +/obj/machinery/cell_charger_multi/attack_hand_secondary(mob/user, list/modifiers) + if(!can_interact(user) || !charging_batteries.len) + return + to_chat(user, span_notice("You press the quick release as all the cells pop out!")) + for(var/i in charging_batteries) + removecell() + return COMPONENT_CANCEL_ATTACK_CHAIN + +/obj/machinery/cell_charger_multi/examine(mob/user) + . = ..() + if(!charging_batteries.len) + . += "There are no cells in [src]." + else + . += "There are [charging_batteries.len] cells in [src]." + for(var/obj/item/stock_parts/cell/charging in charging_batteries) + . += "There's [charging] cell in the charger, current charge: [round(charging.percent(), 1)]%." + if(in_range(user, src) || isobserver(user)) + . += span_notice("The status display reads: Charging power: [charge_rate]W.") + . += span_notice("Right click it to remove all the cells at once!") + +/obj/machinery/cell_charger_multi/attackby(obj/item/tool, mob/user, params) + if(istype(tool, /obj/item/stock_parts/cell) && !panel_open) + if(machine_stat & BROKEN) + to_chat(user, span_warning("[src] is broken!")) + return + if(!anchored) + to_chat(user, span_warning("[src] isn't attached to the ground!")) + return + var/obj/item/stock_parts/cell/inserting_cell = tool + if(inserting_cell.chargerate <= 0) + to_chat(user, span_warning("[inserting_cell] cannot be recharged!")) + return + if(charging_batteries.len >= 4) + to_chat(user, span_warning("[src] is full, and cannot hold anymore cells!")) + return + else + var/area/current_area = loc.loc // Gets our locations location, like a dream within a dream + if(!isarea(current_area)) + return + if(current_area.power_equip == 0) // There's no APC in this area, don't try to cheat power! + to_chat(user, span_warning("[src] blinks red as you try to insert the cell!")) + return + if(!user.transferItemToLoc(tool,src)) + return + + charging_batteries += tool + user.visible_message(span_notice("[user] inserts a cell into [src]."), span_notice("You insert a cell into [src].")) + update_appearance() + else + if(!charging_batteries.len && default_deconstruction_screwdriver(user, icon_state, icon_state, tool)) + return + if(default_deconstruction_crowbar(tool)) + return + if(!charging_batteries.len && default_unfasten_wrench(user, tool)) + return + return ..() + +/obj/machinery/cell_charger_multi/process(delta_time) + if(!charging_batteries.len || !anchored || (machine_stat & (BROKEN|NOPOWER))) + return + + for(var/obj/item/stock_parts/cell/charging in charging_batteries) + if(charging.percent() >= 100) + continue + var/main_draw = use_power_from_net(charge_rate * delta_time, take_any = TRUE) //Pulls directly from the Powernet to dump into the cell + if(!main_draw) + return + charging.give(main_draw) + use_power(charge_rate / 100) //use a small bit for the charger itself, but power usage scales up with the part tier + + update_appearance() + +/obj/machinery/cell_charger_multi/attack_tk(mob/user) + if(!charging_batteries.len) + return + + to_chat(user, span_notice("You telekinetically remove [removecell(user)] from [src].")) + + return COMPONENT_CANCEL_ATTACK_CHAIN + +/obj/machinery/cell_charger_multi/RefreshParts() + charge_rate = 0 // No, you cant get free charging speed! + for(var/obj/item/stock_parts/capacitor/C in component_parts) + charge_rate += charge_rate_base * C.rating + if(charge_rate >= charge_rate_max) // We've hit the charge speed cap, stop iterating. + charge_rate = charge_rate_max + break + if(charge_rate < charge_rate_base) // This should never happen; but we need to pretend it can. + charge_rate = charge_rate_base + +/obj/machinery/cell_charger_multi/emp_act(severity) + . = ..() + + if(machine_stat & (BROKEN|NOPOWER) || . & EMP_PROTECT_CONTENTS) + return + + for(var/obj/item/stock_parts/cell/charging in charging_batteries) + charging.emp_act(severity) + +/obj/machinery/cell_charger_multi/deconstruct() + for(var/obj/item/stock_parts/cell/charging in charging_batteries) + charging.forceMove(drop_location()) + charging_batteries = null + return ..() + + +/obj/machinery/cell_charger_multi/attack_ai(mob/user) + return + +/obj/machinery/cell_charger_multi/attack_hand(mob/user, list/modifiers) + . = ..() + if(.) + return + + var/obj/item/stock_parts/cell/charging = removecell(user) + + if(!charging) + return + + user.put_in_hands(charging) + charging.add_fingerprint(user) + + user.visible_message(span_notice("[user] removes [charging] from [src]."), span_notice("You remove [charging] from [src].")) + +/obj/machinery/cell_charger_multi/proc/removecell(mob/user) + if(!charging_batteries.len) + return FALSE + var/obj/item/stock_parts/cell/charging + if(charging_batteries.len > 1 && user) + var/list/buttons = list() + for(var/obj/item/stock_parts/cell/battery in charging_batteries) + buttons["[battery] [battery.percent()]%"] = battery + var/cell_name = tgui_input_list(user, "Please choose what cell you'd like to remove.", "Remove a cell", buttons) + charging = buttons[cell_name] + else + charging = charging_batteries[1] + if(!charging) + return FALSE + charging.forceMove(drop_location()) + charging.update_appearance() + charging_batteries -= charging + update_appearance() + return charging + +/obj/machinery/cell_charger_multi/Destroy() + for(var/obj/item/stock_parts/cell/charging in charging_batteries) + QDEL_NULL(charging) + charging_batteries = null + return ..() + +/obj/item/circuitboard/machine/cell_charger_multi + name = "Multi-Cell Charger (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/cell_charger_multi + req_components = list(/obj/item/stock_parts/capacitor = 4) + needs_anchored = FALSE + + +/datum/design/board/cell_charger_multi + name = "Machine Design (Multi-Cell Charger Board)" + desc = "The circuit board for a multi-cell charger." + id = "multi_cell_charger" + build_path = /obj/item/circuitboard/machine/cell_charger_multi + category = list ("Misc. Machinery") diff --git a/modular_skyrat/modules/robohand/code/robohand.dm b/modular_skyrat/modules/robohand/code/robohand.dm new file mode 100644 index 000000000000..60cc7d434643 --- /dev/null +++ b/modular_skyrat/modules/robohand/code/robohand.dm @@ -0,0 +1,112 @@ +#define CALIBRE_14MM "14mm" + +/* +* Malorian Arms 3516 14MM +* If you have this, you're a badass. +*/ + +/obj/item/gun/ballistic/automatic/pistol/robohand + name = "Malorian Arms 3516" + desc = "The Malorian Arms 3516 is a 14mm heavy pistol, sporting a titanium frame and unique wooden grip. A custom Dyna-porting and \ + direct integral cyber-interlink means only someone with a cyberarm and smartgun link can take full advantage of the pistol's features." + icon = 'modular_skyrat/modules/robohand/icons/3516.dmi' + icon_state = "3516" + w_class = WEIGHT_CLASS_NORMAL + mag_type = /obj/item/ammo_box/magazine/m14mm + can_suppress = FALSE + fire_sound = 'modular_skyrat/modules/robohand/sound/fire2.ogg' + load_sound = 'modular_skyrat/modules/robohand/sound/reload.ogg' + load_empty_sound = 'modular_skyrat/modules/robohand/sound/reload.ogg' + eject_sound = 'modular_skyrat/modules/robohand/sound/release.ogg' + eject_empty_sound = 'modular_skyrat/modules/robohand/sound/release.ogg' + vary_fire_sound = FALSE + rack_sound = 'modular_skyrat/modules/robohand/sound/slide.ogg' + fire_sound_volume = 100 + bolt_wording = "fuckin' slide" + reload_time = 0 //FAST AS FUCK BOIS! + +//Gun actions + +//The gun cannot shoot if you do not have a cyborg arm. +/obj/item/gun/ballistic/automatic/pistol/robohand/afterattack(atom/target, mob/living/user, flag, params) + //This is where we are checking if the user has a cybernetic arm to USE the gun. ROBOHAND HAS A ROBO HAND + if(ishuman(user)) + return ..() + var/mob/living/carbon/human/human_user = user + var/obj/item/bodypart/selected_hand = human_user.get_active_hand() + if(selected_hand.status != BODYPART_ROBOTIC) + to_chat(user, span_warning("You can't seem to figure out how to use [src], perhaps you need to check the manual?")) + return + . = ..() + +/obj/item/gun/ballistic/automatic/pistol/robohand/insert_magazine(mob/user, obj/item/ammo_box/magazine/inserted_mag, display_message) + if(!istype(inserted_mag, mag_type)) + to_chat(user, span_warning("\The [inserted_mag] doesn't seem to fit into \the [src]...")) + return FALSE + if(!user.transferItemToLoc(inserted_mag, src)) + to_chat(user, span_warning("You cannot seem to get \the [src] out of your hands!")) + return FALSE + magazine = inserted_mag + if(display_message) + to_chat(user, span_notice("You load a new [magazine_wording] into \the [src].")) + playsound(src, load_empty_sound, load_sound_volume, load_sound_vary) + if(bolt_type == BOLT_TYPE_OPEN && !bolt_locked) + chamber_round(TRUE) + drop_bolt(user) + update_appearance() + animate(src, 0.2 SECONDS, 1, transform = turn(matrix(), 120)) //Le johnny robohand woosh woosh twirl + animate(time = 0.2 SECONDS, transform = turn(matrix(), 240)) + animate(time = 0.2 SECONDS, transform = null) + return TRUE + +/obj/item/gun/ballistic/automatic/pistol/robohand/eject_magazine(mob/user, display_message, obj/item/ammo_box/magazine/tac_load) + if(bolt_type == BOLT_TYPE_OPEN) + chambered = null + if(magazine.ammo_count()) + playsound(src, eject_sound, eject_sound_volume, eject_sound_volume) //This is why we've copied this proc, it should play the eject sound when ejecting. + else + playsound(src, eject_empty_sound, eject_sound_volume, eject_sound_volume) + magazine.forceMove(drop_location()) + var/obj/item/ammo_box/magazine/old_mag = magazine + if(tac_load) + if (insert_magazine(user, tac_load, FALSE)) + to_chat(user, span_notice("You perform an elite tactical reload on \the [src].")) + else + to_chat(user, span_warning("You dropped the old [magazine_wording], but the new one doesn't fit. How embarassing.")) + magazine = null + else + magazine = null + user.put_in_hands(old_mag) + old_mag.update_appearance() + if(display_message) + to_chat(user, span_notice("You pull the [magazine_wording] out of \the [src].")) + update_appearance() + animate(src, transform = turn(matrix(), 120), time = 2, loop = 1) //Le johnny robohand again + animate(transform = turn(matrix(), 240), time = 2) + animate(transform = null, time = 2) + +//Magazine stuff + +/obj/item/ammo_box/magazine/m14mm + name = "pistol magazine (14mm)" + icon = 'modular_skyrat/modules/robohand/icons/3516_mag.dmi' + icon_state = "14mm" + base_icon_state = "14mm" + ammo_type = /obj/item/ammo_casing/c14mm + caliber = CALIBRE_14MM + max_ammo = 10 + multiple_sprites = AMMO_BOX_FULL_EMPTY + +/obj/item/ammo_casing/c14mm + name = "14mm bullet casing" + desc = "A 14mm bullet casing. Badass." + caliber = CALIBRE_14MM + projectile_type = /obj/projectile/bullet/c14mm + +/obj/projectile/bullet/c14mm + name = "14mm bullet" + damage = 60 + embedding = list(embed_chance = 90, fall_chance = 3, jostle_chance = 4, ignore_throwspeed_threshold = TRUE, pain_stam_pct = 0.4, pain_mult = 5, jostle_pain_mult = 9, rip_time = 10) + dismemberment = 50 + pierces = 1 + projectile_piercing = PASSCLOSEDTURF|PASSGRILLE|PASSGLASS diff --git a/modular_skyrat/modules/sec_haul/code/guns/gun_spawns.dm b/modular_skyrat/modules/sec_haul/code/guns/gun_spawns.dm new file mode 100644 index 000000000000..4af51d2c4fa3 --- /dev/null +++ b/modular_skyrat/modules/sec_haul/code/guns/gun_spawns.dm @@ -0,0 +1,95 @@ +/obj/effect/spawner/armory_spawn + icon = 'modular_skyrat/modules/sec_haul/icons/guns/spawner.dmi' + icon_state = "random_gun" + layer = OBJ_LAYER + /// How many guns will be spawned here. + var/gun_count = 1 + /// If the same gun can be spawned twice. + var/gun_doubles = TRUE + /// A list of possible guns to spawn. + var/list/guns + /// Do we fan out the items spawned for a natural effect? + var/fan_out_items = TRUE + /// How many mags per gun do we spawn, if it takes magazines. + var/mags_to_spawn = 3 + /// Do we want to angle it so that it is horizontal? + var/vertical_guns = TRUE + + +/obj/effect/spawner/armory_spawn/Initialize(mapload) + ..() + if(guns) + var/current_offset = -10 + var/offset_percent = 20 / guns.len + for(var/gun in guns) // 11/20/21: Gun spawners now spawn 1 of each gun in it's list no matter what, so as to reduce the RNG of the armory stock. + var/obj/item/gun/spawned_gun = new gun(loc) + + if(vertical_guns) + spawned_gun.place_on_rack() + spawned_gun.pixel_x = current_offset + current_offset += offset_percent + + if(istype(spawned_gun, /obj/item/gun/ballistic)) + var/obj/item/gun/ballistic/spawned_ballistic_gun = spawned_gun + if(spawned_ballistic_gun.magazine && !istype(spawned_ballistic_gun.magazine, /obj/item/ammo_box/magazine/internal)) + var/obj/item/storage/box/ammo_box/spawned_box = new(loc) + spawned_box.name = "ammo box - [spawned_ballistic_gun.name]" + for(var/i in 1 to mags_to_spawn) + new spawned_ballistic_gun.mag_type (spawned_box) + + if(istype(spawned_gun, /obj/item/gun/microfusion)) + var/obj/item/gun/microfusion/spawned_microfusion_gun = spawned_gun + var/obj/item/storage/box/ammo_box/microfusion/spawned_box = new(loc) + for(var/i in 1 to mags_to_spawn) + new spawned_microfusion_gun.cell_type (spawned_box) + + return INITIALIZE_HINT_QDEL + +/obj/effect/spawner/armory_spawn/shotguns + icon_state = "random_shotgun" + gun_count = 4 + guns = list( + /obj/item/gun/ballistic/shotgun/m23, + /obj/item/gun/ballistic/shotgun/automatic/as2, + /obj/item/gun/ballistic/shotgun/sas14, + ) + +/obj/structure/closet/ammunitionlocker/useful/PopulateContents() + new /obj/item/storage/box/rubbershot_14gauge(src) + new /obj/item/storage/box/rubbershot_14gauge(src) + new /obj/item/storage/box/rubbershot_14gauge(src) + new /obj/item/storage/box/rubbershot_14gauge(src) + +//////////////////////////AMMO BOXES +/obj/item/storage/box/ammo_box + name = "ammo box" + desc = "A box filled with ammunition." + icon_state = "boxhrifle" + icon = 'modular_skyrat/modules/sec_haul/icons/guns/ammoboxes.dmi' + illustration = null + layer = 2.9 + +/obj/item/storage/box/ammo_box/microfusion + name = "microfusion cell container" + desc = "A box filled with microfusion cells." + +/obj/item/storage/box/ammo_box/microfusion/PopulateContents() + new /obj/item/storage/bag/ammo(src) + + +/obj/effect/spawner/armory_spawn/centcom_rifles + icon_state = "random_rifle" + gun_count = 2 + guns = list( + /obj/item/gun/ballistic/automatic/ar, + /obj/item/gun/ballistic/automatic/assault_rifle/m16, + /obj/item/gun/ballistic/automatic/cfa_rifle, + ) + +/obj/effect/spawner/armory_spawn/centcom_lasers + gun_count = 2 + guns = list( + /obj/item/gun/energy/laser, + /obj/item/gun/energy/laser/cfa_paladin, + /obj/item/gun/energy/e_gun, + ) diff --git a/monkestation/code/modules/gunsgalore/code/guns/ballistic_master.dm b/monkestation/code/modules/gunsgalore/code/guns/ballistic_master.dm new file mode 100644 index 000000000000..d24370253500 --- /dev/null +++ b/monkestation/code/modules/gunsgalore/code/guns/ballistic_master.dm @@ -0,0 +1,272 @@ +/obj/item/gun/ballistic + /// Does this gun have mag and nomag on mob variance? + var/alt_icons = FALSE + /// What the icon state is for the on-back guns + var/alt_icon_state + /// Realistic guns that use reliability and dirt + var/realistic = FALSE + /// Is it jammed? + var/jammed = FALSE + /// How dirty a gun is. + var/dirt_level = 0 + /// Tied in with how good a gun is, if firing it causes a lot of dirt to form, then change this accordingly. + var/dirt_modifier = 0.1 + /// Used when calculating if a gun will jam or not. + var/jam_chance = 0 + /// Used when calculating how long a gun takes to unjam. + var/unjam_time = 0 + /// Tracking gun base spred. + var/base_spread = 0 + /// How used this gun is. + var/durability = 100 + /// How quickly a gun will degrade. 0.1 = 1000 shots. Edit this to change a guns base reliability. + var/durability_factor = 0.1 + /// How long it takes to reload a magazine. + var/reload_time = 2 SECONDS + + +/obj/item/gun/ballistic/Initialize() + . = ..() + if(realistic) + base_spread = spread + +/obj/item/gun/ballistic/ComponentInitialize() + . = ..() + if(alt_icons) + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/gun/ballistic/update_overlays() + . = ..() + if(alt_icons) + if(!magazine) + if(alt_icon_state) + inhand_icon_state = "[alt_icon_state]_nomag" + worn_icon_state = "[alt_icon_state]_nomag" + else + inhand_icon_state = "[initial(icon_state)]_nomag" + worn_icon_state = "[initial(icon_state)]_nomag" + else + if(alt_icon_state) + inhand_icon_state = "[alt_icon_state]" + worn_icon_state = "[alt_icon_state]" + else + inhand_icon_state = "[initial(icon_state)]" + worn_icon_state = "[initial(icon_state)]" + +/obj/item/gun/ballistic/insert_magazine(mob/user, obj/item/ammo_box/magazine/AM, display_message) + if(reload_time && !HAS_TRAIT(user, TRAIT_INSTANT_RELOAD) && magazine) //This only happens when you're attempting a tactical reload, e.g. there's a mag already inserted. + to_chat(user, span_notice("You start to insert the magazine into [src]!")) + if(!do_after(user, reload_time, src)) + to_chat(user, span_danger("You fail to insert the magazine into [src]!")) + return + . = ..() + +/obj/item/gun/ballistic/assault_rifle + rack_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/ltrifle_cock.ogg' + load_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/ltrifle_magin.ogg' + load_empty_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/ltrifle_magin.ogg' + eject_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/ltrifle_magout.ogg' + eject_empty_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/ltrifle_magout.ogg' + +/obj/item/gun/ballistic/battle_rifle + rack_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/batrifle_cock.ogg' + load_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/batrifle_magin.ogg' + load_empty_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/batrifle_magin.ogg' + eject_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/batrifle_magout.ogg' + eject_empty_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/batrifle_magout.ogg' + +/obj/item/gun/ballistic/machine_gun + rack_sound = 'sound/weapons/gun/l6/l6_rack.ogg' + load_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/lmg_magin.ogg' + load_empty_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/lmg_magin.ogg' + eject_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/lmg_magout.ogg' + eject_empty_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/lmg_magout.ogg' + +/obj/item/gun/ballistic/sniper_rifle + rack_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/sfrifle_cock.ogg' + load_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/sfrifle_magin.ogg' + load_empty_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/sfrifle_magin.ogg' + eject_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/sfrifle_magout.ogg' + eject_empty_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/sfrifle_magout.ogg' + +/obj/item/gun/ballistic/submachine_gun + rack_sound = 'sound/weapons/gun/smg/smgrack.ogg' + load_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/smg_magin.ogg' + load_empty_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/smg_magin.ogg' + eject_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/smg_magout.ogg' + eject_empty_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/smg_magout.ogg' + +/obj/item/gun/ballistic/proc/jam(unjam = FALSE, mob/living/user) + if(unjam && jammed != TRUE) + unjam_time = clamp((jam_chance*10)/(durability/10), 0, 50) + jammed = TRUE + playsound(src, 'sound/effects/stall.ogg', 60, TRUE) + to_chat(user, span_danger("The [src] jams!")) + SEND_SIGNAL(src, COMSIG_GUN_JAMMED) + else if(jammed) + to_chat(user, "You start to unjam the bolt!") + if(do_after(user, unjam_time)) + jammed = FALSE + to_chat(user, span_notice("You unjam the [src]'s bolt.")) + playsound(src, 'sound/weapons/gun/l6/l6_rack.ogg', 60, TRUE) + +/obj/item/gun/ballistic/can_trigger_gun(mob/living/user) + . = ..() + if(realistic && jammed) + to_chat(user, span_warning("[src] is jammed!")) + return FALSE + +/obj/item/gun/ballistic/shoot_live_shot(mob/living/user, pointblank, atom/pbtarget, message) + if(realistic) + if(jammed) + return FALSE + + dirt_level += dirt_modifier + + durability = clamp(durability -= durability_factor, 1, 1000) + + jam_chance = dirt_level/5 + + spread = base_spread + ((jam_chance / durability)*100) + + switch(FLOOR(durability, 1)) + if(0 to 9) + if(prob(90)) + jam(user) + return FALSE + if(10 to 29) + if(prob(10)) + jam(user) + return FALSE + if(30 to 49) + if(prob(5)) + jam(user) + return FALSE + + if(dirt_level > 30 && prob(jam_chance)) + jam(user) + return FALSE + . = ..() + +/obj/item/gun/ballistic/AltClick(mob/user) + if(realistic) + if(!user.canUseTopic(src)) + return + if(jammed) + jam(TRUE, user) + return + if(!internal_magazine && magazine && user.Adjacent(src)) + eject_magazine(user) + . = ..() + +/obj/item/gun/ballistic/examine(mob/user) + . = ..() + if(realistic) + switch(FLOOR(dirt_level, 1)) + if(0 to 10) + . += "It looks clean." + if(11 to 30) + . += "It looks slightly dirty." + if(31 to 50) + . += "It looks dirty." + if(51 to 70) + . += "It looks very dirty." + else + . += span_warning("It is filthy!") + + switch(FLOOR(durability, 1)) + if(0 to 9) + . += span_warning("It is falling apart!") + if(10 to 29) + . += span_warning("It looks battle scarred!") + if(30 to 49) + . += "It looks well worn." + if(50 to 69) + . += "It has minimal wear." + else + . += "It looks factory new." + + if(jammed) + . += span_warning("It is jammed, alt+click it to unjam it!") + else if(durability < 10) + . += span_warning("It is barely functioning!") + else + . += "It is functioning normally." + +/obj/item/gun/ballistic/attackby(obj/item/A, mob/user, params) + . = ..() + if(realistic) + if(istype(A, /obj/item/stack/sheet/cloth)) + var/obj/item/stack/sheet/cloth/C = A + if(!dirt_level) + to_chat(user, "The [src] is already spotless!") + else + if(C.amount < 5) + to_chat(user, "There's not enough [C] to clean the gun with!") + else + to_chat(user, span_notice("You start cleaning the [src].")) + if(do_after(user, 20 SECONDS)) + dirt_level -= 35 + if(dirt_level < 0) + dirt_level = 0 + C.use(5) + to_chat(user, span_notice("You clean the [src], improving it's reliability!")) + if(istype(A, /obj/item/gun_maintenance_supplies)) + to_chat(user, span_notice("You start maintaining the [src].")) + if(do_after(user, 10 SECONDS, target = src)) + user.visible_message(span_notice("[user] finishes maintenance of [src].")) + dirt_level = 0 + qdel(A) + +//CRATES + +//all that shit +/obj/structure/closet/crate/secure/weapon/ww2 + name = "ww2 weapons crate" + desc = "A secure weapons crate. Looks like it's from the old-era world war 2." + icon_state = "weaponcrate" + +/obj/structure/closet/crate/secure/weapon/ww2/PopulateContents() + . = ..() + new /obj/item/gun/ballistic/automatic/battle_rifle/fg42(src) + new /obj/item/ammo_box/magazine/fg42(src) + new /obj/item/gun/ballistic/automatic/assault_rifle/akm(src) + new /obj/item/ammo_box/magazine/akm(src) + new /obj/item/gun/ballistic/automatic/assault_rifle/m16(src) + new /obj/item/ammo_box/magazine/m16(src) + new /obj/item/gun/ballistic/automatic/mg34(src) + new /obj/item/ammo_box/magazine/mg34(src) + new /obj/item/gun/ballistic/automatic/submachine_gun/mp40(src) + new /obj/item/ammo_box/magazine/mp40(src) + new /obj/item/gun/ballistic/automatic/assault_rifle/stg(src) + new /obj/item/ammo_box/magazine/stg(src) + new /obj/item/gun/ballistic/automatic/submachine_gun/ppsh(src) + new /obj/item/ammo_box/magazine/ppsh(src) + new /obj/item/gun/ballistic/automatic/submachine_gun/pps(src) + new /obj/item/ammo_box/magazine/pps(src) + +/obj/structure/closet/crate/secure/weapon/ww2 + name = "modern weapons crate" + desc = "A secure weapons crate. Looks like it's from the 25th century." + icon_state = "weaponcrate" + +/obj/structure/closet/crate/secure/weapon/ww2/PopulateContents() + . = ..() + new /obj/item/gun/ballistic/automatic/battle_rifle/fg42/modern(src) + new /obj/item/ammo_box/magazine/fg42(src) + new /obj/item/gun/ballistic/automatic/assault_rifle/akm/modern(src) + new /obj/item/ammo_box/magazine/akm(src) + new /obj/item/gun/ballistic/automatic/assault_rifle/m16/modern(src) + new /obj/item/ammo_box/magazine/m16(src) + new /obj/item/gun/ballistic/automatic/submachine_gun/mp40/modern(src) + new /obj/item/ammo_box/magazine/mp40(src) + new /obj/item/gun/ballistic/automatic/assault_rifle/stg/modern(src) + new /obj/item/ammo_box/magazine/stg(src) + new /obj/item/gun/ballistic/automatic/submachine_gun/ppsh/modern(src) + new /obj/item/ammo_box/magazine/ppsh(src) + +/obj/effect/temp_visual/dir_setting/firing_effect + light_system = MOVABLE_LIGHT + light_range = 2 + light_power = 1 + light_color = LIGHT_COLOR_FIRE diff --git a/monkestation/code/modules/gunsgalore/code/guns/skillchip.dm b/monkestation/code/modules/gunsgalore/code/guns/skillchip.dm new file mode 100644 index 000000000000..542114b5c3c0 --- /dev/null +++ b/monkestation/code/modules/gunsgalore/code/guns/skillchip.dm @@ -0,0 +1,9 @@ +/obj/item/skillchip/chameleon/reload + name = "T.A.C.T.I.C00L skillchip" + desc = "If used, allows the user to perform tactical and instant reloads on all weapons with a magazine." + auto_traits = list(TRAIT_INSTANT_RELOAD) + skill_name = "Tactical Reloading" + skill_description = "Fine tune motor skills when performing reloads on weapons to reduce time taken." + skill_icon = "sitemap" + activate_message = span_notice("You suddenly learn the art of tactical reloading.") + deactivate_message = span_danger("You suddenly lose the ability to tactically reload.") diff --git a/monkestation/code/modules/microfusion/code/_microfusion_defines.dm b/monkestation/code/modules/microfusion/code/_microfusion_defines.dm new file mode 100644 index 000000000000..2abc005ea7ef --- /dev/null +++ b/monkestation/code/modules/microfusion/code/_microfusion_defines.dm @@ -0,0 +1,52 @@ +/// The amount of cell charge drained during a drain failure. +#define MICROFUSION_CELL_DRAIN_FAILURE 500 +/// The heavy EMP range for when a cell suffers an EMP failure. +#define MICROFUSION_CELL_EMP_HEAVY_FAILURE 2 +/// The light EMP range for when a cell suffers an EMP failure. +#define MICROFUSION_CELL_EMP_LIGHT_FAILURE 4 +/// The radiation range for when a cell suffers a radiation failure. +#define MICROFUSION_CELL_RADIATION_RANGE_FAILURE 1 + +/// The lower most time for a microfusion cell meltdown. +#define MICROFUSION_CELL_FAILURE_LOWER 10 SECONDS +/// The upper most time for a microfusion cell meltdown. +#define MICROFUSION_CELL_FAILURE_UPPER 15 SECONDS + +/// A charge drain failure. +#define MICROFUSION_CELL_FAILURE_TYPE_CHARGE_DRAIN 1 +/// A small explosion failure. +#define MICROFUSION_CELL_FAILURE_TYPE_EXPLOSION 2 +/// EMP failure. +#define MICROFUSION_CELL_FAILURE_TYPE_EMP 3 +/// Radiation failure. +#define MICROFUSION_CELL_FAILURE_TYPE_RADIATION 4 + +/// Returned when the phase emtiter process is successful. +#define SHOT_SUCCESS "success" +/// Returned when a gun is fired but there is no phase emitter. +#define SHOT_FAILURE_NO_EMITTER "No phase emitter installed!" + +/// The error message returned when the phase emitter is processed but damaged. +#define PHASE_FAILURE_DAMAGED "PHASE EMITTER: Emitter damaged!" +/// The error message returned when the phase emitter has reached it's htermal throttle. +#define PHASE_FAILURE_THROTTLE "PHASE EMITTER: Thermal throttle active!" + +/// The heat dissipation bonus of an emitter being in space! +#define PHASE_HEAT_DISSIPATION_BONUS_SPACE 30 +/// The heat dissipation bonus of an emitter being in air! +#define PHASE_HEAT_DISSIPATION_BONUS_AIR 10 + +// Slot defines for the gun. +/// The gun barrel slot. +#define GUN_SLOT_BARREL "barrel" +/// The gun underbarrel slot. +#define GUN_SLOT_UNDERBARREL "underbarrel" +/// The gun rail slot. +#define GUN_SLOT_RAIL "rail" +/// Unique slots, can hold as many as you want. +#define GUN_SLOT_UNIQUE "unique" + +/// Max name size for changing names +#define GUN_MAX_NAME_CHARS 20 +/// Min name size for changing names +#define GUN_MIN_NAME_CHARS 3 diff --git a/monkestation/code/modules/microfusion/code/cargo_stuff.dm b/monkestation/code/modules/microfusion/code/cargo_stuff.dm new file mode 100644 index 000000000000..b29213fa2325 --- /dev/null +++ b/monkestation/code/modules/microfusion/code/cargo_stuff.dm @@ -0,0 +1,51 @@ +/datum/supply_pack/security/armory/mcr01 + name = "MCR-01 Microfusion Crate" + desc = "Micron Control Systems Incorporated supplied MCR-01 Microfusion weapons platform. Comes with 4 guns and 4 advanced cells!" + cost = CARGO_CRATE_VALUE * 20 + contains = list( + /obj/item/gun/microfusion/mcr01/advanced, + /obj/item/gun/microfusion/mcr01/advanced, + /obj/item/gun/microfusion/mcr01/advanced, + /obj/item/gun/microfusion/mcr01/advanced, + /obj/item/storage/box/ammo_box/microfusion/advanced, + /obj/item/storage/box/ammo_box/microfusion/advanced, + /obj/item/storage/box/ammo_box/microfusion/advanced, + /obj/item/storage/box/ammo_box/microfusion/advanced, + ) + crate_name = "MCR-01 Microfusion Crate" + +/datum/supply_pack/security/microfusion + name = "Assorted Microfusion Cell Crate" + desc = "Micron Control Systems Incorporated supplied Microfusion cells and attachments!" + cost = CARGO_CRATE_VALUE * 5 + contains = list( + /obj/item/stock_parts/cell/microfusion/advanced, + /obj/item/stock_parts/cell/microfusion/advanced, + /obj/item/stock_parts/cell/microfusion/advanced, + /obj/item/stock_parts/cell/microfusion/advanced, + /obj/item/stock_parts/cell/microfusion/advanced, + /obj/item/stock_parts/cell/microfusion/advanced, + /obj/item/microfusion_cell_attachment/rechargeable, + /obj/item/microfusion_cell_attachment/rechargeable, + /obj/item/microfusion_cell_attachment/rechargeable, + /obj/item/microfusion_cell_attachment/rechargeable, + /obj/item/microfusion_cell_attachment/rechargeable, + /obj/item/microfusion_cell_attachment/rechargeable, + ) + crate_name = "Microfusion Cell Crate" + +/datum/supply_pack/security/mcr01_attachments + name = "MCR-01 Military Attachments Crate" + desc = "Micron Control Systems Incorporated supplied MCR-01 Military spec attachments!" + cost = CARGO_CRATE_VALUE * 15 + contains = list( + /obj/item/microfusion_gun_attachment/scope, + /obj/item/microfusion_gun_attachment/scope, + /obj/item/microfusion_gun_attachment/grip, + /obj/item/microfusion_gun_attachment/grip, + /obj/item/microfusion_gun_attachment/rail, + /obj/item/microfusion_gun_attachment/rail, + /obj/item/microfusion_gun_attachment/repeater, + /obj/item/microfusion_gun_attachment/repeater, + ) + crate_name = "MCR-01 Military Attachments Crate" diff --git a/monkestation/code/modules/microfusion/code/gun_types.dm b/monkestation/code/modules/microfusion/code/gun_types.dm new file mode 100644 index 000000000000..4745258a564a --- /dev/null +++ b/monkestation/code/modules/microfusion/code/gun_types.dm @@ -0,0 +1,41 @@ +/obj/item/gun/microfusion/mcr01 + name = "MCR-01" + desc = "An advanced, modular energy weapon produced by Allstar Lasers Incorporated. These cutting edge weapons differ from traditional beam weaponry in producing individual bolts, as well as utilizing hotswapped cells rather than being tied to immobile power sources." + icon_state = "mcr01" + inhand_icon_state = "mcr01" + shaded_charge = TRUE + +/// Gun for cargo crates. +/obj/item/gun/microfusion/mcr01/advanced + name = "Advanced MCR-01" + cell_type = /obj/item/stock_parts/cell/microfusion/advanced + phase_emitter_type = /obj/item/microfusion_phase_emitter/advanced + +/obj/item/gun/microfusion/mcr01/nanocarbon + name = "Nanocarbon Destroyer" + desc = "The pinnacle of the Nanocarbon weapon line. This weapon is the ultimate in power and performance. It is capable of firing a wide variety of beams, including a wide range of energy types, and is capable of firing a wide variety of frequencies." + icon_state = "mcr01" + inhand_icon_state = "mcr01" + shaded_charge = TRUE + +/obj/item/storage/box/ammo_box/microfusion/advanced + name = "advanced microfusion cell container" + desc = "A box filled with microfusion cells." + +/obj/item/storage/box/ammo_box/microfusion/advanced/PopulateContents() + new /obj/item/storage/bag/ammo(src) + new /obj/item/stock_parts/cell/microfusion/advanced(src) + new /obj/item/stock_parts/cell/microfusion/advanced(src) + new /obj/item/stock_parts/cell/microfusion/advanced(src) + +//////////////MICROFUSION SPAWNERS +/obj/effect/spawner/armory_spawn/microfusion + icon_state = "random_rifle" + gun_count = 4 + guns = list( + /obj/item/gun/microfusion/mcr01, + /obj/item/gun/microfusion/mcr01, + /obj/item/gun/microfusion/mcr01, + /obj/item/gun/microfusion/mcr01, + ) + diff --git a/monkestation/code/modules/microfusion/code/microfusion_cell.dm b/monkestation/code/modules/microfusion/code/microfusion_cell.dm new file mode 100644 index 000000000000..f0953d7d9590 --- /dev/null +++ b/monkestation/code/modules/microfusion/code/microfusion_cell.dm @@ -0,0 +1,189 @@ +/* +MICROFUSION CELL SYSTEM + +Microfusion cells are small battery units that house controlled nuclear fusion within, and that fusion is converted into useable energy. + +They cannot be charged as standard, and require upgrades to do so. + +These are basically advanced cells. +*/ + +/obj/item/stock_parts/cell/microfusion //Just a standard cell. + name = "microfusion cell" + desc = "A standard-issue microfusion cell, produced by Micron Control Systems. Smaller than a can of soda, these fulfill the need for a power source where plugging into a recharger is inconvenient or unavailable; although they will eventually run dry due to being shipped without a fuel source." + icon = 'modular_skyrat/modules/microfusion/icons/microfusion_cells.dmi' + charge_overlay_icon = 'modular_skyrat/modules/microfusion/icons/microfusion_cells.dmi' + icon_state = "microfusion" + w_class = WEIGHT_CLASS_NORMAL + maxcharge = 1200 //12 shots + chargerate = 0 //Standard microfusion cells can't be recharged, they're single use. + microfusion_readout = TRUE + + /// A hard referenced list of upgrades currently attached to the weapon. + var/list/attachments = list() + /// Are we melting down? For icon stuffs. + var/meltdown = FALSE + /// How many upgrades can you have on this cell? + var/max_attachments = 1 + /// Hard ref to the parent gun. + var/obj/item/gun/microfusion/parent_gun + /// Do we play an alarm when empty? + var/empty_alarm = TRUE + /// What sound do we play when empty? + var/empty_alarm_sound = 'sound/weapons/gun/general/empty_alarm.ogg' + /// Do we have the self charging upgrade? + var/self_charging = FALSE + +/obj/item/stock_parts/cell + /// Is this cell stabilised? (used in microfusion guns) + var/stabilised = FALSE + /// Do we show the microfusion readout instead of KJ? + var/microfusion_readout = FALSE + +/obj/item/stock_parts/cell/microfusion/Destroy() + if(attachments.len) + for(var/obj/item/iterating_item as anything in attachments) + iterating_item.forceMove(get_turf(src)) + attachments = null + parent_gun = null + return ..() + +/obj/item/stock_parts/cell/microfusion/attackby(obj/item/attacking_item, mob/living/user, params) + if(istype(attacking_item, /obj/item/microfusion_cell_attachment)) + add_attachment(attacking_item, user) + return + return ..() + +/obj/item/stock_parts/cell/microfusion/emp_act(severity) + var/prob_percent = charge / 100 * severity + if(prob(prob_percent) && !meltdown && !stabilised && parent_gun) + process_instability() + +/obj/item/stock_parts/cell/microfusion/use(amount) + if(charge >= amount) + var/check_if_empty = charge - amount + if(check_if_empty < amount && empty_alarm && !self_charging) + playsound(src, empty_alarm_sound, 50) + return ..() + +/obj/item/stock_parts/cell/microfusion/proc/process_instability() + var/seconds_to_explode = rand(MICROFUSION_CELL_FAILURE_LOWER, MICROFUSION_CELL_FAILURE_UPPER) + meltdown = TRUE + say("Malfunction in [seconds_to_explode] seconds!") + playsound(src, 'sound/machines/warning-buzzer.ogg', 30, FALSE, FALSE) + add_filter("rad_glow", 2, list("type" = "outline", "color" = "#ff5e0049", "size" = 2)) + addtimer(CALLBACK(src, .proc/process_failure), seconds_to_explode) + +/obj/item/stock_parts/cell/microfusion/proc/process_failure() + var/fuckup_type = rand(1, 4) + remove_filter("rad_glow") + playsound(src, 'sound/effects/spray.ogg', 70) + switch(fuckup_type) + if(MICROFUSION_CELL_FAILURE_TYPE_CHARGE_DRAIN) + charge = clamp(charge - MICROFUSION_CELL_DRAIN_FAILURE, 0, maxcharge) + if(MICROFUSION_CELL_FAILURE_TYPE_EXPLOSION) + explode() + if(MICROFUSION_CELL_FAILURE_TYPE_EMP) + empulse(get_turf(src), MICROFUSION_CELL_EMP_HEAVY_FAILURE, MICROFUSION_CELL_EMP_LIGHT_FAILURE, FALSE) + if(MICROFUSION_CELL_FAILURE_TYPE_RADIATION) + radiation_pulse(src, MICROFUSION_CELL_RADIATION_RANGE_FAILURE, RAD_MEDIUM_INSULATION) + meltdown = FALSE + +/obj/item/stock_parts/cell/microfusion/update_overlays() + . = ..() + for(var/obj/item/microfusion_cell_attachment/microfusion_cell_attachment as anything in attachments) + . += microfusion_cell_attachment.attachment_overlay_icon_state + +/obj/item/stock_parts/cell/microfusion/screwdriver_act(mob/living/user, obj/item/tool) + if(!attachments.len) + to_chat(user, span_danger("There are no attachments to remove!")) + return + remove_attachments() + playsound(src, 'sound/items/screwdriver.ogg', 70, TRUE) + to_chat(user, span_notice("You remove the upgrades from [src].")) + +/obj/item/stock_parts/cell/microfusion/process(delta_time) + for(var/obj/item/microfusion_cell_attachment/microfusion_cell_attachment as anything in attachments) + microfusion_cell_attachment.process_attachment(src, delta_time) + +/obj/item/stock_parts/cell/microfusion/examine(mob/user) + . = ..() + . += span_notice("It can hold [max_attachments] attachment(s).") + if(attachments.len) + for(var/obj/item/microfusion_cell_attachment/microfusion_cell_attachment as anything in attachments) + . += span_notice("It has a [microfusion_cell_attachment.name] installed.") + . += span_notice("Use a screwdriver to remove the attachments.") + +/obj/item/stock_parts/cell/microfusion/proc/add_attachment(obj/item/microfusion_cell_attachment/microfusion_cell_attachment, mob/living/user) + if(attachments.len >= max_attachments) + to_chat(user, span_warning("[src] cannot fit any more attachments!")) + return FALSE + if(is_type_in_list(microfusion_cell_attachment, attachments)) + to_chat(user, span_warning("[src] already has [microfusion_cell_attachment] installed!")) + return FALSE + attachments += microfusion_cell_attachment + microfusion_cell_attachment.forceMove(src) + microfusion_cell_attachment.add_attachment(src) + to_chat(user, span_notice("You successfully install [microfusion_cell_attachment] onto [src]!")) + playsound(src, 'sound/effects/structure_stress/pop2.ogg', 70, TRUE) + update_appearance() + return TRUE + +/obj/item/stock_parts/cell/microfusion/proc/remove_attachments() + for(var/obj/item/microfusion_cell_attachment/microfusion_cell_attachment in attachments) + microfusion_cell_attachment.remove_attachment(src) + microfusion_cell_attachment.forceMove(get_turf(src)) + attachments -= microfusion_cell_attachment + update_appearance() + +/datum/crafting_recipe/makeshift/microfusion_cell + name = "Makeshift Microfusion Cell" + tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER, TOOL_WELDER) + result = /obj/item/stock_parts/cell/microfusion/makeshift + reqs = list(/obj/item/trash/can = 1, + /obj/item/stack/sheet/iron = 1, + /obj/item/stack/cable_coil = 1) + time = 12 SECONDS + category = CAT_MISC + +//WHY WOULD YOU MAKE THIS? +/obj/item/stock_parts/cell/microfusion/makeshift + name = "makeshift microfusion cell" + desc = "An... Apparatus, comprised of an everyday aluminum can with several civilian-grade batteries tightly packed together and plugged in. This vaguely resembles a microfusion cell, if you tilt your head to a precise fifty degree angle. While the effects on enemy combatants may be dubious, it will certainly do incredible damage to the gun's warranty. What the hell were you thinking when you came up with this?" + icon_state = "microfusion_makeshift" + maxcharge = 600 + max_attachments = 0 + /// The probability of the cell failing + var/fail_prob = 10 + +/obj/item/stock_parts/cell/microfusion/makeshift/use(amount) + if(prob(fail_prob)) + process_instability() + return ..() + +/obj/item/stock_parts/cell/microfusion/enhanced + name = "enhanced microfusion cell" + desc = "A second generation microfusion cell, weighing about the same as the standard-issue cell and having the same space for attachments; however, it has a higher capacity." + icon_state = "microfusion_enhanced" + maxcharge = 1500 + +/obj/item/stock_parts/cell/microfusion/advanced + name = "advanced microfusion cell" + desc = "A third generation microfusion cell, boasting a much higher shot count. Additionally, these come with support for up to three modifications to the cell itself." + icon_state = "microfusion_advanced" + maxcharge = 1700 + max_attachments = 3 + +/obj/item/stock_parts/cell/microfusion/bluespace + name = "bluespace microfusion cell" + desc = "A fourth generation microfusion cell, employing bluespace technology to store power in a medium that's bigger on the inside. This has the highest capacity of any man-portable cell, and has flexibility for four different attachments to the cell itself." + icon_state = "microfusion_bluespace" + maxcharge = 2000 + max_attachments = 4 + +/obj/item/stock_parts/cell/microfusion/nanocarbon + name = "nanocarbon fusion cell" + desc = "This cell combines both top-of-the-line nanotech and advanced microfusion power to brute force the most common issue of Nanotrasen Asset Protection operatives, ammunition, through sheer volume. Intended for use with Nanotrasen-brand capacitor arrays only. Warranty void if dropped in toilet." + icon_state = "microfusion_nanocarbon" + maxcharge = 30000 + max_attachments = 420 diff --git a/monkestation/code/modules/microfusion/code/microfusion_cell_attachments.dm b/monkestation/code/modules/microfusion/code/microfusion_cell_attachments.dm new file mode 100644 index 000000000000..711d1db57aa3 --- /dev/null +++ b/monkestation/code/modules/microfusion/code/microfusion_cell_attachments.dm @@ -0,0 +1,132 @@ +/* +MICROFUSION CELL UPGRADE ATTACHMENTS + +For adding unique abilities to microfusion cells. These cannot directly interact with the gun. +*/ + +/obj/item/microfusion_cell_attachment + name = "microfusion cell attachment" + desc = "broken" + icon = 'modular_skyrat/modules/microfusion/icons/microfusion_cells.dmi' + w_class = WEIGHT_CLASS_NORMAL + /// The overlay that will be automatically added, must be in the cells icon. + var/attachment_overlay_icon_state + /// Does this attachment process with the cell? + var/processing_attachment = FALSE + + +/obj/item/microfusion_cell_attachment/proc/add_attachment(obj/item/stock_parts/cell/microfusion/microfusion_cell) + SHOULD_CALL_PARENT(TRUE) + START_PROCESSING(SSobj, microfusion_cell) + return + +/obj/item/microfusion_cell_attachment/proc/process_attachment(obj/item/stock_parts/cell/microfusion/microfusion_cell, delta_time) + return PROCESS_KILL + +/obj/item/microfusion_cell_attachment/proc/remove_attachment(obj/item/stock_parts/cell/microfusion/microfusion_cell) + SHOULD_CALL_PARENT(TRUE) + STOP_PROCESSING(SSobj, microfusion_cell) + return + +/* +rechargeable ATTACHMENT + +Allows the cell to be recharged at a gun recharger OR cell recharger. +*/ +/obj/item/microfusion_cell_attachment/rechargeable + name = "rechargeable microfusion cell attachment" + desc = "An adapter meant to be plugged into a microfusion cell, allowing the cell to be recharged at recharge stations for both weapons and civilian-grade batteries. Neither Allstar Lasers Incorporated or Micron Control Systems Incorporated suggest licking the prongs." + icon_state = "attachment_rechargeable" + attachment_overlay_icon_state = "microfusion_rechargeable" + /// The bonus charge rate by adding this attachment. + var/bonus_charge_rate = 300 + +/obj/item/microfusion_cell_attachment/rechargeable/add_attachment(obj/item/stock_parts/cell/microfusion/microfusion_cell) + . = ..() + microfusion_cell.chargerate += bonus_charge_rate + +/obj/item/microfusion_cell_attachment/rechargeable/remove_attachment(obj/item/stock_parts/cell/microfusion/microfusion_cell) + . = ..() + microfusion_cell.chargerate -= bonus_charge_rate + +/* +OVERCAPACITY ATTACHMENT + +Increases the cell capacity by a set percentage. +*/ +/obj/item/microfusion_cell_attachment/overcapacity + name = "overcapacity microfusion cell attachment" + desc = "An attachment which increases the capacity of the microfusion cell it's attached to. These are an additional, smaller capacitor, using a system to automatically switch from the cell to the capacitor as it's depleted, maximizing the weapon's charge." + icon_state = "attachment_overcapacity" + attachment_overlay_icon_state = "microfusion_overcapacity" + /// How much the attachment increases the cell's capacity by, as a percentage + var/capacity_increase = 20 + /// The initial capacity of the cell before this upgrade is added! + var/initial_charge_capacity = 0 + +/obj/item/microfusion_cell_attachment/overcapacity/add_attachment(obj/item/stock_parts/cell/microfusion/microfusion_cell) + . = ..() + initial_charge_capacity = microfusion_cell.maxcharge + var/capacity_to_add = microfusion_cell.maxcharge / 100 * capacity_increase + microfusion_cell.maxcharge += capacity_to_add + +/obj/item/microfusion_cell_attachment/overcapacity/remove_attachment(obj/item/stock_parts/cell/microfusion/microfusion_cell) + . = ..() + microfusion_cell.charge = min(microfusion_cell.charge, initial_charge_capacity) + microfusion_cell.maxcharge = initial_charge_capacity + initial_charge_capacity = 0 + +/* +STABILISER ATTACHMENT + +The cell is stable and will not emit sparks when firing. +*/ + +/obj/item/microfusion_cell_attachment/stabiliser + name = "stabilising microfusion cell attachment" + desc = "A stabilizer system attachment combining a grounding system with additional containment coils for self-charging purposes, this gives additional safety to the cell it's attached to; preventing both sparks and leakage." + icon_state = "attachment_stabiliser" + attachment_overlay_icon_state = "microfusion_stabiliser" + +/obj/item/microfusion_cell_attachment/stabiliser/add_attachment(obj/item/stock_parts/cell/microfusion/microfusion_cell) + . = ..() + microfusion_cell.stabilised = TRUE + +/obj/item/microfusion_cell_attachment/stabiliser/remove_attachment(obj/item/stock_parts/cell/microfusion/microfusion_cell) + . = ..() + microfusion_cell.stabilised = FALSE + +/* +SELFCHARGE ATTACHMENT + +The cell will charge itself. +If the cell isn't stabilised by a stabiliser, it may emit a radiation pulse. +*/ +/obj/item/microfusion_cell_attachment/selfcharging + name = "self-charging microfusion cell attachment" + desc = "While microfusion cells are normally shipped without their fuel source, this attachment comes with fifteen grams of hydrogen fuel; allowing the cell to sustain a small, yet active reaction to self-charge. These can keep going for weeks to months in ideal conditions, making them more than enough for most campaigns." + icon_state = "attachment_selfcharge" + attachment_overlay_icon_state = "microfusion_selfcharge" + /// The amount of charge this cell will passively gain! + var/self_charge_amount = 20 + +/obj/item/microfusion_cell_attachment/selfcharging/examine(mob/user) + . = ..() + . += span_warning("WARNING: May cause radiation burns and weapon instability if not stabilized with recommended attachment!") + +/obj/item/microfusion_cell_attachment/selfcharging/add_attachment(obj/item/stock_parts/cell/microfusion/microfusion_cell) + . = ..() + microfusion_cell.self_charging = TRUE + +/obj/item/microfusion_cell_attachment/selfcharging/remove_attachment(obj/item/stock_parts/cell/microfusion/microfusion_cell) + . = ..() + microfusion_cell.self_charging = FALSE + +/obj/item/microfusion_cell_attachment/selfcharging/process_attachment(obj/item/stock_parts/cell/microfusion/microfusion_cell, delta_time) + if(microfusion_cell.charge < microfusion_cell.maxcharge) + microfusion_cell.charge = clamp(microfusion_cell.charge + (self_charge_amount * delta_time), 0, microfusion_cell.maxcharge) + if(microfusion_cell.parent_gun) + microfusion_cell.parent_gun.update_appearance() + if(!microfusion_cell.stabilised && DT_PROB(1, delta_time)) + radiation_pulse(src, 1, RAD_MEDIUM_INSULATION) + diff --git a/monkestation/code/modules/microfusion/code/microfusion_designs.dm b/monkestation/code/modules/microfusion/code/microfusion_designs.dm new file mode 100644 index 000000000000..773777199434 --- /dev/null +++ b/monkestation/code/modules/microfusion/code/microfusion_designs.dm @@ -0,0 +1,218 @@ +// CELLS AND EMITTERS +/datum/design/basic_microfusion_cell + name = "Basic Microfusion Cell" + desc = "A basic microfusion cell with a capacity of 1200 MF and and 1 attachment points." + id = "basic_microfusion_cell" + build_type = PROTOLATHE | AWAY_LATHE | AUTOLATHE + materials = list(/datum/material/iron = 1000, /datum/material/glass = 200) + construction_time = 10 SECONDS + build_path = /obj/item/stock_parts/cell/microfusion + category = list("Misc", "Power Designs", "Machinery", "initial") + +/datum/design/microfusion_phase_emitter_undercharger + name = "Microfusion Phase Emitter Undercharger" + desc = "Inverts the output beam of the phase emitter, popular amongst law enforcement as a non-lethal upgrade." + id = "microfusion_phase_emitter_undercharger" + build_type = PROTOLATHE | AWAY_LATHE | AUTOLATHE + materials = list(/datum/material/iron = 1000, /datum/material/glass = 200) + construction_time = 10 SECONDS + build_path = /obj/item/microfusion_gun_attachment/undercharger + category = list("Misc", "Power Designs", "Machinery", "initial") + +/datum/design/enhanced_microfusion_cell + name = "Enhanced Microfusion Cell" + desc = "An enhanced microfusion cell with a capacity of 1500 MF and 2 attachment points." + id = "enhanced_microfusion_cell" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 200, /datum/material/uranium = 200) + construction_time = 10 SECONDS + build_path = /obj/item/stock_parts/cell/microfusion/enhanced + category = list("Misc", "Power Designs") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/enhanced_microfusion_phase_emitter + name = "Enhanced Microfusion Phase Emitter" + desc = "The core of a microfusion projection weapon, produces the laser." + id = "enhanced_microfusion_phase_emitter" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/silver = 500) + construction_time = 10 SECONDS + build_path = /obj/item/microfusion_phase_emitter/enhanced + category = list("Misc", "Power Designs") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/advanced_microfusion_cell + name = "Advanced Microfusion Cell" + desc = "An advanced microfusion cell with a capacity of 1700 MF and 3 attachment points." + id = "advanced_microfusion_cell" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/gold = 300, /datum/material/silver = 300, /datum/material/glass = 300, /datum/material/uranium = 300) + construction_time = 10 SECONDS + build_path = /obj/item/stock_parts/cell/microfusion/advanced + category = list("Misc", "Power Designs") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/advanced_microfusion_phase_emitter + name = "Advanced Microfusion Phase Emitter" + desc = "The core of a microfusion projection weapon, produces the laser." + id = "advanced_microfusion_phase_emitter" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/silver = 500, /datum/material/gold = 500) + construction_time = 10 SECONDS + build_path = /obj/item/microfusion_phase_emitter/advanced + category = list("Misc", "Power Designs") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/bluespace_microfusion_cell + name = "Bluespace Microfusion Cell" + desc = "A bluespace microfusion cell with a capacity of 2000 MF and 4 attachment points." + id = "bluespace_microfusion_cell" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/gold = 300, /datum/material/glass = 300, /datum/material/diamond = 300, /datum/material/uranium = 300, /datum/material/titanium = 300, /datum/material/bluespace = 300) + construction_time = 10 SECONDS + build_path = /obj/item/stock_parts/cell/microfusion/bluespace + category = list("Misc", "Power Designs") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/bluespace_microfusion_phase_emitter + name = "Bluespace Microfusion Phase Emitter" + desc = "The core of a microfusion projection weapon, produces the laser." + id = "bluespace_microfusion_phase_emitter" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/silver = 500, /datum/material/gold = 500, /datum/material/diamond = 500) + construction_time = 10 SECONDS + build_path = /obj/item/microfusion_phase_emitter/bluespace + category = list("Misc", "Power Designs") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +// CELL UPGRADES +/datum/design/microfusion_cell_attachment_rechargeable + name = "Rechargeable Microfusion Cell Attachment" + desc = "An attachment for microfusion cells that allows conversion of KJ to MF in standard chargers." + id = "microfusion_cell_attachment_rechargeable" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/gold = 1000) + build_path = /obj/item/microfusion_cell_attachment/rechargeable + category = list("Misc", "Power Designs") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/microfusion_cell_attachment_stabiliser + name = "Stabilising Microfusion Cell Attachment" + desc = "Stabilises the internal fusion reaction of microfusion cells." + id = "microfusion_cell_attachment_stabiliser" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/plasma = 1000) + build_path = /obj/item/microfusion_cell_attachment/stabiliser + category = list("Misc", "Power Designs") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/microfusion_cell_attachment_overcapacity + name = "Overcapacity Microfusion Cell Attachment" + desc = "An attachment for microfusion cells that increases MF capacity." + id = "microfusion_cell_attachment_overcapacity" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/plasma = 500, /datum/material/gold = 500) + build_path = /obj/item/microfusion_cell_attachment/overcapacity + category = list("Misc", "Power Designs") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/microfusion_cell_attachment_selfcharging + name = "Self-Charging Microfusion Cell Attachment" + desc = "Contains a small amount of infinitely decaying nuclear material, causing the fusion reaction to be self sustaining. WARNING: May cause radiation burns if not stabilised." + id = "microfusion_cell_attachment_selfcharging" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/diamond = 500, /datum/material/uranium = 1000) + build_path = /obj/item/microfusion_cell_attachment/selfcharging + category = list("Misc", "Power Designs") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +// GUN UPGRADES +/datum/design/microfusion_gun_attachment_grip + name = "Microfusion Weapon Grip" + desc = "A grip... for microfusion weapon platforms." + id = "microfusion_gun_attachment_grip" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/silver = 500) + build_path = /obj/item/microfusion_gun_attachment/grip + category = list("Weapons") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/microfusion_gun_attachment_scope + name = "Microfusion Weapon Scope" + desc = "A scope... for microfusion weapon platforms." + id = "microfusion_gun_attachment_scope" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/silver = 500) + build_path = /obj/item/microfusion_gun_attachment/scope + category = list("Weapons") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/microfusion_gun_attachment_black_camo + name = "Black Camo Microfusion Frame" + desc = "A frame modification for the MCR-10, changing the color of the gun to black." + id = "microfusion_gun_attachment_black_camo" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/gold = 500) + build_path = /obj/item/microfusion_gun_attachment/black_camo + category = list("Weapons") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/microfusion_gun_attachment_rail + name = "Microfusion Weapon Rail" + desc = "A rail system for any additional attachments, such as a torch." + id = "microfusion_gun_attachment_rail" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/silver = 500, /datum/material/gold = 500) + build_path = /obj/item/microfusion_gun_attachment/rail + category = list("Weapons") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/microfusion_gun_attachment_heatsink + name = "Phase Emitter Heatsink" + desc = "A heatsink attachment for your microfusion weapon. Massively increases cooling potential." + id = "microfusion_gun_attachment_heatsink" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/silver = 500, /datum/material/bronze = 500) + build_path = /obj/item/microfusion_gun_attachment/heatsink + category = list("Weapons") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/microfusion_gun_attachment_scatter + name = "Diffuser Microfusion Lens Attachment" + desc = "Splits the microfusion laser beam entering the lens!" + id = "microfusion_gun_attachment_scatter" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/diamond = 500, /datum/material/silver = 500) + build_path = /obj/item/microfusion_gun_attachment/scatter + category = list("Weapons") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/microfusion_gun_attachment_superheat + name = "Superheating Phase Emitter Upgrade" + desc = "Superheats the beam, causing targets to ignite!" + id = "microfusion_gun_attachment_superheat" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/diamond = 500, /datum/material/plasma = 500) + build_path = /obj/item/microfusion_gun_attachment/superheat + category = list("Weapons") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/microfusion_gun_attachment_repeater + name = "Repeating Phase Emitter Upgrade" + desc = "Upgrades the central phase emitter to repeat twice." + id = "microfusion_gun_attachment_repeater" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/diamond = 500, /datum/material/bluespace = 500) + build_path = /obj/item/microfusion_gun_attachment/repeater + category = list("Weapons") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/microfusion_gun_attachment_xray + name = "Phase Inverter Emitter Array" + desc = "Experimental technology that inverts the central phase emitter causing the wave frequency to shift into X-ray. CAUTION: Phase emitter heats up very quickly." + id = "microfusion_gun_attachment_xray" + build_type = PROTOLATHE | AWAY_LATHE | MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000, /datum/material/diamond = 1000, /datum/material/uranium = 500, /datum/material/bluespace = 500) + build_path = /obj/item/microfusion_gun_attachment/xray + category = list("Weapons") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_SCIENCE diff --git a/monkestation/code/modules/microfusion/code/microfusion_energy_master.dm b/monkestation/code/modules/microfusion/code/microfusion_energy_master.dm new file mode 100644 index 000000000000..836459c4cc47 --- /dev/null +++ b/monkestation/code/modules/microfusion/code/microfusion_energy_master.dm @@ -0,0 +1,806 @@ +#define DUALWIELD_PENALTY_EXTRA_MULTIPLIER 1.4 + +// Master file for cell loadable energy guns. PROCS ONLY YOU MONKEYS! +// This file is a copy/paste of _energy.dm with extensive modification. + +/obj/item/gun/microfusion + name = "prototype detatchable cell energy projection aparatus" + desc = "The coders have obviously failed to realise this is broken." + icon = 'modular_skyrat/modules/microfusion/icons/microfusion_gun40x32.dmi' + icon_state = "mcr01" + bayonet_icon = 'modular_skyrat/modules/microfusion/icons/microfusion_gun40x32.dmi' + gunlight_icon = 'modular_skyrat/modules/microfusion/icons/microfusion_gun40x32.dmi' + lefthand_file = 'modular_skyrat/modules/microfusion/icons/guns_lefthand.dmi' + righthand_file = 'modular_skyrat/modules/microfusion/icons/guns_lefthand.dmi' + has_gun_safety = TRUE + can_flashlight = FALSE + can_bayonet = FALSE + w_class = WEIGHT_CLASS_BULKY + + /// What type of power cell this uses + var/obj/item/stock_parts/cell/microfusion/cell + /// The cell we will spawn with + var/cell_type = /obj/item/stock_parts/cell/microfusion + /// The cell type we check when inserting a cell + var/base_cell_type = /obj/item/stock_parts/cell/microfusion + /// If the weapon has custom icons for individual ammo types it can switch between. ie disabler beams, taser, laser/lethals, ect. + var/modifystate = FALSE + /// Can it be charged in a recharger? + var/can_charge = TRUE + /// How many charge sections do we have? + var/charge_sections = 4 + ammo_x_offset = 2 + /// if this gun uses a stateful charge bar for more detail + var/shaded_charge = FALSE + /// If this gun has a "this is loaded with X" overlay alongside chargebars and such + var/single_shot_type_overlay = TRUE + /// Should we give an overlay to empty guns? + var/display_empty = TRUE + /// whether the gun's cell drains the cyborg user's cell to recharge + var/dead_cell = FALSE + + // MICROFUSION SPECIFIC VARS + + /// The microfusion lens used for generating the beams. + var/obj/item/ammo_casing/energy/laser/microfusion/microfusion_lens + /// The time it takes for someone to (tactically) reload this gun. In deciseconds. + var/reload_time = 2 SECONDS + /// The sound played when you insert a cell. + var/sound_cell_insert = 'modular_skyrat/modules/microfusion/sound/mag_insert.ogg' + /// Should the insertion sound played vary? + var/sound_cell_insert_vary = TRUE + /// The volume at which we will play the insertion sound. + var/sound_cell_insert_volume = 50 + /// The sound played when you remove a cell. + var/sound_cell_remove = 'modular_skyrat/modules/microfusion/sound/mag_insert.ogg' + /// Should the removal sound played vary? + var/sound_cell_remove_vary = TRUE + /// The volume at which we will play the removal sound. + var/sound_cell_remove_volume = 50 + /// A list of attached upgrades + var/list/attachments = list() + /// The starting phase emitter in this weapon. + var/phase_emitter_type = /obj/item/microfusion_phase_emitter + /// The base emitter type that we check when putting a new emitter in. + var/base_phase_emitter_type = /obj/item/microfusion_phase_emitter + /// The phase emitter that this gun currently has. + var/obj/item/microfusion_phase_emitter/phase_emitter + /// The amount of heat produced per shot + var/heat_per_shot = 100 + /// The heat dissipation bonus granted by the weapon. + var/heat_dissipation_bonus = 0 + /// What slots does this gun have? + var/attachment_slots = list(GUN_SLOT_BARREL, GUN_SLOT_UNDERBARREL, GUN_SLOT_RAIL, GUN_SLOT_UNIQUE) + /// Our base firedelay. + var/base_fire_delay = 0 + /// Do we use more power because of attachments? + var/extra_power_usage = 0 + +/obj/item/gun/microfusion/emp_act(severity) + . = ..() + if(!(. & EMP_PROTECT_CONTENTS)) + cell.use(round(cell.charge / severity)) + chambered = null //we empty the chamber + recharge_newshot() //and try to charge a new shot + update_appearance() + +/obj/item/gun/microfusion/Initialize(mapload) + . = ..() + if(cell_type) + cell = new cell_type(src) + else + cell = new(src) + cell.parent_gun = src + if(!dead_cell) + cell.give(cell.maxcharge) + if(phase_emitter_type) + phase_emitter = new phase_emitter_type(src) + else + phase_emitter = new(src) + phase_emitter.parent_gun = src + update_microfusion_lens() + recharge_newshot(TRUE) + update_appearance() + AddComponent(/datum/component/ammo_hud) + RegisterSignal(src, COMSIG_ITEM_RECHARGED, .proc/instant_recharge) + base_fire_delay = fire_delay + +/obj/item/gun/microfusion/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/gun/microfusion/add_weapon_description() + AddElement(/datum/element/weapon_description, attached_proc = .proc/add_notes_energy) + +/obj/item/gun/microfusion/Destroy() + if(microfusion_lens) + QDEL_NULL(microfusion_lens) + if(cell) + cell.parent_gun = null + QDEL_NULL(cell) + if(attachments.len) + for(var/obj/item/iterating_item in attachments) + qdel(iterating_item) + attachments = null + if(phase_emitter) + QDEL_NULL(phase_emitter) + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/gun/microfusion/handle_atom_del(atom/to_handle) + if(to_handle == cell) + cell = null + update_appearance() + if(to_handle == phase_emitter) + phase_emitter = null + update_appearance() + return ..() + +/obj/item/gun/microfusion/can_shoot() + return !QDELETED(cell) ? (cell.charge >= microfusion_lens.e_cost) : FALSE + +/obj/item/gun/microfusion/recharge_newshot() + if (!microfusion_lens || !cell || !phase_emitter) + return + chambered = microfusion_lens + if(!chambered.loaded_projectile) + chambered.newshot() + +/obj/item/gun/microfusion/handle_chamber() + if(chambered && !chambered.loaded_projectile && cell) //if loaded_projectile is null, i.e the shot has been fired... + var/obj/item/ammo_casing/energy/shot = chambered + cell.use(shot.e_cost + extra_power_usage)//... drain the cell + chambered = null //either way, released the prepared shot + recharge_newshot() //try to charge a new shot + +/obj/item/gun/microfusion/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) + if(!chambered && can_shoot()) + process_chamber() // If the gun was drained and then recharged, load a new shot. + return ..() + +/obj/item/gun/microfusion/update_icon_state() + var/skip_inhand = initial(inhand_icon_state) //only build if we aren't using a preset inhand icon + var/skip_worn_icon = initial(worn_icon_state) //only build if we aren't using a preset worn icon + + if(skip_inhand && skip_worn_icon) //if we don't have either, don't do the math. + return ..() + + var/ratio = get_charge_ratio() + var/temp_icon_to_use = initial(icon_state) + if(modifystate) + temp_icon_to_use += "[microfusion_lens.select_name]" + + temp_icon_to_use += "[ratio]" + if(!skip_inhand) + inhand_icon_state = temp_icon_to_use + if(!skip_worn_icon) + worn_icon_state = temp_icon_to_use + return ..() + +/obj/item/gun/microfusion/update_overlays() + . = ..() + SEND_SIGNAL(src, COMSIG_UPDATE_AMMO_HUD) //update the ammo hud since it's heavily dependent on the gun's state + if(!phase_emitter) + . += "[icon_state]_phase_emitter_missing" + else if(phase_emitter.damaged) + . += "[icon_state]_phase_emitter_damaged" + else if(cell) + var/ratio = get_charge_ratio() + if(ratio == 0 && display_empty) + . += "[icon_state]_empty" + else if(shaded_charge) + . += "[icon_state]_charge[ratio]_[phase_emitter.icon_state]" + else + . += "[icon_state]_phase_emitter_missing" + + + for(var/obj/item/microfusion_gun_attachment/microfusion_gun_attachment in attachments) + . += "[icon_state]_[microfusion_gun_attachment.attachment_overlay_icon_state]" + + +/obj/item/gun/microfusion/ignition_effect(atom/to_ignite, mob/living/user) + if(!can_shoot() || !microfusion_lens) + shoot_with_empty_chamber() + . = "" + else + var/obj/projectile/energy/loaded_projectile = microfusion_lens.loaded_projectile + if(!loaded_projectile) + . = "" + else if(loaded_projectile.nodamage || !loaded_projectile.damage || loaded_projectile.damage_type == STAMINA) + user.visible_message(span_danger("[user] tries to light [to_ignite.loc == user ? "[user.p_their()] [to_ignite.name]" : to_ignite] with [src], but it doesn't do anything. Dumbass.")) + playsound(user, microfusion_lens.fire_sound, 50, TRUE) + playsound(user, loaded_projectile.hitsound, 50, TRUE) + cell.use(microfusion_lens.e_cost) + . = "" + else if(loaded_projectile.damage_type != BURN) + user.visible_message(span_danger("[user] tries to light [to_ignite.loc == user ? "[user.p_their()] [to_ignite.name]" : to_ignite] with [src], but only succeeds in utterly destroying it. Dumbass.")) + playsound(user, microfusion_lens.fire_sound, 50, TRUE) + playsound(user, loaded_projectile.hitsound, 50, TRUE) + cell.use(microfusion_lens.e_cost) + qdel(to_ignite) + . = "" + else + playsound(user, microfusion_lens.fire_sound, 50, TRUE) + playsound(user, loaded_projectile.hitsound, 50, TRUE) + cell.use(microfusion_lens.e_cost) + . = span_danger("[user] casually lights [to_ignite.loc == user ? "[user.p_their()] [to_ignite.name]" : to_ignite] with [src]. Damn.") + +/obj/item/gun/microfusion/attackby(obj/item/attacking_item, mob/user, params) + . = ..() + if (.) + return + if(istype(attacking_item, base_cell_type)) + insert_cell(user, attacking_item) + if(istype(attacking_item, /obj/item/microfusion_gun_attachment)) + add_attachment(attacking_item, user) + if(istype(attacking_item, base_phase_emitter_type)) + insert_emitter(attacking_item, user) + +/obj/item/gun/microfusion/process_chamber(empty_chamber, from_firing, chamber_next_round) + . = ..() + if(!cell?.stabilised && prob(40)) + do_sparks(2, FALSE, src) //Microfusion guns create sparks! + +/obj/item/gun/microfusion/attack_hand(mob/user, list/modifiers) + if(loc == user && user.is_holding(src) && cell) + eject_cell(user) + return + return ..() + +/obj/item/gun/microfusion/crowbar_act(mob/living/user, obj/item/tool) + if(!phase_emitter) + to_chat(user, span_danger("There is no phase emitter for you to remove!")) + return + playsound(src, 'sound/items/crowbar.ogg', 70, TRUE) + remove_emitter() + +/obj/item/gun/microfusion/AltClick(mob/user) + . = ..() + if(can_interact(user)) + var/obj/item/microfusion_gun_attachment/to_remove = input(user, "Please select what part you'd like to remove.", "Remove attachment") as null|obj in sort_names(attachments) + if(!to_remove) + return + remove_attachment(to_remove, user) + +/obj/item/gun/microfusion/proc/remove_all_attachments() + if(attachments.len) + for(var/obj/item/microfusion_gun_attachment/attachment in attachments) + attachment.remove_attachment(src) + attachment.forceMove(get_turf(src)) + attachments -= attachment + update_appearance() + +/obj/item/gun/microfusion/examine(mob/user) + . = ..() + if(attachments.len) + for(var/obj/item/microfusion_gun_attachment/microfusion_gun_attachment in attachments) + . += span_notice("It has a [microfusion_gun_attachment.name] installed.") + . += span_notice("Alt+click it to remove an upgrade.") + if(phase_emitter) + . += span_notice("It has a [phase_emitter.name] installed, at [phase_emitter.get_heat_percent()]% heat capacity.") + . += span_notice("The [phase_emitter.name] is at [phase_emitter.integrity]% integrity.") + . += span_notice("The [phase_emitter.name] will thermal throttle at [phase_emitter.throttle_percentage]% heat capacity.") + . += span_notice("Use a crowbar to remove the phase emitter.") + else + . += span_danger("It does not have a phase emitter installed!") + + if(cell) + . += span_notice("It has a [cell.name] installed, with a capacity of [cell.charge]/[cell.maxcharge] MF.") + +/obj/item/gun/microfusion/suicide_act(mob/living/user) + if (istype(user) && can_shoot() && can_trigger_gun(user) && user.get_bodypart(BODY_ZONE_HEAD)) + user.visible_message(span_suicide("[user] is putting the barrel of [src] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide!")) + sleep(25) + if(user.is_holding(src)) + user.visible_message(span_suicide("[user] melts [user.p_their()] face off with [src]!")) + playsound(loc, fire_sound, 50, TRUE, -1) + cell.use(microfusion_lens.e_cost) + update_appearance() + return(FIRELOSS) + else + user.visible_message(span_suicide("[user] panics and starts choking to death!")) + return(OXYLOSS) + else + user.visible_message(span_suicide("[user] is pretending to melt [user.p_their()] face off with [src]! It looks like [user.p_theyre()] trying to commit suicide!")) + playsound(src, dry_fire_sound, 30, TRUE) + return (OXYLOSS) + +// To maintain modularity, I am moving this proc override here. +/obj/item/gun/microfusion/fire_gun(atom/target, mob/living/user, flag, params) + if(QDELETED(target)) + return + if(firing_burst) + return + if(flag) //It's adjacent, is the user, or is on the user's person + if(target in user.contents) //can't shoot stuff inside us. + return + if(!ismob(target) || user.combat_mode) //melee attack + return + if(target == user && user.zone_selected != BODY_ZONE_PRECISE_MOUTH) //so we can't shoot ourselves (unless mouth selected) + return + if(iscarbon(target)) + var/mob/living/carbon/carbon = target + for(var/i in carbon.all_wounds) + var/datum/wound/W = i + if(W.try_treating(src, user)) + return // another coward cured! + + if(istype(user))//Check if the user can use the gun, if the user isn't alive(turrets) assume it can. + var/mob/living/living = user + if(!can_trigger_gun(living)) + return + if(flag) + if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH) + handle_suicide(user, target, params) + return + + if(!can_shoot()) //Just because you can pull the trigger doesn't mean it can shoot. + shoot_with_empty_chamber(user) + return + + if(check_botched(user)) + return + + var/obj/item/bodypart/other_hand = user.has_hand_for_held_index(user.get_inactive_hand_index()) //returns non-disabled inactive hands + if(weapon_weight == WEAPON_HEAVY && (user.get_inactive_held_item() || !other_hand)) + to_chat(user, span_warning("You need two hands to fire [src]!")) + return + + var/attempted_shot = process_emitter() + if(attempted_shot != SHOT_SUCCESS) + if(attempted_shot) + to_chat(user, span_danger(attempted_shot)) + return + + //DUAL (or more!) WIELDING + var/bonus_spread = 0 + var/loop_counter = 0 + if(ishuman(user) && user.combat_mode) + var/mob/living/carbon/human/H = user + for(var/obj/item/gun/gun in H.held_items) + if(gun == src || gun.weapon_weight >= WEAPON_MEDIUM) + continue + else if(gun.can_trigger_gun(user)) + bonus_spread += dual_wield_spread + loop_counter++ + addtimer(CALLBACK(gun, /obj/item/gun.proc/process_fire, target, user, TRUE, params, null, bonus_spread), loop_counter) + + return process_fire(target, user, TRUE, params, null, bonus_spread) + +// To maintain modularity, I am moving this proc override here. +/obj/item/gun/microfusion/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) + if(user) + SEND_SIGNAL(user, COMSIG_MOB_FIRED_GUN, user, target, params, zone_override) + + SEND_SIGNAL(src, COMSIG_GUN_FIRED, user, target, params, zone_override) + + add_fingerprint(user) + + if(semicd) + return + + //Vary by at least this much + var/base_bonus_spread = 0 + var/calculated_spread = 0 + var/randomized_gun_spread = 0 + var/random_spread = rand() + if(user && HAS_TRAIT(user, TRAIT_POOR_AIM)) //Nice job hotshot + bonus_spread += 35 + base_bonus_spread += 10 + + if(spread) + randomized_gun_spread = rand(0,spread) + var/randomized_bonus_spread = rand(base_bonus_spread, bonus_spread) + + if(burst_size > 1) + firing_burst = TRUE + var/fire_delay_to_add = 0 + if(phase_emitter) + fire_delay_to_add = phase_emitter.fire_delay + for(var/i = 1 to burst_size) + addtimer(CALLBACK(src, .proc/process_burst, user, target, message, params, zone_override, calculated_spread, randomized_gun_spread, randomized_bonus_spread, random_spread, i), (fire_delay + fire_delay_to_add) * (i - 1)) + else + if(chambered) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal. + if(chambered.harmful) // Is the bullet chambered harmful? + to_chat(user, span_warning("[src] is lethally chambered! You don't want to risk harming anyone...")) + return + calculated_spread = round((rand(0, 1) - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread)) + before_firing(target,user) + process_microfusion() + if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, calculated_spread, src)) + shoot_with_empty_chamber(user) + return + else + if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot + shoot_live_shot(user, 1, target, message) + else + shoot_live_shot(user, 0, target, message) + else + shoot_with_empty_chamber(user) + return + process_chamber() + update_appearance() + semicd = TRUE + var/fire_delay_to_add = 0 + if(phase_emitter) + fire_delay_to_add = phase_emitter.fire_delay + addtimer(CALLBACK(src, .proc/reset_semicd), fire_delay + fire_delay_to_add) + + if(user) + user.update_inv_hands() + SSblackbox.record_feedback("tally", "gun_fired", 1, type) + + SEND_SIGNAL(src, COMSIG_UPDATE_AMMO_HUD) + + return TRUE + +// Same goes for this! +/obj/item/gun/microfusion/process_burst(mob/living/user, atom/target, message = TRUE, params=null, zone_override = "", calculated_spread = 0, randomized_gun_spread = 0, randomized_bonus_spread = 0, random_spread= 0, iteration = 0) + if(!user || !firing_burst) + firing_burst = FALSE + return FALSE + if(!can_shoot()) + firing_burst = FALSE + return FALSE + if(!chambered) + process_chamber() // Ditto. + if(!issilicon(user)) + if(iteration > 1 && !(user.is_holding(src))) //for burst firing + firing_burst = FALSE + return FALSE + if(chambered?.loaded_projectile) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal. + if(chambered.harmful) // Is the bullet chambered harmful? + to_chat(user, span_warning("[src] is lethally chambered! You don't want to risk harming anyone...")) + return + if(randomspread) + calculated_spread = round((rand(0, 1) - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread)) + else //Smart spread + calculated_spread = round((((random_spread/burst_size) * iteration) - (0.5 + (random_spread * 0.25))) * (randomized_gun_spread + randomized_bonus_spread)) + before_firing(target,user) + process_microfusion() + if(!chambered.fire_casing(target, user, params, ,suppressed, zone_override, calculated_spread, src)) + shoot_with_empty_chamber(user) + firing_burst = FALSE + return FALSE + else + if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot + shoot_live_shot(user, 1, target, message) + else + shoot_live_shot(user, 0, target, message) + if (iteration >= burst_size) + firing_burst = FALSE + else + shoot_with_empty_chamber(user) + firing_burst = FALSE + return FALSE + process_chamber() + update_appearance() + SEND_SIGNAL(src, COMSIG_UPDATE_AMMO_HUD) + return TRUE + +/obj/item/gun/microfusion/shoot_live_shot(mob/living/user, pointblank, atom/pbtarget, message) + if(recoil) + shake_camera(user, recoil + 1, recoil) + + var/sound_freq_to_add = 0 + + if(phase_emitter && phase_emitter.sound_freq > 1) + sound_freq_to_add = phase_emitter.sound_freq + + if(suppressed) + playsound(user, suppressed_sound, suppressed_volume, vary_fire_sound, ignore_walls = FALSE, extrarange = SILENCED_SOUND_EXTRARANGE, frequency = sound_freq_to_add, falloff_distance = 0) + else + playsound(user, fire_sound, fire_sound_volume, vary_fire_sound, frequency = sound_freq_to_add) + if(message) + if(pointblank) + user.visible_message(span_danger("[user] fires [src] point blank at [pbtarget]!"), \ + span_danger("You fire [src] point blank at [pbtarget]!"), \ + span_hear("You hear a gunshot!"), COMBAT_MESSAGE_RANGE, pbtarget) + to_chat(pbtarget, span_userdanger("[user] fires [src] point blank at you!")) + if(pb_knockback > 0 && ismob(pbtarget)) + var/mob/PBT = pbtarget + var/atom/throw_target = get_edge_target_turf(PBT, user.dir) + PBT.throw_at(throw_target, pb_knockback, 2) + else + user.visible_message(span_danger("[user] fires [src]!"), \ + span_danger("You fire [src]!"), \ + span_hear("You hear a gunshot!"), COMBAT_MESSAGE_RANGE) + if(user.resting) + user.Immobilize(20, TRUE) + + phase_emitter.add_heat(heat_per_shot) + + if(phase_emitter.current_heat > phase_emitter.max_heat) + if(ishuman(user)) + var/mob/living/carbon/human/human = user + var/obj/item/bodypart/affecting = human.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm") + if(affecting?.receive_damage( 0, 5 )) // 1 burn damage + to_chat(user, span_warning("[src] burns your hand, it's too hot!")) + +/obj/item/gun/microfusion/proc/process_microfusion() + if(attachments.len) + for(var/obj/item/microfusion_gun_attachment/attachment in attachments) + attachment.process_fire(src, chambered) + return TRUE + +/obj/item/gun/microfusion/proc/process_emitter() + if(!phase_emitter) + return SHOT_FAILURE_NO_EMITTER + var/phase_emitter_process = phase_emitter.check_emitter() + if(phase_emitter_process != SHOT_SUCCESS) + return phase_emitter_process + return SHOT_SUCCESS + +/obj/item/gun/microfusion/proc/instant_recharge() + SIGNAL_HANDLER + if(!cell) + return + cell.charge = cell.maxcharge + recharge_newshot() + update_appearance() + +///Used by update_icon_state() and update_overlays() +/obj/item/gun/microfusion/proc/get_charge_ratio() + return can_shoot() ? CEILING(clamp(cell.charge / cell.maxcharge, 0, 1) * charge_sections, 1) : 0 + // Sets the ratio to 0 if the gun doesn't have enough charge to fire, or if its power cell is removed. + +/** + * + * Outputs type-specific weapon stats for energy-based firearms based on its firing modes + * and the stats of those firing modes. Esoteric firing modes like ion are currently not supported + * but can be added easily + * + */ +/obj/item/gun/microfusion/proc/add_notes_energy() + var/list/readout = list() + // Make sure there is something to actually retrieve + if(!microfusion_lens) + return + var/obj/projectile/exam_proj + readout += "Our heroic interns have shown that one can theoretically stay standing after..." + exam_proj = GLOB.proj_by_path_key[microfusion_lens?.projectile_type] + + if(!istype(exam_proj)) + return readout.Join("\n") + + if(exam_proj.damage > 0) // Don't divide by 0!!!!! + readout += "[span_warning("[HITS_TO_CRIT(exam_proj.damage * microfusion_lens.pellets)] shot\s")] on [span_warning("[microfusion_lens.select_name]")] mode before collapsing from [exam_proj.damage_type == STAMINA ? "immense pain" : "their wounds"]." + if(exam_proj.stamina > 0) // In case a projectile does damage AND stamina damage (Energy Crossbow) + readout += "[span_warning("[HITS_TO_CRIT(exam_proj.stamina * microfusion_lens.pellets)] shot\s")] on [span_warning("[microfusion_lens.select_name]")] mode before collapsing from immense pain." + else + readout += "a theoretically infinite number of shots on [span_warning("[microfusion_lens.select_name]")] mode." + + return readout.Join("\n") // Sending over the singular string, rather than the whole list + +/obj/item/gun/microfusion/proc/update_microfusion_lens() + if(!microfusion_lens) + microfusion_lens = new(src) + fire_sound = microfusion_lens.fire_sound + fire_sound_volume = microfusion_lens.fire_sound_volume + fire_delay = microfusion_lens.delay + +// Cell, emitter and upgrade interactions + +/obj/item/gun/microfusion/proc/remove_emitter(mob/user) + playsound(src, sound_cell_insert, 50, TRUE) + phase_emitter.forceMove(get_turf(src)) + if(user) + user.put_in_hands(phase_emitter) + to_chat(user, span_notice("You remove [phase_emitter] from [src]!")) + phase_emitter.parent_gun = null + phase_emitter = null + update_appearance() + +/obj/item/gun/microfusion/proc/insert_emitter(obj/item/microfusion_phase_emitter/inserting_phase_emitter, mob/living/user) + if(phase_emitter) + to_chat(user, span_danger("There is already a phase emitter installed!")) + return FALSE + to_chat(user, span_notice("You carefully insert [inserting_phase_emitter] into the slot.")) + playsound(src, sound_cell_remove, 50, TRUE) + inserting_phase_emitter.forceMove(src) + phase_emitter = inserting_phase_emitter + phase_emitter.parent_gun = src + update_appearance() + + +/// Try to insert the cell into the gun, if successful, return TRUE +/obj/item/gun/microfusion/proc/insert_cell(mob/user, obj/item/stock_parts/cell/microfusion/inserting_cell, display_message = TRUE) + var/tactical_reload = FALSE //We need to do this so that cells don't fall on the ground. + var/obj/item/stock_parts/cell/old_cell = cell + if(cell) + if(reload_time && !HAS_TRAIT(user, TRAIT_INSTANT_RELOAD)) //This only happens when you're attempting a tactical reload, e.g. there's a mag already inserted. + if(display_message) + to_chat(user, span_notice("You start to insert [inserting_cell] into [src]!")) + if(!do_after(user, reload_time, src)) + if(display_message) + to_chat(user, span_warning("You fail to insert [inserting_cell] into [src]!")) + return FALSE + if(display_message) + to_chat(user, span_notice("You tactically reload [src], replacing [cell] inside!")) + tactical_reload = TRUE + eject_cell(user, FALSE, FALSE) + else if(display_message) + to_chat(user, span_notice("You insert [inserting_cell] into [src]!")) + if(sound_cell_insert) + playsound(src, sound_cell_insert, sound_cell_insert_volume, sound_cell_insert_vary) + cell = inserting_cell + inserting_cell.forceMove(src) + cell.parent_gun = src + if(tactical_reload) + user.put_in_hands(old_cell) + recharge_newshot() + update_appearance() + return TRUE + +/// Ejecting a cell. +/obj/item/gun/microfusion/proc/eject_cell(mob/user, display_message = TRUE, put_in_hands = TRUE) + var/obj/item/stock_parts/cell/microfusion/old_cell = cell + old_cell.forceMove(get_turf(src)) + if(user) + if(put_in_hands) + user.put_in_hands(old_cell) + if(display_message) + to_chat(user, span_notice("You remove [old_cell] from [src]!")) + if(sound_cell_remove) + playsound(src, sound_cell_remove, sound_cell_remove_volume, sound_cell_remove_vary) + old_cell.update_appearance() + cell.parent_gun = null + cell = null + update_appearance() + +/// Attatching an upgrade. +/obj/item/gun/microfusion/proc/add_attachment(obj/item/microfusion_gun_attachment/microfusion_gun_attachment, mob/living/user) + if(is_type_in_list(microfusion_gun_attachment, attachments)) + to_chat(user, span_warning("[src] already has [microfusion_gun_attachment] installed!")) + return FALSE + if(!(microfusion_gun_attachment.slot in attachment_slots)) + to_chat(user, span_warning("[src] cannot install [microfusion_gun_attachment]!")) + return FALSE + for(var/obj/item/microfusion_gun_attachment/iterating_attachment in attachments) + if(is_type_in_list(microfusion_gun_attachment, iterating_attachment.incompatable_attachments)) + to_chat(user, span_warning("[microfusion_gun_attachment] is not compatible with [iterating_attachment]!")) + return FALSE + if(iterating_attachment.slot != GUN_SLOT_UNIQUE && iterating_attachment.slot == microfusion_gun_attachment.slot) + to_chat(user, span_warning("[microfusion_gun_attachment] cannot be installed in the same slot as [iterating_attachment]!")) + return FALSE + attachments += microfusion_gun_attachment + microfusion_gun_attachment.forceMove(src) + microfusion_gun_attachment.run_attachment(src) + to_chat(user, span_notice("You successfully install [microfusion_gun_attachment] onto [src]!")) + playsound(src, 'sound/effects/structure_stress/pop2.ogg', 70, TRUE) + return TRUE + +/obj/item/gun/microfusion/proc/remove_attachment(obj/item/microfusion_gun_attachment/microfusion_gun_attachment, mob/living/user) + to_chat(user, span_notice("You remove [microfusion_gun_attachment] from [src]!")) + playsound(src, 'sound/items/screwdriver.ogg', 70) + microfusion_gun_attachment.forceMove(get_turf(src)) + attachments -= microfusion_gun_attachment + microfusion_gun_attachment.remove_attachment(src) + user?.put_in_hands(microfusion_gun_attachment) + update_appearance() + +/obj/item/gun/microfusion/proc/change_name(mob/user) + var/new_name = input(user, "Enter new name:", "Change gun name") as null|text + if(!new_name) + return + var/name_length = length(new_name) + if(name_length > GUN_MAX_NAME_CHARS && name_length < GUN_MIN_NAME_CHARS) + to_chat(user, span_warning("New name cannot be longer than 20 or shorter than 5 characters!")) + return + + name = sanitize(new_name) + update_appearance() + +// UI CONTROL + +/obj/item/gun/microfusion/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "MicrofusionGunControl") + ui.open() + +/obj/item/gun/microfusion/ui_data(mob/user) + var/list/data = list() + + data["gun_name"] = name + data["gun_desc"] = desc + data["gun_heat_dissipation"] = heat_dissipation_bonus + + if(phase_emitter) + data["has_emitter"] = TRUE + data["phase_emitter_data"] = list( + "type" = capitalize(phase_emitter.name), + "integrity" = phase_emitter.integrity, + "current_heat" = phase_emitter.current_heat, + "throttle_percentage" = phase_emitter.throttle_percentage, + "heat_dissipation_per_tick" = phase_emitter.heat_dissipation_per_tick, + "max_heat" = phase_emitter.max_heat, + "damaged" = phase_emitter.damaged, + "hacked" = phase_emitter.hacked, + "heat_percent" = phase_emitter.get_heat_percent(), + "process_time" = phase_emitter.fire_delay, + "cooling_system" = phase_emitter.cooling_system, + "cooling_system_rate" = phase_emitter.cooling_system_rate, + ) + else + data["has_emitter"] = FALSE + + if(cell) + var/list/attachments = list() + for(var/obj/item/microfusion_cell_attachment/attachment in cell.attachments) + attachments += attachment.name + data["has_cell"] = TRUE + data["cell_data"] = list( + "type" = capitalize(cell.name), + "charge" = cell.charge, + "max_charge" = cell.maxcharge, + "status" = cell.meltdown, + "attachments" = attachments, + ) + else + data["has_cell"] = FALSE + + + + if(attachments.len) + data["has_attachments"] = TRUE + data["attachments"] = list() + for(var/obj/item/microfusion_gun_attachment/attachment in attachments) + var/list/attachment_functions = attachment.get_modify_data() + var/has_modifications = FALSE + if(attachment_functions?.len > 0) + has_modifications = TRUE + data["attachments"] += list(list( + "name" = uppertext(attachment.name), + "desc" = attachment.desc, + "slot" = capitalize(attachment.slot), + "information" = attachment.get_information_data(), + "has_modifications" = has_modifications, + "modify" = attachment_functions, + "ref" = REF(attachment), + )) + + else + data["has_attachments"] = FALSE + + return data + +/obj/item/gun/microfusion/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("eject_cell") + if(!cell) + return + eject_cell(usr) + if("change_gun_name") + change_name(usr) + if("overclock_emitter") + if(!phase_emitter) + return + if(!phase_emitter.hacked) + return + phase_emitter.set_overclock(usr) + if("eject_emitter") + if(!phase_emitter) + return + remove_emitter(usr) + if("remove_attachment") + var/obj/item/microfusion_gun_attachment/to_remove = locate(params["attachment_ref"]) in src + if(!to_remove) + return + remove_attachment(to_remove, usr) + if("modify_attachment") + var/obj/item/microfusion_gun_attachment/to_modify = locate(params["attachment_ref"]) in src + if(!to_modify) + return + to_modify.run_modify_data(params["modify_ref"], usr, src) + if("toggle_cooling_system") + if(!phase_emitter) + return + phase_emitter.toggle_cooling_system(usr) + diff --git a/monkestation/code/modules/microfusion/code/microfusion_gun_attachments.dm b/monkestation/code/modules/microfusion/code/microfusion_gun_attachments.dm new file mode 100644 index 000000000000..b7f3e85967ca --- /dev/null +++ b/monkestation/code/modules/microfusion/code/microfusion_gun_attachments.dm @@ -0,0 +1,447 @@ +/** +*MICROFUSION GUN UPGRADE ATTACHMENTS +*For adding unique abilities to microfusion guns, these can directly interact with the gun! +*/ + +/obj/item/microfusion_gun_attachment + name = "microfusion gun attachment" + desc = "broken" + icon = 'modular_skyrat/modules/microfusion/icons/microfusion_gun_attachments.dmi' + w_class = WEIGHT_CLASS_NORMAL + /// The attachment overlay icon state. + var/attachment_overlay_icon_state + /// Any incompatable upgrade types. + var/list/incompatable_attachments = list() + /// The added heat produced by having this module installed. + var/heat_addition = 0 + /// The slot this attachment is installed in. + var/slot = GUN_SLOT_UNIQUE + /// How much extra power do we use? + var/power_usage = 0 + +/obj/item/microfusion_gun_attachment/examine(mob/user) + . = ..() + . += "Compatible slot: [slot]." + +/obj/item/microfusion_gun_attachment/proc/run_attachment(obj/item/gun/microfusion/microfusion_gun) + SHOULD_CALL_PARENT(TRUE) + microfusion_gun.heat_per_shot += heat_addition + microfusion_gun.update_appearance() + microfusion_gun.extra_power_usage += power_usage + return + +/obj/item/microfusion_gun_attachment/proc/process_attachment(obj/item/gun/microfusion/microfusion_gun) + return + +//Firing the gun right before we let go of it, tis is called. +/obj/item/microfusion_gun_attachment/proc/process_fire(obj/item/gun/microfusion/microfusion_gun, obj/item/ammo_casing/chambered) + return + +/obj/item/microfusion_gun_attachment/proc/remove_attachment(obj/item/gun/microfusion/microfusion_gun) + SHOULD_CALL_PARENT(TRUE) + microfusion_gun.heat_per_shot -= heat_addition + microfusion_gun.update_appearance() + microfusion_gun.extra_power_usage -= power_usage + return + +/* +Returns a list of modifications of this attachment, it must return a list within a list list(list()). +All of the following must be returned. +list(list("title" = "Toggle [toggle ? "OFF" : "ON"]", "icon" = "power-off", "color" = "blue" "reference" = "toggle_on_off")) +title - The title of the modification button +icon - The icon of the modification button +color - The color of the modification button +reference - The reference of the modification button, this is used to call the proc when the run modify data proc is called. +*/ +/obj/item/microfusion_gun_attachment/proc/get_modify_data() + return + +/obj/item/microfusion_gun_attachment/proc/run_modify_data(params, mob/living/user, obj/item/gun/microfusion/microfusion_gun) + return + +/obj/item/microfusion_gun_attachment/proc/get_information_data() + return + +/* +SCATTER ATTACHMENT + +The cell is stable and will not emit sparks when firing. +*/ +/obj/item/microfusion_gun_attachment/scatter + name = "diffuser microfusion lens upgrade" + desc = "A diffusing lens system capable of splitting one beam into three. However, the additional ionizing of the air will cause higher recoil." + icon_state = "attachment_scatter" + attachment_overlay_icon_state = "attachment_scatter" + slot = GUN_SLOT_BARREL + /// How many pellets are we going to add to the existing amount on the gun? + var/pellets_to_add = 2 + /// The variation in pellet scatter. + var/variance_to_add = 20 + /// How much recoil are we adding? + var/recoil_to_add = 1 + /// The spread to add. + var/spread_to_add = 10 + +/obj/item/microfusion_gun_attachment/scatter/run_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.recoil += recoil_to_add + microfusion_gun.spread += spread_to_add + microfusion_gun.microfusion_lens.pellets += pellets_to_add + microfusion_gun.microfusion_lens.variance += variance_to_add + +/obj/item/microfusion_gun_attachment/scatter/process_fire(obj/item/gun/microfusion/microfusion_gun, obj/item/ammo_casing/chambered) + . = ..() + chambered.loaded_projectile?.damage = chambered.loaded_projectile.damage / chambered.pellets + +/obj/item/microfusion_gun_attachment/scatter/remove_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.recoil -= recoil_to_add + microfusion_gun.spread -= spread_to_add + microfusion_gun.microfusion_lens.pellets -= microfusion_gun.microfusion_lens.pellets + microfusion_gun.microfusion_lens.variance -= microfusion_gun.microfusion_lens.variance + +/* +REPEATER ATTACHMENT + +The gun can fire volleys of shots. +*/ +/obj/item/microfusion_gun_attachment/superheat + name = "superheating phase emitter upgrade" + desc = "A barrel attachment hooked to the phase emitter, this adjusts the beam's wavelength to carry an intense wave of heat; causing targets to ignite." + icon_state = "attachment_superheat" + attachment_overlay_icon_state = "attachment_superheat" + incompatable_attachments = list(/obj/item/microfusion_gun_attachment/scatter) + heat_addition = 70 + slot = GUN_SLOT_BARREL + var/projectile_override =/obj/projectile/beam/laser/microfusion/superheated + +/obj/item/microfusion_gun_attachment/superheat/run_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.fire_sound = 'modular_skyrat/modules/microfusion/sound/vaporize.ogg' + +/obj/item/microfusion_gun_attachment/superheat/remove_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.fire_sound = microfusion_gun.chambered?.fire_sound + + +/obj/item/microfusion_gun_attachment/superheat/process_fire(obj/item/gun/microfusion/microfusion_gun, obj/item/ammo_casing/chambered) + . = ..() + chambered.loaded_projectile = new projectile_override + +/* +REPEATER ATTACHMENT + +The gun can fire volleys of shots. +*/ +/obj/item/microfusion_gun_attachment/repeater + name = "repeating phase emitter upgrade" + desc = "This barrel attachment upgrades the central phase emitter to fire off two beams in quick succession. While offering an increased rate of fire, the heat output and recoil rises too." + icon_state = "attachment_repeater" + attachment_overlay_icon_state = "attachment_repeater" + heat_addition = 40 + slot = GUN_SLOT_BARREL + /// The spread to add to the gun. + var/spread_to_add = 15 + /// The recoil to add to the gun. + var/recoil_to_add = 1 + /// The burst to add to the gun. + var/burst_to_add = 1 + /// The delay to add to the firing. + var/delay_to_add = 2 + +/obj/item/microfusion_gun_attachment/repeater/run_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.recoil += recoil_to_add + microfusion_gun.burst_size += burst_to_add + microfusion_gun.fire_delay += delay_to_add + microfusion_gun.spread += spread_to_add + +/obj/item/microfusion_gun_attachment/repeater/remove_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.recoil -= recoil_to_add + microfusion_gun.burst_size -= burst_to_add + microfusion_gun.fire_delay -= delay_to_add + microfusion_gun.spread -= spread_to_add + +/* +X-RAY ATTACHMENT + +The gun can fire X-RAY shots. +*/ +/obj/item/microfusion_gun_attachment/xray + name = "quantum phase inverter array" //Yes quantum makes things sound cooler. + desc = "An experimental barrel attachment that modifies the central phase emitter, causing the wave frequency to shift into X-ray. Capable of penetrating both glass and solid matter with ease, though the bolts don't carry a greater effect against armor, due to going through the target and doing more minimal internal damage. These attachments are power-hungry and overheat easily, though engineers have deemed the costs necessary drawbacks." + icon_state = "attachment_xray" + slot = GUN_SLOT_BARREL + attachment_overlay_icon_state = "attachment_xray" + heat_addition = 90 + power_usage = 50 + +/obj/item/microfusion_gun_attachment/xray/examine(mob/user) + . = ..() + . += span_warning("CAUTION: Phase emitter heats up extremely quickly, sustained fire not recommended!") + +/obj/item/microfusion_gun_attachment/xray/run_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.fire_sound = 'modular_skyrat/modules/microfusion/sound/incinerate.ogg' + +/obj/item/microfusion_gun_attachment/xray/remove_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.fire_sound = microfusion_gun.chambered?.fire_sound + +/obj/item/microfusion_gun_attachment/xray/process_fire(obj/item/gun/microfusion/microfusion_gun, obj/item/ammo_casing/chambered) + . = ..() + chambered.loaded_projectile.icon_state = "laser_greyscale" + chambered.loaded_projectile.color = COLOR_GREEN + chambered.loaded_projectile.light_color = COLOR_GREEN + chambered.loaded_projectile.projectile_piercing = PASSCLOSEDTURF|PASSGRILLE|PASSGLASS + +/* +GRIP ATTACHMENT + +Greatly reduces recoil and spread. +*/ +/obj/item/microfusion_gun_attachment/grip + name = "grip attachment" + desc = "A simple grip that increases accuracy." + icon_state = "attachment_grip" + attachment_overlay_icon_state = "attachment_grip" + slot = GUN_SLOT_UNDERBARREL + /// How much recoil are we removing? + var/recoil_to_remove = 1 + /// How much spread are we removing? + var/spread_to_remove = 10 + +/obj/item/microfusion_gun_attachment/grip/run_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.recoil -= recoil_to_remove + microfusion_gun.spread -= spread_to_remove + +/obj/item/microfusion_gun_attachment/grip/remove_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.recoil += recoil_to_remove + microfusion_gun.spread += spread_to_remove + +/* +HEATSINK ATTACHMENT + +"Greatly increases the phase emitter cooling rate." +*/ +/obj/item/microfusion_gun_attachment/heatsink + name = "phase emitter heatsink" + desc = "Greatly increases the phase emitter cooling rate." + icon_state = "attachment_heatsink" + attachment_overlay_icon_state = "attachment_heatsink" + slot = GUN_SLOT_UNDERBARREL + /// Coolant bonus + var/cooling_rate_increase = 50 + +/obj/item/microfusion_gun_attachment/heatsink/run_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.heat_dissipation_bonus += cooling_rate_increase + +/obj/item/microfusion_gun_attachment/heatsink/remove_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.heat_dissipation_bonus -= cooling_rate_increase + +/* +UNDERCHARGER ATTACHMENT + +Massively decreases the output beam of the phase emitter. +Converts shots to STAMNINA damage. +*/ +/obj/item/microfusion_gun_attachment/undercharger + name = "phase emitter undercharger" + desc = "An underbarrel system hooked to the phase emitter, this allows the weapon to also fire an electron bolt, producing a short-lived underpowered electric charge capable of stunning targets. These shots are less demanding on the weapon, leading to an increase in cooling rate." + icon_state = "attachment_undercharger" + attachment_overlay_icon_state = "attachment_undercharger" + slot = GUN_SLOT_UNDERBARREL + var/toggle = FALSE + var/cooling_rate_increase = 10 + /// The projectile we override + var/projectile_override = /obj/projectile/beam/microfusion_disabler + /// How much recoil are we removing? + var/recoil_to_remove = 1 + /// How much spread are we removing? + var/spread_to_remove = 10 + +/obj/item/microfusion_gun_attachment/undercharger/get_modify_data() + return list(list("title" = "Turn [toggle ? "OFF" : "ON"]", "icon" = "power-off", "color" = "[toggle ? "red" : "green"]", "reference" = "toggle_on_off")) + +/obj/item/microfusion_gun_attachment/undercharger/run_modify_data(params, mob/living/user, obj/item/gun/microfusion/microfusion_gun) + if(params == "toggle_on_off") + toggle(microfusion_gun, user) + +/obj/item/microfusion_gun_attachment/undercharger/proc/toggle(obj/item/gun/microfusion/microfusion_gun, mob/user) + if(toggle) + toggle = FALSE + microfusion_gun.heat_dissipation_bonus -= cooling_rate_increase + microfusion_gun.recoil -= recoil_to_remove + microfusion_gun.spread -= spread_to_remove + else + toggle = TRUE + microfusion_gun.heat_dissipation_bonus += cooling_rate_increase + microfusion_gun.recoil += recoil_to_remove + microfusion_gun.spread += spread_to_remove + + if(user) + to_chat(user, span_notice("You toggle [src] [toggle ? "ON" : "OFF"].")) + +/obj/item/microfusion_gun_attachment/undercharger/run_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.fire_sound = 'modular_skyrat/modules/microfusion/sound/burn.ogg' + +/obj/item/microfusion_gun_attachment/undercharger/process_fire(obj/item/gun/microfusion/microfusion_gun, obj/item/ammo_casing/chambered) + . = ..() + if(toggle) + chambered.loaded_projectile = new projectile_override + +/obj/item/microfusion_gun_attachment/undercharger/remove_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + if(toggle) + microfusion_gun.heat_dissipation_bonus -= cooling_rate_increase + microfusion_gun.recoil -= recoil_to_remove + microfusion_gun.spread -= spread_to_remove + microfusion_gun.fire_sound = microfusion_gun.chambered?.fire_sound + +/* +RGB ATTACHMENT + +Enables you to change the light color of the laser. +*/ +/obj/item/microfusion_gun_attachment/rgb + name = "phase emitter spectrograph" + desc = "An attachment hooked up to the phase emitter, allowing the user to adjust the color of the beam outputted. This has seen widespread use by various factions capable of getting their hands on microfusion weapons, whether as a calling card or simply for entertainment." + icon_state = "attachment_rgb" + attachment_overlay_icon_state = "attachment_rgb" + /// What color are we changing the sprite to? + var/color_to_apply = COLOR_MOSTLY_PURE_RED + +/obj/item/microfusion_gun_attachment/rgb/process_fire(obj/item/gun/microfusion/microfusion_gun, obj/item/ammo_casing/chambered) + . = ..() + chambered?.loaded_projectile.icon_state = "laser_greyscale" + chambered?.loaded_projectile.color = color_to_apply + chambered?.loaded_projectile.light_color = color_to_apply + +/obj/item/microfusion_gun_attachment/rgb/proc/select_color(mob/living/user) + var/new_color = input(user, "Please select your new projectile color", "Laser color", color_to_apply) as null|color + + if(!new_color) + return + + color_to_apply = new_color + +/obj/item/microfusion_gun_attachment/rgb/attack_self(mob/user, modifiers) + . = ..() + select_color(user) + +/obj/item/microfusion_gun_attachment/rgb/get_modify_data() + return list(list("title" = "Change Color", "icon" = "wrench", "reference" = "color", "color" = "blue")) + +/obj/item/microfusion_gun_attachment/rgb/run_modify_data(params, mob/living/user) + if(params == "color") + select_color(user) + +/* +RAIL ATTACHMENT + +Allows for flashlights bayonets and adds 1 slot to equipment. +*/ +/obj/item/microfusion_gun_attachment/rail + name = "gun rail attachment" + desc = "A simple set of rails that attaches to weapon hardpoints. Allows for 3 more attachment slots and the instillation of a flashlight or bayonet." + icon_state = "attachment_rail" + attachment_overlay_icon_state = "attachment_rail" + slot = GUN_SLOT_RAIL + +/obj/item/microfusion_gun_attachment/rail/run_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.can_flashlight = TRUE + microfusion_gun.can_bayonet = TRUE + +/obj/item/microfusion_gun_attachment/rail/remove_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.gun_light = initial(microfusion_gun.can_flashlight) + if(microfusion_gun.gun_light) + microfusion_gun.gun_light.forceMove(get_turf(microfusion_gun)) + microfusion_gun.clear_gunlight() + microfusion_gun.can_bayonet = initial(microfusion_gun.can_bayonet) + if(microfusion_gun.bayonet) + microfusion_gun.bayonet.forceMove(get_turf(microfusion_gun)) + microfusion_gun.clear_bayonet() + microfusion_gun.remove_all_attachments() + +/* +SCOPE ATTACHMENT + +Allows for a scope to be attached to the gun. +DANGER: SNOWFLAKE ZONE +*/ +/obj/item/microfusion_gun_attachment/scope + name = "scope attachment" + desc = "A simple telescopic scope, allowing for long-ranged use of the weapon. However, these do not provide any night vision." + icon_state = "attachment_scope" + attachment_overlay_icon_state = "attachment_scope" + slot = GUN_SLOT_RAIL + +/obj/item/microfusion_gun_attachment/scope/run_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + if(microfusion_gun.azoom) + return + + microfusion_gun.azoom = new() + microfusion_gun.azoom.gun = microfusion_gun + microfusion_gun.update_action_buttons() + +/obj/item/microfusion_gun_attachment/scope/remove_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + if(microfusion_gun.azoom) + microfusion_gun.azoom.Remove(microfusion_gun.azoom.owner) + QDEL_NULL(microfusion_gun.azoom) + microfusion_gun.update_action_buttons() + +/* +BLACK CAMO ATTACHMENT + +Allows for a black camo to be applied to the gun. +All tactical, all the time. +*/ +/obj/item/microfusion_gun_attachment/black_camo + name = "black camo microfusion frame" + desc = "A frame modification for the MCR-10, changing the color of the gun to black." + icon_state = "attachment_black" + attachment_overlay_icon_state = "attachment_black" + +/* +PULSE ATTACHMENT + +The gun can fire PULSE shots. +*/ +/obj/item/microfusion_gun_attachment/pulse + name = "pulse induction carriage" + desc = "A cutting-edge bluespace capacitor array and distributing lens overhaul produced in laboratories by Nanotrasen scientists that allow microfusion rifles to fire military-grade pulse rounds. Comes equipped with cyclic cooling to ensure maximum combat efficiency, a munitions counter, and an extra-secure drop cage for the power source. May shorten trigger lifetime." + icon_state = "attachment_pulse" + slot = GUN_SLOT_BARREL + attachment_overlay_icon_state = "attachment_pulse" + heat_addition = 150 + power_usage = 50 + var/added_burst_size = 2 + var/added_fire_delay = 2 + +/obj/item/microfusion_gun_attachment/pulse/examine(mob/user) + . = ..() + . += span_warning("CAUTION: Phase emitter heats up extremely quickly, sustained fire not recommended!") + +/obj/item/microfusion_gun_attachment/pulse/run_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.burst_size += added_burst_size + microfusion_gun.fire_delay += added_fire_delay + +/obj/item/microfusion_gun_attachment/pulse/remove_attachment(obj/item/gun/microfusion/microfusion_gun) + . = ..() + microfusion_gun.burst_size -= added_burst_size + microfusion_gun.fire_delay -= added_fire_delay + +/obj/item/microfusion_gun_attachment/pulse/process_fire(obj/item/gun/microfusion/microfusion_gun, obj/item/ammo_casing/chambered) + . = ..() + chambered.loaded_projectile = new /obj/projectile/beam/pulse diff --git a/monkestation/code/modules/microfusion/code/microfusion_techweb.dm b/monkestation/code/modules/microfusion/code/microfusion_techweb.dm new file mode 100644 index 000000000000..a673f7fce652 --- /dev/null +++ b/monkestation/code/modules/microfusion/code/microfusion_techweb.dm @@ -0,0 +1,87 @@ +/datum/techweb_node/basic_microfusion + id = "basic_microfusion" + starting_node = TRUE + display_name = "Basic Microfusion Technology" + description = "Basic microfusion technology allowing for basic microfusion designs." + design_ids = list( + "basic_microfusion_cell", + "microfusion_phase_emitter_undercharger", + ) + +//Enhanced microfusion +/datum/techweb_node/enhanced_microfusion + id = "enhanced_microfusion" + display_name = "Enhanced Microfusion Technology" + description = "Enhanced microfusion technology allowing for upgraded basic microfusion!" + prereq_ids = list( + "basic_microfusion", + "engineering", + "weaponry", + "high_efficiency", + ) + design_ids = list( + "enhanced_microfusion_cell", + "microfusion_cell_attachment_rechargeable", + "enhanced_microfusion_phase_emitter", + "microfusion_gun_attachment_black_camo", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3500) + +//Advanced microfusion +/datum/techweb_node/advanced_microfusion + id = "advanced_microfusion" + display_name = "Advanced Microfusion Technology" + description = "Advanced microfusion technology allowing for advanced microfusion!" + prereq_ids = list( + "enhanced_microfusion", + "adv_engi", + "adv_weaponry", + "adv_power", + "adv_plasma", + ) + design_ids = list( + "advanced_microfusion_cell", + "microfusion_cell_attachment_overcapacity", + "microfusion_cell_attachment_stabiliser", + "microfusion_gun_attachment_scatter", + "microfusion_gun_attachment_superheat", + "advanced_microfusion_phase_emitter", + "microfusion_gun_attachment_grip", + "microfusion_gun_attachment_rail", + "microfusion_gun_attachment_scope", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000) + + +// Bluespace microfusion +/datum/techweb_node/bluespace_microfusion + id = "bluespace_microfusion" + display_name = "Bluespace Microfusion Technology" + description = "Bluespace tinkering plus microfusion technology!" + prereq_ids = list( + "advanced_microfusion", + "bluespace_power", + "beam_weapons", + "explosive_weapons", + ) + design_ids = list( + "bluespace_microfusion_cell", + "microfusion_gun_attachment_repeater", + "bluespace_microfusion_phase_emitter", + "microfusion_cell_attachment_selfcharging", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000) + +// Bluespace microfusion +/datum/techweb_node/quantum_microfusion + id = "quantum_microfusion" + display_name = "Quantum Microfusion Technology" + description = "Bleeding edge microfusion tech, making use of the latest in materials and components, bluespace or otherwise." + prereq_ids = list( + "bluespace_microfusion", + "alientech", + ) + design_ids = list( + "microfusion_gun_attachment_xray", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 15000) diff --git a/monkestation/code/modules/microfusion/code/phase_emitter.dm b/monkestation/code/modules/microfusion/code/phase_emitter.dm new file mode 100644 index 000000000000..99a72ec27f05 --- /dev/null +++ b/monkestation/code/modules/microfusion/code/phase_emitter.dm @@ -0,0 +1,211 @@ +////////////////////////////////////////////////////////////// PHASE EMITTERS +/* +Basically the heart of the gun, can be upgraded. +*/ +/obj/item/microfusion_phase_emitter + name = "basic microfusion phase emitter" + desc = "A first-generation phase emitter, this is the core of the weapon and the source of the beam." + icon = 'modular_skyrat/modules/microfusion/icons/microfusion_gun_attachments.dmi' + icon_state = "phase_emitter" + base_icon_state = "phase_emitter" + w_class = WEIGHT_CLASS_NORMAL + /// Max heat before it breaks + var/max_heat = 2000 + /// Current heat level + var/current_heat = 0 + /// Thermal throttle percentage + var/throttle_percentage = 80 + /// How much heat it dissipates passively + var/heat_dissipation_per_tick = 30 + /// Active cooling system + var/cooling_system = FALSE + /// How quickly does the active cooling system cool - 1/1 ratio of cell charge to cooling point + var/cooling_system_rate = 30 + /// What is our dynamic integrity? + var/integrity = 100 + /// Are we fucked? + var/damaged = FALSE + /// Hard ref to the gun. + var/obj/item/gun/microfusion/parent_gun + /// Are we "hacked" thus allowing overclocking? + var/hacked = FALSE + /// The fire delay this emitter adds to the gun. + var/fire_delay = 0 + /// The sound playback speed, used for overheating sound effects on fire. + var/sound_freq = 0 + + +/obj/item/microfusion_phase_emitter/Initialize(mapload) + . = ..() + START_PROCESSING(SSobj, src) + +/obj/item/microfusion_phase_emitter/Destroy() + parent_gun = null + return ..() + +/obj/item/microfusion_phase_emitter/process(delta_time) + if(current_heat == 0) + return + var/calculated_heat_dissipation_per_tick = heat_dissipation_per_tick + if(isspaceturf(get_turf(src))) + calculated_heat_dissipation_per_tick += PHASE_HEAT_DISSIPATION_BONUS_SPACE // Passive cooling in space boost! + if(parent_gun) + calculated_heat_dissipation_per_tick += parent_gun.heat_dissipation_bonus + else + calculated_heat_dissipation_per_tick += PHASE_HEAT_DISSIPATION_BONUS_AIR //We get some passive cooling from being out of the gun. + if(cooling_system && parent_gun && parent_gun.cell && parent_gun.cell.use(cooling_system_rate)) + calculated_heat_dissipation_per_tick += cooling_system_rate + + current_heat = clamp(current_heat - (calculated_heat_dissipation_per_tick * delta_time) * 0.5, 0, INFINITY) + if(current_heat > max_heat) + integrity = integrity - current_heat / 1000 * delta_time * 0.5 + + process_fire_delay_and_sound() + + if(integrity <= 0) + kill() + update_appearance() + parent_gun?.update_appearance() + +/obj/item/microfusion_phase_emitter/proc/toggle_cooling_system(mob/user) + if(!parent_gun) + return + if(!cooling_system && !parent_gun.cell) + if(user) + to_chat(user, span_warning("You need a cell to turn on the cooling system.")) + return + + if(cooling_system) + cooling_system = FALSE + else + cooling_system = TRUE + + if(user) + to_chat(user, span_notice("You toggle the cooling system [cooling_system ? "ON" : "OFF"].")) + +/obj/item/microfusion_phase_emitter/multitool_act(mob/living/user, obj/item/tool) + if(hacked) + to_chat(user, span_warning("[src] is already unlocked!")) + return + to_chat(user, span_notice("You begin to override the thermal overclock safety...")) + if(do_after(user, 5 SECONDS, src)) + hacked = TRUE + to_chat(user, span_notice("You override the thermal overclock safety.")) + +/obj/item/microfusion_phase_emitter/proc/set_overclock(mob/living/user) + if(!hacked) + return + var/new_throttle = clamp(input(user, "Please input a new thermal throttle percentage(0-300):", "Phase Emitter Overclock") as null|num, 1, 300) + + to_chat(user, span_notice("Thermal throttle percent set to: [new_throttle].")) + + if(new_throttle > 100) + to_chat(user, span_danger("WARNING: You have input a throttle percentage of more than 100, this may cause emitter damage.")) + + throttle_percentage = new_throttle + +/obj/item/microfusion_phase_emitter/update_icon_state() + . = ..() + + if(damaged) + icon_state = "[base_icon_state]_damaged" + else + switch(get_heat_percent()) + if(40 to 69) + icon_state = "[base_icon_state]_hot" + if(70 to INFINITY) + icon_state = "[base_icon_state]_critical" + else + icon_state = base_icon_state + +/obj/item/microfusion_phase_emitter/proc/process_fire_delay_and_sound() + var/fire_delay_to_add = 0 + if(integrity < 100) + fire_delay_to_add = fire_delay_to_add + (100 - integrity) / 10 + + if(current_heat > max_heat) + fire_delay_to_add = fire_delay_to_add + (current_heat - max_heat) / 100 //Holy shit this emitter is tanking + + fire_delay = round(fire_delay_to_add, 1.45) + +/obj/item/microfusion_phase_emitter/proc/get_heat_icon_state() + switch(get_heat_percent()) + if(40 to 69) + return "hot" + if(70 to INFINITY) + return "critical" + else + return "normal" + +/obj/item/microfusion_phase_emitter/examine(mob/user) + . = ..() + if(damaged) + . += span_danger("It is damaged beyond repair.") + else + . += span_notice("It has a thermal rating of: [max_heat] C") + . += span_notice("It dissipates heat at: [heat_dissipation_per_tick] C") + . += span_notice("Heat capacity: [get_heat_percent()]%") + . += span_notice("Integrity: [integrity]%") + . += span_notice("Thermal throttle: [throttle_percentage]%") + . += span_notice("Cooling system: [cooling_system ? "enabled, cooling at [cooling_system_rate] C/s" : "disabled"].") + +/obj/item/microfusion_phase_emitter/proc/get_heat_percent() + return round(current_heat / max_heat * 100) + +/obj/item/microfusion_phase_emitter/proc/check_emitter() + if(damaged) + return PHASE_FAILURE_DAMAGED + if(get_heat_percent() >= throttle_percentage) + return PHASE_FAILURE_THROTTLE + return SHOT_SUCCESS + +/obj/item/microfusion_phase_emitter/proc/add_heat(heat_to_add) + current_heat += heat_to_add + update_appearance() + +/obj/item/microfusion_phase_emitter/proc/kill() + damaged = TRUE + name = "damaged [name]" + playsound(src, 'modular_skyrat/modules/microfusion/sound/overheat.ogg', 70) + say("ERROR: Integrity failure!") + STOP_PROCESSING(SSobj, src) + +/obj/item/microfusion_phase_emitter/enhanced + name = "enhanced microfusion phase emitter" + desc = "A second-generation phase emitter, this one is made of more robust materials which allow for a higher capacity for heat, a faster dissipation and cooling of it, and more capacity for thermal throttling." + max_heat = 3000 + throttle_percentage = 85 + heat_dissipation_per_tick = 40 + cooling_system_rate = 40 + integrity = 120 + color = "#ffffcc" + +/obj/item/microfusion_phase_emitter/advanced + name = "advanced microfusion phase emitter" + desc = "A third-generation phase emitter, boasting a high capacity for heat, greater dissipation and cooling, and is built using higher-grade materials for more durability." + max_heat = 4000 + throttle_percentage = 90 + heat_dissipation_per_tick = 50 + cooling_system_rate = 50 + integrity = 150 + color = "#99ffcc" + +/obj/item/microfusion_phase_emitter/bluespace + name = "bluespace microfusion phase emitter" + desc = "A fourth-generation phase emitter, utilizing a bluespace medium to store and manage heat, allowing for much cooler temperatures than realspace would allow. This is made of nothing but the latest materials, leading to the highest durability of any phase emitter on the market." + max_heat = 5000 + throttle_percentage = 95 + heat_dissipation_per_tick = 60 + cooling_system_rate = 60 + integrity = 200 + color = "#66ccff" + +/obj/item/microfusion_phase_emitter/nanocarbon + name = "nanocarbon microfusion phase emitter" + desc = "An experimental phase emitter, made of nanocarbon, which is the most durable material on the market. It is capable of storing and managing heat, and is capable of cooling at a much higher rate than the other phase emitters." + max_heat = 15000 + throttle_percentage = 95 + heat_dissipation_per_tick = 130 + cooling_system_rate = 60 + integrity = 500 + color = "#6966ff" diff --git a/monkestation/code/modules/microfusion/code/projectiles.dm b/monkestation/code/modules/microfusion/code/projectiles.dm new file mode 100644 index 000000000000..b11e1446f25c --- /dev/null +++ b/monkestation/code/modules/microfusion/code/projectiles.dm @@ -0,0 +1,44 @@ +/obj/item/ammo_casing + ///What volume should the sound play at? + var/fire_sound_volume = 50 + +/obj/item/ammo_casing/energy/laser/microfusion + name = "microfusion energy lens" + projectile_type = /obj/projectile/beam/laser/microfusion + e_cost = 100 // 10 shots with a normal cell. + select_name = "laser" + fire_sound = 'modular_skyrat/modules/microfusion/sound/laser_1.ogg' + fire_sound_volume = 100 + +/obj/projectile/beam/laser/microfusion + name = "microfusion laser" + icon = 'modular_skyrat/modules/microfusion/icons/projectiles.dmi' + +/obj/projectile/beam/microfusion_disabler + name = "microfusion disabler laser" + icon = 'modular_skyrat/modules/microfusion/icons/projectiles.dmi' + icon_state = "disabler" + damage = 41 + damage_type = STAMINA + flag = ENERGY + hitsound = 'sound/weapons/tap.ogg' + eyeblur = 0 + impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser + light_color = LIGHT_COLOR_BLUE + tracer_type = /obj/effect/projectile/tracer/disabler + muzzle_type = /obj/effect/projectile/muzzle/disabler + impact_type = /obj/effect/projectile/impact/disabler + +/obj/projectile/beam/laser/microfusion/superheated + name = "superheated microfusion laser" + icon_state = "laser_greyscale" + color = LIGHT_COLOR_FIRE + light_color = LIGHT_COLOR_FIRE + + +/obj/projectile/beam/laser/microfusion/superheated/on_hit(atom/target, blocked) + . = ..() + if(isliving(target)) + var/mob/living/living = target + living.fire_stacks += 2 + living.IgniteMob() diff --git a/monkestation/code/modules/microfusion/icons/guns_lefthand.dmi b/monkestation/code/modules/microfusion/icons/guns_lefthand.dmi new file mode 100644 index 0000000000000000000000000000000000000000..6690df96ac0be99c93cc597cec6d6085e63ccdb6 GIT binary patch literal 840 zcmV-O1GoH%P) 005u_0{{R3dEt5<0000aP)t-sz`(#a zHbNjGCzO|%!o 00001bW%=J06^y0W&i*H zdwNt@bVOxyV{&P5bZKvH004NLQ&w -6vuBATk52h zw?`$4n?L#%C#e;2vjr8MI!M*6sDo~A65PT=>M9ifPn%p5Pw)ArH8~kNoX?<_=6r6_ zOdrx1qbQ2 _O)% zXWQ&}>L&yzFLwB!X`g}(>HO^jKc4t0tBpH59P--{LFOy3NBnr=r}*_&{5=K-bNmL4 zAoI2B9ezCV$u+MJM}OyAf$`CXWWLcvr?fu3=Jx*R-tF(U;eKR(ei)z;>yIhHhglf@ zfnS);U+v&?M*bLU^;5Ih>EQjy{3pCX>oXQs*RnQD=R0-0p`s{?q8QIF*_ZYp^QAq= z{9-+LndKMl!Huq6gpOah2akK!?>c_*9(<4g_xg@6?ZIcqm-Zm@r9J5S(jH{Kv ACvTBL~or7!LiM;n%jekA37g= z_hUrmW *1+TZ;A|@acR+t{w#b*nC8;9t1v}kIB`8z^C&u z1$z)ak5m*zQIs(}zvK;PwFfWKW&Y=jum`vLcG1T@ctO@x*B_uBY}%ZvhPVfl^F=&6 zehv5Fy&d*(4>n2u{dfF{J-C_GgZJrj?$Pn3J?Q$<9%R0>2bnMJLFP+)P=9Y$6h%=o z@%)lEoP{0)h Oq902O*Lkgh+Z2BYppnA$|W4D1q$#Lx%MILx}dEGMz80->6fE SiPA>^0000 005u_0{{R3dEt5<0000aP)t-sz`(#D zA}2RCLX?-7!o gT00001bW%=J06^y0W&i*H zeR@<_bVOxyV{&P5bZKvH004NLQ&w gNIu7yvksP0orxTqXbj0xwBKK~!jg?U_AK0znXl_wE7# zD@9_olVGetEew^$k6@>XsC1rQuAn#3jzln_!$j2Hgx@Ks@n)9Ub-AG-&QtO3-8;K{ z6i*JN1VIp{qA=@$%j@a8KGF3zDyjRKnCN=jdw=a|>|Z_-cl);$b(+K=20>j|` Yd?R!MGYLi$v1GS2!zWQYky~dof@c0>ZqTjG6;hMTR-?%A?K;Z zY!MXwBm{@>H#*U8ZQalVRUxb;U!RwRS~axwhlT@=*enl|fxjX9v01Y9!;*2J7q*D7 z5W)l6`jZ0-D@4c^mYgRDf*=V060Q$U^f^9A^*KIB_NVZ{yn|0p<%13C(;4^sXvN(J zuS`DKz*{)`V8`T>-BYxZ)(4Y*y^R*q`XJpOlhOz2{uk+dknaDSisyr<|FyvMLDUxn zK@bF2=KlF2*9Ry1K@8>Y6 004jp0{{R3yS mnY&JgIk!uS*UJSa&AIdO}=LiR3Ppf8MQ6NX*T0yE~ z0NkJ(f$8h`*kC^>G6`shwJ=sHDlK7RV~~=QBsf1hTxQ$<@MXT3PZcE>7a20D{{U2z z2D}R!njTnKSTi#-5nhf28z_~IWvrSrO-)V2s}@B*F{_eca9=%CR8(eWX4b!wS1Kg! znMJx|Log>5z`(%3vt;AbdDO*kysrkZr5GpCv?FbLfPjD}!KWWxXJ21mmX?;Xva* fFDZ*Bkpc$}4! zv2Fq}42I{+Q$(;r1zguI=n!InS3thRM=Wwll^7^*-_TobpaO5U^?!2w+nMV1X1hD+ zX1`NcOqpWPU;6W7p{g5Mb|tA-s&Yst$3v1C7)v 1C=3pQK`Vm5Ugc2hFPOi =~m3Q=;8hdXV1+) z# (X)*YuV7lf3ob`49g0WEIV8V%86*KOkCJ+EVX)cnUC zd4fs( f^cgg 1MT=kwh-y#e{W<~=jm-Ja+5x@Tu!swv2Rx#<1+8zgv+C-{4xo&sOs zyAWg-$^?%N=^4<^rK0!rnZ**+2@W_t1CgMScat#ohdMzbp$H=3{DO|tT_s5DVj`gk zVvXi*ahLU?UEC|^KB%~~G&4BKljLFtKHJmhpW@Gpm_i 2 zjc-Db{Rbm9FCn-n@Yl%;U%Y`kSuf38rpwF25WJrf+c`zs$U^#ryY5bst<)f*+er zCgaz;AiSIr2lM^=v6=5reBT$*`O!;CkfDA}mqlNvMF~#E<4FoZl&0Hi!dG#E(f-II zcp9@~y|rLWX2If^!Qz{(5x 5&@b>PGkII%HxV^jQqo8aH zoG8EGp==El@Vn2s096Y(*8&%QIR*KAPENoVi|i*z>&J(9X}$<4L+}eT8>aWe4+`hu zB77w3;1_<&X1MaB$8z8yAdU%y;o}rNA`pJx@VeUX`+yV-|M`~_d;DqH4<8?==sFX1 z@KdZ)Im7#?zk?Kp(F@r+43_<65QdAzRNlq7KA%sCRX_+vgEkExg3*r?#fH*#_|#u6 zpBS5hpVTm^gI^}Yc*x-UIvnuZT9m6U%hzGh7tf$T_(>y7>fo0_@YVD>F&y#dm#vAY zb@;@hb%yx8)^qu-4I#bfm)q^~dL2p$ppOBuO;z>uhWzK7TJCxCp!Izv&_Q!>2f zxSud25?a!K3#sZUA^qqlzAB#` QZs SBS<{}0000 {uupYV;iVv;m~?~ zC+Y$7K7FQ 7N}Vw9te8Tp2S1g+~kn2otfh5_ S!#K zn$$_K`NfC7H%$jyT%Tw7#m?8Dp)r?%#0a~b4XtS;aNfS@V*tZ9Vq?Miuuld1^J*}s zs$kDo0`Th1g`>`ptp^%ThsLw~=sNYOuQaMZ%m!d4UyJ~N$=FC&%Q7r~cL5p5zQ#kd zPtd#fj;f02^&+u=t(Zde`m`&pL{jy1=x0GQ2b9U>elNC+ETfXR<*|O=0SOR&pDmqH z`gQ6`*KK8yktxCIBK9l!!bXvAWM;wIFH}mW(~4v;IL%(T@9~~?5V{Rf`0XU;(PE%o ziWvH20SYBFq?Xm!v*=#8$j4D#dH9VREZp<|BPr)nb%GT>JB&j~)Va-R$gl^Ptefn4 zom&u9NmPg$bd-YVKD&vTRv`YhkT;N=XhXIf?U*WRDt=p)A>MKIwcbxmJ5|SEwstW% z{6s8ym~v w#RzMTK=7ZwYU4x44sik-0fy_H0qg{eB@!6ZlaNMd~8N z%ye|)N9R^9%*wV7<94C+T;L)Zwl_BokfQPU3Cd0`rsfX1O30Sg$ndR=WHU(lLegie zwVIiHIXTIF;!fj%4{lbjX2!h)x&hP7_1U8+#Eez!apH3YV^vZ^xh zM-i*3Hdfq3RVFO=$tAkQ>+Bp{l(%Q`6Yo%xRIN?2{9fuok89zS-Y)~91e)`6?4me+ zy*?oj&@8zFq(%p*r9=fBFD~}1xzvcK;*V`Dt|~f3Sy_;ZT;u-2x(&6!^X}19$ONpO zUQirpt)W$h_eqcQB0Yz<+r5uNUd3{&xjHAxQGoWoL$BSKPLM*< BFtI>mc>6m|?L==hm6YXFVT3xL z5Blr!7oJv6)DTtNW2c9F_xzRbF5l@sJMWHUcC#GQtF`Ki(L61t>Q)Y`T>3c`uTp=z zU+C=5Wj6y{+OjFd7?-^y HNQaECC?QG7#i(5JS3gYMKD!+Hj&rxLOJ#7$BYj{vV`;maHs3#KW6q^ zJrGAQAo8$Qk%HN?-Nu9#Mss$YTL!WniCX$)-yOVs!}IT=l{ac?hh>VWgVU$pZ4D{) zKaoU8@u6!;qr9cSw)D~Z8dv4cCV8KySv!oPmNz{@#J3f#;HV4d(d8m0t?D6j_wV27 zFTsN?T<#-;uF!U6mncn5Lkb4SOG#cTQtKf}4KokAY^a5FtEqd16D=LT$<>v*fcbT= zg>d(c59PjpY$Uewq}e}~YvgrL6t>UD2d2MVURvG?e7S8}EZl)9nOXf5_nc$T%z$LI z3cBWXw?1H_%v%eD1?_x?QflX`BlN+eg8s);?!J!faPOC5Peam(3h`t=>#wymkayMV z*H`u{t}kLe-@sir$3(+kQVj0G;+Z(W!o0ID>A9+YlaJ*~AcbF|wrwnc+R0*c_joNM z6F-`qdQktfc>mXJ(R+z{#R+?+mTi@Iw#xet?^WLKuZ}cmWI-4(A(Z-uWZP^e1AmA6 zrbB1xJ5sd&Q~<$S_wPh6>XFe=n}dS`AhNRZrihI{SrsPG(thO&yvKje9|}CB)%!qY z+Yr{q>nE4Y6wr#J4n9^`O1p&W+a|)&^YdT4efze~PXq>o0YIKoK&XcZ?KIEmZ-r?H zRps_OVm$rrM6%;DiPpWCu{|2IJz6*||IO?rSPkXH>96+@scYZ9fmiFR0tb7S+?L{9 zsFG7l!lPcRK?lN1u?ryT;EP8Hag}*yjwcZQ$i`ZBA tvXbxTW2+Z2qNX(14Icii;i4m<4 @+yIUG+ z{Ne_+bwL_vWO!H?4u>xjK7IPMvANmfrnHodLZL7}?#RiF<8aSYYNR^qA9JXxl2U;& z9FD$rhQ5SdL*-BqA9 CX*0Vf z6gD;V^DJVxP*X5jx?4b=RnMocS}p41JXBxu5%v~w`IrabEV93$dX7x!d}d{BjU~j@ zQ_m0x+r%v*ET)SFC=3bbJPKd^x;lo(GXPp5#6dIe^l`51ea%}8M6>=tI18ptX%JAq zjY(zYN1oSl)$f2>;;m_p@N>=pCYFv30Ln%kX}1^|jym7)i)r*Wc~3qtH~(sf_J7@w zl5!^BYbPyi7Y7kFICE(XWL$5KpRX3#yNlFCrs?o5xTxT9N(_NvB&USH0QxOLaubyI z=fit_8*{O8{_aN!!6>r&IERL2C4m%mog<2P%YWQq+?gG3!L$XG+qD*jLQuv@bkwug zR;0Sd){>ioaXnva*?8ajTQrvqE%l^IPo(V&bEbpyqY^3IBL6e2@~bdp$Uv~lXHRV0 zR=t%VbRUjL`XcAr-5@DyH{WLguN8#EOJ|gFWie6>l&f1H&J$_J%SoF=meD9DH~PWY z=SctN)A3X%Mlg!FpGiy~;F+Zo>P@G`&|v77B>@=dC#VUuK{*z~JrYtnWAK+U>$2c0 zKl6eH;c^bEy3_#FN9b8-q1A`f3WG*OSOk=B$8DUHTr-1~B0#sq*t(PcYS&Ca+^T>_ z6P@A_Cx=qQIx}Za4a2HNo}EB&= Qn^SV5_-53C+Xut8TnF{ za3{~Xu(NhoSLXz`)zxBljD8Z0=*(@2NNCikKZ}}M%EG&UJ{D8N=(-PUQM2epgG5 dc670D zUNvh AZy;X|e~TwAD DUrfGK-BT>Fb5=pzpe(qQYF>Io=CB`gwU}K;?GAU) z6
J{`&GUfN}XF#Nl^HW*HgfVsp8hd6*AUn5! zB{b*nEYWwS1^vYtLfMMDip291L8dJQ2W$JS`h;0td&Ie_=ZGT}z$OL*EjO$;FD#sE z(b%jq+ 884lTa2K{Oe8c~vRlO*}x-aTj4uM&v}l8S>+x z+~>8NXAQ5 5K-ip?n!M4BB<+_*++f~Uw;fu-w7;~da;u6@Y~`gR ziYb;qv&684(VC`YgfgK7p ix`flva13DQ9X0YL)_C`F2ZbVNE* zMWsk@p(7!Hv`7hGp6?HMXWn^#IOm$#nX|iR&+fHn<4le9SQvR30RUhz(APGnBJSTs zf1bK`s5 oiwI1MNK%CD$*f$>Qj 5TQvQK%(>W+j;y7rccD0Km*|psitv$=ZB_^@gtUbhq4$ z&3oRT6R=Xov5Uuxo4G*3Cr{Kp;2$?4ov_R@iun;&o#Xk+#k?*+3a+i=%dVZXj YA`xDtZ&OHD6BLTG zYP4gqQ{?=C&Dh|6U=qdB9V2?V&gyZd{~@+|<>4O;sSXwL8=E{ORDGfybao@a*zesG z!rcQYnu}t$HWGY$nTyzRGrl0~2R(w0?n^zfQS&I|h=Y;N_ubUTH8m2wwEX cQGdu;_w^CgB;rq~*e&A{Yw&-lY>LJzp7d~|il=F07#vQEE ztFd>4fu@5VwrGg|+Tf5ieEJM=CP!XCgsGf9ZP7mETTp>mQ-k!F*}4=_+~pQyLXc-D zs(sT3?`ZpNSe?bIiIkRQuv`1teF{HT8{E7(GNKUoD 5nkIE@dv#!5#!J zsAG}m;dgZ)U16`!X#9|fmLDHXuXH=exI#@%bF=#lmr}$zy|7S2Rs=OmJ;n9p+*o3l zDiEkN{h_qukfT)9?VS_BkYyM!7)}Scou-}#$Yxs{S4c^MVN`3achB;ie_OKkek@C@ zG*B`D` ^rm!f zddoISfb@HB-g~sLCq(X9&DSxRNGv;0UkG|-e+Va6-dvxIDY8(>tL}OBegKiT!w}O+ zS~LA|Z76 GD_yq MA@^}Sycf4ZG3 zU5 eZgt(&PU8-i zD#K|AY!=THg`~UlJW1U*s>==%(53Q6i(mx+R2H|VY}wg|3S|3FZ0j-$NLI6zJMw-V zhF-_)FJRKb2;(M;f;2LtEpKuALvxXb)Iz?n{Bq4=ohj^&MORHuFl3GTtahkY&t7Qs zCA)}dB;6H-y@iQe>{!_p#uk6coSSsynF#w7e1gbO3~~Jj!4}a)-n;#;X1I96xexoW zIVAMzmxWDeOBjPE8ka``cUFBPyvR+nY_a_?_w_T=&&n;g=7!mRM5QWpGvP$i$$OD) zCnMV`&lu HN1V4&p|X^jgKhF)9j ispEf%@EZO^a@LAKc=zY0YdWCoa^MF71aO{wWM;PrdEX z^Uo?xk&g~zGYl_i4Hxh^?EhO;4`iZUd@C#(97-YnoeJZFk4)`Z-yUdf !@Vz97y4{D+9p;apE{)l+(4(Vi48^J|E9`k$eRIG@_GjVO8a_Ko!rP&CY z`vWew<*)1ey!6tzGeM^`$&559N4**CmwLl*@rg>^R1I9!=Jj0e@4S?AVX_>F; K8c)}gaPx)?SA5PRd#&g~xdXuarnWrHD_+&-g^7euq|KMdS}@Wd@pSSx0flMZB| zf~|MXoun3@QL#GXHC0$X5qyO?iMh?QH-T?F{*R&UKfyT)dqukIM8{GCM=^d*U7nCJ zjXIjTEn^6W%WaUnbtY`t9B(*AaMH`v=ruP?_?A32Zg3$NC5?32Z|3Tcc=JueBpo_~ zq}fMPJ0iOyt0v(Rn9>`0%n`)8{k`+qC2-AxUZRQo_KH&7)JFKIz_1cKwXOIaY^dxc z=v*WD$(?adlOt(>ZPa>LeEO5`V1LfOx~WK`?|!fqdTCXah@FJR)~`Lsg+hMX@q!u? z@e{fB8N&(n%uF-2S+vQfQOVZ_5ZXoL7p^rtAtSAXHIsHMOWw<}rSvN0UNWyn*7)`+ z=wb{L&bJ=;??II0N?0%BNJgH$Hg*8zz1PKIK&t~azaVEV-u02gY@yFpCMJw0DP$YR z1|h5$%TL(7FGsi0e4yk?E2ZlkcY1RZ?g^`Z!R_+No)wl;1l+J+rW&k9C=&DM?xXup zhaG5}Qmkad>%sbZiNFr{`yn#xW$0>A-6((=_$UXqEYCQ?o@R BkcT!495cK2@v1ZUM1hx}6 zDN+>e!(NLCqn_XR{*0h@OSMDs)lT>~N$wOo1-Y3U;30ye$KAgb1S8^X6Cby6c0y%; zRDCc=jy9f4J@)0>f9{VeTk%x+oqZar=rn4O((OWOY0-#_iM19s8%$fdr;abnu8+up zf952N6(;>+)i2f8kVyBd`6$&)2CFZbqxaU#uGD&v@Q)y$&vn98G`+@vekEsfxTwin z&gVL=uJ1LxhvWKdZ1f#&y!Jzpw?V&DVInwF`B(({?$YQ4le96}$sGX%e`4CziL(84 z%3-T*1(#2VQJ(i2on` !+(#e`O?(0616b1|A_e8{c)1c-* z@j4wfEgd|4A9s3e-@CWB+Wwdq#Y=2xP#|1C3JE;;{QJ-Yf|G9OU{|~v`bz5B|Np3D zy;wqIvdF5w3ps(&G%u<`kw{|*c|Jyn26)zW!M7#TjZR8q3FKJw`(TdfPEA!B%KliL z+O5}qPL5ci6Pr1kEGChCB|x;sU7 q!Lu0l3ARXltYBUFolH8R#tHJa{+r102iD@+kVEC&Hw-a zj!8s8R9J=Wlsgi|FbD$yTXBAl?|<9p6NrMmA_;1DW=9lABy)FEm2xwqyj0Xe_|ni8 zS^xm6V3X4d;RdThSHd@gZGRS2N6=D`f=>v-izG$a9Lzy5ItMohgkLakaJcXSvw|nc zWL}cp;fKS|eo}so%Mrj4z!7wG$_GXYQt$~u_?g1*93GU+GxGyH4+r-wR(?eQ0000< KMNUMnLSTXiORv!Y literal 0 HcmV?d00001 diff --git a/monkestation/code/modules/microfusion/sound/burn.ogg b/monkestation/code/modules/microfusion/sound/burn.ogg new file mode 100644 index 0000000000000000000000000000000000000000..ea37d14ccc12f91b1aad4196d3ad615cd389d16d GIT binary patch literal 34599 zcmce;WmsKHvo1Q25Fmu$9-QFrPH=bE;O-VIkl^kFcX!tS!5xA-1P>lO_#Lp;`t~~e z?0e30|J;$$otA2;x2n2p_UwtAsi_hG3H<&1lK3kTCRrQ^fe+zg>tJB+_*4W5xI#Qv zJv=@>J|!9< $S6|d1iMqL+8J(PiDWR3Qf$H-J zgc5{I42(<+ObpC~6mr%!=5`Loj>a}llt>^sFBp_1l%+*@m4qbJjP>pGl*Hwf36-q% zt*i(It@It8j2#F?Y>XXD-3XP<9Bf^U9rOel7-aN>)P$AjT=iXazz&4uWMo8Sm6dqG zwoi>^-b+gpDw)|jIMEO~+L|~KI=b07nHf8o8xk5?nHyRX8W@|{Iv5k0m^+x;m=fAM zKb1L}**eh?$`~7&J6jW4*@ERp#)kTCbhJ+c*xEROGbS{3uywWr%N-3J% @S?-i9Ll$3b|<>X`trT?mWDpmp~ u7U6vYNc?#$J2wY&Q!}UM62gCXK}aFwW&q;xgp;j}BcZUdqbb;j5hdaO zFpXDWUt%J%iqFW3N^t$VYeH*dM@M~A &*BoDH1Jove&`--Al!_yj$nv5ASXp_3yaC=5bF z+rJV2U%{pY8MPvO7KJ0>lgc`{7#l&n_*>Nlg;XH`Sg> F z6A8+^xGV~{#5nP%R1ZUNVq9`YCixD~iKm$G$a1lA%5*DMJrdZ?=4w; 8|9yfd`Pug$l#8KoqHeXa50$xsEviTu$NpCW@Gq5v!~3dD2hCn`vF zfTt?V`~uI^Pv|Snw3{0%%e7mW6T!7dQTs#i6>aT=q%`BSs$qF6jE<+{DCbV<-x1t1 zAB5ms1Yxj^kOX0$x(}kvPjmpg`im_>zz1X#5Q|2Ws6rd6!W$VUl{=+TKxG|g7Ew}` zQ2`%k9W@u@Sr_M77cY%Of2~F@jYfa1Ie(oie|@Zgzu!Mzt5;9&GwXy9Kqd)i!UknF zF;4b6XEu~yKq(|(^aKeZTnsT~OpZjpiAA}2O1)W0z4cHv%}_PMGYPm1K|jp~$TUyR z|KDB{jRfQW`zv79PX!2oSoYcD_1Tk(D3bO$(7b#`xE}yzDxgTy >3$kOM?1HNKT z!UJ*ABj?g@{t?2H*a1L*5wFJ{ZvaF?k#^F7Rn3ui)=_2_gyIR2|MT XUMTFPWcuyP7{Q&D2dvjnY1 zu{S_jQVu5dWG4Mcb{v&JNY48!<%W{-vaDzVEW_SOMeN1a2kFcwl3y0htAlMdek5-f zHlcy3;J6g>x+dkn`ukfgigZ&)J2= E&eBZR^`96 zI5#GQYLKF4m}!XSDJtz6<=U_8LJ~q?1htrk71ZMJy6tqwa!zr}yt)}p>by8bNy@yx zED97Vp;6R(80=>w2}pDpq$~if;@^V%O*)4BcKl!O*a@Bj3K{s0GtnwB^QtJTsJU2c ze05%I@Yk7hUY+$?oekH+4EXPg^&iUtK%?<{w#hhyFvh-AUs*x8zXbe`<=CV3MdJ-b zlS);S%1qFVoU+KBv5cdND6q&Vq3evJJ5OR5E3xTJvKmjS7*D$x&(|AkHF#;%|7|dT zna%2~^S> nnh-k1q3!80I)&*>3NwI zric NHl2eZZKkLH`;*Ow?bs34nd2v4-dP%yz65Ei{)0H4GpsByyU5=Ior zi ?ElJwa}!TDc_KDXrbH2$^fT8?HvKDW z_MCOk|5YqKsgjgktf({Vzwe%1)C_hNoL}sq0%k!6@rU%db8vPEiinDFayk6;wD2UD z-Fwa!zfwV0QF2Mj-)EAU%_}Z{HIFVc9shT>x%`zH3^@De$EPs4dYDD$t9disw6jiQ zxmoc)S+#gD3jjV4F99C_bx;6=5W 3{ zo>Y(rY(A;1l3=6(_M2^%TwL5-`BV(r9k^qeCFcicQzYjXH`B4moCzzS$tbDlVE#?H z*IbRjBllc1>-^;T{x1)>F5_{4YaZ5na%>e0{Tw( WtKjAnu@gxHf#Qfc71oxVir=QSPnf@0l5Wr73L_pvLTWN%yA`6DP zS-&Je2nF~s!VnoD2=-(7J-HQrNGJfn@DucsJ_uq{uo#g6QSg8ORt}ossDwljlPKj5 zVcdkKag^fddy2d|6Uw|)-xx*95k*N(a k=Nxn}usPq-&4NrJe8cou-TGj69oJ8q7n&)ys7&a(># zZh}u)a3_4qg5K0qGteo4d{S^+5jJuxII1Tek-#Mbt1HeCWQr^vo~W15>4^&!SP&XN zaGtcF;|EI=2QoSilE6N37Fz5t59&_djHVN XzV^ouL(4_5=fi zn;qaIS!o{-pIg&8{_`1!CI}!5WPJOdtz9Ay)G%;j&w`7Se5&|Mgn|^ Z^q&d} zGC!23fu87|EB+ejc^cs0S)e>YyYvA_sA#)onag<~#n70f1^fd_p@9i-JrXJfjTwX- zj9x4LlPaqdrTnYxxuWPlB+uFZ|0Ag<2>#*z|6>Yb?gcKQ&p4_milQmDDvtdI92RlZ zv%;7|fYuEv43-m;BxqsqM4;ql1*yt Lr1Vbb<6T54E?b2rg%+@ zaF8UFz86d1vXr$KTs?ZQNc<}6>TxVbSZ6_}trwQS5R~SU 5`?A0(_(24r(t=u1Cee0bXQ044zF))?XJOQqh-t(&u;L03csmvxtZ!Y$Gs zO(r8G-zkGVG^U{U7DY}7*wAFX%3S^kzS@S=S%!$3B~^JST;OL9A41@}fZs4Q ^Pv%d zBmh7{f(8RVPg^M@ ={n5Wn4~eWqCz&QGRS*epzm2VSYkl zQcBX-)NjiukTIiBA9Y)dp~VH+385mhbfinvr_e$*dterOW`8Pa!dPleQDMA=>l(0V za!$%C)u+~=t(mDkMezOJrvG*v35s wk0nPfPJ+)1XOQIt0A;}}E|Bqo>umUtJ z{#kk=?}>y+aonadMiIrAFi!C@l;EpG6n`J5_rWDisp!#jcp5NP;}~h?gxhq!#u8k= zLfKx~K-*t_WsHHp$R;6W>a;j!cAe|-IH5 |I6AD5lKO6G6B@Rza%jf`$Qn2CR4U ze}}J(8y0Z9^aw_2UB_ze8S-~?^sd)$4WzxBked{f${Tk;Le>LHodKtL{7)r3T`ke# z`bWiwtu`Tt4Vb7WQ;3xkhRN&kbJOk4Iwbj57X)&A=~v4OE5!kM=eq1==E)XN!h>Cz z$#7YEkuaefy`n7I1$h&T%Q>r)+9I~UTAbUHOZT!mOg=kCH-u-~^Yh%)ymP}N*VdTW z4DPD8?&w@t6CVur?f4RtADxMdMc5b$6K=#*L^`zLF7_(yP_}|Dv;a% ANvWuLo0cBI-CBD@Q&tKu?narVk$7D^<(6om)mUEG>$>?2 zJ?Iy{7G(t^AE3StB&(5H2pSqu|G|!OCW&e54rsZTvi#M_gHIdAw~CrddbqD&JvT3G zoX|r?S|n~{Q6w2rFlppEs9G$vy_Q_=Fru7Cm{)u<7$|+1d46+6^%e-bw(;kyyBy1C zhM+=hcE1&rO8EYO%@)FQH9jsVU7}5}e7QhuF)vQ=c;QRWH+1$I*RXU!hii|wNhF-f zcrc^gK*9N)qLmo-89pW}`v9H?N3P_?;mHisK86Z5?{__AozFYVscbEi@mEI|v|-!d zc@G-JFg>sCaaVP-#`{hsjR)<9^^R##9<3?dnmMNp9m=trzfD*2Hr|c43SoS7SBVWN z&AGO64O~1pao{s9@J9yxF6h)qls#gix@_-9_f-hRUW##lFSOn_xol`|c<8*+n=H65 zHmvBQ@K(C2i~joPLEoO(Hp`S->v_h(oBt(5uQJiu&5^e* kgd}O;rU|LGWFgl5;&2Xa~;^w zugPUN9~VL%ce!EZYaG()`*SvXyZxrE&AIe`(hx8GYqfrOZNo})Ynb>=LzeMorPDQq zoa86(KfR%rnm&!`ks|$3tm-rtYS?f{n}6=>TE}_MtXv)A wBo~NhC|%04Y|>Dtp|R(=)&V_nEPl^b#W-Q$FMHr zt7dUgw{?E{a8h^o)nQDpE>3koLfONjrv97ZKy&1N>X_%9NYENRvg%G8W3Cq!B(1D6 zMIKH{kB*sM#z{;~^5mwyThA}S&TAosHxdoI#anumlT%DoI^WONvr4iaqxgMaXesQi zh;R|iW144Ce>~f0Y5g>~vDzG| j>@<-Bcs=&2tQO*0O@`SVfi{?a+^VrxkXeRr lSzt8t1OQy zFndtDIWpciC1M`~8yNf?mF^{D++HcZkeC$M7%IufH~27)rsrf>V#scJn9+Yl*-)`P zVftxMchmQv3o`pH-D=I1qcL@Ta$pZH=FnR1{;vw_#?xUf(_?9N7-)u`=}PiyA*^-I z5KB+ElApLb%rCes@G(-6OzAaRY$=$qJlq(@heo_~X! Mc}bh_-be;p?rsIbGNwz9GluSS}5sk)k^GbT-+?_TCBSDAl~i zepfuqeKR-0 & zm^`m>&jOBLwG*u3ej_jsp@+&KGGcdTx4JoGFNh|b%3bf$wroh;LA~^*=LNnGs_%NX zs=bBB$$nYveANL<)BKV=YDH)wa%VCak2e$FgyMK7Ln>63H*Ku_^mlS1-w5-2^8%L_ zJIc*|timwBya12mbr@~56mxuGBg=P+lM#rpwOpX{tO<&XXVf7C(ngJ)NFY#@fSp9D zA;;y c#uM$(Ntr7uP2<^>1$EJPb6rS3$?EBT)>R}T ztbfuw_3^&W#(VD4(7fqeLu%h~n;~zwF-6z#)MA3ak)3QjZ*G_)JfIJuRE8tTevzwJ zi)36>V&>NM`~F^-x $pj z%4nQR7u%!2C2jT-sbkY7iLnb8b7q&g NNM;5r{MBujJCj@RHbFS-gJ}Z(P5rT69N94~k!M z@-|TZD8tP$Rr}6pqVSjc%E7@aD~7r WQz5HU3|gDGH &qU?bXF+5jKP0GmAD2_w}rO)`qRK39$4fJjOph zAGw_hOhNf+D{gh1hkc3xhF8KJG|iD&-BF0{xEm_!;|14F-YxTXSg7u~Hm%fajepc; za9>z$R 9eY{X `-nq&(gDTJ9fdJ~2$fj;)V3a$51%oG9gR<&8P=bB<`8on(oz z`8ChZi8UCO@)fb*iz2|4 DpUBx)oPj!aufj4#uuHPy+FFu#h&SaDCQ`R z+!xq(*AYGGUkH<&Det4oO3fu}#tm8M3=lnzg4>-nsux;{AP2HHJ-O38HeNvdlyKVC zl0QAQ_eIEUWDirbrAhw;pkb_j&eLZwbP(ka^B qXq)wXBd3L}0=Fw5Igh&my&WPj~AFC?;not`(OGppN? zP*(D$-}CKeo(z%ZYMcqD#{x5PSarnlr{lJ2di*}gXg0Bp>_Y%=COx!uoZVgnY0<>4 z(PPGcI9#`0SdRLGO=&$7v&iV^r>YsMTCu&}1ow1qZ50}Wx&&P;ls|mDZEbaJ Ka}2Ews!Kb!Xncc- zh!1m;Tl0cRb5^aT;pE6p)#FZnMUXcAb9+tP&i5*&4gmosW5{i{_XgU@5HeKGSoh6_ zCowvJUH Wgex0e?0K?NAajemw-esNg`7CQ#E;V zWd1wAfR+6+yEs~D6KB5JZR*(8E94QE3Rg7Qeq&{~)8B!2b_lOhUH$84CC-VUEq(c2 zM%JDdH!1Pu#j_Nr(Eh2D_d&Vg@XfG)bR{?9)YZ1nrA}jWwCMj}NDe+amBuo?uY3`@ ztuylQp>zH?M!Tp^a*RBS0uaj?zx11OJ4@vNHebB8{w~$Un}Bz=gak;fY}O!CY>p!` zxZEvR#YJL1Fj&+a=FK!;WvUBLomEzapC7Yt6;SAS*4wxup~1|?;Cl>lZzG#`-k;$< z8ZTL@ACWPq#Vg6eja${yE 1NP#N*bYfc>7-Zi-4Xpa=?JIZe&qeM$8|Ol@ZrMe>{9mBcpxLbSE{ z(}Vrd#lhIa9R4lN$(8k4fI{SeQ(1uNhU@v+% ZncO%u%sm;( zb(qUlvN^*kHR;5JYY52%tT&f8t0%?T9fns%O58ibKb{wNO&6z|T~1@C%=>$b+>6c@ z3dLHu<@g>98pFEsR9o#WL+c!=ZnuM2fYD9*x0O5I;mYB-GgWv3{QKs(GagDluDM?1 zvnnk%d+uM6$8`yjaZ=T-tc{CqzoNDmGrVuSy_dc*YS8B6=ven;{n<}?CrAiUd1X9) zJ<58Y-Zup~^(CJmm}^tRCOW4oaqzw%YBjvKG1V 8SXAsH)R39;YP5^zAUSs#TtjiUJi1jO|yI zS` KvVyKD2-hWx=_Bamx=SGVF!G9e(K!M4ak~jdnusXXSHTvt OJa&wYSfC zB;|OPzdsj_GN22@erZn^huJSZh6sGnR3O%om43xTMf)>Q P+EnJ7*2~zq zQ-)7&$!LPJwf05s#*ExtPSIua8doVgG90XLl(ywR*AR%coXm9+5(A6t36c5bu8ZP` zm-1Udqj&48x+^Ze_(?Xd5HxIZTeG%3JY6mfY)te^6^~(7SfdGEcO1sAT6qs`{xCHh z1X_O+^MKDcSNFoIdu-!%uQEuj))WpK^-orLBPJkx**bWn%kJr9W4Sv!Z7__-Z}sJM zX$jRuG&jjIw?cl6ke2Ik`6B3s0 >f9al-1AR !A3#{mqg$N8wd1^qI$@z!) ztQ1LcwDjvbWOuaTJ+81n_*DTFY(1a4XLN7uk9-{#yFUc3I_s?HU2X@o^M1XDVUnB< z<&n6`Ee};u>MdThLB>P35Y>>K-eV?K_y7gphe_aqP_&^`?zmur8aVCc6-!j~mj888 zWuV)QpZb_@zRL_P&rDpL>DF*t9k*QXAFitF6)NYX;zB+-^h!BvFE09DT JiD z+}5u>sn;$oR&~`vyp)Y>mYCcGdu0D7Hc%wiL`MaXLTih@Y$iQc@?4*|SHIPA)%yyA zYZIu8aQUtWbxEX(ULC2P93A~ilqhGU0 gI||vykKI@h{j~NK6x$uJ`-E?|1-U;m2JXAzcJSDOcX6 zPT^vD0=`mO%+Fp)+|6z;mXI8)?V%5I$iA(QhqhxTms)}31g$gy^h2}#Bu839Ke{Lh zxV3UP{tV+66>xJCu$eOa4j9M=H`>&ZwOi7(jpVKo9Wg$NH}qo-=D9zW`{h1T_W1X| zm0dzQ@#u2?$rJ-;h54dCyPR#rEh=_hb4$?}Q8Hmx(uqepxfczb+$3Z!6DQCR4O6u+ zSPKiQ_%dX{a7o&qX7NFi;8y1T;LPP=!n<@|Lu`iHi?=Fxq)TrPc8g1ApHQZmpdwug zArG-nxp>-=`dmB884_R^#VIrhOdU(up}FgT`uS!*0}*FZpwYrsa8aFr<;UBxi?u?Y zMN_QyMAI=>InhSxpaYXj!87-l6XB{@UBo^t;^pT#g23=5F7Qp11bVc$cMBPJ1fU=h zMUB+ez>e0SkpH7DnQ_~{5!RR!dY_obXnWbWrHjA+XZy9sB+p8WalZM<(XE*XuO;m7 z&yd~hJJ#qTK<4GTomA}$SyuFv8-827nkx>bN8VzJjhTTEss=KOGOn@Bau1)3^ZmM_ zk>y&toML5m75AfFVSL?HRSw9Q<>CruQBh3q?v-3^l>SGyi8O#K`8)zhhFpskA$1F} zQus`R_8U&t6##fb*%Nz%EbH7R=y^&a@0~Z@+FR3mY 6H7YT0ty-f{T zuWz cv;F7y4)Xr>kmol`w~NU|k>v{7F>mII0*8CE zqgf9ve)a>gQHQtKlPfy5Tl@EJb2;T0lZpxaEp10cTh7}pdi-)~>0aLMh1nz|OMk3O zZHiaK_-8*S7qmuF^SVu!?4}3K|B9*GFYZ(9J;=LrIdYYKGh~n*5u6uUJ8KrsQ%p&v zAS|0*n9~>A*Fohzg?;ZKY`&<6e(&@Rr;e1+Tr0w#_{=X*l1}3SXxhS%_&oLI$t-#d z>I6uukYlB?8DGKlQNE97qcfxWBoVt-zYnDvc}?}Z5;w_8(-qoT`;zFhlWNhYvoAW9 z$qmd?t8v}kH7~u6)*O`ED%?jL<*tJ|oQv#EHf^$;Jb#pI(FJ`hT1S+?^_#zS`@VKO zy$~~>J0wpuR^Fy7vk=Jh6Mm<7^2MdjXR(wER-%-y8$+$p^0-4HEW0q6Z_-pIo8++N zM+hpV-##tB==|*cO`E)4gQ)rF7~M;5h2;eSkv=s$x1sIqXgYjs7g?NKA+>O9e{G)*m#CZqh*s5 zxfaFlIP4~kgpaRuib#AT=(AMc<$7Q0O3+;w?Fg0yq#&VUg|Yk2oj0?bDPA}-Y{;)Z zRv465OAX5B4PG|QbMMyLyEI>ajpL5O-)&C{VuAhfEiFWtek$_j=df1$VbY97mPYG~ zZd*PAFXaWe&dtCKK{i-5C{o+snuD@RKeh4k;uggQ=T_v%E9hNKhUmnWZxcuJiDLwN z&<@|#1rAxql0t&tF#!NRI`wpm&hf+1gKU27Soy%E;=9?V7MAOhQMtt{G1c+J31Q{S zyO_>b)>$#3Z~9o%q^Mzj^SvD< DeNZgWcD_vR!JyIVb+=1SvM;q z1O>z6NO%Q+hGS1OGv!W(syeb6&>>BQc@<70fQJVI;9;8>ktc@h4808W_<=wFI`b!j zFl`rdaxAl$iJ-KVU)U@P23-{~InvF(yw}^6g#&0G6B-8U`A(o>#Gf#e?A0(nf?eLP z4*{9u{<(hnMKR81hZpmf@$NG`+a7&~uQ-|qvte``mps!P&7Eh&tI-rBPzSCgse=YM zQfZeM(|d~*G-tF@ btGZ-fUUJJiX6ls4DNa>E2M?hE z!VjYo0rsMtTlbO6yv#878z}}CcKj!|a@&~Ip=Ym;j_le{i5b1SG_P5cq#h0=TDQM; zI3k+Ov9cam=Zni}(wn2+Dqg>A*R%gw1@C uZHUH0DqziPrB%ht!Svn?srA}5+)JS zDUq;9dq1zc^B+|UMiuNF68$*vYeN#U?S RD^2$Ir@LmO!ab+f&JRx`^iqFGJRKOC3hAGG-HU~o<@fSn{v8w(Cj;&*I zi382W<(zl>;Gar->7hBjnQw*q&V-LNsd`$G34+XFQ(X-OH`?=&`4w62#uzQNSReMz zEl;K`E-W7&8G4+JCRj3CP+#mPmoEz~_f+?;Y{6|ScWWmIVH&^Hs|zdR9|O;o^>jl~ z*b#Vd$3Aj)@~ey>&6JK$SS5-D&V-1jMYthrq9^6xXBVp@lqfiOZM2a|siqR3EBnQi z#Ei$BP0?n3ghb5QBO?)P;BY~!(}JdNz$rdemPnh!^leg~GS;I^Ko9nnAgD @MKwn>RpfIBk$2G&@x4oA2~nbPvQZbU$UtSkC8>p5kkg+Ok* z=+!}=d_!V{INN&>k(>>AECwEBrpYp*R7n+M&`t!ti*El&NoF4WilJ(ID!++4t1$YY z ?ds1OM*;zO2w0mNqHkAyO0K;i01AQ~KBcYdNZrkr8El8-LDE!eL(z|C2#tDiZ z#vY2rS;Sycz6572dc{AIC+6{+v`pY5>|n;D&Bd$LYszL)wr&58 w{VA zTn{`3o5{7s6^Exe(Jgf(BCJeoyv}E780t%GILve&V(N^p&904$mWsd$%#(hH%gaku z6%DhoM#?E}6HUxt)cEuty~__32J*Rz=}E1u$C=-;%A(}+s8`DvrPI$~-dtHsHWE^} zm5yA96yQD_7OplKkuy8OSaV@ldGPS0hxyqvl@BP(Jk}4{u?4(eN-7hlS`f;`$jc$b z6P7hA8|`_$T}T2mdXoA2HCB(0SIx#HS?jLs1t$LPyYDZUv8hyJUdl5S#E}s8TKT;G z@S>{-8)`Ze4c#aJ8_N#b+Kzz{R^SJ`39 KYST S?mQ zD6UQ1I=td6hn-iacwpP+n(w;vDAdJ^VLk7h?{d>52C0vZJ1N7kd+Wdg+pzD$y1$ z?q&(K9KPOZBoox3S9UK*_mYf*P5d~LoGe_ec}+FEO6T7wWKgp5KMEpR;!7EQlxRvH zb6-=tETZpB>L*Ms;boapu!Gz?2=pAS#gU?tb4&KmUsO2NeLKTg!8wJHI|t?GXR%op zbca8Tl!lphQ>-;Gem^f=Xem79jMupzMnkT|-{tTTrG-KOg%(n0SBS5=1(p_ny03Z@ z2PrFtkp}$W7d2H8FT2&}xP&sDG3aakBUvs%pQyl5F5>Pe*jif*(|~Grd+O|5+jD#b zy *|75~odP{cQ-7)5p|BI3hQU-^L5i3cMfZM{`X4`6*ENy5(6g#d zk1}#?{v7Wu7P!f_JikG%IDQOK>R9!dOtg(xu=8@*>-H&}d*%HL(XoBr(ET%yTsZwG zSERk=ZQPqhQg#nf#Ro fSH_Z3$H$RTU9$8U8)k!HvdP#l^yv^&zQo6MukKMz3` ;IiszBt`HHQ036BW?Xe~H6;t>Eu+i60Z=-wT;FsVgrXu0emb#$xKE&R{BBeH z5_Kv*_QRnqkRmfI{ZgegMnTx`+{E{f`&?!`so*y zcqUNmSbu(@(FCmDG@QYueTV%LDtgE(>ySjMBbJNEg`^K3oAXi#`BaO>-iOo%&OS5# z-JaEV2-$69@j^BvVtZ0-2vH=FX*Q;2`S13KL>aI!@WYVul-~qIyA^^0fxj-!e5Zf% z2Z85{Gf%g0+Oqbc(81#>V=zo5M} lJ0no4P7jN5CelF zlO*;74PUlT5W2Y%ZN721aR==Te5kRCu3jlo;LY#4;yV1v H?Mdm$iq(; zK1qb|U>x&g&hOIj@4BfEB}nU7Dwioi8K@@NS{3}!&`JY>mkzC`t(AV*O^jUPn&3o! zCC;|Eq_e5@b|n+yu6-kzYHRLl6TWP(Y@wv0_#UqT@1~ToCuLQ~BrAc2KaWs1s=r;y zo=g?tzUi0hP8xB@VWI)$uZ7IQ{z@DbJ@XD4<8Ix$(k@DpJPL)Aua>rDHQaLF)U?hE zhhpeyt`9Vui_B#hiZiVD`H%#-6Rm#jAc>_-(MiVBXf`MyT3?kJ>XI~Qtz?rjF^M|C zj`;-1=U{-)B|0NVaW kC9! z)CbnpybHsXIB(u*m|wey?UH+$C&}XA?VCM_)nLFp>`JA*H!4@lfwL)}Fud39A%@Wv zk%jIqdtCom9e8`w;HO}Rly6sAFiTw`-TH&}*3Kz)niVBHVf(i-6F)~=^8V0JRt_O2 z+-gAWAaUUMI_bRIubzbcNI1@Z>36GfEY-}er3Y7VmXS-8Nz{-0tzyAsMqHc7+$69n z-kds=zG9VkyK*giyKVQ{=SL5%@o$b>g`oqKJTNCOKDltRVorbjz0xG-I{2rMWLH`_ zl2wP>G_rJ>j##x|(X{QcNvgbSF>*^bV8mhf4iO`Naq1}TLM}Pi09htnnK(vU0pntv zvSvZD6eTg&P?gpHGTy=>CK)G2A}<+dnG#o{5q3;*gHm7P9b8Q2knnV4_dDUDdh4n{ z{t-=$(d85~anj;rzN Nu!XD{6{X{4dA6Rpz23J%J&{>gHQ2^=O zEHh%o-cb@P><@lqKEc82B!(R^8=Qh@`oFR$H0Xj0$A#gg60D4A6s!azJ}VpKhCshI z0en!$ZV}=k9gW&>g@uKU7RQuTb1o^GuqK32)cwT;)Zb!A!+j_UPgZ0=H5G_;f)gRr znS}$Q*S`O#`XO{e{B(b<9=3E^v;xNGbt6wbHX$7VG1d9>i$tH4jb(%6`cA&LI$g(@ zd<6a0S7!zI;q%` @SfdaH6VSxH|ZVN89vf`8F5>k-lP zyWk7?;aLvvso +HeiVx+Dc0|TGkBz;#aQFB(>L`7@= z;$^ky3Pgd>_IASvZPrlIW}zK;kDOTh$_bsFN7b2f^)BLV)->?@ExJ*;q?QYPN@J{b zE97#vC3(jVlcelU$pqV$4%-do?85oBrJmiL-T0oV`NR5_;1F2_PuD3$#2HrSBAO^A zSqsybmGYZyc0qMeJW_IL_txySw7YqD%$3nqeh3NRMN2?nif3i=rn~6{M>0MtS+~&| z-3HeEv~pQ~q**kL#F~Ojt)Cb17$s*Dxm* N3co$ZZR^QKt>}hbm(TY{9N&=i5}=Y^aqnuZv&~J! ztyOm??m#syWSFyieH!F6ZKEy^-7Oi&BO-G%{_Z6>;rAAIfo;NFE7|tG@^H -Qh@Bv(dYwllnG zHCv%rw)u+I2hh@J=b3#Jd)<+vn5DjsMe9nQ(R?4R?n~vL)pUKVAUm2*u>db_yu$r; zi 0HdGTa3@f8nu+J@)px-hxP_8p5>*TZ0VyhsO33(*!Ff1p zq0hz${c+aL9~gd#8st$ZJJ$><#S5Cne1~=+BA>Y&imOY%x@vC8$TmIMUHWJ_O#VRN zy14P+zEF0%%JX*8DPr|XFX8CIQ!}I4A#Lm0siJ3T_G&-3*h(a(*w~r-UQEzTyjRMA zVmu%fb)gswdwkUlw%H<2%3eE6ADK*^Im$;V8~b2_6_{0zeNPw;A91uBBv&J @}JJ-H$#4u~8PsLM41eVJ4s zi&A1WcaQar+8}{|%dQVQKH$-C%c&643g;dE?ARW1MCfiy)GlR0ehSCkW+S`noO(ar zbwgUrW46%J-X9+sLQ$dx9J=%!yzw!T(AOi)8PQ(qByitxe)$)M>|ZZ<%|p_lAR}kC zj>^aCZ$<5YX0Cu_4ZO){p;@eDs?f4X$opt0-v6<+BP6MK=qDflJ7H ~w>nEOiXv~}1 z$iv*%Gvr1w+fK|z?UuqyJC7~I*G+2smi%;KpN_OW9rtp-Xf*1bRrspC41~P9aBV`A zJNq=;)9~#pc1q7?LCQ_5WG7{)s4xm8P)+)Uh*=h&5)C%n1l!Q&mHb1Bqf~_^;`b zxvxwj G 0n-dw9bF!-%0|<^_F>QYf#ipU8#)zhiyZj% l@dtyUrfyk6zUlLKj~Kkqe`N^`p#uURi%a zfNjhDv4_)*PWm#a{~XXI>|82@Uh0H^73BUdrq(qUc#XLpX?yH)(j@9x-(`SN9p9mD ziPbY`qbIvBblwrpkg6jvmg=m!EN7Yf0yt(J ^g`N^7(hskC-Zc=482Ki8GvT4cVjp=lK!n_JuN}>C209R--?cxu?o$U6u0^hoq zCpRgj20f>Bb0q)3H8eE57rgWgi?$})LLL&cJAp4Yp;+n5UyF&1l5MLr;m-Jjop#y^ zg*CEDGPq+acZ!3$DLp~2x+umwvBQvnZ{7;AVwh?DMJr=I(dr{YM(L1@6F6cljXX6! zxqC|<8fW;pf28GiS(OoVZDkz=lLq0jx|eyy=%xqixT@FJ2 {)e8HJ3!R=G>$*XMTB}wFs=DO?U{G8+~ zmuBOsd@a$&A`7lr%d%b!wqB7m+LExnjPRZP99-aM33(4}L#5cMqiX{yT)U^n?{h{~ zl8+v1`oDkVSTdw_<3EhA9&(&>(Rg!J)}3K@Rnv$C|Dis(8h5J+DLK61a#~y(dTVWH zraDPc3r#Ez@j~CU4Z6AC&_D1SPQ3;WEG?utvEAY{1A33D_#b@WrwcAlQ}40!?2cRp z|AXF@R)bBMx5o|ERb=*Ij~thzBLkmy#ABZ)!D>3|)Z!eL`7}=5jM=P5n_||e;1-H) z@ovT1q`ekijVy+v4jUe04_*kPn8VcX^2pm$KD)ky_6&TZk0qrBI6?$5Cej7J&E4yB zA}8>=1z3Npjm`e({bkKHJ(N)A9#LA|v#~TUTS|73I1K(0FvPFlvkGVEQEMrX+U?Uf zp7b2Ww!5uv1C6AwtXW6n3OSK{gHy>&wIVc3WjP5rLuTgeGQ9>kAw~Jg7DcZFvcJbU z+q71r;BdVm3CPZmdzEA8?$~i2iaD9`?y|a7s)Wk4gxI52h510ehf%%7FCL=0?sH6# z`cS7>DbeH?cuOP*fagFKk-GMhw1mF6jxswNe&|%RPfAK+praSx&%8`kIDaZxq;~%^ zYmxL8;>M#5lw**_9=Z;8*;gFZt+!`lxi-&Rm;kEW%E!}X##=lR4MEq~pOcn4n6l#P z^3j%Y-~T*zA@<&CFU=0y>z-|RoTM>p&*gA%bP(m6kwYQwuEv%+g@2XqffgI^KO$9| z8Dw%mr!B0%)>qlf$TPFLRSMv6P30YIz2`g05x1;Qaa|}CV&aqLD+(-J!(GKnlaj5l zV_LhLxz*4*yTBKt!lj(e$jlZ;n9|i-W;ENNG9CQxzwYxPIilHv_?D+jt?rihpmq}c z;&!;z;(()L!DyK>tZ_dV@$5%d>Ysuu??w*|Ez7GKds8Ourg_GJvy!{t4H+cspWV7Q z?{j$HdI@xHzged20sJFcUw>3yitGx_o}*7aOS_S5r%)|Z_NnjR@p!AZx|2u0p!lJy zxYlI^E+;fZ(TcxyKzcT0o3fb8RpB^3ZZWJT5tiFhLa? 1bLix8-;a5iic^r9y|FlU zyl8)Vrpooh%^F~C$$((kN&C{evB}fuFjHTa$^2#~?Z|KJHM)sl9Mjlgvuf?ik?Q)2 zrd7;t;`<$l7Ava0Ii?*P+nLefn90%M(o|W|9f;2w4rnALTVH+q17e^5U_n%}ZA1H? z+nw9h fPw1Mpq42j8zjPPr>|`s1uq>5M3QgWv0EmINIAj`vrGy1mr`BUfI?ZL zf|(6qLMGJw#9l`2^@#hp8n*qXdsZVY`kr7*Mx71@8iyH8_!8=TGY(A)RSr_3FOca} z`;nB#rF^S%Tk UgHs^YJn9J@Tf&G(-n+T__dVR`W#%3(tWUZeeXmA?74gHpXqJ z7GB24IYoiON4A8oWRvP)TmMf@-vQKA7i}FNv=A@>f^ HX;JUk8k*ln;GP&)uB6C8-bZ8cMeNlWn z*C9Y(C;f4Jh1cayr|KW>tksr$^{lqeik48Z&^>wPsyO3x&t1Ud`lc&)F04u>ym3F4 zxcOXav}#UD_{RPW+3HB%=l;v^?XM`|{)Y{oD1+UZ3Bsb&@^J&T*wy6_w@rh6 =WpI;^$=A4E~M#`drx%jS~UP3C&m*ccs2IHxdjPFDcaha{hMGSg3TbKDl4< z2hoV>0ioaf-@s@(qF>XwWH$H^dEqJD)SFcA| z0~36C+-S&!u>txxc2^`SgKhIkr2RlY@?!VbvPf8F$h7kD2*z_<&BB*X{Rv#5f~~l| zX<_}aId^&G7eD@>JgcVCKbVVmBmXtzaEHdR6R|V #=U~+ejhUo+vWp?YsNsMRBT Dlpw7a&h4H=K5O;1iv=eJf|J7dhz;(?a`I{Dd~VHVi_wvm+Is$h~6F^t3aX@B?> zyd31oW%rq0=yY>+;wsW3?#i1Wx5mP9CgAN&^ZV>;+S+`{?b{{0)4^N{zht}ebkCXu zKWs0bF^k3A)peoa2?7^uL+;8VlFQD+lha+Qe}_1w&J_J?FPO%Lr~V7obGql-_v?9k zhA1s!_1&4Z_B*HM&K>}B4SGj>_qA#`%u`vYh2}{fVP5lu5 zsOaD=TS yo5R0qTEhGlt4?2QPtMlcbmF#OQ|u&OYvDmN1^&d _*>WQ;f&r z }jCNFUZA+Lp+d=-X& zLiT0^zSFv&Kz=guP9bdP*TCEnPuIyoxt;bw)Z+>P%>=)TM=YT|0PZDzn)LE@e9E C#uyD|d;m#@mbj-48ljePh;-RHO!N902$ zXGL_}MH9)gZ+ma4Z*SjkrNGB^obQ&c9gd7p^MlNF&wT(~*uKB(czNVk{`+q#=T8`x zoc-yc@^!7OQ}y-ebkjrqGLGOQg+@@=`?(mq7d3U+nZI=PR%Sk!?bqW1!)K|!+^4D( zl5DtZ6dU=jZ6!D-6tmLuWkJW~hUzR1DP^>fkp&{l#4yKwo%-#;1-=()U-83;8EDO5 zyadGPy<%hYiyx4OgYBF2PZ&P$pJvlOk=|=-=a0WQ@z(3uZGUd+t(iCPCT~nyh*|cZ z?V8o3Pd08HXe`ydFL$hVaO BxKj%-fZ9sEcjdB@NNYn*mmyu z-4Y9l Hmzf)(SX@jLvr)=p~x8-O u>SzB6t60Bql`Oo0z3+OW%7U{F94kO3b)*xuEE4FoD5oVGrT z6(X^i2S|nV;(V1v0Du$KApADg8&ozvN%wVG)G~4*hBVY#`Q+J#-&WB3FBAdV*lKZa zWwE3;r (T z$7DH=&`>E4pbun@?ePSs@N+lnNDUR6cF^ZoJwLCvJ#;{ZCrEk z#Wv9DsYx;gv${T?u5qvXg6+}Q`p2&-)zUI-DQPf3#VDbD-ujmmQ1`W{-P9U^pJ5pf zUGnXwq^0N9*@^jaETl(yA)HpCcgoSrEENqPzc>^xe(c_tH?#2WoX?$bsEX(9Q1-98 z-*1TFUN$=XtVwwLxVdk&-v9RBXJ?qHr!NG?KdgLz dL$~5;?DT4K-A8)^+5qbCW zGa046YjTTO22pdAvlFk5FJiAQyjtasCuRHZ_X{1s|IluomL7*ZITh~Q9CrG_i$5}p z+oq8biIIlVlSR_*t}~bFY}S=8x1Ii1ZCe5hJrKtC(MOAqo}jY*;2T=*Skwx%cXi{i zE(}=gm++IRI$2`dpx%(@hW>fhi8@@J)wxm _n(bjAq?$MAkGD4XE%lAGx2XQCnZnrUwm%KvRITVbHo?eGx^4CV#?JMIo zdIShk{6aJc`>9v8=_?FCH?`GNSAdJ^3}~UEAo5Mkj )_(9mBgo zBhu^(H^CSb(t<=qP6KIhT?fWeW~no51veN?2Mk)2f`nvOj{&?P0Ej89fnyLV@SkRU zqZX#6jBn-vYQbroK=#&2GbDi>9|Xe%LO8(HDLN%5mi`N#zq|W6>>?00$7B~+g`5>P z{Z-zV1eH&@=PB&s=QtHRK}($thmHO7(j*?`wB^D<-QUlC+#D7?nthP+jkNoK@Pa@e zoe!@nj8=wGtu$xp+YVxub@K)h+4q~S-gIw$I<^_qN+A4=@>=mG2ODHq;&7&TdQ!bB z4AgEP+sE9TS|&n;EZIa=5tRB;wnwVy5hY=8mexh?9#Fc50*B925RzD}lo7BxlK41q zv!Zh=7LkGYG49;4x$xbizF56FeWcZEVxBpQVO;cu?iZd^m>=oBt1#nQDkU% *o-Elimod6}QQ-hX@Z=J6pM z^EDIU`k_&D;7~L7Ow{(j+$86Qf#HBH;jT0Zhi@{;o7dGX`j0;t({_KlP<`Xy!)tRc zU;jJ^!?jD}izi62F56pCeZvoa#rI5TbB)8x_}sUjb2XJ+7J9VpaLV)fE5on7++>ZC zw1L{})bBB%dmkplvuw_lpJt!AY*-{ZV*H?f^FkvlwI@8QeJkC^r=7O);rGO~TS%XW zyG3>M@2a($eoq#TRNC${o1OMd2&qP&wC);)n{H_{Awc|NFI=(LX%s)&eS>ZJOH&iW zPx`HNO_@%{L1kJQ3$#K8*{hZx-2L3+mO%HAI(E!-(j poKK6 qJN!Bcf8x2*yPv_OD&b(|NcZrxJHJP zA!uJ#`}KaJP#tR}cqV#@AFSlQKTaFQ70LMlQimw#(OSIV)1`p%#|Z|f&o=JGoGy9p zB9CU+mik`rJjG;wd#qZp``$5C>V;(+asu?2dH2C9@foKp-<^9irM$2A@@cSKbvOsz zj}Fz?Q4F1lPI1}OXUEj%>eknPQa3@tMYAX8x^!EU;~YOp+b$F9QBckQVnXNU=GJnC z6Pq+$LMnyb-dWT!vd?;6P+#yaDH-d682uYel-Gw=pG($t&{d_pY=XTVQ~gJ;tSt77 zb!=>XV0&-5-IWl#Y-V1lXU=x~#EE-h#=>l=vSjBB{Wp(eF8A(!`{zoO=lz 6Jb18_iN~DQ0bFI z86sVI7+E{xE8w#GBoqD2H)$vHb6XZlbl-JlnX$AOrFIX~NX9!adOj(eNj*~Z<=%Q0Wrm}}S@k*b*TcD=%0A83pItZJhsQ}BZIo@?$$pvhV>n(+EZh0+VaDm4D;I7p z!-k>*aUUozvsbIV&VIbKI=lEL*)c;fr<=E`R-CyO@32cBm(1{0{Wm`Jc0f}rZ7$*G zx4FQZ*TaPdvL$@);(vR+I4L05Z2s?MMm&&+RC60U1y_mFnbysezS8*nynd#IU_r>+ zk+%~H$8QuJJ=e!hoPm9wN+cn;XHPXw@C&?XkJY*Q=ch+nDA@saf4JkDe(~({>(qBf z$N*7+Q>P6$O1)v-e6Tz1&{} `uikzpSjqTG$zGRY?jFMF z?o$%GF&G@Bf aDX~{>iEz(fs zJ-H#x9Y6;JFvi1EFq-39Eesa5shxE-koZ3^!|lJ&`Cp0!i_HfJ@#a+j56sYF4J>P% zW(^b8IQ}A5f}h=Kqmin*DRjyJ;J@(`3(X`9nnpcC0OOm>JhqP|PV3ej$an90Vtym2 zc{-@PCrx7-56( oj-Y6Q0Gjz~m#A!PUv)N}upqnBRZ=M`18dFt!rR z4IXLse}>hB_{V5^jX K$dU%AASj*C(l^y+$ zjMHAV`7+zWm~FQEg5CC@e3!m=y4ALb^2tBuyG%T61Yh#y8T6i8B*P57w&st0eYbGn zhz~}ye*^I-4~b2V`op~#Gi;ff@cBWm>n7#>^&Xd&Gl^+ylD-l*PF^=Q?$I+B_6*Z~ zITfr|W72M1*gAVg=Ky{4>H2YBxLyx_*sP0jH_&~Q$FD>;JM`3_L5+ZhpliB*GY$8q zR8$g9*0z-t<{;bc-=+38o@mQG5A8Qze#<1<7b$K(f3b(e=AG3N2wjSIRs#xuXewtk zMDf-ySj&Ho_|5yi=A%rhiH8OoNi5LzlgTY(UBy5HgE`qN5iY(L*Iba9a(Aj#cFRp) zL|4}&PaES+eHZZ?6YHTd!`@|SS*Q`c?=iEV2NFJ *JBy5wUgo>53S7_ZRGwL zZT`92u9E)rE2l>nizw*oEK?mETc5$Po-Dv3#Q`m3cT()y%AypC;N>QP;(B}(^~U-T zh4M-(17p%K+~{?b6HF*E4iO$+bgtjQ0lZ(kc%WN)ha%{bBK$1v2bVi{1 TKW%aF*$}@JodxJ^c!*l<_(#PV)}rYNaAoJ4z82CUC*~j+f>c z!3s^3(xPX09i2)8H3rqH*8s%FI=ji6TOYd^%_KWVb38KH#tce!5?Hp3g@siT-fw8u z{pxh6xJq=iKUgq`7p|aTajgslfJ1ELi=VO$0!TNV^+N$Klo*)AGfAV>?04Ga=@5>N z34o_hOnF> V-SI)=ZexuS_8NrpZGWZ{D_eYtr8 z#&@}CW;IMtYGJ5z?Hzj>B_G*|Y~!LSdchW-IJ}K&Qd6-a92C83s9hbYSa{;3 9%}o|URiQ(__b{*t+sZxKtih$i6YWB*0HqhM!Z-7Lh3n~bTBR@nA>M2 zS&93O53F5TWRNe-yxGa#RR}awv0X_Vs7pR2h(KIBK`LMw;XS?W<=AEEnR;zgHDFFf zsW{d_sdb_K|V;`$t=^he#JhP -J?n4aZ~pQkSY)X%W-9TC?9jvHwarQPz$mA8z+M>nUBrxPCs6 z5FK1W{@agvt@JZ?#(Z8- c5Nh z`}qMj8ObFvcQew`?RS__m4xl$&nsF^jDU%a_#8bEyE^8FQ)?|6l1!Be`)x&wY!Ihu zxc+LwcgJ0WeJjE7@rYidB|I7B#%ZU5&&rlvw5-t&vacO!Y!aZf7zOR=WL_+7EeYj( z -$`d$yL_M$gDlqX$`#zsXZ9WiJUPf| z)Sgf5(g~wK S)|dp2#jni>dMqcnk}EA!(b$vQD8GQ`uCM2;M1t`_-+@K8 zZLMdPsvYjL^DW4^w_@Etp+b0{>=9h5f|Co7?sm>5qvZEb-tt!hbAeDsk{m_yX1W-Q zXRgf10ZI*R &o8+_9g9gE oo6?rE0wAX_3yRzG!(tP zVEM2 A^+t z9ezv4lZxL2W!=<>LmE7ejfIaop1K)6)Wu@4K%Ay98q63YQmE7~dPu;uVA&R}rZ@O& zynaV74gE|Ft;;`Mi~lyC@b=pq`-@}J0-sv$8c#pg*nQ(obDWGkT!GfxwKR!#*8Gk% zQ+;Ew=-bpoyn{Iid4xqn8}MN56RJdfqn_Hy_ZfNPzV%fwwozic$hX{zl>@zDxdQ0H z8BZw5)=2-|&}HyrfiMU 3hf8Rng!#+B3RPe0vz{_nHK z3a>7L!Jxj@xp8UD<+l6AyL07Lr7~|azZ$drQ2NTQzrML{on_|amN>7<(w!NytRv#- zX}i)T@_c^{X*>TM8A+Ic*36at7geWDpFHst`T1d?r59GwyF-2S{3|uKBk8-^-y^tj zW~$MTwi-Qr6rIX{`Cqvn1Y;`-_s+qF0t#4Yma}M-eT|^7z3-aCw*PKd<5Y=&lJYNQ z7$1SiOvj)0`6H>Y{x_2{(5Wnq8YW`%ZT&SUWI0u{>~RZ#a6TMb0BVq{7U(j1;t=`8 zwxR{ZkEdl!j+)t7HHW5b)#RL0n2j}))BLEdbHn2MFP76o_?mm*;z|0TbBn+7$>9y!8Sev6uvTj?7i2)x^kd_H4N1)#9 zY=3o7QPh)-QD6`fh~ccJBELoxljStDt1%} 9Q-N)E@2NqYr*vR3oy zTrre7c4Nb^@3+7I>h%KVkWwcqo#N`%c*E(~)Ksz^yQo(0z3jq_05|{>Q#-@!VP2D8 z0Z@Ir_IYU`sC?(!V?Nr&@Pn4MT7e`Q4L~A$m&UxSW%_2Y!{JZ%JQ6A-(nOPjZ-3TT z*Q@j8w;-d>6CIR|H8L+I)dxB7*4XytR}KmY^rSuWr6Bz*JJM_z%z$q9F`Wh)@eLFP zU23ToKZBsm2J`7cz`(q3Swk|H@X9qr%KM(AjXG=#7YSjh8zRz>)9*TMaGTKYLwIh0 z^cEFKsM(1ZJ=*Dg?$7S@(m$7TW5TOaKLu`o+vdkE>A!+tb>{UYMPJ 6hPJ|1HO*VV~O+td7}!J&3iwE7o|;9o9|0%4R8{xhwNb!pcuZ$XPF8r6O)YW9%Wp zC&T_&_4ITZt}A!cgv(4U3a_C|t_Oz;b dztbhy=Y6 zTxzeINqughYHDzDF~&8AUige)(6Lrjb9L1L#pRi%k2apV;9MRNQmzM6w^Xa(t_#Nj z*z;NdbFx#NnYNYAc;!LwDd9w62|nHwz9QL!pYeY!KY6lo!m}+3N7oN;&4%Z=U$#AG zYkN*W<-w+LkRlg|^MZUq($nIPVW)YjYYWAM^=ey5I4avvHBFNOX6Hz?VnbG9WQDOM zmiE)go}^k|c{zJH>-|9nXp1~iuHpsK!PIbJEdWJ}xfxud5O@}?V!T3&LbTQ8N_C8= z;2CvnT+O!7DA5s5wxU5K>hFJl3DR?@)xw(iw3%d&0$rZ~H~>nvp4yR?f;)2g7ryM6 zJiLly2uUysf+Qc=S=l2TzKq%?beO#!ZCPUEZII Wd)kq@@fLg7< XVZ+_QD+_KGSgPsMwU#5 zFcxqrdQh6U7aNa*;E{(79Z^};)8wl3Kc0_wc`_~8?j)+QJaLY^hgF`+aJYn)iRRcj zgx-8GdcBVf;cLPdJRwLP`;6CZLO{duhau)lfDY%-Pu8Nd2JZEcnSV>yj$fQKvbXw; zW`js>XtnF}T~L-~b9NcEDfS@PUvT)j$%YoL#{GV_d$dNdXA^Fe{x;q^rhx-wP`ftn z?Hkr6CsADPG~G{+b7eFtXDbZ~>SEW;nD+6(kDe%s9D=|wELQkZK-E5JRClSC$by(F zUlt<3YhyZgl_HEvP=%jl%&}mIxjo}4R!xEGg>%47Yp!GbJ^T8L5*T)LW}qLWVEWr^ zNyzQwFglclKvr1yKK{43vWvdf=G5;5f9yzo`25m`x|Q-%N Wf9W#XY*eFJo`iy#y5PJC!r{wDRql3na&2T?fzrBAlkl7Gk0l`Q=^ zEFH#sZl65&xWVFbHhIIj9Q|%4uW#bcX~P}uI7;%T^!(oHUqWA%EGpoIA7pMRij^uJ zH~v;*k#?{_vl43(n{lZ;-&^O`BW&%HY$sx!5ijt0TO@>OuQJaUb$$0sBwfI|uHdCw zo7PvlL0|Uo(eV%e`Ul*cH$Y|8?)4?0TD!1WtD19EP`)1qYNJ&o3@RU{I6Rh5fga}y z$oJ;JV36H-ty3YVUipT7dYni~23VEZ0NtOT5IVelJb$+ZdZ*q>G0~I_2C+9+6s1(i zf(j L%}`!;kAFYKpje z30Q9fN?C5#Hr0k<&8eI(&+4VYYnlX?AEeuR%A^iHSC+wXmVyQ`|LMa3V1@R~0;p z=% ?C}rjVBj$trBv6Gm$)=(jFwd@6-qqcnR*u#%c}S;d#{LqbAi8rW(X^4+ zs>w+jj$#^J^zvF=`ThI5?}G>sd+Lz3b@%>GQU*IH`7pqLrtk9)sqHJH=h<+aUpm%x z(=yt7txaCT&$zadK>2WaFA?*}!YWrz-y>Ex4Q!K7aZMra(z3B6d!MSWc#Li%5w}Ey zWl~tCueJA=3mY;!PbU*vUK1Ds<3WGHAi%u9bY(PovZ~@z;f;nSq0GYF>X82VMW4NM z78;T@c5%9bmdf>0w?#Oh&|W2fu%<(>6#BWbWK7&zIp62%QD1{9`fiy%X?GSx10^Mq z%TjF3=80?rMg0~_eiqbi@4#5ih?rpt%cL7X|HSeR9rzjCGW6{7d}dz}@$ro1y}*XH zVDN48kM+Tw2BZh}OvcxTGbc?iJG9$13)!CY{hM;hv^@Ntvi<}Q-~F#$1E21BOnrlx zOE4~kfvraij5AIPfG*z^y?vK? g;&`+g=60r)Z%IUD2ZkpKd)7 zQQ #>9f+sJ2tT3!eS_PWejYv^woBhn2~Jnderac#FW*726(riv2ndeN-t z=X}Ujzv)6-v8N5Jf{ed&{g~oolM0g|ktB`LZ$LyGy^sjX$I=D^+gIOx0qf1nZi))o zi}comDp(G?-X=t?m9d Yj_Oq@ROO0{7R6Pv%3(o z(#l(238s`! `Kp_*z!qrs3ZaC3wR3#sfR7%dr3x<>cIMa!# F3SH#;?5v(wE(ERyiNqlL~3GDWF$i$S^MB~&iqzu>yAZzr87W;)Md zF*}}OB0%C)gXtI62-wX0 #4hhM! *g)5C6CY?MhGv6a!~0kis~hi zSt?a 2Vi4rf1fA1Pl;3v!~k zzJ!{#;KAWF)g8M{MfYuw?T|o;a$OukX8`J|MAkM$TJ4+2&gNVnh?XAlEXI5Fg|rOK z&BVUQD*o;A#dk~3OZQo?L}u7d`m*{(wV! BQ_XY^L?y{jmt$n8K=0wlu>UDs~B zSODwYH45>%dLjQBqx4;)6+=_HF7oLu#bIn}eo;Dv^ZqU{IeO~&%Afw(1n6hBi`x^& zzlvBM+6^zS%g0#neNME+t1@hecbwL2p4-7cCeRFh#IhaTgl2yI7E?7oLm!_U2O&8i z0Y%2)F*i?N5nd8rFSS592B78|VJZ{3S^-@}9rb@(mS$|%%VxJ3*D&>dY(rwU;w%Gs zK;@gttC2?I&S4Qf-;68U@n$0jfRVTwOv?}FqNe;}oX)5>Mgw*ls)9%?I{2o)(BWx_ zM-8!+Kk|D_(fJi`sWPlzuY?^0*Trcq-YsNma+%HMY4SRmo!-j<@GS1|0Hx47L_r&F zLCqa(QgxnAwX1k*$`S>br3n{wq39%;P3my*yOe+pV2h-Q4cqBzwjytcXr71_1 M5hfNdmtE*^cX6PC!y_e9RO|VI-lI&vlr7L{#?4l_Xp2C!Ud*J~{MUqI|**iOuUX`+I;0#n0z zG%Gf!z#OC#iuw0>V4Ckz7uMWTr~4f kMtUHrbN z>ab~DU?%~nVVT(b+x9#(GTO`@NBgAPy Ps2T$*jc_+i|GTbkK?XN&jGw|?u76vU)o^mu>ohjg)kYusC{Pm0>?0xUGe z_mOH_V&IYX`JF>LVqHysG9HR0kv^t_lj?{&DjuyG4gqHOYDQ$82l_vF#P?9DW9?$8 zD+OFAN7MxGC3?