Bitrunning Gimmick Loadout Disks (feat. minor disk load refactor) (#88716)

## About The Pull Request

Where I forget about a pr for 5 months.

This started because there was a pr that made it so skills transfer
between the real and the digital, and so I thought it'd be really funny
if I could train boxing in virtual reality... but there's no easy way to
get boxing gloves or anything like it in the virtual world...
So! I decided to make a disk for it, but then thought about fishing and
gaming and-
Anyhow it all went downhill from there and here we are now: Gimmick
Disks. (And a refactor of disk loading)

This implements a new type of bitrunning disk that instead of a single
item or ability, grants a full set of thematic items/abilities!
Unhelpfully cosplay as a wizard! Game inside your game! Down a digital
protein shake and box some simplemobs!
As the name "Gimmick" implies, these are primarily intended to help
shake up the sometimes stale bitrunner gameplay.
By letting you invoke, if you so desire, what to me is the most
enjoyable gaming experience: doing stupid shit with your buddies.

To facilitate the new type of disk I had to refactor disk loading, as it
was hardcoded to the item types.
Instead, we make disk loading send a signal to the bitrunner, and
register for this when held in their inventory.
This allows us to do things like making the lead acid battery give you
shock touch when held, without needing to make an explicit typecheck or
iterate over every item in the bitrunner's nested contents to see if
they have a loadable item.
## Why It's Good For The Game

I think it'd be really funny if you could train your boxing in the
digital realm.
As said above, I feel the bitrunner gameplay can get stale sometimes,
and this is how I hope to help people shake it up for themselves
sometimes. By giving them more stupid shit to do.
Doing stupid extended bits with other people is one of the things I
enjoy most out of ss13, and this is there to let the bitrunners do
exactly that with each other.

And sometimes you just have to roleplay as Gamers™️ entering virtual
reality to fight the virtual syndicate in bad cosplay while roleplaying
as a wizard smoking his magic weed, an overly edgy rogue, and the healer
desperately trying to keep them from exploding into a million pieces.
## Changelog
🆑
refactor: Bitrunning item/ability loading has been refactored. Please
report any issues.
add: Added Bitrunning gimmick loadout disks. These disks contain full
sets of equipment for all your digital cosplay needs, each including
questionably helpful equipment. Currently includes Sports (Boxer,
Skater, Archer, Fisher, Gamer) and Dungeon Crawling (Alchemist, Rogue,
Healer, Wizard).
add: Taking a lead acid battery into the netpod with you now gives your
bit avatar shock touch.
/🆑
This commit is contained in:
_0Steven
2025-01-30 13:17:08 +01:00
committed by GitHub
parent b28c314f4f
commit fe1e071499
12 changed files with 658 additions and 41 deletions
+34
View File
@@ -19,6 +19,10 @@
icon_state = "[base_icon_state][rand(0, 7)]"
update_icon()
AddComponent(/datum/component/loads_avatar_gear, \
load_callback = CALLBACK(src, PROC_REF(load_onto_avatar)), \
)
/obj/item/bitrunning_disk/examine(mob/user)
. = ..()
. += span_infoplain("This disk must be carried on your person into a netpod to be used.")
@@ -30,6 +34,11 @@
. += span_info("It has been used to select: <b>[choice_made]</b>.")
. += span_notice("It cannot make another selection.")
/// Handles loading our stuff onto avatars
/obj/item/bitrunning_disk/proc/load_onto_avatar(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, external_load_flags)
return NONE
/obj/item/bitrunning_disk/ability
desc = "A disk containing source code. It can be used to preload abilities into the virtual domain. Duplicate abilities will be ignored."
/// The selected ability that this grants
@@ -37,6 +46,20 @@
/// The list of actions that this can grant
var/list/datum/action/selectable_actions = list()
/obj/item/bitrunning_disk/ability/load_onto_avatar(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, external_load_flags)
if(external_load_flags & DOMAIN_FORBIDS_ABILITIES)
return BITRUNNER_GEAR_LOAD_BLOCKED
if(isnull(granted_action))
return BITRUNNER_GEAR_LOAD_FAILED
if(locate(granted_action) in avatar.actions)
return BITRUNNER_GEAR_LOAD_FAILED
var/datum/action/our_action = new granted_action()
our_action.Grant(avatar)
return NONE
/obj/item/bitrunning_disk/ability/attack_self(mob/user, modifiers)
. = ..()
@@ -87,6 +110,7 @@
/datum/action/cooldown/spell/shapeshift/polar_bear,
)
/obj/item/bitrunning_disk/item
desc = "A disk containing source code. It can be used to preload items into the virtual domain."
/// The selected item that this grants
@@ -94,6 +118,16 @@
/// The list of actions that this can grant
var/list/obj/selectable_items = list()
/obj/item/bitrunning_disk/item/load_onto_avatar(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, external_load_flags)
if(external_load_flags & DOMAIN_FORBIDS_ITEMS)
return BITRUNNER_GEAR_LOAD_BLOCKED
if(isnull(granted_item))
return BITRUNNER_GEAR_LOAD_FAILED
avatar.put_in_hands(new granted_item())
return NONE
/obj/item/bitrunning_disk/item/attack_self(mob/user, modifiers)
. = ..()
@@ -0,0 +1,115 @@
/**
* Bitrunning tech disks which let you load full loadouts into the vdom on first avatar generation.
*/
/obj/item/bitrunning_disk/gimmick
desc = "A disk containing source code. It can be used to preload gimmick loadouts into the virtual domain."
/// The selected loadout that this grants
var/datum/bitrunning_gimmick/granted_loadout
/// The list of loadouts that this can grant
var/list/datum/bitrunning_gimmick/selectable_loadouts
/obj/item/bitrunning_disk/gimmick/Destroy()
QDEL_NULL(granted_loadout)
return ..()
/obj/item/bitrunning_disk/gimmick/load_onto_avatar(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, external_load_flags)
if(isnull(granted_loadout))
return BITRUNNER_GEAR_LOAD_FAILED
return granted_loadout.grant_loadout(neo, avatar, external_load_flags)
/obj/item/bitrunning_disk/gimmick/attack_self(mob/user, modifiers)
. = ..()
if(granted_loadout)
return
var/names = list()
for(var/datum/bitrunning_gimmick/loadout as anything in selectable_loadouts)
names += initial(loadout.name)
var/choice = tgui_input_list(user, message = "Select a gimmick loadout", title = "Bitrunning Program", items = names)
if(isnull(choice) || !user.is_holding(src))
return
for(var/datum/bitrunning_gimmick/loadout as anything in selectable_loadouts)
if(initial(loadout.name) == choice)
granted_loadout = new loadout()
balloon_alert(user, "selected")
playsound(user, 'sound/items/click.ogg', 50, TRUE)
choice_made = choice
/**
* The datum used by the gimmick loadout disk to determine what a loadout actually spawns.
*/
/datum/bitrunning_gimmick
/// Player readable name of the gimmick loadout
var/name = "Gimmick Loadout"
/// The list of actions that this will grant
var/list/datum/action/granted_actions
/// The list of items that this will grant
var/list/obj/item/granted_items
/// The item type we will use as a container for our granted items, given to the avatar
var/obj/item/container_item_type = /obj/item/storage/briefcase/secure/digital_storage
/// Prefix our name onto the
var/prefix_container_name = TRUE
/// Grants out loadout.
/datum/bitrunning_gimmick/proc/grant_loadout(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, external_load_flags)
var/return_flags = NONE
return_flags |= grant_items(neo, avatar, external_load_flags)
return_flags |= grant_abilities(neo, avatar, external_load_flags)
return return_flags
/datum/bitrunning_gimmick/proc/grant_items(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, external_load_flags)
if(!length(granted_items))
return NONE
if(external_load_flags & DOMAIN_FORBIDS_ITEMS)
return BITRUNNER_GEAR_LOAD_BLOCKED
var/obj/item/container_item = new container_item_type()
if(prefix_container_name)
container_item.name = "[LOWER_TEXT(name)]'s [initial(container_item.name)]"
for(var/obj/item/granted_item as anything in granted_items)
new granted_item(container_item)
avatar.put_in_hands(container_item)
return NONE
/datum/bitrunning_gimmick/proc/grant_abilities(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, external_load_flags)
if(!length(granted_actions))
return NONE
if(external_load_flags & DOMAIN_FORBIDS_ABILITIES)
return BITRUNNER_GEAR_LOAD_BLOCKED
var/return_flags = NONE
for(var/datum/action/granted_action as anything in granted_actions)
if(locate(granted_action) in avatar.actions)
return_flags |= BITRUNNER_GEAR_LOAD_FAILED
continue
var/datum/action/our_action = new granted_action()
our_action.Grant(avatar)
return return_flags
/**
* Base container used for gimmick disks.
*/
/obj/item/storage/briefcase/secure/digital_storage
name = "digi-case"
desc = "It's made of AUTHENTIC digital leather and has a price-tag still attached. Its owner must be a real professional."
icon_state = "secure"
base_icon_state = "secure"
inhand_icon_state = "sec-case"
/obj/item/storage/briefcase/secure/digital_storage/PopulateContents()
return
@@ -0,0 +1,226 @@
/**
* Bitrunning gimmick loadouts themed around dungeon crawling.
* Mostly for fun, have niche but not insignificant advantages.
*/
/obj/item/bitrunning_disk/gimmick/dungeon
name = "bitrunning gimmick: dungeon crawling"
selectable_loadouts = list(
/datum/bitrunning_gimmick/alchemist,
/datum/bitrunning_gimmick/rogue,
/datum/bitrunning_gimmick/healer,
/datum/bitrunning_gimmick/wizard,
)
/datum/bitrunning_gimmick/alchemist
name = "Alchemist"
granted_items = list(
/obj/item/clothing/suit/bio_suit/plaguedoctorsuit,
/obj/item/clothing/mask/gas/plaguedoctor,
/obj/item/clothing/head/bio_hood/plague,
/obj/item/storage/box/alchemist_basic_chems,
/obj/item/storage/box/alchemist_basic_chems,
/obj/item/storage/box/alchemist_random_chems,
/obj/item/storage/box/alchemist_chemistry_kit,
)
/obj/item/reagent_containers/cup/bottle/alchemist_basic
name = "unlabeled bottle"
desc = "A small bottle. You don't remember what you put in it."
/// List of possible reagents we may pick from
var/static/list/possible_reagents = list(
/datum/reagent/aluminium, // Basic chems
/datum/reagent/bromine,
/datum/reagent/carbon,
/datum/reagent/chlorine,
/datum/reagent/copper,
/datum/reagent/consumable/ethanol,
/datum/reagent/fluorine,
/datum/reagent/hydrogen,
/datum/reagent/iodine,
/datum/reagent/iron,
/datum/reagent/lithium,
/datum/reagent/mercury,
/datum/reagent/nitrogen,
/datum/reagent/oxygen,
/datum/reagent/phosphorus,
/datum/reagent/potassium,
/datum/reagent/uranium/radium,
/datum/reagent/silicon,
/datum/reagent/sodium,
/datum/reagent/stable_plasma,
/datum/reagent/consumable/sugar,
/datum/reagent/sulfur,
/datum/reagent/toxin/acid,
/datum/reagent/water,
/datum/reagent/fuel,
/datum/reagent/toxin/leadacetate, // Funny chems
/datum/reagent/consumable/liquidgibs,
/datum/reagent/consumable/nutriment,
/datum/reagent/consumable/coffee,
)
/obj/item/reagent_containers/cup/bottle/alchemist_basic/add_initial_reagents()
var/our_reagent = pick(possible_reagents)
reagents.add_reagent(our_reagent, 50)
/obj/item/reagent_containers/cup/bottle/alchemist_random
name = "skull-labeled bottle"
desc = "A small bottle. You don't remember what you put in it."
/// List of random adjectives this bottle may have
var/static/list/possible_adjectives = list(
"unlabeled",
"skull-labeled", // Labels
"heart-labeled",
"explosion-labeled",
"fish-labeled",
"smiley-labeled",
"frown-labeled",
"interrobang-labeled",
"plus-labeled",
"d20-labeled",
"unreadably-labeled",
"black-labeled",
"empty-labeled",
"age-yellowed", // Qualities
"blood-tinged",
"ash-marred",
"claw-scratched",
"marker-marked",
"cracked",
"ominous", // Abstract
)
/obj/item/reagent_containers/cup/bottle/alchemist_random/Initialize(mapload)
. = ..()
name = "[pick(possible_adjectives)] bottle"
/obj/item/reagent_containers/cup/bottle/alchemist_random/add_initial_reagents()
var/our_reagent = get_random_reagent_id()
var/our_amount = rand(20, 50)
reagents.add_reagent(our_reagent, our_amount)
/obj/item/storage/box/alchemist_basic_chems
name = "box of alchemical bases"
desc = "Contains a set of basic reagents, for all your potion-making needs! If only you labeled them."
illustration = "beaker"
/obj/item/storage/box/alchemist_basic_chems/PopulateContents()
for(var/i in 1 to 7)
if(prob(1))
new /obj/item/reagent_containers/cup/glass/coffee(src)
continue
new /obj/item/reagent_containers/cup/bottle/alchemist_basic(src)
/obj/item/storage/box/alchemist_random_chems
name = "box of potions"
desc = "An especially fancy box to keep your finished potions safe."
icon_state = "syndiebox"
illustration = "beaker"
/obj/item/storage/box/alchemist_random_chems/PopulateContents()
for(var/i in 1 to 7)
if(prob(1))
new /obj/item/reagent_containers/cup/glass/coffee(src)
continue
new /obj/item/reagent_containers/cup/bottle/alchemist_random(src)
/obj/item/storage/box/alchemist_chemistry_kit
name = "box of alchemy tools"
desc = "Contains everything needed for the up and coming chemistry student to enact hazardous chemical mishaps in the comfort of their own home."
/obj/item/storage/box/alchemist_chemistry_kit/PopulateContents()
new /obj/item/reagent_containers/cup/mortar(src)
new /obj/item/pestle(src)
new /obj/item/lighter/skull(src)
new /obj/item/ph_booklet(src)
new /obj/item/thermometer(src)
new /obj/item/storage/test_tube_rack/full(src)
new /obj/item/reagent_containers/cup/glass/coffee(src)
/datum/bitrunning_gimmick/rogue
name = "Rogue"
granted_items = list(
/obj/item/clothing/under/color/black,
/obj/item/clothing/shoes/sneakers/black,
/obj/item/clothing/mask/facescarf/rogue,
/obj/item/clothing/glasses/eyepatch,
/obj/item/bedsheet/black/rogue_cape,
/obj/item/storage/belt/fannypack/black/rogue,
/obj/item/knife/combat/survival,
)
/obj/item/clothing/shoes/sneakers/black
name = "sneaker of SNEAKING"
/obj/item/clothing/mask/facescarf/rogue
name = "cloth of DOOM"
greyscale_colors = "#292929"
/obj/item/clothing/glasses/eyepatch
name = "eyepatch of SEALING"
/obj/item/bedsheet/black/rogue_cape
name = "cape of DARKNESS"
/obj/item/storage/belt/fannypack/black/rogue
name = "fannypack of ULTIMATE DESPAIR"
/obj/item/storage/belt/fannypack/black/rogue/PopulateContents()
new /obj/item/food/drug/saturnx(src)
new /obj/item/reagent_containers/cup/blastoff_ampoule(src)
new /obj/item/reagent_containers/hypospray/medipen/methamphetamine(src)
/datum/bitrunning_gimmick/healer
name = "Healer"
granted_items = list(
/obj/item/clothing/under/costume/singer/yellow,
/obj/item/clothing/shoes/singery,
/obj/item/rod_of_asclepius,
/obj/item/storage/medkit/surgery,
/obj/item/emergency_bed,
/obj/item/food/canned/larvae,
/obj/item/reagent_containers/dropper,
)
/datum/bitrunning_gimmick/wizard
name = "Wizard"
granted_items = list(
/obj/item/clothing/head/wizard/fake,
/obj/item/clothing/suit/wizrobe/fake,
/obj/item/clothing/glasses/eyepatch/medical/chuuni,
/obj/item/staff,
/obj/item/toy/eightball,
/obj/item/storage/fancy/cigarettes/cigpack_cannabis,
/obj/item/storage/box/matches,
)
granted_actions = list(
/datum/action/cooldown/spell/pointed/untie_shoes/digital,
/datum/action/cooldown/spell/smoke/digital,
)
/datum/action/cooldown/spell/pointed/untie_shoes/digital
name = "Untie Digi-Shoes"
spell_requirements = SPELL_REQUIRES_WIZARD_GARB
/datum/action/cooldown/spell/smoke/digital
name = "Digi-Smoke"
desc = "This spell spawns a small cloud of smoke at your location."
school = SCHOOL_CONJURATION
cooldown_time = 36 SECONDS
spell_requirements = SPELL_REQUIRES_WIZARD_GARB
smoke_type = /datum/effect_system/fluid_spread/smoke
smoke_amt = 2
@@ -0,0 +1,152 @@
/**
* Bitrunning gimmick loadouts themed around sports.
* Mostly for fun, have niche or little advantages.
*/
/obj/item/bitrunning_disk/gimmick/sports
name = "bitrunning gimmick: sports"
selectable_loadouts = list(
/datum/bitrunning_gimmick/boxer,
/datum/bitrunning_gimmick/skater,
/datum/bitrunning_gimmick/archer,
/datum/bitrunning_gimmick/fisher,
/datum/bitrunning_gimmick/gamer,
)
/datum/bitrunning_gimmick/boxer
name = "Boxer"
granted_items = list(
/obj/item/clothing/gloves/boxing/evil,
/obj/item/clothing/under/shorts/red,
/obj/item/reagent_containers/condiment/protein,
/obj/item/reagent_containers/cup/glass/drinkingglass/filled/protein_blend,
)
/obj/item/reagent_containers/cup/glass/drinkingglass/filled/protein_blend
name = "Protein Blend"
list_reagents = list(/datum/reagent/consumable/ethanol/protein_blend = 50)
/datum/bitrunning_gimmick/skater
name = "Skater"
granted_items = list(
/obj/item/clothing/shoes/wheelys,
/obj/item/melee/skateboard,
/obj/item/clothing/suit/costume/wellworn_shirt/graphic,
/obj/item/clothing/head/soft/black,
/obj/item/clothing/shoes/sneakers/black,
/obj/item/storage/cans/sixenergydrink,
)
/obj/item/storage/cans/sixenergydrink
name = "energy drink bottle ring"
desc = "Holds six energy drink cans. Remember to recycle when you're done!"
/// Pool of energy drinks tm we may add from
var/list/energy_drink_options = list(
/obj/item/reagent_containers/cup/soda_cans/space_mountain_wind = 50,
/obj/item/reagent_containers/cup/soda_cans/monkey_energy = 30,
/obj/item/reagent_containers/cup/soda_cans/volt_energy = 15,
/obj/item/reagent_containers/cup/soda_cans/thirteenloko = 5,
)
/obj/item/storage/cans/sixenergydrink/PopulateContents()
for(var/i in 1 to 6)
var/obj/item/chosen_energy_drink = pick_weight(energy_drink_options)
new chosen_energy_drink(src)
/datum/bitrunning_gimmick/archer
name = "Archer"
granted_items = list(
/obj/item/clothing/under/costume/kimono,
/obj/item/clothing/shoes/sandal/alt,
/obj/item/storage/bag/quiver/endless,
/obj/item/gun/ballistic/bow/longbow,
/obj/item/ammo_casing/arrow/holy/blazing,
)
/obj/item/storage/bag/quiver/endless
name = "endless quiver"
desc = "Holds arrows for your bow. A deep digital void is contained within."
max_slots = 1
/obj/item/storage/bag/quiver/endless/Initialize(mapload)
. = ..()
RegisterSignal(src, COMSIG_ATOM_EXITED, PROC_REF(handle_removed))
/obj/item/storage/bag/quiver/endless/PopulateContents()
. = ..()
new arrow_path(src)
/obj/item/storage/bag/quiver/endless/proc/handle_removed(datum/source, obj/item/gone)
new arrow_path(src)
/datum/bitrunning_gimmick/fisher
name = "Fisher"
granted_items = list(
/obj/item/clothing/under/misc/overalls,
/obj/item/clothing/suit/jacket/miljacket,
/obj/item/clothing/head/soft/black,
/obj/item/clothing/shoes/jackboots,
/obj/item/storage/toolbox/fishing/small,
/obj/item/bait_can/worm/premium,
/obj/item/grenade/iedcasing/spawned,
/obj/item/stock_parts/power_store/cell/lead,
/obj/item/reagent_containers/cup/glass/bottle/beer/light,
)
/datum/bitrunning_gimmick/gamer
name = "Gamer"
granted_items = list(
/obj/item/clothing/under/suit/black_really,
/obj/item/clothing/shoes/laceup,
/obj/item/clothing/gloves/color/black,
/obj/item/clothing/glasses/sunglasses,
/obj/item/modular_computer/laptop/gamer,
/obj/item/storage/cans/sixgamerdrink,
)
/obj/item/storage/cans/sixgamerdrink
name = "gamer drink bottle ring"
desc = "Holds six gamer drink cans. Remember to recycle when you're done!"
/// Pool of gamer drinks tm we may add from
var/list/gamer_drink_options = list(
/obj/item/reagent_containers/cup/soda_cans/pwr_game = 55,
/obj/item/reagent_containers/cup/soda_cans/space_mountain_wind = 15,
/obj/item/reagent_containers/cup/soda_cans/monkey_energy = 15,
/obj/item/reagent_containers/cup/soda_cans/volt_energy = 10,
/obj/item/reagent_containers/cup/soda_cans/thirteenloko = 5,
)
/obj/item/storage/cans/sixgamerdrink/PopulateContents()
for(var/i in 1 to 6)
var/obj/item/chosen_gamer_drink = pick_weight(gamer_drink_options)
new chosen_gamer_drink(src)
/obj/item/modular_computer/laptop/gamer
desc = "A high-end laptop often used for metagaming."
device_theme = PDA_THEME_TERMINAL
starting_programs = list(
/datum/computer_file/program/themeify,
/datum/computer_file/program/filemanager,
/datum/computer_file/program/notepad,
/datum/computer_file/program/arcade/eazy,
/datum/computer_file/program/mafia,
)
start_open = FALSE
/obj/item/modular_computer/laptop/gamer/install_default_programs()
// Only install starting programs, we don't want the software downloading program from default programs
for(var/programs as anything in starting_programs)
var/datum/computer_file/program/program_type = new programs
store_file(program_type)