Files
Bubberstation/code/game/objects/structures/mystery_box.dm
T
necromanceranne 997dac9616 Imports and Contraband: Different! Cargo crates without locks! MEAT! (#74490)
## About The Pull Request

### **Cargo Black Market goods should stay in cargo's hands**

#### New Cargo Console Category: Imports

This category is explicitly the non-departmental category beyond simply
having a Misc category. It is meant for material that nobody is meant to
be buying for their departments, and mostly for the odd-ball crates that
might show up. It also allows us to maintain contraband as exactly that;
contraband that the departments shouldn't have access too whatsoever. If
someone is buying from this category, they probably intend to be a
cheeky fuck.

<details>
  <summary>The New Changes</summary>

#### Baseline Imports

MEAT: MEAT (meat backpack you can eat)

<details>
  <summary>MEAT</summary>
  
![MEAT
MEAT](https://user-images.githubusercontent.com/40847847/229593459-f3c98abe-114b-43c1-a3e2-afc16b76c84f.png)
![MEAT MEAT MEAT
MEAT](https://user-images.githubusercontent.com/40847847/229593473-07a30781-a05e-4ca5-893b-778900cd2d1c.png)

</details>

Duct Spiders: They're adorable and cause a mess, but that doesn't stop
Nanotrasen from importing them from the Australicus sector to your
station!

Stack of 50 Bamboo Cuttings: Pretty expensive and kind of a premium.
Allows for those people looking to make bamboo decorations without
hoping botany exists, and are at least willing to pay. Also lets them
make horribly dangerous stuff with bamboo, of course.

A Single Sheet of Bananium: The problems this will cause I think speak
for themselves. (mostly due to a clown fruitlessly attempting to make
something actually disruptive while bankrupting cargo)

Natural Fish Bait: It isn't cheating, it's homemade. (Really good bait
but expensive and obviously drugs)

A dumpster...: A corpse in a dumpster, doesn't get more complicated than
that. Useful for corpse reasons.

Made using some code I borrowed from over here!
https://github.com/lizardqueenlexi/orbstation/pull/354

#### Contraband Imports

Foam Force Pistols: Same as it ever was with a price reduction. I
brought it down because riot darts are like 8 bullets a clip, and do
less damage than a disabler using riot darts. It feels like a sidegrade
weapon, and even if it technically is a ballistic weapon, it...isn't
that strong. I think this is pretty safe.

Definitely Not a Duct Spider: It's actually a giant spider in a box. If
you want to waste cargo's money while also sending them a mess to deal
with, this is the crate for you.

Russian Surplus Military Gear Crate: I took this opportunity to futz
with boltaction rifles. There are two kinds of mosin nagant you can get
in this crate. One of them is the good kind (no jamming). The other is
the shit kind (yes jamming), but you get more of them. You can get the
good ammo, or you can get the shit ammo. You'll have to pick through it
a lot more carefully to make sure you know which ones you've received.
Since this dilutes the pool even further, getting a good number of
mosins that aren't trash is even more expensive, and even if you do get
mosins at all, you might still only get the bad ammunition that doesn't
work against actual human threats as well. It also now cannot be
purchased through the security cargo supply console, and as to why they
could in the first place baffles me. Doesn't have a lock anymore
because...it's contraband? Who is locking this stuff?

**Side note: _You can make surplus 7.62 in the autolathe as well. It is
not very good except to fight fauna or naked assistants._**

**Side Side note: _I've killed off the shitty brand_new subtype and
brought peace once more to this land._**

#### Illegal Imports (Emag)

NULL_ENTRY: A journal that suggests how to make a...very interesting
weapon. The Regal Condor. Kind of an evolution on some other ideas I've
had over the years. This one is basically a secret weapon with a few
hurdles to jump through. Very lethal. Very expensive.

**Side note: _For reference, it's effectively 19 TC worth of gear to
make, but there does exist some methods to acquire this more cheaply if
you can get some bits and pieces from world spawns. Given it requires
you to get some pieces of equipment that might require additional
purchases of contraband, and getting into the captain's office to loot a
specific piece of clothing, the stakes more than make up for the
effectiveness._**

Smuggled WT-550 Autorifle Crate: This is basically the same, but you
might have noticed had you recently attempted, like me, to buy these
when you emagged them using a personal account and discovered a tragic
oversight. You couldn't, because they still needed armory access. This
removes that access, because you've already gone to the effort of
getting your hands on an illicit firearm through cargo, and if they
techs somehow miss the fact that you've purchased a WT-550...all the
better for you!

Smuggled WT-550 Ammo Crate: Includes AP and Incendiary!

**Side note: _You can get WT-550 ammo again via the Illegal Technology
node._**

Shocktrooper: Replaces the Special Ops crate. Contains a box of EMPs,
smoke grenades, a couple of gluon grenades and a couple of frag
grenades. Funsies.

Special Ops: The NEW Special Ops crate. Contains a chameleon mask,
jumpsuit and agent card. And a knife.

**Side note: _This is what appears in some cargo loan events._**

Refurbished Mosin Nagant Crate: The actual good mosin nagants. There are
6 of them. But they don't come with spare ammo. Hand them out to your
techs!
</details>

#### New Crates

- MEAT crate - Standard
- Duct Spider crate - Standard
- Giant Hostile Spider crate - Contraband
- 50 sheets of Bamboo crate - Standard
- A single sheet of bananium crate - Standard
- Natural (drugs) fish bait - Standard
- Dumpster with a corpse in it - Standard
- Shocktrooper crate (Grenades) - Emag
- Special Ops crate (Disguise) - Emag - Appears in some cargo loan
events
- Refurbished Mosin Nagant crate - Emag
- Regal Condor construction journal (NULL_ENTRY) - Emag

#### Changed Crates

- Foam Force Pistols (cheaper) - Contraband
- Russian Surplus Crate (less reliable, can't be bought by security
console) - Contraband
- WT-550 crate (more obtainable via personal accounts, thus
incriminating, not armory locked) - Emag
- WT-550 ammo (includes incendiary and AP) - Emag

#### Crates that got moved, unchanged, into Imports

- Foam Force Crate 
- Cosa Nostra Crate 
- Black Market LTSRBT 
- 'Contraband' Crate 
- Biker Gang Crate

#### Not crate changes
- You can print Surplus 7.62 (same as normal 7.62 but it sucks against
armor) from hacked autolathes.
- You can get WT-550 ammo from illegal tech.
- Removes the redundant Brand New Mosin subtype
- Fixes a potential exploit with jamming chance on Mosins.

## Why It's Good For The Game

I just think some of the magic of Cargo getting their hands on obviously
dangerous equipment and either hording it for themselves or attempting
to pawn it off was lost in recent times. A lot of this 'black market'
gear, however, suddenly became openly available to the crew anyway. For
_free_. Contraband crates and mafia crates could be purchased via the
Service budget. Security could just stock up en masse on mosins through
their console. And one fairly unfortunate consequence of a few recent
changes has made it nearly impossible to actually get illicit gear in
the first place, even if you did go to the effort of getting the money
for it.

On top of this, most of cargo's goods are pretty safe purchases. There
isn't much that would be considered 'actually a really bad idea to buy'
other than maybe supermatter shards. I wouldn't mind there existing ways
for someone to waste cargo's money while also causing them to have to
clean up the mess.

## Changelog
🆑
balance: A significant overhaul of various illicit and dubiously legal
goods and gadgets available via cargo.
balance: Cargo now has an Import category for all non-departmental
goods. (And black market goods)
balance: Most contraband that already exists has been moved into
Imports.
adds: Includes several new imports of dubious quality. You get what you
pay for.
code: Removes the brand new mosin subtype as it is now defunct.
fix: Fixes potentially exploitative code in the jamming proc. Cleans up
that code while I'm at it.
/🆑

---------

Co-authored-by: Jacquerel <hnevard@gmail.com>
Co-authored-by: carlarctg <53100513+carlarctg@users.noreply.github.com>
2023-04-08 18:35:10 +00:00

294 lines
12 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/lasercannon,
/obj/item/gun/energy/recharge/ebow/large,
/obj/item/gun/energy/e_gun,
/obj/item/gun/energy/e_gun/advtaser,
/obj/item/gun/energy/e_gun/nuclear,
/obj/item/gun/energy/e_gun/turret,
/obj/item/gun/energy/laser,
/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,
/obj/item/gun/energy/pulse/pistol,
/obj/item/gun/ballistic/shotgun/lethal,
/obj/item/gun/ballistic/shotgun/automatic/combat,
/obj/item/gun/ballistic/shotgun/bulldog,
/obj/item/gun/ballistic/rifle/boltaction,
/obj/item/gun/ballistic/automatic/ar,
/obj/item/gun/ballistic/automatic/proto,
/obj/item/gun/ballistic/automatic/c20r,
/obj/item/gun/ballistic/automatic/l6_saw,
/obj/item/gun/ballistic/automatic/m90,
/obj/item/gun/ballistic/automatic/tommygun,
/obj/item/gun/ballistic/automatic/wt550,
/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/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,
))
/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_open.ogg'
var/crate_close_sound = 'sound/machines/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/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
/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)
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 = list()
for(var/iter_path in typesof(selectable_base_type))
if(!ispath(iter_path, /obj/item))
continue
var/obj/item/iter_item = iter_path
if((initial(iter_item.item_flags) & ABSTRACT) || !initial(iter_item.icon_state) || !initial(iter_item.inhand_icon_state))
continue
valid_types += iter_path
/// 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(loc)
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)
/// 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)
/// 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/obj/item/instantiated_weapon = new presented_item.selected_path(src)
user.put_in_hands(instantiated_weapon)
if(isgun(instantiated_weapon)) // 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))
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.mag_type(loc)
user.put_in_hands(extra_mag)
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()
/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
/// 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/mystery_box_item
name = "???"
desc = "Who knows what it'll be??"
icon = 'icons/obj/weapons/guns/ballistic.dmi'
icon_state = "revolver"
pixel_y = -8
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/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/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/mystery_box_item/attack_hand(mob/living/user, list/modifiers)
. = ..()
if(claimable)
parent_box.grant_weapon(user)
/// Start pushing the prize up
/obj/mystery_box_item/proc/start_animation(atom/parent)
parent_box = parent
loop_icon_changes()
/// Keep changing the icon and selected path
/obj/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_y = 6, 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/mystery_box_item/proc/update_random_icon(new_item_type)
var/obj/item/new_item = new_item_type
icon = initial(new_item.icon)
icon_state = initial(new_item.icon_state)
/obj/mystery_box_item/proc/present_item()
var/obj/item/selected_item = selected_path
add_filter("ready_outline", 2, list("type" = "outline", "color" = "#FBFF23", "size" = 0.2))
name = initial(selected_item.name)
parent_box.present_weapon()
claimable = TRUE
/// Sink back into the box
/obj/mystery_box_item/proc/expire_animation()
var/matrix/shrink_back = matrix()
shrink_back.Scale(0.5,0.5)
animate(src, pixel_y = -8, 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