mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-13 17:14:47 +01:00
d76d2f5438
## About The Pull Request Updates the laser gun into four proper subtypes: Standard, Pistol, Rifle and Carbine. <img width="229" height="210" alt="image" src="https://github.com/user-attachments/assets/12c03076-8ebf-4d87-8c98-6a8cce6821db" /> Current sprites are pending a palette change. **Standard:** Functions as you would expect. Same as ever. **Pistol**: Lower charge, 20 force, normal sized, recharges faster. **Carbine**: 15 force, 26 mag, two round burst. Projectiles flight slightly faster. Cannot dual-wield. **Rifle**: 20 force. 40 mag. Two round burst. EMP resistant (not immune). Projectiles fly slightly faster. Cannot dual-wield (not that you need to). All but the rifle can be sourced from cargo. You can also buy the sovl version of the laser gun if you're especially nostalgic. ### Armory Changes The Armory now can potentially spawn either pistols, carbines or standard. The weighting leans closer to spawning carbines and standard as opposed to pistols. ### Lore Dump The laser line of weapons now all have lore. That rich, deep lore that every game needs and is totally not important at all to the meat and potatoes of the game. I'm paid by the hour ($0.00) ### Code Tidying Lasers are old and a total mess code-wise so we've tidied up while we're here. ## Why It's Good For The Game Variety is the spice of life and also some of these weapons could have used a face lift. Especially the laser carbine. Both functionaltiy wise and appearance wise. A bit of randomness in the armory means some rounds might have unique outcomes compared to others. Sometimes, items in cargo don't see particularly much use, so peppering in a few random potential deviations can maybe nudge people to utilize variant gear on future rounds. I'm obsessed with writing too much information. I blame Hatterhat. ## Changelog 🆑 add: Three variants of the laser gun; Carbine (replacing the existing one), Pistol and Rifle! Find it (possibly) in your armory today! balance: The armory laser guns might be different variants of the laser gun, rather than always being the standard. The standard is the same as ever, even if it looks different. add: If you care, the sovl version is available as a goodie. And in the hands of pirates... spellcheck: Lore! LORE FOR LASER GUNS! LOOOORE! Examine laser guns closely and you might learn more about them. balance: The new set of laser guns come with brand new sprites. /🆑 --------- Co-authored-by: StaringGasMask <62149527+Exester509@users.noreply.github.com>
387 lines
16 KiB
Plaintext
387 lines
16 KiB
Plaintext
// the different states of the mystery box
|
|
/// Closed, can't interact
|
|
#define MYSTERY_BOX_COOLING_DOWN 0
|
|
/// Closed, ready to be interacted with
|
|
#define MYSTERY_BOX_STANDBY 1
|
|
/// The box is choosing the prize
|
|
#define MYSTERY_BOX_CHOOSING 2
|
|
/// The box is presenting the prize, for someone to claim it
|
|
#define MYSTERY_BOX_PRESENTING 3
|
|
|
|
// delays for the different stages of the box's state, the visuals, and the audio
|
|
/// How long the box takes to decide what the prize is
|
|
#define MBOX_DURATION_CHOOSING (5 SECONDS)
|
|
/// How long the box takes to start expiring the offer, though it's still valid until MBOX_DURATION_EXPIRING finishes. Timed to the sound clips
|
|
#define MBOX_DURATION_PRESENTING (3.5 SECONDS)
|
|
/// How long the box takes to start lowering the prize back into itself. When this finishes, the prize is gone
|
|
#define MBOX_DURATION_EXPIRING (4.5 SECONDS)
|
|
/// How long after the box closes until it can go again
|
|
#define MBOX_DURATION_STANDBY (2.7 SECONDS)
|
|
|
|
GLOBAL_LIST_INIT(mystery_box_guns, list(
|
|
/obj/item/gun/energy/recharge/ebow/large,
|
|
/obj/item/gun/energy/e_gun,
|
|
/obj/item/gun/energy/e_gun/nuclear,
|
|
/obj/item/gun/energy/laser,
|
|
/obj/item/gun/energy/laser/soul,
|
|
/obj/item/gun/energy/laser/pistol,
|
|
/obj/item/gun/energy/laser/assault,
|
|
/obj/item/gun/energy/laser/hellgun,
|
|
/obj/item/gun/energy/laser/captain,
|
|
/obj/item/gun/energy/laser/scatter,
|
|
/obj/item/gun/energy/temperature,
|
|
/obj/item/gun/ballistic/revolver/c38/detective,
|
|
/obj/item/gun/ballistic/revolver/mateba,
|
|
/obj/item/gun/ballistic/automatic/pistol/deagle/camo,
|
|
/obj/item/gun/ballistic/automatic/pistol/suppressed,
|
|
/obj/item/gun/energy/pulse/carbine/taserless,
|
|
/obj/item/gun/energy/pulse/pistol/taserless,
|
|
/obj/item/gun/ballistic/shotgun/lethal,
|
|
/obj/item/gun/ballistic/shotgun/automatic/combat,
|
|
/obj/item/gun/ballistic/shotgun/bulldog/unrestricted,
|
|
/obj/item/gun/ballistic/rifle/boltaction,
|
|
/obj/item/gun/ballistic/automatic/ar,
|
|
/obj/item/gun/ballistic/automatic/proto/unrestricted,
|
|
/obj/item/gun/ballistic/automatic/c20r/unrestricted,
|
|
/obj/item/gun/ballistic/automatic/l6_saw/unrestricted,
|
|
/obj/item/gun/ballistic/automatic/m90/unrestricted,
|
|
/obj/item/gun/ballistic/automatic/tommygun,
|
|
/obj/item/gun/ballistic/automatic/wt550,
|
|
/obj/item/gun/ballistic/automatic/smartgun,
|
|
/obj/item/gun/ballistic/rifle/sniper_rifle,
|
|
/obj/item/gun/ballistic/rifle/boltaction,
|
|
))
|
|
|
|
GLOBAL_LIST_INIT(mystery_box_extended, list(
|
|
/obj/item/clothing/gloves/tackler/combat,
|
|
/obj/item/clothing/gloves/race,
|
|
/obj/item/clothing/gloves/rapid,
|
|
/obj/item/shield/riot/flash,
|
|
/obj/item/grenade/stingbang/mega,
|
|
/obj/item/storage/belt/sheath/sabre,
|
|
/obj/item/knife/combat,
|
|
/obj/item/melee/baton/security/loaded,
|
|
/obj/item/reagent_containers/hypospray/combat,
|
|
/obj/item/defibrillator/compact/combat/loaded/nanotrasen,
|
|
/obj/item/melee/energy/sword/saber,
|
|
/obj/item/spear,
|
|
/obj/item/circular_saw,
|
|
))
|
|
|
|
GLOBAL_LIST_INIT(mystery_magic, list(
|
|
/obj/item/gun/magic/wand/arcane_barrage,
|
|
/obj/item/gun/magic/wand/arcane_barrage/blood,
|
|
/obj/item/gun/magic/wand/fireball,
|
|
/obj/item/gun/magic/wand/resurrection,
|
|
/obj/item/gun/magic/wand/teleport,
|
|
/obj/item/gun/magic/wand/door,
|
|
/obj/item/gun/magic/wand/nothing,
|
|
/obj/item/storage/belt/wands/full,
|
|
/obj/item/gun/magic/staff/healing,
|
|
/obj/item/gun/magic/staff/chaos,
|
|
/obj/item/gun/magic/staff/door,
|
|
/obj/item/gun/magic/staff/honk,
|
|
/obj/item/gun/magic/staff/spellblade,
|
|
/obj/item/gun/magic/staff/flying,
|
|
/obj/item/gun/magic/staff/babel,
|
|
/obj/item/singularityhammer,
|
|
/obj/item/runic_vendor_scepter,
|
|
))
|
|
|
|
GLOBAL_LIST_INIT(mystery_fishing, list(
|
|
/obj/item/storage/toolbox/fishing/master,
|
|
/obj/item/storage/box/fish_revival_kit,
|
|
/obj/item/fishing_rod/telescopic/master,
|
|
/obj/item/bait_can/super_baits,
|
|
/obj/item/storage/fish_case/tiziran,
|
|
/obj/item/storage/fish_case/syndicate,
|
|
/obj/item/claymore/cutlass/old,
|
|
/obj/item/gun/energy/laser/retro/old,
|
|
/obj/item/gun/energy/laser/musket,
|
|
/obj/item/gun/energy/disabler/smoothbore,
|
|
/obj/item/gun/ballistic/rifle/boltaction/surplus,
|
|
/obj/item/food/rationpack,
|
|
/obj/item/food/canned/squid_ink,
|
|
/obj/item/reagent_containers/cup/glass/bottle/rum/aged,
|
|
/obj/item/storage/bag/money/dutchmen,
|
|
/obj/item/language_manual/piratespeak,
|
|
/obj/item/clothing/head/costume/pirate/armored,
|
|
/obj/item/clothing/suit/costume/pirate/armored,
|
|
/obj/structure/cannon/mystery_box,
|
|
/obj/item/stack/cannonball/trashball/four,
|
|
/obj/item/stack/cannonball/four,
|
|
))
|
|
|
|
/obj/structure/mystery_box
|
|
name = "mystery box"
|
|
desc = "A wooden crate that seems equally magical and mysterious, capable of granting the user all kinds of different pieces of gear."
|
|
icon = 'icons/obj/storage/crates.dmi'
|
|
icon_state = "wooden"
|
|
pixel_y = -4
|
|
anchored = TRUE
|
|
density = TRUE
|
|
max_integrity = 99999
|
|
damage_deflection = 100
|
|
|
|
var/crate_open_sound = 'sound/machines/crate/crate_open.ogg'
|
|
var/crate_close_sound = 'sound/machines/crate/crate_close.ogg'
|
|
var/open_sound = 'sound/effects/mysterybox/mbox_full.ogg'
|
|
var/grant_sound = 'sound/effects/mysterybox/mbox_end.ogg'
|
|
/// The box's current state, and whether it can be interacted with in different ways
|
|
var/box_state = MYSTERY_BOX_STANDBY
|
|
/// The object that represents the rapidly changing item that will be granted upon being claimed. Is not, itself, an item.
|
|
var/obj/effect/abstract/mystery_box_item/presented_item
|
|
/// A timer for how long it takes for the box to start its expire animation
|
|
var/box_expire_timer
|
|
/// A timer for how long it takes for the box to close itself
|
|
var/box_close_timer
|
|
/// Every type that's a child of this that has an icon, icon_state, and isn't ABSTRACT is fair game. More granularity to come
|
|
var/selectable_base_type = /obj/item
|
|
/// The instantiated list that contains all of the valid items that can be chosen from. Generated in [/obj/structure/mystery_box/proc/generate_valid_types]
|
|
var/list/valid_types
|
|
/// If the prize is a ballistic gun with an external magazine, should we grant the user a spare mag?
|
|
var/grant_extra_mag = TRUE
|
|
/// Stores the current sound channel we're using so we can cut off our own sounds as needed. Randomized after each roll
|
|
var/current_sound_channel
|
|
/// How many time can it still be used?
|
|
var/uses_left = INFINITY
|
|
/// A list of weakrefs to mind datums of people that opened it and how many times.
|
|
var/list/datum/weakref/minds_that_opened_us
|
|
|
|
/obj/structure/mystery_box/Initialize(mapload)
|
|
. = ..()
|
|
generate_valid_types()
|
|
|
|
/obj/structure/mystery_box/Destroy()
|
|
QDEL_NULL(presented_item)
|
|
if(current_sound_channel)
|
|
SSsounds.free_sound_channel(current_sound_channel)
|
|
minds_that_opened_us = null
|
|
return ..()
|
|
|
|
/obj/structure/mystery_box/attack_hand(mob/living/user, list/modifiers)
|
|
. = ..()
|
|
switch(box_state)
|
|
if(MYSTERY_BOX_STANDBY)
|
|
activate(user)
|
|
|
|
if(MYSTERY_BOX_PRESENTING)
|
|
if(presented_item.claimable)
|
|
grant_weapon(user)
|
|
|
|
/obj/structure/mystery_box/update_icon_state()
|
|
icon_state = "[initial(icon_state)][box_state > MYSTERY_BOX_STANDBY ? "open" : ""]"
|
|
return ..()
|
|
|
|
/// This proc is used to define what item types valid_types is filled with
|
|
/obj/structure/mystery_box/proc/generate_valid_types()
|
|
valid_types = get_sane_item_types(selectable_base_type)
|
|
|
|
/obj/structure/mystery_box/IsContainedAtomAccessible(atom/contained, atom/movable/user)
|
|
return TRUE
|
|
|
|
/// The box has been activated, play the sound and spawn the prop item
|
|
/obj/structure/mystery_box/proc/activate(mob/living/user)
|
|
box_state = MYSTERY_BOX_CHOOSING
|
|
update_icon_state()
|
|
presented_item = new(src)
|
|
presented_item.vis_flags = VIS_INHERIT_PLANE
|
|
vis_contents += presented_item
|
|
presented_item.start_animation(src)
|
|
current_sound_channel = SSsounds.reserve_sound_channel(src)
|
|
playsound(src, open_sound, 70, FALSE, channel = current_sound_channel, falloff_exponent = 10)
|
|
playsound(src, crate_open_sound, 80)
|
|
if(user.mind)
|
|
LAZYINITLIST(minds_that_opened_us)
|
|
var/datum/weakref/ref = WEAKREF(user.mind)
|
|
minds_that_opened_us[ref] += 1
|
|
uses_left--
|
|
|
|
/// The box has finished choosing, mark it as available for grabbing
|
|
/obj/structure/mystery_box/proc/present_weapon()
|
|
visible_message(span_notice("[src] presents [presented_item]!"), vision_distance = COMBAT_MESSAGE_RANGE)
|
|
box_state = MYSTERY_BOX_PRESENTING
|
|
box_expire_timer = addtimer(CALLBACK(src, PROC_REF(start_expire_offer)), MBOX_DURATION_PRESENTING, TIMER_STOPPABLE)
|
|
|
|
/// The prize is still claimable, but the animation will show it start to recede back into the box
|
|
/obj/structure/mystery_box/proc/start_expire_offer()
|
|
presented_item.expire_animation()
|
|
box_close_timer = addtimer(CALLBACK(src, PROC_REF(close_box)), MBOX_DURATION_EXPIRING, TIMER_STOPPABLE)
|
|
|
|
/// The box is closed, whether because the prize fully expired, or it was claimed. Start resetting all of the state stuff
|
|
/obj/structure/mystery_box/proc/close_box()
|
|
box_state = MYSTERY_BOX_COOLING_DOWN
|
|
update_icon_state()
|
|
QDEL_NULL(presented_item)
|
|
deltimer(box_close_timer)
|
|
deltimer(box_expire_timer)
|
|
playsound(src, crate_close_sound, 100)
|
|
box_close_timer = null
|
|
box_expire_timer = null
|
|
addtimer(CALLBACK(src, PROC_REF(ready_again)), MBOX_DURATION_STANDBY)
|
|
if(uses_left <= 0)
|
|
visible_message("[src] breaks down.")
|
|
deconstruct(disassembled = FALSE)
|
|
|
|
/// The cooldown between activations has finished, shake to show that
|
|
/obj/structure/mystery_box/proc/ready_again()
|
|
SSsounds.free_sound_channel(current_sound_channel)
|
|
current_sound_channel = null
|
|
box_state = MYSTERY_BOX_STANDBY
|
|
Shake(3, 0, 0.5 SECONDS)
|
|
|
|
/// Someone attacked the box with an empty hand, spawn the shown prize and give it to them, then close the box
|
|
/obj/structure/mystery_box/proc/grant_weapon(mob/living/user)
|
|
var/atom/movable/instantiated_weapon = new presented_item.selected_path(loc)
|
|
user.visible_message(span_notice("[user] takes [presented_item] from [src]."), span_notice("You take [presented_item] from [src]."), vision_distance = COMBAT_MESSAGE_RANGE)
|
|
playsound(src, grant_sound, 70, FALSE, channel = current_sound_channel, falloff_exponent = 10)
|
|
close_box()
|
|
|
|
if(!isitem(instantiated_weapon))
|
|
return
|
|
user.put_in_hands(instantiated_weapon)
|
|
|
|
if(!isgun(instantiated_weapon))
|
|
return
|
|
// handle pins + possibly extra ammo
|
|
var/obj/item/gun/instantiated_gun = instantiated_weapon
|
|
instantiated_gun.unlock()
|
|
if(!grant_extra_mag || !istype(instantiated_gun, /obj/item/gun/ballistic))
|
|
return
|
|
var/obj/item/gun/ballistic/instantiated_ballistic = instantiated_gun
|
|
if(!instantiated_ballistic.internal_magazine)
|
|
var/obj/item/ammo_box/magazine/extra_mag = new instantiated_ballistic.spawn_magazine_type(loc)
|
|
user.put_in_hands(extra_mag)
|
|
|
|
/obj/structure/mystery_box/guns
|
|
desc = "A wooden crate that seems equally magical and mysterious, capable of granting the user all kinds of different pieces of gear. This one seems focused on firearms."
|
|
|
|
/obj/structure/mystery_box/guns/generate_valid_types()
|
|
valid_types = GLOB.summoned_guns
|
|
|
|
/obj/structure/mystery_box/tdome
|
|
desc = "A wooden crate that seems equally magical and mysterious, capable of granting the user all kinds of different pieces of gear. This one has an extended array of weaponry."
|
|
|
|
/obj/structure/mystery_box/tdome/generate_valid_types()
|
|
valid_types = GLOB.mystery_box_guns + GLOB.mystery_box_extended
|
|
|
|
/obj/structure/mystery_box/wands
|
|
desc = "A wooden crate that seems equally magical and mysterious, capable of granting the user all kinds of different magical items."
|
|
|
|
/obj/structure/mystery_box/wands/generate_valid_types()
|
|
valid_types = GLOB.mystery_magic
|
|
|
|
/obj/structure/mystery_box/wildcard
|
|
desc = "A wooden crate that seems equally magical and mysterious, capable of granting the user all kinds of different pieces of gear. This one has an EXTREAMLY extended array of weaponry."
|
|
|
|
/obj/structure/mystery_box/wildcard/generate_valid_types()
|
|
valid_types = GLOB.summoned_all_guns
|
|
|
|
///A fishing and pirate-themed mystery box, rarely found by fishing in the ocean, then another cannot be caught for the next 30 minutes.
|
|
/obj/structure/mystery_box/fishing
|
|
name = "treasure chest"
|
|
desc = "A piratey coffer equally magical and mysterious, capable of granting different pieces of gear to whoever opens it."
|
|
icon_state = "treasure"
|
|
uses_left = 18
|
|
max_integrity = 100
|
|
damage_deflection = 30
|
|
grant_extra_mag = FALSE
|
|
anchored = FALSE
|
|
|
|
/obj/structure/mystery_box/handle_deconstruct(disassembled)
|
|
new /obj/item/stack/sheet/mineral/wood(drop_location(), 2)
|
|
return ..()
|
|
|
|
/obj/structure/mystery_box/fishing/generate_valid_types()
|
|
valid_types = GLOB.mystery_fishing
|
|
|
|
/obj/structure/mystery_box/fishing/activate(mob/living/user)
|
|
if(user.mind && minds_that_opened_us?[WEAKREF(user.mind)] >= 3)
|
|
to_chat(user, span_warning("[src] refuses to open to you anymore. Perhaps you should present it to someone else..."))
|
|
return
|
|
return ..()
|
|
|
|
/// This represents the item that comes out of the box and is constantly changing before the box finishes deciding. Can probably be just an /atom or /movable.
|
|
/obj/effect/abstract/mystery_box_item
|
|
name = "???"
|
|
desc = "Who knows what it'll be??"
|
|
icon = 'icons/obj/weapons/guns/ballistic.dmi'
|
|
icon_state = "revolver"
|
|
pixel_z = -4
|
|
uses_integrity = FALSE
|
|
|
|
/// The currently selected item. Constantly changes while choosing, determines what is spawned if the prize is claimed, and its current icon
|
|
var/selected_path = /obj/item/gun/ballistic/revolver/c38/detective
|
|
/// The box that spawned this
|
|
var/obj/structure/mystery_box/parent_box
|
|
/// Whether this prize is currently claimable
|
|
var/claimable = FALSE
|
|
|
|
|
|
/obj/effect/abstract/mystery_box_item/Initialize(mapload)
|
|
. = ..()
|
|
var/matrix/starting = matrix()
|
|
starting.Scale(0.5,0.5)
|
|
transform = starting
|
|
add_filter("weapon_rays", 3, list("type" = "rays", "size" = 28, "color" = COLOR_VIVID_YELLOW))
|
|
|
|
/obj/effect/abstract/mystery_box_item/Destroy(force)
|
|
parent_box = null
|
|
return ..()
|
|
|
|
// this way, clicking on the prize will work the same as clicking on the box
|
|
/obj/effect/abstract/mystery_box_item/attack_hand(mob/living/user, list/modifiers)
|
|
. = ..()
|
|
if(claimable)
|
|
parent_box.grant_weapon(user)
|
|
|
|
/// Start pushing the prize up
|
|
/obj/effect/abstract/mystery_box_item/proc/start_animation(atom/parent)
|
|
parent_box = parent
|
|
loop_icon_changes()
|
|
|
|
/// Keep changing the icon and selected path
|
|
/obj/effect/abstract/mystery_box_item/proc/loop_icon_changes()
|
|
var/change_delay = 1 // the running count of the delay
|
|
var/change_delay_delta = 1 // How much to increment the delay per step so the changing slows down
|
|
var/change_counter = 0 // The running count of the running count
|
|
|
|
var/matrix/starting = matrix()
|
|
animate(src, pixel_z = 10, transform = starting, time = MBOX_DURATION_CHOOSING, easing = QUAD_EASING | EASE_OUT)
|
|
|
|
while((change_counter + change_delay_delta + change_delay) < MBOX_DURATION_CHOOSING)
|
|
change_delay += change_delay_delta
|
|
change_counter += change_delay
|
|
selected_path = pick(parent_box.valid_types)
|
|
addtimer(CALLBACK(src, PROC_REF(update_random_icon), selected_path), change_counter)
|
|
|
|
addtimer(CALLBACK(src, PROC_REF(present_item)), MBOX_DURATION_CHOOSING)
|
|
|
|
/// animate() isn't up to the task for queueing up icon changes, so this is the proc we call with timers to update our icon
|
|
/obj/effect/abstract/mystery_box_item/proc/update_random_icon(new_item_type)
|
|
var/atom/movable/new_item = new_item_type
|
|
icon = new_item::icon
|
|
icon_state = new_item::icon_state
|
|
|
|
/obj/effect/abstract/mystery_box_item/proc/present_item()
|
|
var/atom/movable/selected_item = selected_path
|
|
add_filter("ready_outline", 2, list("type" = "outline", "color" = COLOR_VIVID_YELLOW, "size" = 0.2))
|
|
name = selected_item::name
|
|
parent_box.present_weapon()
|
|
claimable = TRUE
|
|
|
|
/// Sink back into the box
|
|
/obj/effect/abstract/mystery_box_item/proc/expire_animation()
|
|
var/matrix/shrink_back = matrix()
|
|
shrink_back.Scale(0.5,0.5)
|
|
animate(src, pixel_z = -4, transform = shrink_back, time = MBOX_DURATION_EXPIRING)
|
|
|
|
#undef MYSTERY_BOX_COOLING_DOWN
|
|
#undef MYSTERY_BOX_STANDBY
|
|
#undef MYSTERY_BOX_CHOOSING
|
|
#undef MYSTERY_BOX_PRESENTING
|
|
#undef MBOX_DURATION_CHOOSING
|
|
#undef MBOX_DURATION_PRESENTING
|
|
#undef MBOX_DURATION_EXPIRING
|
|
#undef MBOX_DURATION_STANDBY
|