From b670443c1f49f8cc239f0f31b618e592b5ade7dc Mon Sep 17 00:00:00 2001 From: Vekter Date: Fri, 28 Jun 2024 15:42:46 -0500 Subject: [PATCH 01/45] Fixes Bitrunner lacking job-based bounties (#84367) ## About The Pull Request Fixes #84283 by just adding actual bounties for Bitrunner, giving them something more interesting to do than random bounties for jobs they don't have access to. These bounties revolve around optionally turning in the special rewards from each of the bitrunning domains (a bike horn, the abductor plush, beach ball, etc) for a credit reward. I considered the idea of making each one drop a specific bounty chit that you turned in for a reward, but at that point it's just "paying you per domain" and I'm not sure how that would balance with economy. This is more just meant to be a framework for others to add things that might be a little more interesting. This also adds a special drop from the Pizzeria domain because I thought it was funny. --- code/__DEFINES/economy.dm | 3 +- code/game/objects/items/food/pizza.dm | 7 +++ code/modules/bitrunning/job.dm | 2 +- .../domains/fredingtonfastingbear.dm | 1 + code/modules/cargo/bounties/bitrunning.dm | 48 +++++++++++++++++++ code/modules/cargo/bounty.dm | 4 +- tgstation.dme | 1 + 7 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 code/modules/cargo/bounties/bitrunning.dm diff --git a/code/__DEFINES/economy.dm b/code/__DEFINES/economy.dm index ce8b47224d6c..344b575aa41d 100644 --- a/code/__DEFINES/economy.dm +++ b/code/__DEFINES/economy.dm @@ -58,7 +58,8 @@ #define CIV_JOB_MED 11 #define CIV_JOB_GROW 12 #define CIV_JOB_ATMOS 13 -#define CIV_JOB_RANDOM 14 +#define CIV_JOB_BITRUN 14 +#define CIV_JOB_RANDOM 15 //These defines are to be used to with the payment component, determines which lines will be used during a transaction. If in doubt, go with clinical. #define PAYMENT_CLINICAL "clinical" diff --git a/code/game/objects/items/food/pizza.dm b/code/game/objects/items/food/pizza.dm index 3700bf09ee47..ef88427d4dfe 100644 --- a/code/game/objects/items/food/pizza.dm +++ b/code/game/objects/items/food/pizza.dm @@ -124,6 +124,13 @@ foodtypes = GRAIN | VEGETABLES | DAIRY | MEAT crafting_complexity = FOOD_COMPLEXITY_3 +/obj/item/food/pizzaslice/meat/pizzeria //Reward for pizzeria bitrunning domain + name = "pizzeria meatpizza slice" + desc = "An ostensibly nutritious slice of meatpizza from a long-closed pizzeria." + food_reagents = null + tastes = list("crust" = 1, "ketchup" = 1, "'cheese'" = 1, "mystery meat" = 1, "glue" = 1) + foodtypes = null + /obj/item/food/pizza/mushroom name = "mushroom pizza" desc = "Very special pizza." diff --git a/code/modules/bitrunning/job.dm b/code/modules/bitrunning/job.dm index a45578affa44..e59c4c82c53d 100644 --- a/code/modules/bitrunning/job.dm +++ b/code/modules/bitrunning/job.dm @@ -13,7 +13,7 @@ paycheck = PAYCHECK_CREW paycheck_department = ACCOUNT_CAR display_order = JOB_DISPLAY_ORDER_BITRUNNER - bounty_types = CIV_JOB_RANDOM + bounty_types = CIV_JOB_BITRUN departments_list = list( /datum/job_department/cargo, ) diff --git a/code/modules/bitrunning/virtual_domain/domains/fredingtonfastingbear.dm b/code/modules/bitrunning/virtual_domain/domains/fredingtonfastingbear.dm index 841956675ca9..f3c803384c45 100644 --- a/code/modules/bitrunning/virtual_domain/domains/fredingtonfastingbear.dm +++ b/code/modules/bitrunning/virtual_domain/domains/fredingtonfastingbear.dm @@ -2,6 +2,7 @@ name = "Pizza Party" cost = BITRUNNER_COST_MEDIUM desc = "A famous pizzeria that got ruined by what it had to offer." + completion_loot = list(/obj/item/food/pizzaslice/meat/pizzeria = 1) difficulty = BITRUNNER_DIFFICULTY_MEDIUM help_text = "Pick up a flashlight and get going. Your favourite bear has been waiting for you..." key = "fredingtonfastingbear" diff --git a/code/modules/cargo/bounties/bitrunning.dm b/code/modules/cargo/bounties/bitrunning.dm new file mode 100644 index 000000000000..301f19f0e412 --- /dev/null +++ b/code/modules/cargo/bounties/bitrunning.dm @@ -0,0 +1,48 @@ +/datum/bounty/item/bitrunning/abductor_plush + name = "Abductor Plush" + description = "Rear Admiral Raynes' child won't stop bothering him for one of those little plush aliens. You can get one from the Abductor Ship domain." + reward = CARGO_CRATE_VALUE * 6 + required_count = 1 + wanted_types = list(/obj/item/toy/plush/abductor/agent = TRUE) + +/datum/bounty/item/bitrunning/bubblegum + name = "Bubblegum Plush" + description = "Some of the miners have been talking about the breakroom being too sterile. We need something cute to spruce the place up a bit." + reward = CARGO_CRATE_VALUE * 10 + required_count = 1 + wanted_types = list(/obj/item/toy/plush/bubbleplush = TRUE) + +/datum/bounty/item/bitrunning/honk + name = "Bike Horn" + description = "Bonbon the Clown lost his bike horn when Rear Admiral Raynes hurled him out an airlock for slipping him. Can you get us a replacement?" + reward = CARGO_CRATE_VALUE * 4 + required_count = 1 + wanted_types = list(/obj/item/bikehorn = TRUE) + +/datum/bounty/item/bitrunning/pizzeria + name = "Slice of Pizzeria Pizza" + description = "Admiral Chivara keeps talking about wanting a slice of pizza from that old pizza joint that closed down. Y'know, the one with the robots." + reward = CARGO_CRATE_VALUE * 8 + required_count = 1 + wanted_types = list(/obj/item/food/pizzaslice/meat/pizzeria = TRUE) + +/datum/bounty/item/bitrunning/psyker + name = "Psyker Headset" + description = "We're doing some research on psykers and could use some of their equipment. Can you send us one of their headsets if you find it?" + reward = CARGO_CRATE_VALUE * 8 + required_count = 1 + wanted_types = list(/obj/item/radio/headset/psyker = TRUE) + +/datum/bounty/item/bitrunning/polar + name = "Polar Ushanka" + description = "A bunch of Russians raided one of our outposts and won't leave. They keep swearing at us and demanding we give them vodka and ushankas. Can you send us one?" + reward = CARGO_CRATE_VALUE * 3 + required_count = 1 + wanted_types = list(/obj/item/clothing/head/costume/ushanka/polar = TRUE) + +/datum/bounty/item/bitrunning/syndicate + name = "Syndicate Plush" + description = "We're aware of some contraband that's made it aboard some of our stations that depict Syndicate agents as cute, marketable plushies. Please send us one so we can further investigate." + reward = CARGO_CRATE_VALUE * 8 + required_count = 1 + wanted_types = list(/obj/item/toy/plush/nukeplushie = 1) diff --git a/code/modules/cargo/bounty.dm b/code/modules/cargo/bounty.dm index aa527e847625..38b26e440273 100644 --- a/code/modules/cargo/bounty.dm +++ b/code/modules/cargo/bounty.dm @@ -1,5 +1,5 @@ /// How many jobs have bounties, minus the random civ bounties. PLEASE INCREASE THIS NUMBER AS MORE DEPTS ARE ADDED TO BOUNTIES. -#define MAXIMUM_BOUNTY_JOBS 13 +#define MAXIMUM_BOUNTY_JOBS 14 /datum/bounty var/name @@ -77,6 +77,8 @@ chosen_type = pick(subtypesof(/datum/bounty/item/botany)) if(CIV_JOB_ATMOS) chosen_type = pick(subtypesof(/datum/bounty/item/atmospherics)) + if(CIV_JOB_BITRUN) + chosen_type = pick(subtypesof(/datum/bounty/item/bitrunning)) bounty_ref = new chosen_type if(bounty_ref.can_get()) bounty_succeeded = TRUE diff --git a/tgstation.dme b/tgstation.dme index 90710db7a252..021b5d5ba611 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3646,6 +3646,7 @@ #include "code\modules\cargo\universal_scanner.dm" #include "code\modules\cargo\bounties\assistant.dm" #include "code\modules\cargo\bounties\atmos.dm" +#include "code\modules\cargo\bounties\bitrunning.dm" #include "code\modules\cargo\bounties\botany.dm" #include "code\modules\cargo\bounties\chef.dm" #include "code\modules\cargo\bounties\engineering.dm" From 44887cec37e511c6f4998bf02bae84f96ec03bfc Mon Sep 17 00:00:00 2001 From: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Date: Tue, 2 Jul 2024 20:03:12 -0700 Subject: [PATCH 02/45] Bitrunning: Combat domain [READY] (#84196) Finally dusts off this project to make a deathmatch style bitrunning map. Don't be too intimidated by the file diff, lots of code organization + resized a large map. Changes: 1. Reuses the gateway beach map as a combat zone (99% of the file diff) (maptainers: i just added spawners and areas) 2. Alters how bitrunning handles spawning: Custom spawns are now available, which can be anything Misc organization: - Splits netpod.dm into separate files. - Fixes some wording in vdom map documentation. - Organizes vdom variables a bit. - Adds a permanent hololadder spawn. How bitrunning deathmatch works: - Temporary spawners are offered to both ghosts and bitrunners. - Runners spawn in like usual. Ghost can use the spawner menu. - Ghosts work to prevent avatars from collecting side objectives or try to cause mass brain damage. - The domain completes after a number of deaths accrue. Any faction. Blood for the blood god, etc. - This map can be played solo. ANY deaths. I've been toying with the idea of a deathmatch style map for some time. I liked syndicate assault, the spawners were intentionally left there, and the possibility of player-controlled players made the experience more tense and challenging. This PR leans into this idea: The virtual world is dangerous. Players get a chance to compete on both sides here. It offers a lot of variety to bitrunning other than "run for box". It's also very lucrative if ghosts join in. :cl: add: Added a bitrunning deathmatch map: Island Brawl. Both ghosts and runners get many more spawns than normal. fix: Lowered the static vision time in domain load in. /:cl: --- _maps/virtual_domains/README.md | 5 +- _maps/virtual_domains/island_brawl.dmm | 12262 ++++++++++++++++ .../dcs/signals/signals_bitrunning.dm | 1 + code/controllers/subsystem/bitrunning.dm | 3 +- .../components/bitrunning_points.dm | 6 +- code/modules/bitrunning/netpod/_netpod.dm | 137 + code/modules/bitrunning/netpod/container.dm | 74 + code/modules/bitrunning/netpod/outfitting.dm | 30 + code/modules/bitrunning/netpod/signals.dm | 64 + code/modules/bitrunning/netpod/tools.dm | 22 + code/modules/bitrunning/netpod/ui.dm | 41 + code/modules/bitrunning/netpod/utils.dm | 144 + code/modules/bitrunning/objects/landmarks.dm | 5 + code/modules/bitrunning/outfits.dm | 50 + code/modules/bitrunning/server/_parent.dm | 6 + code/modules/bitrunning/server/loot.dm | 3 + .../modules/bitrunning/server/map_handling.dm | 20 + .../bitrunning/server/obj_generation.dm | 37 +- .../bitrunning/server/signal_handlers.dm | 6 + code/modules/bitrunning/server/threats.dm | 8 + code/modules/bitrunning/server/util.dm | 93 +- code/modules/bitrunning/spawners.dm | 19 +- .../virtual_domain/domains/island_brawl.dm | 46 + .../virtual_domain/domains/pirates.dm | 1 + .../domains/syndicate_assault.dm | 1 + .../virtual_domain/virtual_domain.dm | 2 + icons/effects/bitrunning.dmi | Bin 3048 -> 3268 bytes tgstation.dme | 9 +- .../tgui/interfaces/QuantumConsole.tsx | 33 +- 29 files changed, 13067 insertions(+), 61 deletions(-) create mode 100644 _maps/virtual_domains/island_brawl.dmm create mode 100644 code/modules/bitrunning/netpod/_netpod.dm create mode 100644 code/modules/bitrunning/netpod/container.dm create mode 100644 code/modules/bitrunning/netpod/outfitting.dm create mode 100644 code/modules/bitrunning/netpod/signals.dm create mode 100644 code/modules/bitrunning/netpod/tools.dm create mode 100644 code/modules/bitrunning/netpod/ui.dm create mode 100644 code/modules/bitrunning/netpod/utils.dm create mode 100644 code/modules/bitrunning/virtual_domain/domains/island_brawl.dm diff --git a/_maps/virtual_domains/README.md b/_maps/virtual_domains/README.md index 6e9abc655682..f0a8a1ebfd21 100644 --- a/_maps/virtual_domains/README.md +++ b/_maps/virtual_domains/README.md @@ -1,11 +1,12 @@ # Making new virtual domains ## REQUIRED: -1. One way that the encrypted cache can spawn. This can be from a mob drop, a landmark (place a few, it'll pick one), or a signable landmark if you have a points system. +1. One way that the encrypted cache can spawn. This can be from a mob drop, a landmark (place a few, it'll pick one), or a signal landmark if you have a points system. 2. Place a virtual domain baseturf helper in each area. 3. If you're using modular safehouses, ensure that the map has ONE tile marked with the safehouse modular map loader (and set the KEY). it will need an open 7x6 area. 4. Placing a safehouse area is redundant, but it will ensure there is power in the starting safehouse. 5. Create the dm file that defines the map qualities. You can use the existing ones as a template. +6. Place a virtual domain baseturf helper in each area. ## Converting an existing map 1. Create a new map using the existing map's size - give yourself enough room to enclose it with a binary wall. There's no need for any space outside of it, so ensure that it fits and is enclosed, nothing outside of this. @@ -18,7 +19,7 @@ You shouldn't need to fully enclose your map in 15 tiles of binary filler. Using For areas, ideally just one on the map and one for the safehouse. Vdoms should never last so long as to need individual area power and atmos unless you're specifically going for a gimmick. -Use modular mob segments! Use modular map segments! Add some variety to your maps! Just make sure you've set your map to have "is_modular" afterwards. +Make it modular: Add modular map and mob segments! It adds variety. Just make sure you've set your map to have "is_modular" afterwards. Adding some open tile padding around the safehouse is a good touch. About 7 tiles West/East for the visual effect of a larger map. diff --git a/_maps/virtual_domains/island_brawl.dmm b/_maps/virtual_domains/island_brawl.dmm new file mode 100644 index 000000000000..dd2ed14f2f3c --- /dev/null +++ b/_maps/virtual_domains/island_brawl.dmm @@ -0,0 +1,12262 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"al" = ( +/obj/structure/flora/coconuts{ + pixel_x = -5; + pixel_y = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"as" = ( +/obj/structure/flora/tree/palm/style_2{ + pixel_y = 6 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"aw" = ( +/obj/structure/railing{ + dir = 1 + }, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"aA" = ( +/obj/structure/chair/stool/bamboo{ + dir = 1 + }, +/obj/effect/turf_decal/siding/wood/corner{ + dir = 8 + }, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"aD" = ( +/obj/item/storage/medkit/regular{ + pixel_y = 15; + pixel_x = -2 + }, +/obj/structure/table/glass, +/obj/item/emergency_bed{ + pixel_y = 6 + }, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"aE" = ( +/obj/structure/chair/stool/directional/south, +/turf/open/floor/iron/freezer, +/area/virtual_domain) +"aQ" = ( +/obj/structure/closet/secure_closet/personal/cabinet, +/obj/item/clothing/under/costume/sailor, +/turf/open/floor/wood, +/area/virtual_domain) +"aY" = ( +/obj/structure/table/wood, +/obj/item/tape{ + pixel_y = 7; + pixel_x = 11 + }, +/obj/item/reagent_containers/cup/glass/coffee{ + pixel_x = -7; + pixel_y = 12 + }, +/obj/effect/spawner/random/bureaucracy/pen{ + pixel_x = -3; + pixel_y = -2 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"aZ" = ( +/obj/structure/table/wood, +/obj/item/food/grown/watermelon{ + pixel_y = 8; + pixel_x = 6 + }, +/obj/item/food/watermelonslice, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"ba" = ( +/obj/effect/landmark/bitrunning/curiosity_spawn, +/turf/open/floor/iron/dark/textured_large, +/area/virtual_domain) +"bb" = ( +/obj/item/circular_saw{ + pixel_y = 4 + }, +/obj/item/scalpel{ + pixel_y = 15 + }, +/obj/structure/table/glass, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"bp" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 10 + }, +/obj/effect/turf_decal/tile/dark/half{ + pixel_y = -1; + pixel_x = 5; + dir = 8 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"bx" = ( +/obj/machinery/defibrillator_mount/directional/west, +/obj/structure/bed/pod{ + desc = "An old medical bed, just waiting for replacement with something up to date."; + dir = 4; + name = "medical bed" + }, +/obj/machinery/iv_drip{ + pixel_y = 21; + pixel_x = -4 + }, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"bD" = ( +/obj/structure/chair/plastic{ + dir = 8 + }, +/obj/item/reagent_containers/cup/soda_cans/melon_soda{ + pixel_y = 13; + pixel_x = -9 + }, +/obj/item/fishing_rod{ + pixel_x = 8; + pixel_y = -6 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"bE" = ( +/turf/open/floor/wood, +/area/virtual_domain) +"bK" = ( +/turf/open/floor/wood/parquet, +/area/virtual_domain/fullbright) +"bT" = ( +/obj/item/cigbutt{ + pixel_y = -4; + pixel_x = 2 + }, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"bX" = ( +/obj/effect/turf_decal/sand/plating, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"bY" = ( +/turf/open/misc/beach/coast{ + dir = 4 + }, +/area/virtual_domain/fullbright) +"cc" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 6 + }, +/obj/machinery/vending/cigarette/beach, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"ce" = ( +/obj/structure/reagent_dispensers/fueltank, +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/decal/cleanable/cobweb/cobweb2, +/turf/open/floor/plating, +/area/virtual_domain) +"cg" = ( +/obj/machinery/medical_kiosk, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"ch" = ( +/obj/machinery/recharge_station, +/obj/machinery/light/directional/east, +/turf/open/floor/carpet/cyan, +/area/virtual_domain) +"cp" = ( +/obj/item/reagent_containers/cup/soda_cans/cola{ + pixel_x = -8; + pixel_y = -6 + }, +/obj/item/reagent_containers/cup/soda_cans/space_mountain_wind{ + pixel_x = 15 + }, +/obj/structure/fluff/beach_umbrella/science, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"cr" = ( +/obj/structure/closet/secure_closet/personal/cabinet, +/obj/machinery/button/door/directional/north{ + id = "beach_room_9"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/obj/item/claymore/cutlass{ + desc = "A piratey, foam sword used by kids to train themselves in the business of \"negotiating\" the transfer of treasure."; + force = 0; + name = "foam cutlass"; + throwforce = 0 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"ct" = ( +/obj/effect/turf_decal/siding/wood/corner{ + dir = 1 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"cB" = ( +/obj/structure/closet/secure_closet/personal/cabinet, +/obj/item/clothing/under/costume/pirate, +/turf/open/floor/wood, +/area/virtual_domain) +"cC" = ( +/obj/structure/reagent_dispensers/watertank, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/plating, +/area/virtual_domain) +"cE" = ( +/turf/open/misc/beach/coast, +/area/virtual_domain/fullbright) +"cH" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/machinery/door/airlock/wood{ + name = "Room 7"; + id_tag = "beach_room_7" + }, +/turf/open/floor/wood, +/area/virtual_domain) +"cK" = ( +/obj/item/reagent_containers/cup/glass/bottle/wine{ + pixel_y = 16; + pixel_x = -5 + }, +/obj/item/reagent_containers/cup/glass/drinkingglass/shotglass{ + pixel_y = 3; + pixel_x = -8 + }, +/obj/structure/table/bronze, +/obj/item/reagent_containers/cup/glass/drinkingglass/shotglass{ + pixel_y = 10; + pixel_x = 6 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"cO" = ( +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"cW" = ( +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/carpet/cyan, +/area/virtual_domain) +"db" = ( +/obj/machinery/door/airlock/wood{ + name = "Room 11"; + id_tag = "beach_room_11" + }, +/obj/effect/turf_decal/siding/wood, +/turf/open/floor/wood, +/area/virtual_domain) +"de" = ( +/obj/effect/turf_decal/sand, +/mob/living/basic/crab, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"dj" = ( +/obj/machinery/door/airlock/wood/glass{ + name = "Hotel's Facilities" + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"dl" = ( +/obj/structure/table/bronze, +/obj/item/reagent_containers/condiment/bbqsauce{ + pixel_y = 13; + pixel_x = 3 + }, +/obj/item/reagent_containers/condiment/hotsauce{ + pixel_y = 9; + pixel_x = -3 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"dp" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"du" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/effect/turf_decal/tile/dark/half{ + pixel_y = -1; + pixel_x = 5; + dir = 8 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"dv" = ( +/obj/structure/table, +/obj/item/wrench{ + pixel_y = 4 + }, +/obj/item/storage/toolbox/emergency, +/obj/effect/decal/cleanable/oil, +/turf/open/floor/iron/dark/herringbone, +/area/virtual_domain) +"dA" = ( +/obj/structure/table/bronze, +/obj/item/paper_bin/carbon{ + pixel_y = 8 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"dB" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 6 + }, +/turf/open/floor/eighties, +/area/virtual_domain) +"dD" = ( +/obj/item/trash/can{ + pixel_y = -9; + pixel_x = 10 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"dG" = ( +/obj/effect/turf_decal/siding/blue, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"dH" = ( +/obj/structure/closet/crate/bin, +/turf/open/floor/wood, +/area/virtual_domain) +"dS" = ( +/obj/structure/table/bronze, +/obj/structure/sign/poster/random/directional/east, +/obj/item/table_clock{ + pixel_y = 4 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"dW" = ( +/obj/structure/railing/corner/end/flip{ + dir = 1 + }, +/obj/machinery/door/airlock/wood{ + name = "Balcony"; + id_tag = "beach_room_x" + }, +/turf/open/floor/wood, +/area/virtual_domain) +"dX" = ( +/obj/item/toy/seashell{ + pixel_y = -5 + }, +/turf/open/misc/beach/coast, +/area/virtual_domain/fullbright) +"dY" = ( +/obj/structure/table/wood/poker, +/obj/item/stack/spacecash/c100{ + pixel_y = 5 + }, +/obj/item/toy/cards/deck{ + pixel_y = 13; + pixel_x = -5 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"ee" = ( +/obj/structure/table/reinforced, +/obj/item/defibrillator/loaded{ + pixel_y = 7 + }, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"ef" = ( +/obj/effect/turf_decal/stripes/red/line{ + dir = 8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"eh" = ( +/obj/structure/table/wood/fancy, +/obj/effect/turf_decal/tile/dark_red/full, +/obj/item/reagent_containers/cup/soda_cans/cola{ + pixel_x = -13; + pixel_y = 14 + }, +/obj/item/reagent_containers/cup/soda_cans/cola{ + pixel_x = 7; + pixel_y = 8 + }, +/turf/open/floor/iron/smooth_large, +/area/virtual_domain/fullbright) +"en" = ( +/turf/open/floor/eighties, +/area/virtual_domain) +"ev" = ( +/obj/structure/mineral_door/sandstone{ + name = "Sandcastle" + }, +/obj/effect/turf_decal/sand/plating, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"ew" = ( +/obj/effect/landmark/bitrunning/cache_goal_turf, +/obj/effect/landmark/bitrunning/loot_signal, +/turf/open/indestructible/binary, +/area/virtual_domain/protected_space/fullbright) +"eH" = ( +/turf/open/misc/beach/coast{ + dir = 9 + }, +/area/virtual_domain/fullbright) +"eJ" = ( +/obj/structure/table/bronze, +/obj/item/camera{ + pixel_y = 5 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"eK" = ( +/turf/closed/wall/mineral/wood, +/area/virtual_domain/fullbright) +"eM" = ( +/obj/item/trash/can{ + pixel_y = -5; + pixel_x = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"eO" = ( +/obj/structure/table/bronze, +/obj/item/camera{ + pixel_y = 8; + pixel_x = -3 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"eP" = ( +/obj/effect/decal/cleanable/dirt, +/obj/machinery/food_cart, +/turf/open/floor/plating, +/area/virtual_domain) +"fe" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/effect/turf_decal/tile/dark/half{ + pixel_y = -2; + pixel_x = 5; + dir = 8 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"fg" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/wall/mineral/wood, +/area/virtual_domain) +"fv" = ( +/obj/item/toy/seashell{ + pixel_x = 12; + pixel_y = -9 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"fH" = ( +/obj/structure/table/bronze, +/obj/item/camera{ + pixel_y = 8; + pixel_x = 6 + }, +/obj/item/pen/blue{ + pixel_x = -11; + pixel_y = 2 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"fI" = ( +/turf/open/misc/beach/sand, +/area/virtual_domain/protected_space/fullbright) +"fJ" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 4 + }, +/turf/open/water/beach, +/area/virtual_domain) +"fL" = ( +/obj/machinery/computer/order_console/cook, +/turf/open/floor/wood/large, +/area/virtual_domain) +"fY" = ( +/obj/structure/window/reinforced/spawner/directional/west, +/obj/structure/window/reinforced/spawner/directional/south, +/obj/item/binoculars{ + pixel_y = 20 + }, +/obj/item/megaphone{ + pixel_y = 2; + pixel_x = 1 + }, +/obj/structure/table/wood, +/obj/effect/turf_decal/siding/wood{ + dir = 10 + }, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"gc" = ( +/obj/structure/table/bronze, +/obj/item/pai_card{ + desc = "A real Nanotrasen success, these personal AIs provide all of the companionship of an AI without any law related red-tape."; + name = "\improper Nanotrasen-brand personal AI device exhibit"; + pixel_y = 4 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"gf" = ( +/obj/machinery/button/door/directional/south{ + id = "beach_room_5"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"gi" = ( +/obj/effect/turf_decal/siding/wood/corner, +/obj/item/stack/arcadeticket{ + pixel_y = 12; + pixel_x = -16 + }, +/turf/open/floor/eighties, +/area/virtual_domain) +"gs" = ( +/obj/structure/closet/crate, +/obj/item/clothing/suit/hooded/carp_costume, +/obj/item/clothing/head/hooded/carp_hood, +/obj/item/toy/plush/carpplushie, +/obj/item/toy/plush/shark, +/turf/open/floor/eighties, +/area/virtual_domain) +"gw" = ( +/turf/open/floor/carpet/cyan, +/area/virtual_domain) +"gA" = ( +/obj/effect/turf_decal/sand{ + density = 1 + }, +/obj/effect/decal/fakelattice, +/turf/open/floor/pod/light{ + density = 1 + }, +/area/virtual_domain/fullbright) +"gB" = ( +/obj/effect/landmark/bitrunning/curiosity_spawn, +/turf/open/floor/plastic, +/area/virtual_domain) +"gC" = ( +/obj/item/reagent_containers/cup/soda_cans/cola{ + pixel_x = -5; + pixel_y = -5 + }, +/obj/structure/fluff/beach_umbrella/syndi, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"gH" = ( +/obj/structure/tank_holder/extinguisher, +/turf/open/floor/wood/large, +/area/virtual_domain) +"gI" = ( +/obj/item/storage/toolbox/fishing{ + pixel_x = 2; + pixel_y = -13 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"gK" = ( +/obj/structure/bed/double, +/obj/item/bedsheet/hos/double{ + name = "bedsheet"; + desc = "A bedsheet from the beach hotel." + }, +/turf/open/floor/carpet, +/area/virtual_domain) +"gL" = ( +/turf/open/misc/beach/coast/corner, +/area/virtual_domain/fullbright) +"gN" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 4 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"gP" = ( +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"gV" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 25 + }, +/obj/structure/flora/coconuts{ + pixel_x = -7; + pixel_y = 7 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"hh" = ( +/obj/effect/turf_decal/sand/plating, +/obj/item/shovel{ + pixel_y = 15; + pixel_x = -8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"ho" = ( +/obj/structure/chair/stool/bar/directional/west, +/turf/open/floor/wood/large, +/area/virtual_domain) +"hu" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/misc/beach/sand, +/area/virtual_domain/protected_space/fullbright) +"hA" = ( +/obj/structure/table/wood, +/obj/machinery/chem_dispenser/drinks/fullupgrade{ + dir = 4 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"hB" = ( +/obj/item/reagent_containers/cup/soda_cans/lemon_lime{ + pixel_x = -12 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"hD" = ( +/obj/effect/decal/cleanable/oil, +/obj/machinery/button/door/directional/south{ + id = "BeachGarage" + }, +/turf/open/floor/iron/dark/herringbone, +/area/virtual_domain) +"hM" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/obj/machinery/button/door/directional/south{ + id = "BeachBoats" + }, +/obj/effect/landmark/bitrunning/curiosity_spawn, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"hT" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/mapping_helpers/broken_floor, +/obj/effect/decal/cleanable/cobweb, +/obj/machinery/power/smes/full, +/turf/open/floor/plating, +/area/virtual_domain) +"hZ" = ( +/obj/structure/table/bronze, +/obj/item/storage/bag/tray, +/turf/open/floor/wood/large, +/area/virtual_domain) +"ii" = ( +/obj/effect/turf_decal/sand, +/obj/effect/turf_decal/siding/wood{ + dir = 5 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"ij" = ( +/turf/open/misc/beach/coast{ + dir = 10 + }, +/area/virtual_domain/fullbright) +"ik" = ( +/obj/structure/dresser, +/obj/machinery/digital_clock/directional/north, +/turf/open/floor/wood, +/area/virtual_domain) +"il" = ( +/obj/structure/chair/plastic, +/obj/item/fishing_rod{ + pixel_x = 11; + pixel_y = -8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"io" = ( +/obj/structure/railing{ + dir = 10 + }, +/obj/item/kirbyplants/organic/plant8{ + pixel_y = 9; + pixel_x = 6 + }, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"iy" = ( +/obj/structure/window/reinforced/spawner/directional/east, +/obj/structure/window/reinforced/spawner/directional/north{ + layer = 2.9 + }, +/obj/structure/chair/stool/directional/south, +/obj/item/clothing/glasses/sunglasses{ + pixel_y = -2; + pixel_x = 2 + }, +/obj/item/bikehorn/airhorn{ + pixel_y = -15; + pixel_x = -13 + }, +/obj/effect/turf_decal/siding/wood{ + dir = 5 + }, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"iF" = ( +/obj/structure/table/bronze, +/obj/structure/desk_bell{ + pixel_x = -11; + pixel_y = 9 + }, +/obj/item/phone{ + pixel_y = 9; + pixel_x = 8 + }, +/turf/open/floor/carpet, +/area/virtual_domain) +"iP" = ( +/obj/structure/chair/plastic, +/obj/item/clothing/head/collectable/paper{ + desc = "What looks like an ordinary paper hat is actually a rare and valuable collector's edition paper hat. Keep away from fire, Curators, and ocean waves."; + pixel_y = -4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"iQ" = ( +/obj/machinery/button/door/directional/north{ + id = "beach_room_3"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"iU" = ( +/obj/structure/chair/plastic{ + dir = 8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"iY" = ( +/obj/item/storage/bag/tray, +/obj/item/food/grown/watermelon{ + pixel_y = 4; + pixel_x = -1 + }, +/obj/effect/turf_decal/stripes/red/box, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"jb" = ( +/obj/structure/tank_holder/extinguisher{ + pixel_y = 11 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"jm" = ( +/obj/effect/decal/cleanable/dirt, +/obj/machinery/light/small/directional/west, +/obj/effect/landmark/bitrunning/curiosity_spawn, +/turf/open/floor/plating, +/area/virtual_domain) +"jn" = ( +/obj/item/cigbutt{ + pixel_y = -6; + pixel_x = -12 + }, +/obj/structure/railing/corner/end/flip{ + dir = 4 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"jp" = ( +/obj/structure/closet/secure_closet/personal/cabinet, +/obj/item/clothing/neck/necklace/dope, +/obj/item/clothing/suit/jacket/letterman, +/turf/open/floor/wood, +/area/virtual_domain) +"jq" = ( +/obj/structure/musician/piano, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"jr" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 10; + pixel_x = 7 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"jw" = ( +/obj/machinery/space_heater, +/obj/structure/sign/clock/directional/north, +/turf/open/floor/wood, +/area/virtual_domain) +"jK" = ( +/obj/machinery/reagentgrinder{ + pixel_y = 15; + pixel_x = -1 + }, +/obj/structure/table/bronze, +/obj/structure/desk_bell{ + pixel_x = 12; + pixel_y = 3 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"jL" = ( +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"jP" = ( +/turf/open/indestructible/binary, +/area/virtual_domain/protected_space/fullbright) +"jV" = ( +/turf/open/floor/iron/dark/herringbone, +/area/virtual_domain) +"jW" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/floor/wood/large, +/area/virtual_domain) +"ka" = ( +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"ke" = ( +/obj/vehicle/ridden/atv{ + dir = 4 + }, +/turf/open/floor/iron/dark/herringbone, +/area/virtual_domain) +"ky" = ( +/obj/machinery/vending/snack, +/obj/effect/turf_decal/sand, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"kD" = ( +/obj/machinery/door/airlock/maintenance, +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/turf/open/floor/plating, +/area/virtual_domain) +"kI" = ( +/obj/effect/turf_decal/sand, +/turf/closed/wall/mineral/wood, +/area/virtual_domain) +"kJ" = ( +/obj/machinery/shower/directional/south, +/obj/effect/turf_decal/siding/wood{ + dir = 6 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain/fullbright) +"kL" = ( +/obj/structure/billboard/space_cola, +/obj/effect/turf_decal/siding/dark_red, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"kN" = ( +/obj/effect/turf_decal/sand/plating, +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"lc" = ( +/obj/machinery/space_heater, +/obj/machinery/button/door/directional/north{ + id = "beach_room_2"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"lh" = ( +/obj/effect/decal/cleanable/dirt, +/obj/item/storage/toolbox/mechanical/old, +/turf/open/floor/plating, +/area/virtual_domain) +"lk" = ( +/obj/effect/turf_decal/siding/dark_red/corner, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"lx" = ( +/obj/effect/spawner/structure/window/reinforced, +/turf/open/floor/plating, +/area/virtual_domain/fullbright) +"lz" = ( +/obj/effect/turf_decal/siding/blue/corner{ + dir = 1 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"lA" = ( +/obj/machinery/shower/directional/west, +/obj/effect/turf_decal/siding/wood{ + dir = 9 + }, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"lF" = ( +/obj/structure/table/reinforced, +/obj/item/storage/medkit/o2{ + pixel_y = 17; + pixel_x = -1 + }, +/obj/item/storage/medkit/brute{ + pixel_y = 23; + pixel_x = -1 + }, +/obj/item/food/lollipop{ + pixel_y = 8; + pixel_x = 12 + }, +/obj/item/food/lollipop{ + pixel_y = 5; + pixel_x = 12 + }, +/obj/item/reagent_containers/cup/bottle/multiver{ + pixel_x = -9; + pixel_y = 9 + }, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"lG" = ( +/obj/item/toy/seashell{ + pixel_y = 11; + pixel_x = -7 + }, +/turf/open/misc/beach/coast, +/area/virtual_domain/fullbright) +"lI" = ( +/obj/structure/table/wood/poker, +/turf/open/floor/wood/large, +/area/virtual_domain) +"lJ" = ( +/obj/structure/table/wood, +/obj/item/reagent_containers/cup/beaker/large{ + pixel_y = 7 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"lP" = ( +/obj/structure/chair, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"lS" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 6 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"lU" = ( +/obj/machinery/door/airlock/security/glass{ + name = "Temporary Holding Cell" + }, +/obj/effect/mapping_helpers/airlock/unres{ + dir = 8 + }, +/obj/effect/mapping_helpers/airlock/access/all/security/entrance, +/turf/open/floor/iron/dark/textured_large, +/area/virtual_domain) +"lW" = ( +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"mi" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 24 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"mv" = ( +/obj/effect/turf_decal/siding/dark_red{ + dir = 1 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"mx" = ( +/obj/machinery/door/airlock/wood{ + name = "Arcade" + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"mJ" = ( +/obj/structure/flora/tree/palm/style_2{ + pixel_y = 25 + }, +/obj/item/stack/sheet/mineral/sandstone/thirty{ + pixel_y = -10; + pixel_x = -5 + }, +/obj/item/stack/sheet/mineral/sandstone/thirty{ + pixel_y = -2; + pixel_x = 9 + }, +/obj/item/stack/sheet/mineral/sandstone/thirty{ + pixel_y = -4; + pixel_x = 1 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"mL" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/obj/machinery/door/airlock/wood{ + name = "Tropical Kitchen" + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"mP" = ( +/obj/structure/chair/plastic{ + dir = 8 + }, +/obj/item/reagent_containers/cup/soda_cans/starkist{ + pixel_y = 10; + pixel_x = -17 + }, +/obj/item/fishing_rod{ + pixel_x = -13; + pixel_y = 2 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"mV" = ( +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"mW" = ( +/obj/effect/turf_decal/siding/blue/corner{ + dir = 8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"nc" = ( +/turf/open/misc/beach/coast/corner{ + dir = 1 + }, +/area/virtual_domain/fullbright) +"nk" = ( +/obj/machinery/door/airlock/wood{ + name = "Balcony"; + id_tag = "beach_room_x" + }, +/obj/structure/railing/corner/end/flip{ + dir = 1 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"nR" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 6 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"nX" = ( +/obj/structure/table/wood, +/obj/item/melee/baseball_bat{ + pixel_y = 3; + pixel_x = 6 + }, +/obj/item/clothing/glasses/blindfold/white{ + pixel_y = 7; + pixel_x = -5 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"oa" = ( +/turf/open/misc/beach/coast{ + dir = 1 + }, +/area/virtual_domain/fullbright) +"ob" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"og" = ( +/obj/effect/turf_decal/siding/wood/corner{ + dir = 4 + }, +/turf/open/water/beach, +/area/virtual_domain) +"oq" = ( +/obj/structure/table/wood, +/obj/item/food/grown/watermelon{ + pixel_y = 13; + pixel_x = 6 + }, +/obj/item/food/grown/watermelon{ + pixel_y = 8; + pixel_x = -4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"os" = ( +/obj/structure/flora/tree/palm/style_2{ + pixel_y = 12; + pixel_x = -7 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"ot" = ( +/turf/template_noop, +/area/template_noop) +"ow" = ( +/obj/structure/reagent_dispensers/watertank, +/turf/open/floor/wood/large, +/area/virtual_domain) +"oC" = ( +/obj/item/reagent_containers/cup/soda_cans/cola{ + pixel_x = -8; + pixel_y = 13 + }, +/turf/open/floor/carpet/blue, +/area/virtual_domain/fullbright) +"oE" = ( +/obj/structure/closet/secure_closet/personal/cabinet, +/obj/item/clothing/glasses/heat, +/obj/item/clothing/head/cowboy/brown, +/turf/open/floor/wood, +/area/virtual_domain) +"oJ" = ( +/obj/item/flashlight/flare/torch{ + pixel_x = -11; + pixel_y = -6 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"oQ" = ( +/obj/structure/table/bronze, +/obj/item/pai_card{ + desc = "A real Nanotrasen success, these personal AIs provide all of the companionship of an AI without any law related red-tape."; + name = "\improper Nanotrasen-brand personal AI device exhibit"; + pixel_y = 2 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"oX" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/virtual_domain/fullbright) +"oZ" = ( +/obj/effect/turf_decal/sand, +/obj/effect/turf_decal/siding/wood{ + dir = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"pj" = ( +/obj/structure/table/bronze, +/obj/item/camera{ + pixel_y = 12; + pixel_x = -10 + }, +/obj/item/camera{ + pixel_y = 4; + pixel_x = 1 + }, +/obj/item/camera_film{ + pixel_y = -11; + pixel_x = -10 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"pq" = ( +/obj/structure/closet/secure_closet/personal/cabinet, +/obj/item/clothing/suit/jacket/letterman_syndie, +/turf/open/floor/wood, +/area/virtual_domain) +"pv" = ( +/obj/machinery/light/directional/north, +/turf/open/floor/wood/large, +/area/virtual_domain) +"py" = ( +/turf/open/floor/iron/stairs, +/area/virtual_domain/fullbright) +"pz" = ( +/obj/structure/closet/secure_closet/personal/cabinet, +/obj/item/clothing/under/costume/lobster, +/obj/item/clothing/head/costume/lobsterhat, +/turf/open/floor/wood, +/area/virtual_domain) +"pC" = ( +/obj/effect/turf_decal/siding/wood/corner{ + dir = 1 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"pE" = ( +/obj/machinery/door/airlock/wood/glass{ + name = "Rooms" + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"pJ" = ( +/obj/structure/closet/secure_closet/personal/cabinet, +/obj/item/clothing/suit/jacket/letterman_red, +/turf/open/floor/wood, +/area/virtual_domain) +"pV" = ( +/obj/item/stack/arcadeticket{ + pixel_y = 5; + pixel_x = 13 + }, +/turf/open/floor/eighties, +/area/virtual_domain) +"pX" = ( +/obj/structure/chair/office{ + dir = 8 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"qa" = ( +/obj/effect/turf_decal/siding/wood/corner{ + dir = 4 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"qb" = ( +/obj/machinery/button/door/directional/west{ + id = "beach_room_1"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"qc" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/vending/clothing, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"qB" = ( +/obj/item/toy/seashell{ + pixel_x = 5; + pixel_y = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"qH" = ( +/obj/structure/bed/medical/emergency, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"qR" = ( +/obj/structure/chair/office{ + dir = 8 + }, +/obj/item/cigbutt{ + pixel_y = -6; + pixel_x = -12 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"qZ" = ( +/obj/effect/turf_decal/siding/dark_red/corner{ + dir = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"ra" = ( +/obj/item/stack/sheet/mineral/sandstone{ + pixel_x = 11 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"rj" = ( +/obj/machinery/grill, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"rl" = ( +/obj/structure/rack, +/obj/item/melee/skateboard/hoverboard{ + pixel_x = -7 + }, +/obj/item/melee/skateboard/hoverboard, +/obj/item/melee/skateboard/hoverboard{ + pixel_x = 7 + }, +/turf/open/misc/beach/coast/corner{ + dir = 4 + }, +/area/virtual_domain/fullbright) +"rn" = ( +/obj/machinery/computer/operating{ + dir = 8 + }, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"ro" = ( +/obj/structure/closet/secure_closet/personal, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"rw" = ( +/obj/structure/table/bronze, +/obj/machinery/button/door/directional/south{ + id = "beach_room_4"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/obj/item/storage/toolbox/fishing{ + pixel_y = 2 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"rz" = ( +/obj/effect/turf_decal/siding/wood, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"rB" = ( +/obj/item/toy/seashell{ + pixel_x = -10; + pixel_y = -4 + }, +/obj/structure/fluff/beach_umbrella, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"rD" = ( +/obj/structure/flora/coconuts, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"rE" = ( +/obj/structure/table/bronze, +/obj/machinery/button/door/directional/south{ + id = "beach_room_11"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/obj/item/storage/toolbox/fishing{ + pixel_y = 2 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"rG" = ( +/obj/machinery/telecomms/allinone, +/obj/item/wirecutters{ + pixel_y = 9 + }, +/obj/machinery/light/directional/south, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"rI" = ( +/obj/machinery/door/airlock/wood{ + name = "Room 12"; + id_tag = "beach_room_12" + }, +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"rK" = ( +/obj/machinery/vending/cigarette/beach, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"rQ" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 7; + pixel_x = 9 + }, +/obj/effect/turf_decal/siding/wood, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"rR" = ( +/obj/structure/table/bronze, +/turf/open/floor/wood/large, +/area/virtual_domain) +"rW" = ( +/obj/structure/chair{ + dir = 8 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"sa" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/door/airlock/wood{ + name = "Bathroom"; + id_tag = "toilet3beach" + }, +/turf/open/floor/iron/white, +/area/virtual_domain) +"sh" = ( +/obj/structure/statue/sandstone/venus{ + pixel_y = 9; + dir = 1; + anchored = 1 + }, +/obj/structure/flora/bush/flowers_br/style_random, +/turf/open/floor/grass, +/area/virtual_domain) +"si" = ( +/obj/structure/table/wood, +/obj/item/reagent_containers/condiment/enzyme{ + layer = 5; + pixel_x = 9; + pixel_y = 5 + }, +/obj/item/food/seaweedsheet{ + pixel_y = 6; + pixel_x = -4 + }, +/obj/item/food/seaweedsheet{ + pixel_x = -15; + pixel_y = 2 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"sl" = ( +/obj/effect/turf_decal/sand/plating, +/obj/item/reagent_containers/cup/bucket/wooden{ + pixel_y = 17; + pixel_x = -6 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"st" = ( +/obj/structure/table/bronze, +/obj/structure/sign/poster/random/directional/east, +/obj/machinery/button/door/directional/south{ + id = "beach_room_8"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/obj/item/camera{ + pixel_y = 5 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"sz" = ( +/obj/item/toy/seashell, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"sC" = ( +/obj/item/reagent_containers/cup/soda_cans/space_mountain_wind, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"sI" = ( +/obj/structure/table/bronze, +/obj/item/clothing/suit/costume/hawaiian{ + pixel_y = 2; + pixel_x = 5 + }, +/obj/item/clothing/suit/costume/hawaiian{ + pixel_y = 6; + pixel_x = -4 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain/fullbright) +"sO" = ( +/obj/structure/fluff/beach_umbrella/syndi, +/obj/item/reagent_containers/cup/soda_cans/cola{ + pixel_x = -8; + pixel_y = -6 + }, +/obj/item/reagent_containers/cup/soda_cans/thirteenloko{ + pixel_x = 15; + pixel_y = -11 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"sR" = ( +/obj/structure/flora/tree/palm/style_2{ + pixel_y = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"sV" = ( +/obj/structure/table, +/obj/item/storage/belt/utility, +/obj/item/reagent_containers/cup/glass/drinkingglass/shotglass{ + pixel_y = 14; + pixel_x = -8 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"sW" = ( +/obj/machinery/door/airlock/wood{ + name = "Room 5"; + id_tag = "beach_room_5" + }, +/obj/effect/turf_decal/siding/wood, +/turf/open/floor/wood, +/area/virtual_domain) +"ta" = ( +/obj/effect/turf_decal/siding/wood/corner{ + dir = 8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"tf" = ( +/obj/effect/decal/cleanable/dirt, +/obj/item/storage/box/lights/mixed{ + pixel_x = -4; + pixel_y = 18 + }, +/obj/item/storage/box/lights/mixed{ + pixel_x = 6; + pixel_y = 12 + }, +/obj/item/grenade/chem_grenade/cleaner{ + pixel_x = -7; + pixel_y = 12 + }, +/obj/structure/table, +/obj/item/reagent_containers/cup/bucket{ + pixel_x = 10; + pixel_y = 4 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"th" = ( +/obj/structure/flora/tree/palm, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"tk" = ( +/turf/open/misc/beach/coast/corner{ + dir = 4 + }, +/area/virtual_domain/fullbright) +"tw" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"ty" = ( +/obj/item/trash/boritos/purple{ + pixel_y = -16; + pixel_x = -5 + }, +/turf/open/floor/eighties, +/area/virtual_domain) +"tI" = ( +/obj/structure/sign/departments/custodian/directional/north{ + pixel_x = -32 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"tM" = ( +/obj/structure/table/wood, +/obj/item/book/manual/chef_recipes{ + pixel_x = 2; + pixel_y = 6 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"tQ" = ( +/obj/effect/turf_decal/sand, +/obj/effect/turf_decal/siding/wood/corner{ + dir = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"tW" = ( +/obj/structure/chair/plastic{ + dir = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"tX" = ( +/obj/structure/table/bronze, +/obj/item/reagent_containers/condiment/ketchup{ + pixel_y = 14; + pixel_x = 1 + }, +/obj/item/reagent_containers/condiment/mayonnaise{ + pixel_y = 8; + pixel_x = 8 + }, +/obj/machinery/light/directional/east, +/turf/open/floor/wood/large, +/area/virtual_domain) +"up" = ( +/obj/item/storage/box/syringes{ + pixel_y = 14; + pixel_x = -5 + }, +/obj/item/clothing/neck/stethoscope{ + pixel_y = 3; + pixel_x = 4 + }, +/obj/structure/table/glass, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"ut" = ( +/obj/structure/window/reinforced/spawner/directional/east, +/obj/effect/turf_decal/siding/wood{ + dir = 6 + }, +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"uu" = ( +/turf/open/floor/iron/freezer, +/area/virtual_domain) +"uz" = ( +/obj/effect/turf_decal/siding/dark_red/corner{ + dir = 8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"uC" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 14 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"uD" = ( +/obj/structure/table/wood, +/obj/item/food/seaweedsheet, +/obj/item/kitchen/rollingpin{ + pixel_x = -12; + pixel_y = 3 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"uH" = ( +/obj/structure/bonfire, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"uI" = ( +/obj/structure/bed, +/turf/open/floor/iron/dark/textured_large, +/area/virtual_domain) +"uX" = ( +/obj/effect/turf_decal/siding/dark_red{ + dir = 4 + }, +/obj/item/trash/can{ + pixel_y = -9; + pixel_x = 10 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"uY" = ( +/turf/open/floor/carpet, +/area/virtual_domain) +"vx" = ( +/obj/structure/table/glass, +/obj/item/storage/box/coffeepack{ + pixel_x = 2; + pixel_y = 4 + }, +/obj/item/storage/box/donkpockets{ + pixel_y = 10; + pixel_x = 11 + }, +/obj/item/storage/fancy/donut_box{ + pixel_y = 18; + pixel_x = -11 + }, +/obj/item/food/donut/berry{ + pixel_y = 5; + pixel_x = -6 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"vF" = ( +/obj/structure/railing{ + dir = 9 + }, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"vM" = ( +/obj/effect/turf_decal/sand, +/obj/effect/turf_decal/siding/wood{ + dir = 9 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"vU" = ( +/obj/machinery/chem_master/condimaster{ + name = "CondiMaster Neo" + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"wb" = ( +/turf/open/misc/beach/coast{ + dir = 5 + }, +/area/virtual_domain/fullbright) +"wf" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 10 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"ww" = ( +/obj/structure/chair/stool/directional/south{ + dir = 8 + }, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"wx" = ( +/obj/structure/closet/crate/bin, +/obj/structure/sign/clock/directional/north, +/turf/open/floor/wood, +/area/virtual_domain) +"wz" = ( +/obj/structure/rack, +/obj/item/mop, +/obj/item/extinguisher, +/turf/open/floor/wood/large, +/area/virtual_domain) +"wC" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/obj/structure/reagent_dispensers/fueltank/large, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"wH" = ( +/obj/structure/table/wood/fancy, +/obj/effect/turf_decal/tile/dark_red/full, +/obj/item/reagent_containers/cup/soda_cans/cola{ + pixel_x = -5; + pixel_y = 14 + }, +/obj/item/reagent_containers/cup/soda_cans/cola{ + pixel_y = 13; + pixel_x = 6 + }, +/obj/item/reagent_containers/cup/soda_cans/cola{ + pixel_y = 10 + }, +/obj/item/reagent_containers/cup/soda_cans/cola{ + pixel_y = 6; + pixel_x = 11 + }, +/turf/open/floor/iron/smooth_large, +/area/virtual_domain/fullbright) +"wI" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 6 + }, +/obj/machinery/shower/directional/south, +/turf/open/floor/wood/parquet, +/area/virtual_domain/fullbright) +"wN" = ( +/obj/item/cigbutt{ + pixel_y = 21; + pixel_x = -13 + }, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"wO" = ( +/obj/structure/dresser, +/obj/machinery/light/directional/north, +/turf/open/floor/wood, +/area/virtual_domain) +"wT" = ( +/obj/effect/spawner/random/entertainment/arcade{ + dir = 8 + }, +/turf/open/floor/eighties, +/area/virtual_domain) +"wZ" = ( +/obj/item/clothing/head/soft/green, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"xe" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/door/airlock/wood{ + name = "Bathroom"; + id_tag = "toiletfarbeach" + }, +/turf/open/floor/iron/white, +/area/virtual_domain) +"xg" = ( +/turf/open/misc/beach/sand, +/area/virtual_domain) +"xj" = ( +/obj/item/toy/seashell{ + pixel_x = -8; + pixel_y = 9 + }, +/turf/open/misc/beach/coast/corner{ + dir = 1 + }, +/area/virtual_domain/fullbright) +"xk" = ( +/obj/structure/closet/crate/freezer, +/obj/item/food/meat/slab/rawcrab, +/obj/item/food/meat/slab/rawcrab, +/obj/item/food/meat/slab/rawcrab, +/obj/item/food/meat/slab/chicken, +/obj/item/food/meat/slab/chicken, +/obj/item/food/meat/slab/chicken, +/obj/item/food/meat/slab, +/obj/item/food/meat/slab, +/obj/item/food/meat/slab, +/obj/item/stack/sheet/mineral/coal/ten, +/obj/item/stack/sheet/mineral/coal/ten, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"xw" = ( +/obj/item/kirbyplants/organic/applebush{ + pixel_x = 7 + }, +/obj/item/kirbyplants/organic/plant17{ + pixel_y = 2; + pixel_x = -8 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"xx" = ( +/obj/item/stack/arcadeticket{ + pixel_y = 6; + pixel_x = -7 + }, +/obj/effect/landmark/bitrunning/curiosity_spawn, +/turf/open/floor/eighties, +/area/virtual_domain) +"xJ" = ( +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"xO" = ( +/obj/structure/chair/stool/directional/south, +/obj/machinery/light/directional/north, +/turf/open/floor/iron/freezer, +/area/virtual_domain) +"xP" = ( +/obj/structure/chair/plastic{ + dir = 1 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"xR" = ( +/obj/structure/railing{ + dir = 5 + }, +/obj/item/kirbyplants/organic/plant8{ + pixel_y = 3; + pixel_x = -2 + }, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"xT" = ( +/obj/machinery/oven/range, +/turf/open/floor/wood/large, +/area/virtual_domain) +"yc" = ( +/obj/structure/closet/gmcloset, +/turf/open/floor/plastic, +/area/virtual_domain) +"yg" = ( +/obj/machinery/light/directional/east, +/turf/open/floor/wood/large, +/area/virtual_domain) +"yy" = ( +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/carpet/red, +/area/virtual_domain/fullbright) +"yz" = ( +/obj/structure/table/bronze, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"yB" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 9 + }, +/obj/effect/turf_decal/tile/dark/half{ + pixel_y = -1; + pixel_x = 5; + dir = 8 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"yG" = ( +/obj/effect/spawner/structure/window/reinforced, +/turf/open/floor/plating, +/area/virtual_domain) +"yI" = ( +/obj/structure/dresser, +/obj/structure/sign/calendar/directional/north, +/turf/open/floor/wood, +/area/virtual_domain) +"yJ" = ( +/obj/structure/closet/crate/hydroponics, +/obj/item/popsicle_stick, +/obj/item/popsicle_stick, +/obj/item/popsicle_stick, +/obj/item/reagent_containers/condiment/flour, +/obj/item/reagent_containers/condiment/flour, +/obj/item/reagent_containers/condiment/rice, +/obj/item/reagent_containers/condiment/rice, +/obj/item/food/grown/banana/bunch, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"yN" = ( +/obj/structure/chair/stool/bamboo{ + dir = 1 + }, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"yQ" = ( +/obj/structure/table/bronze, +/obj/structure/sign/poster/random/directional/east, +/obj/machinery/button/door/directional/south{ + id = "beach_room_10"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/obj/item/table_clock{ + pixel_y = 4 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"ze" = ( +/obj/effect/turf_decal/tile/dark_red/full, +/turf/open/floor/iron/smooth_large, +/area/virtual_domain/fullbright) +"zg" = ( +/obj/structure/table/bronze, +/obj/item/clothing/suit/costume/hawaiian{ + pixel_y = 6; + pixel_x = 4 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain/fullbright) +"zm" = ( +/obj/structure/table/reinforced, +/obj/item/stack/medical/mesh{ + pixel_y = 1; + pixel_x = 1 + }, +/obj/item/clothing/suit/toggle/labcoat{ + pixel_y = 9; + pixel_x = -8 + }, +/obj/item/storage/box/bodybags{ + pixel_y = 17; + pixel_x = 8 + }, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"zn" = ( +/obj/structure/table/optable, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"zp" = ( +/obj/structure/chair{ + dir = 8 + }, +/turf/open/floor/iron/dark/textured_large, +/area/virtual_domain) +"zq" = ( +/obj/item/stack/sheet/mineral/sandstone{ + pixel_x = -11; + pixel_y = -2 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"zH" = ( +/obj/item/reagent_containers/cup/soda_cans/lemon_lime{ + pixel_x = -12; + pixel_y = 14 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"zK" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 6 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"zL" = ( +/obj/structure/table/bronze, +/obj/item/paper/paperslip{ + pixel_y = 6; + pixel_x = 10 + }, +/obj/item/pen/fourcolor{ + pixel_y = 7; + pixel_x = 1 + }, +/turf/open/floor/carpet, +/area/virtual_domain) +"zO" = ( +/obj/item/retractor{ + pixel_y = 8; + pixel_x = -2 + }, +/obj/item/cautery{ + pixel_y = 10 + }, +/obj/item/hemostat{ + pixel_y = -11 + }, +/obj/item/bonesetter{ + pixel_y = -12 + }, +/obj/structure/table/glass, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"zS" = ( +/obj/item/kirbyplants/organic/plant8{ + pixel_y = 18; + pixel_x = -8 + }, +/obj/item/cigbutt{ + pixel_y = -4; + pixel_x = 2 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"Ac" = ( +/obj/machinery/chem_dispenser/drinks/beer/fullupgrade{ + dir = 4 + }, +/obj/structure/table/wood, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Ae" = ( +/obj/machinery/space_heater, +/obj/machinery/light/directional/north, +/turf/open/floor/wood, +/area/virtual_domain) +"Ag" = ( +/obj/effect/turf_decal/siding/wood, +/obj/item/kirbyplants/organic/plant8{ + pixel_y = 6 + }, +/obj/machinery/light/directional/east, +/turf/open/floor/eighties, +/area/virtual_domain) +"Ai" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 4 + }, +/turf/open/floor/eighties, +/area/virtual_domain) +"Aj" = ( +/obj/structure/railing/corner/end{ + dir = 8 + }, +/obj/effect/turf_decal/siding/wood/corner{ + dir = 1 + }, +/turf/open/water/beach, +/area/virtual_domain) +"Al" = ( +/obj/effect/turf_decal/siding/wood/corner{ + dir = 4 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Aq" = ( +/obj/structure/table/wood/poker, +/obj/item/storage/dice{ + pixel_y = 3; + pixel_x = 5 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Ay" = ( +/obj/structure/table/reinforced, +/obj/item/storage/medkit/brute{ + pixel_y = 9; + pixel_x = 2 + }, +/obj/item/storage/medkit/o2{ + pixel_y = 15; + pixel_x = 2 + }, +/obj/item/reagent_containers/cup/bottle/multiver{ + pixel_x = -9; + pixel_y = 3 + }, +/obj/item/reagent_containers/syringe{ + pixel_y = -4; + pixel_x = 3 + }, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"AG" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/effect/turf_decal/tile/dark/half{ + pixel_x = 5; + dir = 8 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"AK" = ( +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/plastic, +/area/virtual_domain) +"AX" = ( +/obj/vehicle/ridden/atv{ + dir = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"AZ" = ( +/obj/effect/spawner/structure/window, +/turf/open/floor/plating, +/area/virtual_domain/fullbright) +"Bc" = ( +/obj/item/reagent_containers/cup/bucket/wooden{ + pixel_y = -5; + pixel_x = -11 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Bf" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 4 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"Bk" = ( +/obj/machinery/door/airlock/wood{ + name = "Room 2"; + id_tag = "beach_room_2" + }, +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/obj/structure/sign/poster/random/directional/east, +/turf/open/floor/wood, +/area/virtual_domain) +"Bq" = ( +/turf/closed/wall/mineral/wood, +/area/virtual_domain) +"Bx" = ( +/obj/item/reagent_containers/cup/soda_cans/starkist{ + pixel_x = -12; + pixel_y = -6 + }, +/obj/structure/fluff/beach_umbrella/cap, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Bz" = ( +/obj/structure/closet, +/obj/item/key/atv{ + pixel_x = -4 + }, +/obj/item/key/atv{ + pixel_x = 5; + pixel_y = 3 + }, +/obj/item/key/atv{ + pixel_x = 1; + pixel_y = 1 + }, +/obj/item/clothing/head/soft/black, +/obj/item/clothing/head/soft/black, +/obj/item/clothing/head/soft/black, +/obj/effect/decal/cleanable/oil, +/obj/item/clothing/glasses/sunglasses, +/obj/item/clothing/glasses/heat, +/turf/open/floor/iron/dark/herringbone, +/area/virtual_domain) +"BB" = ( +/obj/item/toy/plush/moth{ + pixel_y = 5 + }, +/turf/open/floor/carpet/red, +/area/virtual_domain/fullbright) +"BD" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 19; + pixel_x = 8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"BI" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 5 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"BM" = ( +/obj/structure/closet/crate/bin, +/obj/item/flashlight/glowstick/yellow, +/obj/item/trash/candy, +/turf/open/floor/eighties, +/area/virtual_domain) +"BU" = ( +/obj/item/storage/toolbox/fishing{ + pixel_x = 10 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"BV" = ( +/obj/structure/sign/directions/dorms/directional/north{ + pixel_y = 35 + }, +/obj/structure/sign/directions/medical/directional/north{ + pixel_y = 29 + }, +/obj/machinery/computer/slot_machine, +/turf/open/floor/wood/large, +/area/virtual_domain) +"BW" = ( +/obj/structure/dresser, +/obj/structure/sign/clock/directional/east, +/turf/open/floor/wood, +/area/virtual_domain) +"Co" = ( +/obj/machinery/processor, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Cw" = ( +/obj/structure/sign/poster/official/fruit_bowl/directional/north, +/turf/open/floor/carpet, +/area/virtual_domain) +"CA" = ( +/obj/item/reagent_containers/cup/soda_cans/sol_dry{ + pixel_y = 18; + pixel_x = 9 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"CH" = ( +/obj/machinery/light/directional/north, +/turf/open/floor/wood, +/area/virtual_domain) +"CP" = ( +/obj/structure/table/reinforced, +/obj/item/toy/plush/slimeplushie{ + pixel_y = 8; + pixel_x = 4 + }, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"CQ" = ( +/obj/structure/closet/crate/trashcart/laundry, +/turf/open/floor/plastic, +/area/virtual_domain) +"CR" = ( +/obj/structure/fluff/beach_umbrella/syndi, +/turf/open/misc/beach/coast/corner{ + dir = 8 + }, +/area/virtual_domain/fullbright) +"CS" = ( +/obj/machinery/door/airlock/wood{ + name = "Room 1"; + id_tag = "beach_room_1" + }, +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"CU" = ( +/obj/structure/closet/secure_closet/personal/cabinet, +/obj/machinery/button/door/directional/west{ + id = "beach_room_6"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/obj/item/clothing/suit/jacket/leather/biker, +/turf/open/floor/wood, +/area/virtual_domain) +"CV" = ( +/obj/item/toy/seashell{ + pixel_x = 8; + pixel_y = 14 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Dg" = ( +/obj/item/toy/beach_ball, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Do" = ( +/obj/effect/landmark/bitrunning/curiosity_spawn, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Dq" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/structure/railing{ + dir = 1 + }, +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/water/beach, +/area/virtual_domain) +"Dt" = ( +/obj/machinery/icecream_vat, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Dx" = ( +/obj/structure/chair/stool/bar/directional/east, +/obj/machinery/light/directional/west, +/turf/open/floor/wood/large, +/area/virtual_domain) +"DA" = ( +/obj/item/cigbutt{ + pixel_y = -7; + pixel_x = 14 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"DG" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/mapping_helpers/broken_floor, +/turf/open/floor/plating, +/area/virtual_domain) +"DL" = ( +/obj/structure/table, +/obj/item/storage/toolbox/mechanical{ + pixel_y = 12; + pixel_x = 3 + }, +/obj/item/stock_parts/power_store/cell/emproof{ + pixel_x = -4; + pixel_y = 6 + }, +/obj/item/clothing/head/soft/mime{ + pixel_y = 3; + pixel_x = 5 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"DP" = ( +/obj/effect/spawner/structure/window, +/turf/open/floor/plating, +/area/virtual_domain) +"DR" = ( +/turf/open/floor/carpet/blue, +/area/virtual_domain/fullbright) +"DS" = ( +/obj/machinery/door/airlock/wood{ + name = "Changing Room" + }, +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/turf/open/floor/iron/freezer, +/area/virtual_domain) +"DT" = ( +/obj/structure/table/wood, +/obj/item/knife/kitchen{ + pixel_y = 12; + pixel_x = -13 + }, +/obj/item/clothing/head/utility/chefhat, +/turf/open/floor/wood/large, +/area/virtual_domain) +"DU" = ( +/obj/machinery/door/airlock/wood{ + name = "Room 10"; + id_tag = "beach_room_10" + }, +/obj/effect/turf_decal/siding/wood, +/turf/open/floor/wood, +/area/virtual_domain) +"Eb" = ( +/obj/item/reagent_containers/cup/soda_cans/cola{ + pixel_x = -8; + pixel_y = -4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Ej" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 19; + pixel_x = 7 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Er" = ( +/obj/effect/turf_decal/sand, +/obj/structure/bedsheetbin{ + pixel_y = 3; + pixel_x = -1 + }, +/obj/structure/table, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Et" = ( +/obj/item/toy/seashell, +/turf/open/misc/beach/coast/corner{ + dir = 8 + }, +/area/virtual_domain/fullbright) +"EA" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/button/door/directional/east{ + id = "toiletfarbeach"; + name = "restroom lock"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/obj/structure/toilet{ + pixel_y = 8 + }, +/obj/structure/sink/directional/west, +/obj/machinery/light/small/directional/west, +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/floor/iron/white, +/area/virtual_domain) +"EI" = ( +/turf/open/misc/beach/coast{ + dir = 8 + }, +/area/virtual_domain/fullbright) +"EP" = ( +/obj/machinery/door/airlock/wood{ + name = "Room 3"; + id_tag = "beach_room_3" + }, +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"EQ" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/water/beach, +/area/virtual_domain) +"ET" = ( +/obj/item/reagent_containers/cup/glass/bottle/beer/light{ + pixel_x = -14; + pixel_y = 15 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"EY" = ( +/obj/structure/statue/sandstone/venus{ + dir = 4; + pixel_y = 9; + anchored = 1 + }, +/obj/structure/flora/bush/flowers_br/style_random, +/turf/open/floor/grass, +/area/virtual_domain) +"Fk" = ( +/obj/effect/decal/cleanable/dirt, +/obj/item/storage/box/mousetraps{ + pixel_x = -5; + pixel_y = 14 + }, +/obj/structure/table, +/obj/item/storage/box/mousetraps{ + pixel_x = 12; + pixel_y = 15 + }, +/obj/item/grenade/chem_grenade/cleaner{ + pixel_x = -7; + pixel_y = 6 + }, +/obj/item/grenade/chem_grenade/cleaner{ + pixel_x = -1; + pixel_y = 3 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Fp" = ( +/obj/structure/closet/crate/bin, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Fx" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 6 + }, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"FB" = ( +/obj/machinery/medical_kiosk, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"FE" = ( +/obj/machinery/washing_machine, +/obj/machinery/light/directional/north, +/turf/open/floor/plastic, +/area/virtual_domain) +"FO" = ( +/obj/structure/chair/plastic, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"FX" = ( +/turf/open/floor/carpet/green, +/area/virtual_domain/fullbright) +"Ga" = ( +/obj/item/stack/arcadeticket{ + pixel_y = -1; + pixel_x = -6 + }, +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Gm" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/turf/closed/wall/mineral/wood, +/area/virtual_domain) +"Go" = ( +/obj/structure/closet/secure_closet/personal/cabinet, +/obj/item/clothing/suit/jacket/letterman_nanotrasen, +/obj/item/clothing/glasses/salesman, +/turf/open/floor/wood, +/area/virtual_domain) +"Gp" = ( +/obj/structure/no_effect_signpost{ + pixel_y = 6; + desc = "Northwest: Hotel. South: Seaside Bar. East: Dressing Rooms." + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Gq" = ( +/obj/effect/turf_decal/siding/wood/corner{ + dir = 1 + }, +/turf/closed/wall/mineral/wood, +/area/virtual_domain) +"Gw" = ( +/turf/open/indestructible/binary, +/area/virtual_domain/fullbright) +"Gx" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/spawner/random/structure/closet_empty/crate, +/obj/effect/spawner/random/maintenance/two, +/obj/item/storage/toolbox/fishing{ + pixel_y = 2 + }, +/turf/open/floor/plating, +/area/virtual_domain) +"GD" = ( +/obj/effect/landmark/bitrunning/permanent_exit, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/protected_space/fullbright) +"GI" = ( +/turf/open/floor/iron/dark/textured_large, +/area/virtual_domain) +"GK" = ( +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/wood/large, +/area/virtual_domain) +"GQ" = ( +/obj/item/toy/seashell{ + pixel_y = 3; + pixel_x = -6 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"GX" = ( +/obj/machinery/light/directional/west, +/turf/open/floor/carpet, +/area/virtual_domain) +"GY" = ( +/obj/effect/turf_decal/siding/wood, +/obj/machinery/shower/directional/south, +/obj/structure/sink/directional/east, +/turf/open/floor/wood/parquet, +/area/virtual_domain/fullbright) +"Hd" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/structure/railing/corner/end{ + dir = 8 + }, +/turf/open/water/beach, +/area/virtual_domain) +"Ho" = ( +/obj/structure/table/wood, +/obj/item/modular_computer/laptop{ + pixel_y = 5 + }, +/obj/item/multitool{ + pixel_x = -8; + pixel_y = -8 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"Hp" = ( +/obj/structure/table/wood, +/obj/item/clothing/suit/apron/chef{ + pixel_y = 5; + pixel_x = 2 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"HE" = ( +/obj/machinery/griddle, +/turf/open/floor/wood/large, +/area/virtual_domain) +"HH" = ( +/obj/effect/turf_decal/siding/wood/corner, +/obj/effect/turf_decal/siding/wood/corner{ + dir = 4 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"HI" = ( +/obj/structure/statue/sandstone/venus{ + dir = 4; + pixel_y = 9 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"HK" = ( +/obj/structure/closet/crate/bin, +/obj/effect/spawner/random/trash/cigbutt, +/obj/effect/spawner/random/trash/food_packaging, +/obj/machinery/light/directional/north, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"HO" = ( +/obj/machinery/door/airlock/wood{ + name = "Room 9"; + id_tag = "beach_room_9" + }, +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"HP" = ( +/turf/open/water/beach, +/area/virtual_domain) +"HR" = ( +/obj/structure/sign/poster/official/tactical_game_cards/directional/west, +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/eighties, +/area/virtual_domain) +"HT" = ( +/obj/structure/bed/pod{ + desc = "An old medical bed, just waiting for replacement with something up to date."; + dir = 4; + name = "medical bed" + }, +/obj/machinery/defibrillator_mount/directional/east, +/obj/machinery/iv_drip{ + pixel_y = 21; + pixel_x = -4 + }, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"HV" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/plating, +/area/virtual_domain) +"Ib" = ( +/obj/structure/tank_holder/extinguisher{ + pixel_y = 11 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"If" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 10 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"In" = ( +/obj/item/kirbyplants/organic/plant8{ + pixel_y = 4; + pixel_x = 8 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"It" = ( +/obj/effect/turf_decal/siding/dark_red{ + dir = 8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Iw" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/obj/machinery/door/poddoor/shutters{ + id = "BeachBoats" + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"IA" = ( +/obj/machinery/shower/directional/west, +/obj/effect/turf_decal/siding/wood{ + dir = 10 + }, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"IF" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"IG" = ( +/obj/machinery/door/poddoor/shutters{ + dir = 4; + id = "BeachGarage" + }, +/turf/open/floor/plating, +/area/virtual_domain/fullbright) +"II" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"IJ" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/obj/item/blood_filter{ + pixel_y = 8; + pixel_x = -6 + }, +/obj/item/surgical_drapes{ + pixel_x = -1; + pixel_y = 2 + }, +/obj/item/surgical_drapes{ + pixel_x = 13; + pixel_y = 2 + }, +/obj/structure/table/glass, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"IL" = ( +/obj/effect/turf_decal/siding/dark_red, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"IS" = ( +/obj/effect/turf_decal/siding/wood/corner, +/turf/open/floor/wood/large, +/area/virtual_domain) +"IX" = ( +/obj/item/kirbyplants/organic/plant8{ + pixel_y = 18; + pixel_x = -8 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Jb" = ( +/obj/structure/table/glass, +/obj/machinery/coffeemaker/impressa{ + pixel_y = 13; + pixel_x = -1 + }, +/obj/item/reagent_containers/cup/glass/mug{ + pixel_x = -6; + pixel_y = 2 + }, +/obj/item/reagent_containers/cup/glass/mug{ + pixel_x = 11; + pixel_y = -1 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"Jc" = ( +/obj/effect/turf_decal/siding/wood/corner{ + dir = 8 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"Jd" = ( +/obj/effect/turf_decal/siding/wood, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"Je" = ( +/obj/structure/table/bronze, +/obj/item/table_clock{ + pixel_y = 13 + }, +/obj/structure/sign/poster/random/directional/east, +/turf/open/floor/wood, +/area/virtual_domain) +"Jp" = ( +/obj/effect/turf_decal/siding/wood, +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"Jv" = ( +/obj/item/reagent_containers/cup/soda_cans/starkist{ + pixel_x = -6 + }, +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Jw" = ( +/obj/item/toy/seashell{ + pixel_x = 12; + pixel_y = 5 + }, +/turf/open/misc/beach/coast{ + dir = 4 + }, +/area/virtual_domain/fullbright) +"JF" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 10 + }, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"JG" = ( +/obj/machinery/light/directional/west, +/turf/open/floor/wood/large, +/area/virtual_domain) +"JH" = ( +/obj/structure/marker_beacon/burgundy, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"JJ" = ( +/obj/machinery/door/airlock/wood{ + name = "Room 8"; + id_tag = "beach_room_8" + }, +/turf/open/floor/wood, +/area/virtual_domain) +"JN" = ( +/obj/effect/turf_decal/sand, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"JU" = ( +/obj/item/instrument/guitar{ + pixel_y = 4; + pixel_x = -1 + }, +/turf/open/misc/beach/coast, +/area/virtual_domain/fullbright) +"Ka" = ( +/obj/structure/closet/secure_closet/personal/cabinet, +/obj/machinery/button/door/directional/west{ + id = "beach_room_7"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/obj/item/clothing/suit/apron/overalls, +/turf/open/floor/wood, +/area/virtual_domain) +"Ko" = ( +/obj/item/toy/beach_ball, +/turf/open/misc/beach/coast/corner{ + dir = 4 + }, +/area/virtual_domain/fullbright) +"Ks" = ( +/obj/structure/table/glass, +/obj/machinery/microwave{ + pixel_y = 15 + }, +/obj/item/cigbutt{ + pixel_y = 2; + pixel_x = -12 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"KH" = ( +/obj/structure/closet/athletic_mixed, +/turf/open/floor/iron/freezer, +/area/virtual_domain) +"KI" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/spawner/random/structure/crate, +/turf/open/floor/plating, +/area/virtual_domain) +"KJ" = ( +/obj/machinery/vending/coffee, +/turf/open/floor/wood/large, +/area/virtual_domain) +"KL" = ( +/obj/structure/closet/secure_closet/freezer/empty, +/obj/item/food/popsicle/creamsicle_berry, +/obj/item/food/popsicle/creamsicle_berry, +/obj/item/food/popsicle/creamsicle_orange, +/obj/item/food/popsicle/creamsicle_orange, +/obj/item/food/popsicle/jumbo, +/obj/item/food/popsicle/pineapple_pop, +/obj/item/food/popsicle/pineapple_pop, +/obj/item/food/fishmeat, +/obj/item/food/fishmeat, +/obj/item/food/fishmeat, +/obj/item/food/fishmeat, +/obj/item/food/fishmeat, +/turf/open/floor/wood/large, +/area/virtual_domain) +"KP" = ( +/obj/item/clothing/head/soft/black{ + pixel_x = -1; + pixel_y = -3 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"KS" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/obj/structure/railing{ + dir = 1 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"KT" = ( +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"KZ" = ( +/obj/structure/railing{ + dir = 1 + }, +/obj/effect/landmark/bitrunning/curiosity_spawn, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"La" = ( +/obj/machinery/light/directional/south, +/turf/open/floor/wood, +/area/virtual_domain) +"Ld" = ( +/obj/structure/table/wood, +/obj/item/cigarette/cigar{ + pixel_y = 16; + pixel_x = -2 + }, +/obj/item/reagent_containers/cup/glass/coffee{ + pixel_x = 8; + pixel_y = 9 + }, +/obj/machinery/cell_charger, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"Lm" = ( +/obj/machinery/door/airlock/wood{ + name = "Balcony"; + id_tag = "beach_room_x" + }, +/turf/open/floor/wood, +/area/virtual_domain) +"LD" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/door/airlock/wood{ + name = "Bathroom"; + id_tag = "toilet1beach" + }, +/turf/open/floor/iron/white, +/area/virtual_domain) +"LN" = ( +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/carpet/blue, +/area/virtual_domain/fullbright) +"LR" = ( +/obj/effect/turf_decal/siding/wood/corner, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"LV" = ( +/obj/effect/turf_decal/siding/wood/corner{ + dir = 8 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"LY" = ( +/obj/effect/turf_decal/siding/blue{ + dir = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Ma" = ( +/obj/machinery/vending/games, +/turf/open/floor/eighties, +/area/virtual_domain) +"Mb" = ( +/turf/open/floor/wood/large, +/area/virtual_domain) +"Md" = ( +/obj/structure/fluff/beach_umbrella/syndi, +/obj/item/reagent_containers/cup/soda_cans/wellcheers{ + pixel_y = -10; + pixel_x = -6 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Mj" = ( +/obj/structure/table, +/obj/item/weldingtool{ + pixel_y = -7; + pixel_x = 4 + }, +/obj/item/trash/can{ + pixel_y = 10; + pixel_x = -5 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"Ml" = ( +/obj/machinery/washing_machine, +/turf/open/floor/plastic, +/area/virtual_domain) +"Mo" = ( +/obj/machinery/door/airlock/wood{ + name = "Docks" + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"Mr" = ( +/obj/structure/table/wood, +/obj/item/stack/arcadeticket{ + pixel_y = 6; + pixel_x = -7 + }, +/obj/item/pen{ + pixel_y = 5; + pixel_x = 8 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Mx" = ( +/obj/structure/table/reinforced, +/obj/machinery/cell_charger{ + pixel_y = 13; + pixel_x = 21 + }, +/obj/item/clothing/glasses/hud/health{ + pixel_y = 1; + pixel_x = 18 + }, +/obj/item/stack/medical/gauze{ + pixel_x = -15; + pixel_y = 10 + }, +/obj/item/storage/belt/medical{ + pixel_y = 3; + pixel_x = -3 + }, +/obj/machinery/light/directional/north, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"MA" = ( +/obj/effect/turf_decal/siding/dark_red/corner{ + dir = 1 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"MH" = ( +/obj/item/toy/seashell{ + pixel_x = -7; + pixel_y = 5 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"MO" = ( +/obj/structure/table/wood, +/obj/machinery/recharger, +/obj/item/reagent_containers/cup/glass/mug{ + pixel_x = -12; + pixel_y = 9 + }, +/obj/effect/spawner/random/bureaucracy/stamp{ + pixel_y = 14; + pixel_x = 8 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"MR" = ( +/obj/effect/spawner/random/entertainment/arcade{ + dir = 4 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"MT" = ( +/turf/open/misc/beach/coast/corner{ + dir = 8 + }, +/area/virtual_domain/fullbright) +"MV" = ( +/obj/item/radio/off{ + pixel_y = 16; + pixel_x = -5 + }, +/obj/item/storage/box/monkeycubes{ + pixel_y = 10; + pixel_x = 4 + }, +/obj/item/storage/toolbox/mechanical{ + pixel_y = 2 + }, +/obj/structure/table/glass, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"MX" = ( +/obj/effect/turf_decal/stripes/red/box, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"MZ" = ( +/obj/machinery/button/door/directional/north{ + id = "beach_room_12"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"Nc" = ( +/obj/item/tank/internals/anesthetic{ + pixel_y = 4 + }, +/obj/item/stack/medical/bone_gel{ + pixel_y = 16; + pixel_x = -7 + }, +/obj/item/clothing/mask/breath/medical{ + pixel_y = 3; + pixel_x = 3 + }, +/obj/structure/table/bronze, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"Ne" = ( +/obj/machinery/light/directional/east, +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Nj" = ( +/obj/structure/fluff/beach_umbrella/cap, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Np" = ( +/obj/structure/chair/stool/bamboo{ + dir = 4 + }, +/obj/effect/turf_decal/siding/wood/corner, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"Nt" = ( +/obj/effect/turf_decal/siding/wood/corner, +/turf/open/floor/eighties, +/area/virtual_domain) +"NB" = ( +/obj/item/storage/box/fishing_hooks{ + pixel_y = 14 + }, +/obj/item/storage/box/fishing_lines{ + pixel_y = 12; + pixel_x = 18 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"NC" = ( +/mob/living/basic/crab, +/obj/effect/turf_decal/stripes/red/line{ + dir = 8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"NH" = ( +/obj/structure/closet/crate/bin, +/obj/item/trash/candy, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"NT" = ( +/obj/item/toy/seashell, +/turf/open/misc/beach/coast, +/area/virtual_domain/fullbright) +"NU" = ( +/obj/machinery/vending/medical{ + req_access = "201" + }, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"NZ" = ( +/obj/effect/turf_decal/sand, +/turf/closed/wall/mineral/wood, +/area/virtual_domain/fullbright) +"Of" = ( +/obj/item/reagent_containers/cup/soda_cans/space_mountain_wind{ + pixel_x = -17; + pixel_y = 17 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Og" = ( +/obj/structure/table/bronze, +/obj/item/reagent_containers/condiment/peppermill{ + pixel_y = 12 + }, +/obj/item/reagent_containers/condiment/saltshaker{ + pixel_x = -5; + pixel_y = 9 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Ol" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/power/port_gen/pacman, +/turf/open/floor/plating, +/area/virtual_domain) +"Om" = ( +/obj/structure/bed/double, +/obj/item/bedsheet/hop/double{ + desc = "A blue bedsheet. Likely from the beach hotel."; + name = "blue bedsheet" + }, +/turf/open/floor/carpet/cyan, +/area/virtual_domain) +"Or" = ( +/obj/effect/turf_decal/siding/dark_red{ + dir = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Oy" = ( +/mob/living/basic/crab, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"OA" = ( +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"OK" = ( +/obj/item/storage/box/gloves{ + pixel_y = 14; + pixel_x = 7 + }, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"OM" = ( +/turf/open/floor/iron/stairs/old, +/area/virtual_domain/fullbright) +"OO" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 16; + pixel_x = 12 + }, +/obj/structure/flora/coconuts, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"OV" = ( +/mob/living/basic/crab/kreb, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Po" = ( +/obj/structure/sink/kitchen/directional/east, +/turf/open/floor/plastic, +/area/virtual_domain) +"Pq" = ( +/obj/structure/table/bronze, +/obj/item/reagent_containers/cup/rag{ + pixel_y = 5 + }, +/obj/item/reagent_containers/condiment/coconut_milk{ + pixel_y = 14; + pixel_x = 5 + }, +/obj/machinery/light/directional/east, +/turf/open/floor/wood/large, +/area/virtual_domain) +"PD" = ( +/obj/item/kirbyplants/organic/plant8{ + pixel_y = 9; + pixel_x = 6 + }, +/obj/structure/railing{ + dir = 10 + }, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"PE" = ( +/obj/machinery/space_heater, +/turf/open/floor/wood, +/area/virtual_domain) +"PG" = ( +/obj/structure/table/bronze, +/obj/item/food/grown/banana/bunch{ + pixel_y = 10; + offset_at_init = 0 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"PH" = ( +/obj/item/toy/seashell{ + pixel_x = 12; + pixel_y = 5 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"PJ" = ( +/obj/item/cigbutt{ + pixel_y = -11; + pixel_x = -15 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"PR" = ( +/obj/effect/turf_decal/siding/blue{ + dir = 1 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"PZ" = ( +/obj/structure/table/bronze, +/obj/item/camera{ + pixel_y = 12; + pixel_x = 6 + }, +/obj/item/camera_film{ + pixel_y = 4; + pixel_x = -2 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Qc" = ( +/obj/item/kirbyplants/organic/plant8{ + pixel_y = 5 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Qj" = ( +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/iron/freezer, +/area/virtual_domain) +"Qk" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/button/door/directional/east{ + id = "toilet2beach"; + name = "restroom lock"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/obj/structure/toilet{ + pixel_y = 8 + }, +/obj/structure/sink/directional/west, +/obj/machinery/light/small/directional/west, +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/iron/white, +/area/virtual_domain) +"Qm" = ( +/obj/structure/sink/kitchen/directional/south, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Qq" = ( +/obj/effect/turf_decal/siding/wood/corner{ + dir = 1 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Qu" = ( +/obj/structure/tank_holder/extinguisher, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"Qv" = ( +/obj/machinery/door/airlock/command/glass{ + name = "Admin" + }, +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/effect/mapping_helpers/airlock_note_placer{ + note_info = "Dear IT: Please fix the fax machine" + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"Qx" = ( +/obj/effect/spawner/random/entertainment/arcade{ + dir = 4 + }, +/turf/open/floor/eighties, +/area/virtual_domain) +"QG" = ( +/obj/structure/railing/corner/end/flip{ + dir = 4 + }, +/obj/item/wrench{ + pixel_y = -1; + pixel_x = 6 + }, +/obj/item/storage/toolbox/fishing{ + pixel_y = -8; + pixel_x = -3 + }, +/obj/structure/railing/corner/end/flip{ + dir = 4 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"QH" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/machinery/door/airlock/wood{ + name = "Room 6"; + id_tag = "beach_room_6" + }, +/turf/open/floor/wood, +/area/virtual_domain) +"QI" = ( +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"QN" = ( +/obj/item/cigbutt{ + pixel_y = -6; + pixel_x = -12 + }, +/obj/item/clothing/head/soft/black, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"QX" = ( +/obj/machinery/vending/boozeomat/all_access{ + desc = "A technological marvel, supposedly able to mix just the mixture you'd like to drink the moment you ask for one. May not work for bartenders that don't have Nanotrasen bank accounts." + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"QY" = ( +/obj/structure/window/reinforced/spawner/directional/north, +/obj/structure/window/reinforced/spawner/directional/west, +/obj/structure/table/wood, +/obj/item/storage/medkit/o2{ + pixel_y = 4; + pixel_x = 1 + }, +/obj/effect/turf_decal/siding/wood{ + dir = 9 + }, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"Rc" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 31 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Rd" = ( +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/carpet, +/area/virtual_domain) +"Re" = ( +/obj/structure/table/wood, +/obj/item/stack/arcadeticket{ + pixel_y = 3; + pixel_x = -5 + }, +/obj/item/reagent_containers/cup/soda_cans/pwr_game{ + pixel_y = 12; + pixel_x = -8 + }, +/obj/item/paper_bin/carbon{ + pixel_y = 6; + pixel_x = 4 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Rf" = ( +/obj/effect/turf_decal/siding/wood/corner, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Rk" = ( +/obj/structure/table/glass, +/obj/machinery/fax{ + fax_name = "Beach Hotel Fax"; + name = "Beach Hotel's Fax Machine"; + pixel_y = 8; + visible_to_network = 0 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"Rs" = ( +/obj/machinery/door/airlock/wood{ + name = "Garage" + }, +/turf/open/floor/iron/dark/herringbone, +/area/virtual_domain) +"Rw" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/machinery/door/airlock/wood{ + name = "Room 1" + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Ry" = ( +/obj/structure/closet{ + name = "Holding Cell Storage" + }, +/obj/item/taperecorder, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"Rz" = ( +/obj/item/kirbyplants/organic/plant21{ + pixel_y = 3; + pixel_x = -6 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"RB" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 5 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"RF" = ( +/obj/effect/turf_decal/siding/wood, +/turf/closed/wall/mineral/wood, +/area/virtual_domain/fullbright) +"RJ" = ( +/obj/item/reagent_containers/cup/soda_cans/starkist{ + pixel_y = 16; + pixel_x = 10 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"RM" = ( +/obj/effect/baseturf_helper/beach/water, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"RO" = ( +/obj/item/reagent_containers/cup/soda_cans/lemon_lime{ + pixel_x = -12; + pixel_y = -7 + }, +/obj/item/reagent_containers/cup/soda_cans/dr_gibb{ + pixel_x = 13; + pixel_y = -7 + }, +/obj/structure/fluff/beach_umbrella, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"RP" = ( +/obj/structure/table, +/obj/item/book/manual/wiki/engineering_guide{ + pixel_y = 7; + pixel_x = -6 + }, +/obj/item/pen{ + pixel_x = 7; + pixel_y = 6 + }, +/obj/item/clothing/head/utility/welding{ + pixel_x = -10; + pixel_y = -8 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"RS" = ( +/obj/structure/table/wood, +/obj/item/food/donut/plain{ + pixel_y = -2; + pixel_x = 14 + }, +/obj/item/assembly/signaler{ + pixel_x = -6; + pixel_y = -5 + }, +/obj/effect/spawner/random/bureaucracy/folder{ + pixel_y = 11; + pixel_x = 4 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"Sb" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 19; + pixel_x = 10 + }, +/obj/machinery/hydroponics/constructable, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Sc" = ( +/obj/structure/table/wood, +/obj/item/storage/box/drinkingglasses{ + pixel_y = 18; + pixel_x = -5 + }, +/obj/item/reagent_containers/cup/glass/shaker{ + pixel_y = 12; + pixel_x = -7 + }, +/obj/item/clothing/head/hats/tophat{ + pixel_y = 2; + pixel_x = 3 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Se" = ( +/obj/structure/table/wood, +/obj/effect/spawner/random/entertainment/lighter, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Sf" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"So" = ( +/obj/effect/turf_decal/siding/wood, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Sr" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/rack, +/obj/effect/spawner/random/maintenance/three, +/obj/effect/decal/cleanable/cobweb, +/obj/item/wheelchair, +/turf/open/floor/plating, +/area/virtual_domain) +"Su" = ( +/obj/machinery/door/airlock/wood{ + name = "Room 4"; + id_tag = "beach_room_4" + }, +/obj/effect/turf_decal/siding/wood, +/turf/open/floor/wood, +/area/virtual_domain) +"Sx" = ( +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"Sz" = ( +/obj/structure/chair/stool/bamboo{ + dir = 1 + }, +/obj/effect/turf_decal/siding/wood/corner, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"SJ" = ( +/obj/structure/rack, +/obj/effect/turf_decal/sand, +/obj/item/clothing/shoes/sandal{ + pixel_y = 4; + pixel_x = 8 + }, +/obj/item/clothing/shoes/sandal{ + pixel_x = 4 + }, +/obj/item/clothing/shoes/sandal{ + pixel_y = -4 + }, +/obj/item/clothing/shoes/sandal{ + pixel_y = -7; + pixel_x = -3 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"SL" = ( +/obj/machinery/door/airlock/wood{ + name = "Trash Disposal" + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"SM" = ( +/obj/effect/turf_decal/siding/blue{ + dir = 4 + }, +/obj/structure/chair/plastic{ + dir = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"SO" = ( +/obj/structure/table/bronze, +/obj/structure/desk_bell{ + pixel_x = 7; + pixel_y = 8 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"SR" = ( +/turf/closed/indestructible/binary, +/area/virtual_domain/fullbright) +"Ta" = ( +/obj/machinery/light/directional/south, +/turf/open/floor/carpet, +/area/virtual_domain) +"Tb" = ( +/obj/effect/landmark/bitrunning/curiosity_spawn, +/turf/open/floor/iron/dark/herringbone, +/area/virtual_domain) +"Tq" = ( +/obj/machinery/light/small/directional/north, +/turf/open/floor/iron/dark/herringbone, +/area/virtual_domain) +"Tr" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Tt" = ( +/obj/machinery/recharge_station, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Tw" = ( +/obj/effect/turf_decal/stripes/white/full, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Ty" = ( +/turf/open/floor/carpet, +/area/virtual_domain/fullbright) +"TB" = ( +/obj/effect/turf_decal/tile/dark_red/full, +/obj/machinery/vending/cola/red, +/turf/open/floor/iron/smooth_large, +/area/virtual_domain/fullbright) +"TC" = ( +/obj/effect/turf_decal/sand, +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"TK" = ( +/obj/structure/table/bronze, +/obj/item/clothing/suit/costume/hawaiian{ + pixel_y = 1; + pixel_x = 7 + }, +/obj/item/clothing/suit/costume/hawaiian{ + pixel_x = -14 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain/fullbright) +"TM" = ( +/obj/effect/turf_decal/siding/blue/corner, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"TO" = ( +/obj/structure/closet/crate/bin, +/obj/item/reagent_containers/syringe{ + pixel_y = -4; + pixel_x = 3 + }, +/obj/effect/spawner/random/trash/food_packaging, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"TR" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/plating, +/area/virtual_domain) +"TS" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/obj/item/clothing/mask/breath/medical{ + pixel_y = 7; + pixel_x = -9 + }, +/obj/item/clothing/mask/breath/medical{ + pixel_y = 4; + pixel_x = 3 + }, +/obj/item/clothing/gloves/latex, +/obj/structure/table/glass, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"TW" = ( +/obj/structure/table/bronze, +/obj/item/clothing/suit/costume/hawaiian{ + pixel_y = 7; + pixel_x = -3 + }, +/obj/machinery/light/directional/west, +/turf/open/floor/wood/parquet, +/area/virtual_domain/fullbright) +"TY" = ( +/obj/machinery/door/airlock/medical/glass{ + name = "Medbay Clinic" + }, +/obj/effect/turf_decal/siding/blue{ + dir = 8 + }, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"TZ" = ( +/obj/structure/fluff/beach_umbrella/science, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Ub" = ( +/obj/machinery/light/directional/west, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"Ud" = ( +/obj/effect/turf_decal/siding/wood, +/obj/item/cigbutt{ + pixel_y = -9; + pixel_x = 13 + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"Uf" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/door/airlock/wood{ + name = "Bathroom"; + id_tag = "toilet2beach" + }, +/turf/open/floor/iron/white, +/area/virtual_domain) +"Ug" = ( +/obj/structure/chair/office{ + dir = 4 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"Uk" = ( +/obj/machinery/vending/autodrobe/all_access, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Um" = ( +/obj/structure/closet/secure_closet/personal/cabinet, +/obj/item/clothing/suit/jacket/miljacket, +/turf/open/floor/wood, +/area/virtual_domain) +"Uy" = ( +/obj/machinery/door/airlock/medical/glass{ + name = "Medbay Clinic" + }, +/obj/effect/turf_decal/siding/blue{ + dir = 4 + }, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"UE" = ( +/obj/effect/turf_decal/siding/wood, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"UI" = ( +/obj/machinery/door/airlock/wood{ + name = "Janitorial" + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"UJ" = ( +/obj/vehicle/ridden/atv{ + dir = 4 + }, +/obj/effect/decal/cleanable/oil, +/turf/open/floor/iron/dark/herringbone, +/area/virtual_domain) +"UO" = ( +/obj/machinery/vending/cola/pwr_game, +/turf/open/floor/eighties, +/area/virtual_domain) +"US" = ( +/obj/machinery/shower/directional/west, +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"UW" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"UZ" = ( +/obj/item/stack/sheet/mineral/sandstone{ + pixel_x = -8 + }, +/obj/item/stack/sheet/mineral/sandstone{ + pixel_x = 5 + }, +/obj/item/stack/sheet/mineral/sandstone{ + pixel_x = 1; + pixel_y = 7 + }, +/obj/item/stack/sheet/mineral/sandstone{ + pixel_x = -12; + pixel_y = 6 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Va" = ( +/obj/machinery/door/airlock/wood{ + name = "Employees' Only" + }, +/turf/open/floor/plastic, +/area/virtual_domain) +"Vs" = ( +/obj/structure/table/wood, +/obj/item/pai_card{ + desc = "A real Nanotrasen success, these personal AIs provide all of the companionship of an AI without any law related red-tape."; + name = "\improper Nanotrasen-brand personal AI device exhibit"; + pixel_y = 3; + pixel_x = 10 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"VD" = ( +/obj/effect/turf_decal/sand, +/obj/effect/turf_decal/siding/wood/corner{ + dir = 1 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"VI" = ( +/obj/machinery/vending/dinnerware, +/turf/open/floor/wood/large, +/area/virtual_domain) +"VK" = ( +/obj/machinery/door/airlock/wood/glass{ + name = "Cabin" + }, +/turf/open/floor/carpet, +/area/virtual_domain) +"VL" = ( +/turf/open/misc/beach/coast{ + dir = 6 + }, +/area/virtual_domain/fullbright) +"We" = ( +/obj/structure/table/bronze, +/obj/item/book/manual/wiki/barman_recipes{ + pixel_x = 9; + pixel_y = 7; + name = "Tropical Mixing for Tropical Bartenders" + }, +/obj/item/reagent_containers/cup/glass/drinkingglass/shotglass{ + pixel_y = 10; + pixel_x = -12 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Wg" = ( +/obj/structure/closet/crate/trashcart/filled, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"Wm" = ( +/obj/effect/landmark/bitrunning/curiosity_spawn, +/turf/open/floor/iron/white/textured_large, +/area/virtual_domain) +"Ws" = ( +/obj/machinery/vending/cigarette/beach, +/turf/open/floor/wood/large, +/area/virtual_domain) +"WG" = ( +/obj/effect/turf_decal/siding/blue{ + dir = 8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"WX" = ( +/obj/structure/marker_beacon/teal, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"Xe" = ( +/obj/structure/table/bronze, +/obj/structure/sign/poster/random/directional/east, +/obj/item/camera{ + pixel_y = 5 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"Xl" = ( +/obj/structure/flora/tree/palm/style_2{ + pixel_y = 25 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Xt" = ( +/turf/open/floor/plastic, +/area/virtual_domain) +"Xu" = ( +/obj/effect/turf_decal/siding/blue{ + dir = 8 + }, +/obj/structure/chair/plastic{ + dir = 8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Xx" = ( +/obj/machinery/door/airlock/wood/glass{ + name = "Beach Access" + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"XJ" = ( +/obj/machinery/door/airlock/public/glass{ + name = "Locker Room" + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"XO" = ( +/obj/structure/flora/tree/palm/style_2{ + pixel_y = 25 + }, +/obj/structure/chair/plastic{ + dir = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"XP" = ( +/obj/structure/mop_bucket/janitorialcart{ + dir = 8 + }, +/obj/machinery/light/directional/south, +/turf/open/floor/wood/large, +/area/virtual_domain) +"XT" = ( +/obj/machinery/light/directional/west, +/turf/open/floor/carpet/cyan, +/area/virtual_domain) +"XW" = ( +/obj/structure/fluff/beach_umbrella/syndi, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Yb" = ( +/obj/structure/sign/poster/contraband/pwr_game/directional/west, +/turf/open/floor/eighties, +/area/virtual_domain) +"Yd" = ( +/obj/structure/flora/tree/palm/style_2{ + pixel_y = 3; + pixel_x = -4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Yo" = ( +/turf/closed/wall/mineral/sandstone, +/area/virtual_domain/fullbright) +"Yr" = ( +/obj/item/reagent_containers/cup/watering_can{ + pixel_y = 19; + pixel_x = -6 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Yz" = ( +/obj/item/trash/candy{ + pixel_y = 9; + pixel_x = -9 + }, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"YD" = ( +/obj/effect/turf_decal/sand, +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"YI" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/button/door/directional/east{ + id = "toilet1beach"; + name = "restroom lock"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/obj/structure/toilet{ + pixel_y = 8 + }, +/obj/structure/sink/directional/west, +/obj/machinery/light/small/directional/west, +/turf/open/floor/iron/white, +/area/virtual_domain) +"YM" = ( +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"YN" = ( +/obj/item/toy/seashell{ + pixel_x = 12; + pixel_y = -5 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"YP" = ( +/obj/structure/table/glass, +/obj/item/paper{ + pixel_y = 7; + pixel_x = 2 + }, +/obj/item/screwdriver{ + pixel_y = 13; + pixel_x = 3 + }, +/obj/item/paper_bin/carbon{ + pixel_y = 4; + pixel_x = -6 + }, +/obj/effect/spawner/random/bureaucracy/pen{ + pixel_x = 7 + }, +/turf/open/floor/iron/dark/diagonal, +/area/virtual_domain) +"YR" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 10 + }, +/obj/machinery/vending/cola/red, +/turf/open/floor/wood/large, +/area/virtual_domain/fullbright) +"YW" = ( +/obj/structure/chair{ + dir = 1 + }, +/turf/open/floor/iron/dark/textured_large, +/area/virtual_domain) +"YZ" = ( +/obj/structure/railing/corner/end/flip{ + dir = 4 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain) +"Zd" = ( +/obj/structure/railing{ + dir = 8 + }, +/turf/open/floor/wood, +/area/virtual_domain/fullbright) +"Zj" = ( +/obj/structure/flora/tree/palm{ + pixel_y = 16 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Zn" = ( +/obj/item/reagent_containers/cup/soda_cans/dr_gibb{ + pixel_x = -9; + pixel_y = -9 + }, +/obj/structure/fluff/beach_umbrella/engine, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"Zp" = ( +/obj/structure/dresser, +/turf/open/floor/wood, +/area/virtual_domain) +"Zs" = ( +/obj/machinery/door/poddoor/shutters{ + id = "BeachBoats" + }, +/turf/open/water/beach, +/area/virtual_domain/fullbright) +"Zy" = ( +/obj/structure/sign/chalkboard_menu{ + pixel_y = 32 + }, +/turf/open/floor/wood/large, +/area/virtual_domain) +"Zz" = ( +/obj/effect/turf_decal/siding/wood/corner{ + dir = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"ZB" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/button/door/directional/east{ + id = "toilet3beach"; + name = "restroom lock"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/obj/structure/toilet{ + pixel_y = 8 + }, +/obj/structure/sink/directional/west, +/obj/item/cigbutt{ + pixel_y = -7; + pixel_x = -13 + }, +/obj/machinery/light/small/directional/west, +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/floor/iron/white, +/area/virtual_domain) +"ZE" = ( +/obj/structure/table/bronze, +/turf/open/floor/carpet, +/area/virtual_domain) +"ZH" = ( +/obj/item/kirbyplants/organic/plant24{ + pixel_x = 7; + pixel_y = 10 + }, +/obj/item/reagent_containers/cup/watering_can{ + pixel_x = -3; + pixel_y = 15 + }, +/obj/structure/table, +/turf/open/floor/wood/large, +/area/virtual_domain) +"ZI" = ( +/obj/effect/baseturf_helper/beach/sand, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"ZV" = ( +/obj/structure/chair/plastic, +/obj/item/pai_card{ + desc = "A real Nanotrasen success, these personal AIs provide all of the companionship of an AI without any law related red-tape."; + name = "\improper Nanotrasen-brand personal AI device exhibit"; + pixel_y = 3; + pixel_x = -1 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) +"ZX" = ( +/obj/structure/table/bronze, +/obj/item/storage/toolbox/fishing{ + pixel_y = 2 + }, +/turf/open/floor/wood, +/area/virtual_domain) +"ZY" = ( +/obj/effect/turf_decal/siding/blue/corner{ + dir = 4 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/fullbright) + +(1,1,1) = {" +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(2,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(3,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(4,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(5,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(6,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(7,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(8,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +LR +Bf +Bf +Bf +qa +cO +LR +Bf +Bf +Bf +HH +Bf +Bf +Bf +qa +cO +LR +Bf +Bf +Bf +qa +cO +cO +cO +cO +cO +cO +cO +cO +SR +SR +SR +SR +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(9,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +Jd +vF +Zd +io +tw +cO +Jd +vF +Zd +io +Jp +vF +Zd +io +tw +cO +Jd +vF +Zd +PD +tw +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(10,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +eK +Lm +AZ +AZ +eK +cO +eK +Lm +AZ +AZ +eK +Lm +AZ +AZ +eK +cO +eK +Lm +AZ +AZ +eK +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(11,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +eK +CH +uY +PE +Bq +eK +Bq +MZ +gw +PE +Bq +bE +uY +oE +Bq +eK +Bq +iQ +gw +jp +eK +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(12,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +eK +cB +Rd +bE +db +Mb +rI +bE +cW +aQ +Bq +jw +Rd +bE +Su +Mb +EP +bE +cW +La +eK +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(13,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +eK +yI +gK +rE +Bq +Mb +Bq +yI +ch +Xe +Bq +wO +gK +rw +Bq +Mb +Bq +Je +Om +Zp +eK +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(14,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +eK +Bq +Bq +Bq +Bq +pv +Bq +Bq +Bq +Bq +Bq +Bq +Bq +Bq +Bq +Mb +Bq +Bq +Bq +Bq +eK +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +SR +SR +SR +SR +SR +SR +SR +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(15,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +vF +nk +bE +XT +PE +Bq +Mb +Mb +yg +Mb +Mb +Mb +Mb +yg +Mb +Mb +Mb +Mb +yg +gH +Bq +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(16,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +aw +AZ +Go +cW +bE +DU +Mb +Bq +Bq +Bq +Bq +Bq +Bq +Bq +Bq +Bq +Mb +Bq +Bq +Bq +Bq +eK +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(17,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +xR +AZ +Zp +Om +yQ +Bq +Mb +Bq +cr +uY +dH +Bq +Um +gw +gf +Bq +pv +Bq +lc +uY +pq +eK +cO +eH +EI +EI +EI +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(18,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +eK +Bq +Gm +Bq +Bq +Mb +HO +bE +Rd +La +Bq +Ae +cW +bE +sW +Mb +Bk +bE +Rd +La +eK +eH +tk +qB +ka +MH +nc +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +oX +"} +(19,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +vF +dW +bE +GX +dH +Bq +Mb +Bq +ik +gK +ZX +Bq +yI +Om +dS +Bq +Mb +Bq +ik +gK +ZX +eK +tk +CV +wZ +QI +gI +ka +nc +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(20,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +KZ +AZ +pz +Rd +bE +JJ +Mb +Bq +Bq +Bq +Bq +Bq +Bq +Bq +Bq +Bq +Mb +Bq +Bq +Bq +Bq +eK +BI +ka +mP +rB +bD +ka +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(21,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +xR +AZ +BW +gK +st +Bq +Mb +Mb +yg +Mb +Bq +xg +Bq +Mb +Mb +Mb +Mb +yg +Mb +Mb +Xx +TC +JN +ka +ka +ka +ka +ka +rD +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(22,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +eK +Bq +Bq +Bq +Bq +Mb +Bq +Bq +cH +Bq +Bq +Bq +QH +Bq +Bq +Mb +Bq +Bq +Bq +Bq +eK +NB +rD +ka +ka +ka +BI +ka +nc +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(23,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +eK +eK +Bq +Fk +Yr +wz +Bq +Mb +Bq +Ka +gw +PE +Bq +CU +uY +bE +Bq +Mb +CS +qb +gw +pJ +eK +ka +ka +eK +Rw +eK +eK +eK +Ej +nc +EI +EI +EI +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(24,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +eK +tf +GK +XP +Bq +pv +Bq +bE +cW +La +Bq +PE +Rd +La +Bq +Mb +Bq +wx +cW +La +eK +Sf +eK +Bq +Mb +Dx +Mb +Bq +eK +Ej +ka +ka +ka +nc +EI +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(25,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +eK +IX +Mb +Mb +Bq +pE +Bq +ik +Om +gc +Bq +yI +gK +ZX +Bq +pE +Bq +Zp +Om +eJ +Bq +eK +Bq +jb +dY +lI +Aq +MR +Bq +eK +th +ka +ka +ka +ka +nc +EI +EI +EI +EI +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(26,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +eK +ow +Bq +UI +Bq +Mb +Bq +Bq +Bq +Bq +Bq +Bq +Bq +Bq +Bq +Mb +Bq +Bq +Bq +Bq +Bq +BV +Mb +Mb +ho +ho +ho +Mb +Rz +AZ +ka +sz +ka +ka +yy +ka +ka +ka +ka +ka +nc +EI +ij +cO +cO +cO +cO +eH +EI +EI +EI +EI +EI +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(27,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +LR +Bf +eK +Bq +Bq +tI +Mb +Mb +Mb +Mb +Mb +Mb +JG +Mb +Mb +Mb +Mb +Mb +Mb +JG +Mb +dj +Mb +Mb +IS +gN +Al +Mb +Mb +Mb +xw +AZ +ka +ka +ka +Md +BB +ka +ka +ka +rD +ka +ka +ka +nc +ij +cO +cO +cO +oa +sz +th +ka +ka +ka +nc +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(28,1,1) = {" +SR +cO +cO +cO +cO +cO +LR +lS +wN +xJ +SL +Mb +Mb +Mb +Mb +Mb +Mb +Mb +Mb +Mb +Mb +Mb +Mb +Mb +Mb +Mb +Mb +Mb +dj +Mb +Mb +So +EY +UW +Mb +Mb +ZH +AZ +AZ +JN +ka +ka +ka +ka +ka +ka +DR +ka +ka +BD +ka +sz +nc +EI +EI +EI +tk +ka +ka +ka +ka +ka +sz +nc +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(29,1,1) = {" +SR +cO +cO +cO +cO +cO +Jd +Yz +xJ +eK +Bq +Bq +yG +yG +Bq +Qv +Bq +Bq +Bq +Bq +Bq +DP +TY +DP +Bq +Bq +Bq +Bq +Bq +Bq +pv +LV +II +Qq +Mb +Mb +AZ +AZ +JN +JN +ka +ka +ka +ka +ka +TZ +oC +ka +ka +ka +ka +ka +ka +ka +ka +ka +ka +ka +ka +ka +ka +Oy +ka +Dg +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(30,1,1) = {" +SR +cO +cO +cO +cO +cO +Jd +mV +xJ +eK +Jb +zS +lW +lW +PJ +lW +Qu +Bq +lF +bx +jL +qH +gP +FB +NU +Bq +CQ +CQ +Bq +cK +Mb +dA +Mb +Mb +Mb +Mb +eK +sR +JN +de +JN +ka +ka +ka +rD +ka +ka +ka +ka +eK +AZ +AZ +eK +ka +eK +AZ +AZ +eK +uC +ka +ka +Nj +ka +ka +nc +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(31,1,1) = {" +SR +cO +cO +cO +cO +cO +Ud +bT +Wg +eK +vx +lW +Ug +aY +Ug +lW +YP +Gq +zm +Wm +gP +gP +gP +gP +gP +Va +Xt +Xt +Bq +Cw +uY +zL +uY +uY +uY +uY +VK +Ty +JN +JN +JN +ka +ka +th +ka +ka +ka +ka +eK +Bq +Qx +Qx +Bq +AZ +Bq +BM +gs +Bq +eK +rD +ka +DR +LN +ka +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(32,1,1) = {" +SR +cO +cO +cO +cO +cO +Jc +If +Wg +eK +Ks +lW +Ho +RS +Ld +lW +Rk +Bq +Mx +gP +gP +up +gP +gP +TO +Bq +Ml +Xt +Bq +uY +Rd +ZE +uY +uY +uY +Ta +eK +Ty +Ty +JN +JN +ka +ka +ka +ka +ka +ka +ka +eK +UO +en +ty +Yb +en +HR +gi +Ai +Ga +eK +ka +ka +ka +nR +ka +gL +VL +JH +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(33,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +Jc +IF +eK +HK +lW +qR +MO +pX +PJ +rG +Bq +CP +HT +gP +aD +OK +gP +Ay +Bq +Ml +Xt +Bq +Cw +uY +iF +uY +uY +uY +uY +VK +Ty +Ty +Ty +JN +ka +ka +ka +ka +ka +ka +ka +eK +Bq +Ma +xx +en +pV +Nt +dB +Mr +Mb +AZ +ka +ka +ka +rD +ka +cE +cO +cO +cO +cO +WX +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(34,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +eK +Bq +lW +lW +KT +DA +In +Bq +eK +AZ +Bq +gP +gP +gP +Bq +Bq +Bq +FE +Xt +Bq +PG +Mb +oQ +Mb +Mb +Mb +eK +eK +wf +Ty +Ty +Ty +ka +ka +ka +rD +ka +ka +sz +ka +eK +AZ +Bq +wT +wT +Ag +Mb +Vs +Mb +eK +ka +ka +ka +ka +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(35,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +eK +lW +rW +Ry +MV +Bq +eK +zO +bb +eK +Uy +Bq +ee +Bq +Ml +Po +gB +Xt +Bq +Bq +Mb +IS +gN +Al +Mb +AZ +wf +SJ +JN +Ty +Ty +Ty +ka +ka +ka +ka +ka +ka +th +ka +JN +eK +AZ +AZ +Bq +Mb +Re +fg +eK +as +ka +ka +ka +ka +nc +ij +cO +cO +cO +cO +cO +cO +cO +JH +cO +cO +cO +cO +cO +SR +"} +(36,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +eK +lU +yG +yG +Bq +eK +IJ +xJ +xJ +xJ +xJ +eK +AZ +Bq +Ml +AK +Xt +Xt +Xt +Va +Mb +So +sh +UW +Mb +AZ +AZ +SJ +JN +JN +Ty +Ty +Ty +ka +ka +ka +ka +ka +ka +ka +ka +ka +ka +JN +eK +mx +AZ +eK +Ib +JN +ka +ka +ka +Oy +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(37,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +eK +GI +GI +YW +eK +cO +TS +mV +xJ +xJ +xJ +tw +cO +eK +Bq +yc +yc +yc +Bq +Bq +Bq +LV +Ne +Qq +Mb +Qc +AZ +SJ +JN +JN +ka +Ty +Ty +Ty +ka +ka +ka +ka +ka +ka +ka +JN +ka +ka +ta +YD +dp +ct +ka +ka +ka +JN +ka +ka +ka +NT +cO +cO +cO +cO +cO +cO +cO +cO +cO +WX +cO +cO +cO +SR +"} +(38,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +lx +uI +ba +YW +lx +cO +bp +rn +zn +Nc +yB +pC +cO +cO +eK +Bq +Bq +Bq +Bq +Sr +Bq +kD +Bq +Mb +Mb +Tt +AZ +Er +JN +rD +ka +ka +Ty +Ty +Ty +ka +ka +ka +rD +ka +JN +JN +JN +ka +JN +ka +JN +ka +JN +ka +ka +ka +ka +ka +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(39,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +eK +Bq +zp +Bq +eK +cO +Jc +fe +du +AG +pC +cO +cO +cO +eK +hT +lh +TR +jm +TR +TR +TR +Bq +Ws +KJ +fg +eK +JN +ka +ka +ka +ka +ka +Ty +Ty +Ty +ka +ka +ka +JN +JN +JN +ka +JN +ka +JN +JN +ka +ka +ka +JN +ka +ka +FO +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +JH +cO +SR +"} +(40,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +eK +eK +eK +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +eK +TR +TR +HV +TR +TR +eP +KI +Bq +eK +eK +eK +ka +ka +Xl +ka +sz +ka +ka +Gp +Ty +Ty +JN +JN +JN +JN +JN +ka +ka +JN +JN +ka +ka +JN +ka +ka +ka +ka +ka +RO +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(41,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +eK +Ol +TR +TR +DG +Gx +Bq +eK +eK +rD +ka +ka +ka +ka +ka +ka +ka +ka +th +ka +JN +JN +JN +JN +JN +JN +ka +NH +eK +eK +AZ +AZ +eK +eK +ky +ka +JN +ka +ka +FO +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(42,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +eK +eK +Bq +ce +cC +Bq +eK +mJ +ka +ka +ka +Yo +Yo +Yo +ka +ka +ka +ka +ka +rD +JN +JN +JN +ka +ka +JN +JN +yJ +eK +QX +hA +Ac +Sc +eK +YR +ka +ka +ka +ka +ka +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +WX +SR +"} +(43,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +eK +eK +eK +eK +bY +Et +ka +ra +Yo +Yo +bX +Yo +Yo +sz +ka +ka +ka +ka +JN +JN +jr +ka +ka +ka +JN +eK +Bq +Qm +Mb +Mb +Mb +jK +aA +JF +ka +ka +FO +ka +gL +VL +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(44,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +wb +MT +UZ +Yo +bX +hh +bX +Yo +sz +ka +lA +US +IA +JN +JN +ka +ka +ka +ka +JN +mL +Mb +GK +Se +vU +Mb +rR +yN +UE +ka +ka +Bx +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(45,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +zq +Yo +bX +kN +bX +ev +ka +ka +NZ +NZ +eK +JN +JN +ka +ka +ka +ka +Sb +eK +Zy +lJ +tM +Hp +Mb +rR +yN +UE +ka +ka +FO +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +JH +SR +"} +(46,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +Bc +Yo +sl +bX +bX +Yo +sz +ka +NZ +YI +LD +JN +JN +rD +ka +ka +ka +eK +eK +pv +uD +DT +Mb +Mb +We +yN +UE +ka +ka +Eb +ka +nc +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(47,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +ka +Yo +Yo +bX +Yo +Yo +sz +ka +NZ +kI +eK +JN +JN +ka +ka +ka +GQ +eK +VI +Mb +si +Co +Mb +Fp +Pq +Sz +Fx +ka +ka +ka +FO +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(48,1,1) = {" +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +ka +ka +Yo +Yo +Yo +ka +ka +ka +NZ +Qk +Uf +JN +JN +ka +ka +ka +ka +AZ +HE +Mb +jW +Mb +Mb +dl +eK +Fx +ka +ka +ka +th +Zn +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(49,1,1) = {" +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +cO +cO +cO +cO +cO +cO +cO +cO +oa +ka +sz +ka +ka +ka +th +ka +eK +kI +kI +eK +JN +JN +ka +ka +ka +ka +AZ +fL +Mb +Mb +Mb +hZ +rR +UE +ka +ka +ka +ka +ka +FO +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(50,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +oa +ka +ka +rD +ka +ka +ka +ka +ka +NZ +ZB +sa +JN +JN +ka +th +ka +ka +AZ +xT +Mb +Mb +Og +SO +Np +Fx +ka +as +ka +ka +ka +sC +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +WX +SR +"} +(51,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +wb +MT +th +ka +ka +ka +ka +ka +ka +eK +eK +eK +JN +JN +ka +ka +rD +ka +eK +xT +Do +KL +tX +Np +Fx +ka +ka +ka +ka +ka +ka +ka +gL +VL +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(52,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +ka +ka +ka +RM +ka +ka +ka +eK +rK +JN +JN +JN +ka +ka +ka +ka +eK +eK +AZ +AZ +eK +cc +ka +ka +ka +ka +ka +ka +ka +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(53,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +ka +ka +ka +ZI +ka +ka +as +ka +cg +JN +JN +ka +ka +ka +ka +ka +ka +os +ka +eM +ka +rD +ka +ka +ka +ka +ka +rj +ka +gL +VL +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(54,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +tW +ka +ka +th +ka +ka +ka +ka +ka +JN +JN +ka +ka +ka +ka +th +ka +BI +ka +ka +as +ka +ka +ka +ka +ka +ka +xk +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +JH +SR +"} +(55,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +Rc +Of +ka +ka +rD +ka +ka +ka +ka +JN +JN +ka +ka +ka +ka +ka +ka +sz +ka +ka +ka +ka +ka +QY +fY +gA +ka +ka +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(56,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +wb +MT +ka +ka +ka +ka +ka +ka +ka +ka +JN +JN +JN +lk +Or +uX +qZ +ka +ka +ka +rD +ka +ka +ka +iy +ut +OM +ka +ka +ka +nc +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(57,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +XO +ka +ka +ka +ka +ka +ka +ka +as +JN +JN +kL +TB +ze +mv +ka +ka +ka +ka +ka +ka +ka +ka +ka +ka +ka +th +ka +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(58,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +ka +ET +tW +ka +XW +CA +ka +ka +ka +JN +JN +IL +wH +ze +mv +ka +ka +ka +ka +ka +ka +ka +ka +ka +ka +ka +ka +rD +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +WX +cO +SR +"} +(59,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +wb +bY +CR +ka +zH +LN +DR +ka +ka +ka +JN +JN +IL +eh +ze +mv +ka +rD +ka +ka +th +ka +ka +th +ka +ka +Oy +ka +ka +BU +nc +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(60,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +JH +cO +cO +cO +wb +bY +MT +ka +ka +ka +ka +ka +JN +JN +uz +It +It +MA +ka +ka +ka +ka +ka +ka +tW +tW +tW +tW +ka +ka +ka +il +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(61,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +ka +TZ +RJ +ka +JN +JN +JN +Yd +ka +ka +ka +ka +dD +ka +ka +ka +TM +SM +SM +SM +SM +LY +ZY +ka +cp +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +JH +cO +cO +cO +SR +"} +(62,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +WX +cO +cO +cO +cO +cO +cO +oa +ka +FX +FX +ka +JN +JN +ka +ka +ka +ka +ka +ka +ka +sz +ZV +FO +dG +ka +ka +ka +ka +ka +PR +ka +FO +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(63,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +wb +MT +ka +ka +ka +JN +JN +ka +ka +ka +ka +ka +ka +OO +ka +FO +FO +dG +ka +ka +ka +ka +ka +PR +ka +ka +gL +VL +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(64,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +ka +ka +ka +JN +JN +ka +mi +ka +ka +ka +ka +ka +ka +FO +FO +dG +ka +ka +ka +ka +ka +PR +th +gL +VL +cO +cO +cO +cO +cO +cO +cO +cO +cO +WX +cO +cO +cO +cO +SR +"} +(65,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +JH +cO +cO +cO +cO +cO +cO +cO +oa +rD +ka +ka +JN +JN +ka +ka +ka +ka +rD +ka +JN +ka +ka +ka +dG +ka +Dg +ka +ka +ka +PR +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(66,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +ka +ka +ka +JN +JN +JN +JN +JN +ka +ka +ka +fI +jP +fI +lP +dG +ka +ka +ka +ka +ka +PR +Oy +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(67,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +th +ka +ka +JN +JN +JN +ka +JN +JN +ka +JN +GD +ew +fI +sO +dG +Tw +Tw +Tw +Tw +Tw +PR +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +JH +cO +cO +cO +cO +cO +cO +SR +"} +(68,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +WX +cO +cO +cO +cO +cO +cO +eH +Ko +ka +ka +rD +JN +JN +ka +yz +ka +ka +ka +Gw +fI +jP +hu +lP +dG +ka +ka +ka +ka +ka +PR +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(69,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +eH +tk +ka +ka +Dt +ka +JN +JN +ka +ka +ka +ka +JN +ka +ka +th +ka +ka +dG +ka +ka +ka +ka +ka +PR +ka +cE +cO +cO +cO +cO +cO +cO +WX +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(70,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +JH +cO +cO +eH +EI +rl +ka +ka +ka +ka +ka +JN +JN +JN +ka +ka +ka +ka +ka +ka +rD +FO +FO +dG +ka +ka +ka +ka +ka +PR +al +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(71,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +eH +tk +th +ka +ka +eK +eK +eK +eK +qc +JN +JN +ka +ka +ka +ka +ka +JN +ka +FO +FO +dG +ka +ka +ka +ka +ka +PR +ka +xj +ij +cO +cO +JH +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(72,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +ka +Oy +eK +eK +Bq +ro +ro +fg +eK +oZ +tQ +ka +ka +eO +PZ +ka +ka +ka +FO +FO +dG +ka +ka +ka +ka +ka +PR +ka +ka +nc +ij +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(73,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +eH +tk +ka +eK +Bq +ro +Ub +Sx +Sx +Sx +XJ +bK +ii +tQ +ka +pj +fH +ka +ka +ka +ka +ka +mW +Xu +Xu +Xu +Xu +WG +lz +ka +iP +ka +lG +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(74,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +eH +tk +ka +ka +eK +ro +Sx +Sx +Sx +Sx +Sx +XJ +bK +bK +TC +ka +ka +ka +rD +ka +ka +ka +ka +th +iU +iU +iU +iU +ka +ka +ka +hB +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(75,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +oa +sz +ka +ka +eK +Bq +DS +Bq +Bq +ro +Sx +eK +bK +bK +TC +ka +ka +ka +ka +ka +ka +ka +ka +rD +ka +ka +ka +ka +ka +rD +ka +FO +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(76,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +eH +tk +ka +sz +JN +eK +KH +uu +KH +Bq +ro +Sx +TW +bK +bK +TC +ka +ka +th +ka +ka +ka +ka +ka +ka +ka +ka +ka +th +ka +ka +ka +gC +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(77,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +oa +ka +ka +eK +Rs +Bq +Bq +uu +uu +Bq +Bq +Sx +TK +bK +bK +TC +ka +ka +ka +tW +tW +tW +ka +tW +tW +ka +ka +ka +ka +ka +ka +ka +FO +ka +dX +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(78,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +oa +ka +ka +eK +jV +dv +Bq +KH +uu +Qj +Bq +Sx +sI +bK +bK +TC +ka +ka +ka +tW +tW +tW +ka +tW +tW +ka +ka +ka +ka +ka +sR +ka +Jv +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(79,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +oa +th +ka +eK +Tq +hD +Bq +Bq +xO +uu +Bq +Bq +zg +bK +vM +VD +ka +ka +rD +ka +ka +ka +ka +ka +ka +ka +ka +ka +tW +ka +ka +ka +Oy +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(80,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +oa +ka +ka +eK +Bz +Tb +UJ +Bq +aE +uu +KH +Bq +RF +YD +VD +ka +ka +ka +ka +ka +Rf +Tr +Tr +Tr +Zz +ka +ka +FO +uH +xP +ka +ka +ka +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +"} +(81,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +wb +MT +ka +eK +eK +jV +ke +Bq +aE +uu +KH +eK +GY +ka +ka +ka +ka +th +ka +Rf +zK +OA +OA +OA +RB +Zz +Oy +oJ +iU +ka +ka +ka +YN +gL +VL +cO +cO +cO +cO +cO +cO +cO +SR +SR +SR +SR +SR +SR +SR +"} +(82,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +oa +sz +AX +eK +IG +IG +eK +Bq +KH +Bq +eK +wI +ka +ka +ka +ka +ka +ka +rz +jq +OA +OA +OA +OA +ob +ka +ka +ka +ka +Zj +rD +gL +VL +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +"} +(83,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +wb +MT +ka +ka +ka +ka +ka +eK +eK +eK +kJ +ka +OV +ka +ka +ka +ka +ka +rQ +ww +OA +OA +OA +OA +py +ka +Dg +ka +ka +ka +ka +cE +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +"} +(84,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +wb +MT +ka +ka +ka +ka +ka +ka +sR +ka +ka +ka +ka +ka +ka +ka +KP +eK +eK +eK +eK +eK +eK +eK +Uk +ka +ka +ka +ka +gL +VL +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +"} +(85,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +wb +MT +Oy +ka +ka +ka +ka +ka +ka +tW +tW +ka +tW +tW +Rf +eK +Bq +RP +YM +Ub +Mj +sV +fg +eK +ka +ka +gL +bY +VL +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +"} +(86,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +th +ka +NZ +NZ +eK +ka +ka +ka +ka +ka +ka +ka +rz +Mo +Sx +jn +YZ +YZ +jn +YZ +QG +Bq +eK +bY +VL +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +SR +SR +ot +ot +ot +ot +ot +ot +"} +(87,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +wb +MT +ka +NZ +EA +xe +ka +ka +ef +ef +ef +ef +NC +ta +eK +DL +Dq +Dq +Dq +Dq +Dq +KS +QN +eK +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +"} +(88,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +wb +MT +eK +eK +eK +ka +oq +ka +ka +ka +ka +ka +ka +eK +Bq +Aj +Aj +Aj +Aj +Aj +Hd +hM +eK +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +"} +(89,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +wb +MT +PH +ka +ka +aZ +iY +ka +ka +ka +MX +ka +JU +eK +fJ +og +HP +HP +HP +HP +wC +eK +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +SR +SR +ot +ot +ot +ot +ot +ot +ot +ot +"} +(90,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +oa +ka +ka +Oy +ka +ka +ka +ka +nX +gL +Jw +VL +eK +HI +EQ +HP +HP +HP +HP +Bq +eK +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(91,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +wb +MT +gV +ka +ka +fv +gL +bY +bY +VL +cO +cO +eK +eK +Zs +Iw +Iw +Iw +eK +eK +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(92,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +wb +bY +bY +bY +bY +VL +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +SR +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(93,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(94,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(95,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +SR +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(96,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(97,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +cO +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} +(98,1,1) = {" +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +SR +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +ot +"} diff --git a/code/__DEFINES/dcs/signals/signals_bitrunning.dm b/code/__DEFINES/dcs/signals/signals_bitrunning.dm index fa7891e98156..ccf47c226044 100644 --- a/code/__DEFINES/dcs/signals/signals_bitrunning.dm +++ b/code/__DEFINES/dcs/signals/signals_bitrunning.dm @@ -45,6 +45,7 @@ #define COMSIG_BITRUNNER_STOCKING_GEAR "bitrunner_stocking_gear" // Ladder + /// from /obj/structure/hololadder/disconnect() #define COMSIG_BITRUNNER_LADDER_SEVER "bitrunner_ladder_sever" diff --git a/code/controllers/subsystem/bitrunning.dm b/code/controllers/subsystem/bitrunning.dm index d08d9497ea1b..2ea36ebcb390 100644 --- a/code/controllers/subsystem/bitrunning.dm +++ b/code/controllers/subsystem/bitrunning.dm @@ -24,7 +24,8 @@ SUBSYSTEM_DEF(bitrunning) var/can_view = domain.difficulty < scanner_tier && domain.cost <= points + 5 var/can_view_reward = domain.difficulty < (scanner_tier + 1) && domain.cost <= points + 3 - levels += list(list( + UNTYPED_LIST_ADD(levels, list( + "announce_ghosts"= domain.announce_to_ghosts, "cost" = domain.cost, "desc" = can_view ? domain.desc : "Limited scanning capabilities. Cannot infer domain details.", "difficulty" = domain.difficulty, diff --git a/code/modules/bitrunning/components/bitrunning_points.dm b/code/modules/bitrunning/components/bitrunning_points.dm index 328a70679e65..ea8f63f76d8d 100644 --- a/code/modules/bitrunning/components/bitrunning_points.dm +++ b/code/modules/bitrunning/components/bitrunning_points.dm @@ -5,6 +5,7 @@ /// A special condition limits this from spawning a crate var/points_received = 0 + /datum/component/bitrunning_points/Initialize(datum/lazy_template/virtual_domain/domain) . = ..() if(!isturf(parent)) @@ -12,6 +13,7 @@ RegisterSignal(domain, COMSIG_BITRUNNER_GOAL_POINT, PROC_REF(on_add_points)) + /// Listens for points to be added which will eventually spawn a crate. /datum/component/bitrunning_points/proc/on_add_points(datum/source, points_to_add) SIGNAL_HANDLER @@ -23,12 +25,14 @@ reveal() + /// Spawns the crate with some effects /datum/component/bitrunning_points/proc/reveal() playsound(src, 'sound/magic/blink.ogg', 50, TRUE) var/turf/tile = parent - new /obj/structure/closet/crate/secure/bitrunning/encrypted(tile) + var/obj/structure/closet/crate/secure/bitrunning/encrypted/crate = new() + crate.forceMove(tile) // Triggers any on-move effects on that turf var/datum/effect_system/spark_spread/quantum/sparks = new(tile) sparks.set_up(number = 5, location = tile) diff --git a/code/modules/bitrunning/netpod/_netpod.dm b/code/modules/bitrunning/netpod/_netpod.dm new file mode 100644 index 000000000000..2208c56a7414 --- /dev/null +++ b/code/modules/bitrunning/netpod/_netpod.dm @@ -0,0 +1,137 @@ +#define BASE_DISCONNECT_DAMAGE 40 + + +/obj/machinery/netpod + name = "netpod" + + base_icon_state = "netpod" + circuit = /obj/item/circuitboard/machine/netpod + desc = "A link to the netverse. It has an assortment of cables to connect yourself to a virtual domain." + icon = 'icons/obj/machines/bitrunning.dmi' + icon_state = "netpod" + max_integrity = 300 + obj_flags = BLOCKS_CONSTRUCTION + state_open = TRUE + interaction_flags_mouse_drop = NEED_HANDS | NEED_DEXTERITY + + /// Whether we have an ongoing connection + var/connected = FALSE + /// A player selected outfit by clicking the netpod + var/datum/outfit/netsuit = /datum/outfit/job/bitrunner + /// Holds this to see if it needs to generate a new one + var/datum/weakref/avatar_ref + /// The linked quantum server + var/datum/weakref/server_ref + /// The amount of brain damage done from force disconnects + var/disconnect_damage + /// Static list of outfits to select from + var/list/cached_outfits = list() + + +/obj/machinery/netpod/post_machine_initialize() + . = ..() + + disconnect_damage = BASE_DISCONNECT_DAMAGE + find_server() + + RegisterSignal(src, COMSIG_ATOM_TAKE_DAMAGE, PROC_REF(on_damage_taken)) + RegisterSignal(src, COMSIG_MACHINERY_POWER_LOST, PROC_REF(on_power_loss)) + RegisterSignals(src, list(COMSIG_QDELETING, COMSIG_MACHINERY_BROKEN),PROC_REF(on_broken)) + + register_context() + update_appearance() + + +/obj/machinery/netpod/Destroy() + . = ..() + + QDEL_LIST(cached_outfits) + + +/obj/machinery/netpod/examine(mob/user) + . = ..() + + if(isnull(server_ref?.resolve())) + . += span_infoplain("It's not connected to anything.") + . += span_infoplain("Netpods must be built within 4 tiles of a server.") + return + + if(!isobserver(user)) + . += span_infoplain("Drag yourself into the pod to engage the link.") + . += span_infoplain("It has limited resuscitation capabilities. Remaining in the pod can heal some injuries.") + . += span_infoplain("It has a security system that will alert the occupant if it is tampered with.") + + if(isnull(occupant)) + . += span_infoplain("It's currently unoccupied.") + return + + . += span_infoplain("It's currently occupied by [occupant].") + + if(isobserver(user)) + . += span_notice("As an observer, you can click this netpod to jump to its avatar.") + return + + . += span_notice("It can be pried open with a crowbar, but its safety mechanisms will alert the occupant.") + + +/obj/machinery/netpod/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + + if(isnull(held_item)) + context[SCREENTIP_CONTEXT_LMB] = "Select Outfit" + return CONTEXTUAL_SCREENTIP_SET + + if(istype(held_item, /obj/item/crowbar) && occupant) + context[SCREENTIP_CONTEXT_LMB] = "Pry Open" + return CONTEXTUAL_SCREENTIP_SET + + +/obj/machinery/netpod/update_icon_state() + if(!is_operational) + icon_state = base_icon_state + return ..() + + if(state_open) + icon_state = base_icon_state + "_open_active" + return ..() + + if(panel_open) + icon_state = base_icon_state + "_panel" + return ..() + + icon_state = base_icon_state + "_closed" + if(occupant) + icon_state += "_active" + + return ..() + + +/obj/machinery/netpod/mouse_drop_receive(mob/target, mob/user, params) + var/mob/living/carbon/player = user + + if(!iscarbon(player) || !is_operational || !state_open || player.buckled) + return + + close_machine(target) + + +/obj/machinery/netpod/attack_hand(mob/living/user, list/modifiers) + . = ..() + if(!state_open && user == occupant) + container_resist_act(user) + + +/obj/machinery/netpod/attack_ghost(mob/dead/observer/our_observer) + var/our_target = avatar_ref?.resolve() + if(isnull(our_target) || !our_observer.orbit(our_target)) + return ..() + + +/// When the server is upgraded, drops brain damage a little +/obj/machinery/netpod/proc/on_server_upgraded(obj/machinery/quantum_server/source) + SIGNAL_HANDLER + + disconnect_damage = BASE_DISCONNECT_DAMAGE * (1 - source.servo_bonus) + + +#undef BASE_DISCONNECT_DAMAGE diff --git a/code/modules/bitrunning/netpod/container.dm b/code/modules/bitrunning/netpod/container.dm new file mode 100644 index 000000000000..616554454451 --- /dev/null +++ b/code/modules/bitrunning/netpod/container.dm @@ -0,0 +1,74 @@ +/obj/machinery/netpod/Exited(atom/movable/gone, direction) + . = ..() + if(!state_open && gone == occupant) + container_resist_act(gone) + + +/obj/machinery/netpod/relaymove(mob/living/user, direction) + if(!state_open) + container_resist_act(user) + + +/obj/machinery/netpod/container_resist_act(mob/living/user) + user.visible_message(span_notice("[occupant] emerges from [src]!"), + span_notice("You climb out of [src]!"), + span_notice("With a hiss, you hear a machine opening.")) + open_machine() + + +/obj/machinery/netpod/open_machine(drop = TRUE, density_to_set = FALSE) + playsound(src, 'sound/machines/tramopen.ogg', 60, TRUE, frequency = 65000) + flick("[base_icon_state]_opening", src) + SEND_SIGNAL(src, COMSIG_BITRUNNER_NETPOD_OPENED) + update_use_power(IDLE_POWER_USE) + + return ..() + + +/obj/machinery/netpod/close_machine(mob/user, density_to_set = TRUE) + if(!state_open || panel_open || !is_operational || !iscarbon(user)) + return + + playsound(src, 'sound/machines/tramclose.ogg', 60, TRUE, frequency = 65000) + flick("[base_icon_state]_closing", src) + ..() + + enter_matrix() + + +/obj/machinery/netpod/default_pry_open(obj/item/crowbar, mob/living/pryer) + if(isnull(occupant) || !iscarbon(occupant)) + if(!state_open) + if(panel_open) + return FALSE + open_machine() + else + shut_pod() + + return TRUE + + pryer.visible_message( + span_danger("[pryer] starts prying open [src]!"), + span_notice("You start to pry open [src]."), + span_notice("You hear loud prying on metal.") + ) + playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) + + SEND_SIGNAL(src, COMSIG_BITRUNNER_CROWBAR_ALERT, pryer) + + if(do_after(pryer, 15 SECONDS, src)) + if(!state_open) + sever_connection() + open_machine() + + return TRUE + + +/// Closes the machine without shoving in an occupant +/obj/machinery/netpod/proc/shut_pod() + state_open = FALSE + playsound(src, 'sound/machines/tramclose.ogg', 60, TRUE, frequency = 65000) + flick("[base_icon_state]_closing", src) + set_density(TRUE) + + update_appearance() diff --git a/code/modules/bitrunning/netpod/outfitting.dm b/code/modules/bitrunning/netpod/outfitting.dm new file mode 100644 index 000000000000..07f251541980 --- /dev/null +++ b/code/modules/bitrunning/netpod/outfitting.dm @@ -0,0 +1,30 @@ +/// Creates a list of outfit entries for the UI. +/obj/machinery/netpod/proc/make_outfit_collection(identifier, list/outfit_list) + var/list/collection = list( + "name" = identifier, + "outfits" = list() + ) + + for(var/datum/outfit/outfit as anything in outfit_list) + var/outfit_name = initial(outfit.name) + if(findtext(outfit_name, "(") != 0 || findtext(outfit_name, "-") != 0) // No special variants please + continue + + collection["outfits"] += list(list("path" = outfit, "name" = outfit_name)) + + return list(collection) + + +/// Resolves a path to an outfit. +/obj/machinery/netpod/proc/resolve_outfit(text) + var/path = text2path(text) + if(!ispath(path, /datum/outfit)) + return + + for(var/wardrobe in cached_outfits) + for(var/outfit in wardrobe["outfits"]) + if(path == outfit["path"]) + return path + + message_admins("[usr]:[usr.ckey] attempted to select an unavailable outfit from a netpod") + return diff --git a/code/modules/bitrunning/netpod/signals.dm b/code/modules/bitrunning/netpod/signals.dm new file mode 100644 index 000000000000..8f8416aeb7b1 --- /dev/null +++ b/code/modules/bitrunning/netpod/signals.dm @@ -0,0 +1,64 @@ +/// Machine has been broken - handles signals and reverting sprites +/obj/machinery/netpod/proc/on_broken(datum/source) + SIGNAL_HANDLER + + sever_connection() + + +/// Checks the integrity, alerts occupants +/obj/machinery/netpod/proc/on_damage_taken(datum/source, damage_amount) + SIGNAL_HANDLER + + if(isnull(occupant) || !connected) + return + + var/total = max_integrity - damage_amount + var/integrity = (atom_integrity / total) * 100 + if(integrity > 50) + return + + SEND_SIGNAL(src, COMSIG_BITRUNNER_NETPOD_INTEGRITY) + + +/// Puts points on the current occupant's card account +/obj/machinery/netpod/proc/on_domain_complete(datum/source, atom/movable/crate, reward_points) + SIGNAL_HANDLER + + if(isnull(occupant) || !connected) + return + + var/mob/living/player = occupant + + var/datum/bank_account/account = player.get_bank_account() + if(isnull(account)) + return + + account.bitrunning_points += reward_points * 100 + + +/// The domain has been fully purged, so we should double check our avatar is deleted +/obj/machinery/netpod/proc/on_domain_scrubbed(datum/source) + SIGNAL_HANDLER + + var/mob/avatar = avatar_ref?.resolve() + if(isnull(avatar)) + return + + QDEL_NULL(avatar) + + +/// Boots out anyone in the machine && opens it +/obj/machinery/netpod/proc/on_power_loss(datum/source) + SIGNAL_HANDLER + + if(state_open) + return + + if(isnull(occupant) || !connected) + connected = FALSE + open_machine() + return + + sever_connection() + + diff --git a/code/modules/bitrunning/netpod/tools.dm b/code/modules/bitrunning/netpod/tools.dm new file mode 100644 index 000000000000..0d31bdab8665 --- /dev/null +++ b/code/modules/bitrunning/netpod/tools.dm @@ -0,0 +1,22 @@ +/obj/machinery/netpod/crowbar_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + attack_hand(user) + return ITEM_INTERACT_SUCCESS + + if(default_pry_open(tool, user) || default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + + +/obj/machinery/netpod/screwdriver_act(mob/living/user, obj/item/tool) + if(occupant) + balloon_alert(user, "in use!") + return ITEM_INTERACT_SUCCESS + + if(state_open) + balloon_alert(user, "close first.") + return ITEM_INTERACT_SUCCESS + + if(default_deconstruction_screwdriver(user, "[base_icon_state]_panel", "[base_icon_state]_closed", tool)) + update_appearance() // sometimes icon doesnt properly update during flick() + ui_close(user) + return ITEM_INTERACT_SUCCESS diff --git a/code/modules/bitrunning/netpod/ui.dm b/code/modules/bitrunning/netpod/ui.dm new file mode 100644 index 000000000000..93719fe64ebe --- /dev/null +++ b/code/modules/bitrunning/netpod/ui.dm @@ -0,0 +1,41 @@ +/obj/machinery/netpod/ui_interact(mob/user, datum/tgui/ui) + if(!is_operational || occupant) + return + + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "NetpodOutfits") + ui.set_autoupdate(FALSE) + ui.open() + + +/obj/machinery/netpod/ui_data() + var/list/data = list() + + data["netsuit"] = netsuit + return data + + +/obj/machinery/netpod/ui_static_data() + var/list/data = list() + + if(!length(cached_outfits)) + cached_outfits += make_outfit_collection("Jobs", subtypesof(/datum/outfit/job)) + + data["collections"] = cached_outfits + + return data + + +/obj/machinery/netpod/ui_act(action, params) + . = ..() + if(.) + return TRUE + switch(action) + if("select_outfit") + var/datum/outfit/new_suit = resolve_outfit(params["outfit"]) + if(new_suit) + netsuit = new_suit + return TRUE + + return FALSE diff --git a/code/modules/bitrunning/netpod/utils.dm b/code/modules/bitrunning/netpod/utils.dm new file mode 100644 index 000000000000..6e0033fe02d9 --- /dev/null +++ b/code/modules/bitrunning/netpod/utils.dm @@ -0,0 +1,144 @@ +/// Puts the occupant in netpod stasis, basically short-circuiting environmental conditions +/obj/machinery/netpod/proc/add_healing(mob/living/target) + if(target != occupant) + return + + target.AddComponent(/datum/component/netpod_healing, pod = src) + target.playsound_local(src, 'sound/effects/submerge.ogg', 20, vary = TRUE) + target.extinguish_mob() + update_use_power(ACTIVE_POWER_USE) + + +/// Disconnects the occupant after a certain time so they aren't just hibernating in netpod stasis. A balance change +/obj/machinery/netpod/proc/auto_disconnect() + if(isnull(occupant) || state_open || connected) + return + + var/mob/player = occupant + player.playsound_local(src, 'sound/effects/splash.ogg', 60, TRUE) + to_chat(player, span_notice("The machine disconnects itself and begins to drain.")) + open_machine() + + +/// Handles occupant post-disconnection effects like damage, sounds, etc +/obj/machinery/netpod/proc/disconnect_occupant(cause_damage = FALSE) + connected = FALSE + + var/mob/living/mob_occupant = occupant + if(isnull(occupant) || mob_occupant.stat == DEAD) + open_machine() + return + + mob_occupant.playsound_local(src, 'sound/magic/blink.ogg', 25, TRUE) + mob_occupant.set_static_vision(2 SECONDS) + mob_occupant.set_temp_blindness(1 SECONDS) + mob_occupant.Paralyze(2 SECONDS) + + if(!is_operational) + open_machine() + return + + var/heal_time = 1 + if(mob_occupant.health < mob_occupant.maxHealth) + heal_time = (mob_occupant.stat + 2) * 5 + addtimer(CALLBACK(src, PROC_REF(auto_disconnect)), heal_time SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME) + + if(!cause_damage) + return + + mob_occupant.flash_act(override_blindness_check = TRUE, visual = TRUE) + mob_occupant.adjustOrganLoss(ORGAN_SLOT_BRAIN, disconnect_damage) + INVOKE_ASYNC(mob_occupant, TYPE_PROC_REF(/mob/living, emote), "scream") + to_chat(mob_occupant, span_danger("You've been forcefully disconnected from your avatar! Your thoughts feel scrambled!")) + + +/** + * ### Enter Matrix + * Finds any current avatars from this chair - or generates a new one + * + * New avatars cost 1 attempt, and this will eject if there's none left + * + * Connects the mind to the avatar if everything is ok + */ +/obj/machinery/netpod/proc/enter_matrix() + var/mob/living/carbon/human/neo = occupant + if(!ishuman(neo) || neo.stat == DEAD || isnull(neo.mind)) + balloon_alert(neo, "invalid occupant.") + return + + var/obj/machinery/quantum_server/server = find_server() + if(isnull(server)) + balloon_alert(neo, "no server connected!") + return + + var/datum/lazy_template/virtual_domain/generated_domain = server.generated_domain + if(isnull(generated_domain) || !server.is_ready) + balloon_alert(neo, "nothing loaded!") + return + + var/mob/living/carbon/current_avatar = avatar_ref?.resolve() + if(isnull(current_avatar) || current_avatar.stat != CONSCIOUS) // We need a viable avatar + current_avatar = server.start_new_connection(neo, netsuit) + if(isnull(current_avatar)) + balloon_alert(neo, "out of bandwidth!") + return + + neo.set_static_vision(2 SECONDS) + add_healing(occupant) + + if(!validate_entry(neo, current_avatar)) + open_machine() + return + + current_avatar.AddComponent( \ + /datum/component/avatar_connection, \ + old_mind = neo.mind, \ + old_body = neo, \ + server = server, \ + pod = src, \ + help_text = generated_domain.help_text, \ + ) + + connected = TRUE + + +/// Finds a server and sets the server_ref +/obj/machinery/netpod/proc/find_server() + var/obj/machinery/quantum_server/server = server_ref?.resolve() + if(server) + return server + + server = locate(/obj/machinery/quantum_server) in oview(4, src) + if(isnull(server)) + return + + server_ref = WEAKREF(server) + RegisterSignal(server, COMSIG_MACHINERY_REFRESH_PARTS, PROC_REF(on_server_upgraded)) + RegisterSignal(server, COMSIG_BITRUNNER_DOMAIN_COMPLETE, PROC_REF(on_domain_complete)) + RegisterSignal(server, COMSIG_BITRUNNER_DOMAIN_SCRUBBED, PROC_REF(on_domain_scrubbed)) + + return server + + +/// Severs the connection with the current avatar +/obj/machinery/netpod/proc/sever_connection() + if(isnull(occupant) || !connected) + return + + SEND_SIGNAL(src, COMSIG_BITRUNNER_NETPOD_SEVER) + + +/// Checks for cases to eject/fail connecting an avatar +/obj/machinery/netpod/proc/validate_entry(mob/living/neo, mob/living/avatar) + if(!do_after(neo, 2 SECONDS, src)) + return FALSE + + // Very invalid + if(QDELETED(neo) || QDELETED(avatar) || QDELETED(src) || !is_operational) + return FALSE + + // Invalid + if(occupant != neo || isnull(neo.mind) || neo.stat > SOFT_CRIT || avatar.stat == DEAD) + return FALSE + + return TRUE diff --git a/code/modules/bitrunning/objects/landmarks.dm b/code/modules/bitrunning/objects/landmarks.dm index b123a793801e..170e7ab3075b 100644 --- a/code/modules/bitrunning/objects/landmarks.dm +++ b/code/modules/bitrunning/objects/landmarks.dm @@ -12,6 +12,11 @@ name = "Bitrunning hololadder spawn" icon_state = "hololadder" +/// A permanent exit for the domain +/obj/effect/landmark/bitrunning/permanent_exit + name = "Bitrunning permanent exit" + icon_state = "perm_exit" + /// Where the crates need to be taken /obj/effect/landmark/bitrunning/cache_goal_turf name = "Bitrunning goal turf" diff --git a/code/modules/bitrunning/outfits.dm b/code/modules/bitrunning/outfits.dm index d8a288f8af2a..4cbf2ea60d7d 100644 --- a/code/modules/bitrunning/outfits.dm +++ b/code/modules/bitrunning/outfits.dm @@ -10,12 +10,62 @@ suit = /obj/item/clothing/suit/jacket/leather_trenchcoat id = /obj/item/card/id/advanced + /datum/outfit/echolocator/post_equip(mob/living/carbon/human/user, visualsOnly) . = ..() user.psykerize() + /datum/outfit/bitductor name = "Bitrunning Abductor" uniform = /obj/item/clothing/under/abductor gloves = /obj/item/clothing/gloves/fingerless shoes = /obj/item/clothing/shoes/jackboots + + +/datum/outfit/beachbum_combat + name = "Beachbum: Island Combat" + id = /obj/item/card/id/advanced + l_pocket = null + r_pocket = null + shoes = /obj/item/clothing/shoes/sandal + uniform = /obj/item/clothing/under/pants/jeans + /// Available ranged weapons + var/list/ranged_weaps = list( + /obj/item/gun/ballistic/automatic/pistol, + /obj/item/gun/ballistic/rifle/boltaction, + /obj/item/gun/ballistic/automatic/mini_uzi, + /obj/item/gun/ballistic/automatic/pistol/deagle, + /obj/item/gun/ballistic/rocketlauncher/unrestricted, + /obj/item/gun/ballistic/automatic/ar, + + ) + /// Corresponding ammo + var/list/corresponding_ammo = list( + /obj/item/ammo_box/magazine/m9mm, + /obj/item/ammo_box/strilka310, + /obj/item/ammo_box/magazine/uzim9mm, + /obj/item/ammo_box/magazine/m50, + /obj/item/food/pizzaslice/dank, // more silly, less destructive + /obj/item/ammo_box/magazine/m223, + ) + + +/datum/outfit/beachbum_combat/post_equip(mob/living/carbon/human/bum, visualsOnly) + . = ..() + + var/choice = rand(1, length(ranged_weaps)) + var/weapon = ranged_weaps[choice] + bum.put_in_active_hand(new weapon) + + var/ammo = corresponding_ammo[choice] + var/obj/item/ammo1 = new ammo + var/obj/item/ammo2 = new ammo + + if(!bum.equip_to_slot_if_possible(new ammo, ITEM_SLOT_LPOCKET)) + ammo1.forceMove(get_turf(bum)) + if(!bum.equip_to_slot_if_possible(new ammo, ITEM_SLOT_RPOCKET)) + ammo2.forceMove(get_turf(bum)) + + if(prob(50)) + bum.equip_to_slot_if_possible(new /obj/item/clothing/glasses/sunglasses, ITEM_SLOT_EYES) diff --git a/code/modules/bitrunning/server/_parent.dm b/code/modules/bitrunning/server/_parent.dm index b398d8d1aa33..c299192147f5 100644 --- a/code/modules/bitrunning/server/_parent.dm +++ b/code/modules/bitrunning/server/_parent.dm @@ -78,12 +78,18 @@ . += span_infoplain("Can be resource intensive to run. Ensure adequate power supply.") + var/upgraded = FALSE if(capacitor_coefficient < 1) . += span_infoplain("- Its coolant capacity reduces cooldown time by [(1 - capacitor_coefficient) * 100]%.") + upgraded = TRUE if(servo_bonus > 0.2) . += span_infoplain("- Its manipulation potential is increasing rewards by [servo_bonus]x.") . += span_infoplain("- Injury from unsafe ejection reduced [servo_bonus * 100]%.") + upgraded = TRUE + + if(!upgraded) + . += span_notice("Its output is suboptimal. Improved components will grant domain information, reduce cooldowns and increase rewards.") if(!is_ready) . += span_notice("It is currently cooling down. Give it a few moments.") diff --git a/code/modules/bitrunning/server/loot.dm b/code/modules/bitrunning/server/loot.dm index 36fdd2b39d03..db99973c21d7 100644 --- a/code/modules/bitrunning/server/loot.dm +++ b/code/modules/bitrunning/server/loot.dm @@ -68,6 +68,8 @@ chosen_forge.start_to_spawn(reward_cache) return TRUE + +/// Builds secondary loot if the achievements were met /obj/machinery/quantum_server/proc/generate_secondary_loot(obj/curiosity, obj/machinery/byteforge/chosen_forge) spark_at_location(curiosity) // abracadabra! qdel(curiosity) // and it's gone! @@ -77,6 +79,7 @@ chosen_forge.start_to_spawn(reward_curiosity) return TRUE + /// Returns the markdown text containing domain completion information /obj/machinery/quantum_server/proc/get_completion_certificate() var/base_points = generated_domain.reward_points diff --git a/code/modules/bitrunning/server/map_handling.dm b/code/modules/bitrunning/server/map_handling.dm index fb5b8b431495..d72316f02883 100644 --- a/code/modules/bitrunning/server/map_handling.dm +++ b/code/modules/bitrunning/server/map_handling.dm @@ -24,6 +24,7 @@ reset() + /// Links all the loading processes together - does validation for booting a map /obj/machinery/quantum_server/proc/cold_boot_map(map_key) if(!is_ready) @@ -67,8 +68,15 @@ if(broadcasting) start_broadcasting_network(BITRUNNER_CAMERA_NET) + if(generated_domain.announce_to_ghosts) + notify_ghosts("Bitrunners have loaded a domain that offers ghost interactions. Check the spawners menu for more information.", + src, + "Matrix Glitch", + ) + return TRUE + /// Initializes a new domain if the given key is valid and the user has enough points /obj/machinery/quantum_server/proc/load_domain(map_key) for(var/datum/lazy_template/virtual_domain/available in SSbitrunning.all_domains) @@ -80,6 +88,7 @@ return FALSE + /// Loads in necessary map items like hololadder spawns, caches, etc /obj/machinery/quantum_server/proc/load_map_items() var/turf/goal_turfs = list() @@ -114,6 +123,15 @@ var/turf/signaler_turf = get_turf(thing) signaler_turf.AddComponent(/datum/component/bitrunning_points, generated_domain) qdel(thing) + continue + + if(istype(thing, /obj/effect/landmark/bitrunning/permanent_exit)) + var/turf/tile = get_turf(thing) + exit_turfs += tile + qdel(thing) + + new /obj/structure/hololadder(tile) + if(!length(exit_turfs)) CRASH("Failed to find exit turfs on generated domain.") @@ -132,6 +150,7 @@ return TRUE + /// Stops the current virtual domain and disconnects all users /obj/machinery/quantum_server/proc/reset(fast = FALSE) is_ready = FALSE @@ -153,6 +172,7 @@ stop_broadcasting_network(BITRUNNER_CAMERA_NET) + /// Tries to clean up everything in the domain /obj/machinery/quantum_server/proc/scrub_vdom() sever_connections() /// just in case someone's connected diff --git a/code/modules/bitrunning/server/obj_generation.dm b/code/modules/bitrunning/server/obj_generation.dm index 54c265f8e33a..0e88ff2c5a16 100644 --- a/code/modules/bitrunning/server/obj_generation.dm +++ b/code/modules/bitrunning/server/obj_generation.dm @@ -15,6 +15,7 @@ new /obj/structure/closet/crate/secure/bitrunning/encrypted(chosen_turf) return TRUE + /// Attempts to spawn a lootbox /obj/machinery/quantum_server/proc/attempt_spawn_curiosity(list/possible_turfs) if(!length(possible_turfs)) // Out of turfs to place a curiosity @@ -35,9 +36,10 @@ new /obj/item/storage/lockbox/bitrunning/encrypted(chosen_turf) return chosen_turf + /// Generates a new avatar for the bitrunner. -/obj/machinery/quantum_server/proc/generate_avatar(obj/structure/hololadder/wayout, datum/outfit/netsuit) - var/mob/living/carbon/human/avatar = new(wayout.loc) +/obj/machinery/quantum_server/proc/generate_avatar(turf/destination, datum/outfit/netsuit) + var/mob/living/carbon/human/avatar = new(destination) avatar.mind_initialize() avatar.mind.set_level(/datum/skill/eva, SKILL_LEVEL_MASTER, silent = TRUE) @@ -67,8 +69,9 @@ if(istype(hat)) hat.set_armor(/datum/armor/none) - for(var/obj/thing in avatar.held_items) - qdel(thing) + if(!generated_domain.forced_outfit) + for(var/obj/thing in avatar.held_items) + qdel(thing) var/obj/item/storage/backpack/bag = avatar.back if(istype(bag)) @@ -94,32 +97,9 @@ network = BITRUNNER_CAMERA_NET, \ emp_proof = TRUE, \ ) - return avatar - -/// Generates a new hololadder for the bitrunner. Effectively a respawn attempt. -/obj/machinery/quantum_server/proc/generate_hololadder() - if(!length(exit_turfs)) - return - - if(retries_spent >= length(exit_turfs)) - return - var/turf/destination - for(var/turf/dest_turf in exit_turfs) - if(!locate(/obj/structure/hololadder) in dest_turf) - destination = dest_turf - break - - if(isnull(destination)) - return - - var/obj/structure/hololadder/wayout = new(destination, src) - if(isnull(wayout)) - return - - retries_spent += 1 + return avatar - return wayout /// Loads in any mob segments of the map /obj/machinery/quantum_server/proc/load_mob_segments() @@ -152,6 +132,7 @@ return TRUE + /// Scans over neo's contents for bitrunning tech disks. Loads the items or abilities onto the avatar. /obj/machinery/quantum_server/proc/stock_gear(mob/living/carbon/human/avatar, mob/living/carbon/human/neo, datum/lazy_template/virtual_domain/generated_domain) var/domain_forbids_flags = generated_domain.domain_flags diff --git a/code/modules/bitrunning/server/signal_handlers.dm b/code/modules/bitrunning/server/signal_handlers.dm index 0e5e949e8bb5..f5d2840143e1 100644 --- a/code/modules/bitrunning/server/signal_handlers.dm +++ b/code/modules/bitrunning/server/signal_handlers.dm @@ -4,12 +4,14 @@ sever_connections() + /// Whenever a corpse spawner makes a new corpse, add it to the list of potential mutations /obj/machinery/quantum_server/proc/on_corpse_spawned(datum/source, mob/living/corpse) SIGNAL_HANDLER mutation_candidate_refs.Add(WEAKREF(corpse)) + /// Being qdeleted - make sure the circuit and connected mobs go with it /obj/machinery/quantum_server/proc/on_delete(datum/source) SIGNAL_HANDLER @@ -26,6 +28,7 @@ if(circuit) qdel(circuit) + /// Whenever something enters the send tiles, check if it's a loot crate. If so, alert players. /obj/machinery/quantum_server/proc/on_goal_turf_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) SIGNAL_HANDLER @@ -51,6 +54,7 @@ generate_secondary_loot(arrived, chosen_forge, generated_domain) return + /// Handles examining the server. Shows cooldown time and efficiency. /obj/machinery/quantum_server/proc/on_goal_turf_examined(datum/source, mob/examiner, list/examine_text) SIGNAL_HANDLER @@ -58,6 +62,7 @@ examine_text += span_info("Beneath your gaze, the floor pulses subtly with streams of encoded data.") examine_text += span_info("It seems to be part of the location designated for retrieving encrypted payloads.") + /// Scans over the inbound created_atoms from lazy templates /obj/machinery/quantum_server/proc/on_template_loaded(datum/lazy_template/source, list/created_atoms) SIGNAL_HANDLER @@ -98,6 +103,7 @@ /// Just in case there's any special handling for the domain generated_domain.setup_domain(created_atoms) + /// Handles when cybercops are summoned into the area or ghosts click a ghost role spawner /obj/machinery/quantum_server/proc/on_threat_created(datum/source, mob/living/threat) SIGNAL_HANDLER diff --git a/code/modules/bitrunning/server/threats.dm b/code/modules/bitrunning/server/threats.dm index 4c44355412bf..025e60714a4e 100644 --- a/code/modules/bitrunning/server/threats.dm +++ b/code/modules/bitrunning/server/threats.dm @@ -4,6 +4,7 @@ SEND_SIGNAL(src, COMSIG_BITRUNNER_THREAT_CREATED) threat.AddComponent(/datum/component/virtual_entity, src) + /// Choses which antagonist role is spawned based on threat /obj/machinery/quantum_server/proc/get_antagonist_role() var/list/available = list() @@ -19,6 +20,7 @@ return chosen + /// Selects a target to mutate. Gives two attempts, then crashes if it fails. /obj/machinery/quantum_server/proc/get_mutation_target() var/datum/weakref/target_ref = pick(mutation_candidate_refs) @@ -35,6 +37,7 @@ resolved = target_ref.resolve() return resolved + /// Finds any mobs with minds in the zones and gives them the bad news /obj/machinery/quantum_server/proc/notify_spawned_threats() for(var/datum/weakref/baddie_ref as anything in spawned_threat_refs) @@ -52,10 +55,12 @@ to_chat(baddie, span_userdanger("You have been flagged for deletion! Thank you for your service.")) + /// Removes a specific threat - used when station spawning /obj/machinery/quantum_server/proc/remove_threat(mob/living/threat) spawned_threat_refs.Remove(WEAKREF(threat)) + /// Selects the role and waits for a ghost orbiter /obj/machinery/quantum_server/proc/setup_glitch(datum/antagonist/bitrunning_glitch/forced_role) if(!validate_mutation_candidates()) @@ -82,6 +87,7 @@ return mutation_target + /// Orbit poll has concluded - spawn the antag /obj/machinery/quantum_server/proc/spawn_glitch(datum/antagonist/bitrunning_glitch/chosen_role, mob/living/mutation_target, mob/dead/observer/ghost) if(QDELETED(mutation_target)) @@ -119,6 +125,7 @@ add_threats(new_mob) + /// Oh boy - transports the antag station side /obj/machinery/quantum_server/proc/station_spawn(mob/living/antag, obj/machinery/byteforge/chosen_forge) antag.balloon_alert(antag, "scanning...") @@ -163,6 +170,7 @@ do_teleport(antag, get_turf(chosen_forge), forced = TRUE, asoundin = 'sound/magic/ethereal_enter.ogg', asoundout = 'sound/magic/ethereal_exit.ogg', channel = TELEPORT_CHANNEL_QUANTUM) + /// Removes any invalid candidates from the list /obj/machinery/quantum_server/proc/validate_mutation_candidates() for(var/datum/weakref/creature_ref as anything in mutation_candidate_refs) diff --git a/code/modules/bitrunning/server/util.dm b/code/modules/bitrunning/server/util.dm index 4fe45d382af0..6a852706ab93 100644 --- a/code/modules/bitrunning/server/util.dm +++ b/code/modules/bitrunning/server/util.dm @@ -1,11 +1,13 @@ #define MAX_DISTANCE 4 // How far crates can spawn from the server + /// Resets the cooldown state and updates icons /obj/machinery/quantum_server/proc/cool_off() is_ready = TRUE update_appearance() radio.talk_into(src, "Thermal systems within operational parameters. Proceeding to domain configuration.", RADIO_CHANNEL_SUPPLY) + /// If there are hosted minds, attempts to get a list of their current virtual bodies w/ vitals /obj/machinery/quantum_server/proc/get_avatar_data() var/list/hosted_avatars = list() @@ -31,6 +33,49 @@ return hosted_avatars + +/// I grab the atom here so I can signal it / manipulate spawners etc +/obj/machinery/quantum_server/proc/get_avatar_destination() as /atom + // Branch A: Custom spawns + if(length(generated_domain.custom_spawns)) + var/atom/valid_spawner + + while(isnull(valid_spawner)) + var/atom/chosen = pick(generated_domain.custom_spawns) + if(QDELETED(chosen)) + generated_domain.custom_spawns -= chosen + continue + + valid_spawner = chosen + break + + return valid_spawner + + // Branch B: Hololadders + if(!length(exit_turfs)) + return + + if(retries_spent >= length(exit_turfs)) + return + + var/turf/exit_tile + for(var/turf/dest_turf in exit_turfs) + if(!locate(/obj/structure/hololadder) in dest_turf) + exit_tile = dest_turf + break + + if(isnull(exit_tile)) + return + + var/obj/structure/hololadder/wayout = new(exit_tile, src) + if(isnull(wayout)) + return + + retries_spent += 1 + + return wayout + + /// Locates any turfs with forges on them, returns a random one /obj/machinery/quantum_server/proc/get_random_nearby_forge() var/list/nearby_forges = list() @@ -40,6 +85,7 @@ return pick(nearby_forges) + /// Gets a random available domain given the current points. /obj/machinery/quantum_server/proc/get_random_domain_id() if(points < 1) @@ -85,6 +131,7 @@ SEND_SIGNAL(src, COMSIG_BITRUNNER_QSRV_SEVER) + /// Do some magic teleport sparks /obj/machinery/quantum_server/proc/spark_at_location(obj/cache) playsound(cache, 'sound/magic/blink.ogg', 50, vary = TRUE) @@ -92,16 +139,33 @@ sparks.set_up(5, location = get_turf(cache)) sparks.start() -/// Returns a turf if it's not dense, else will find a neighbor. -/obj/machinery/quantum_server/proc/validate_turf(turf/chosen_turf) - if(!chosen_turf.is_blocked_turf()) - return chosen_turf - for(var/turf/tile in get_adjacent_open_turfs(chosen_turf)) - if(!tile.is_blocked_turf()) - return chosen_turf +/// Starts building a new avatar for the player. +/// Called by netpods when they don't have a current avatar. +/// This is a procedural proc which links several others together. +/obj/machinery/quantum_server/proc/start_new_connection(mob/living/carbon/human/neo, datum/outfit/netsuit) as /mob/living/carbon/human + var/atom/entry_atom = get_avatar_destination() + if(isnull(entry_atom)) + return + + var/mob/living/carbon/new_avatar = generate_avatar(get_turf(entry_atom), netsuit) + stock_gear(new_avatar, neo, generated_domain) + + // Cleanup for domains with one time use custom spawns + if(!length(generated_domain.custom_spawns)) + return new_avatar + + // If we're spawning from some other fuckery, no need for this + if(istype(entry_atom, /obj/effect/mob_spawn/ghost_role/human/virtual_domain)) + var/obj/effect/mob_spawn/ghost_role/human/virtual_domain/spawner = entry_atom + spawner.artificial_spawn(new_avatar) + + if(!generated_domain.keep_custom_spawns) + generated_domain.custom_spawns -= entry_atom + qdel(entry_atom) + + return new_avatar -#undef MAX_DISTANCE /// Toggles broadcast on and off /obj/machinery/quantum_server/proc/toggle_broadcast() @@ -116,3 +180,16 @@ // And we only flip TVs when there's a domain, because otherwise there's no cams to watch set_network_broadcast_status(BITRUNNER_CAMERA_NET, broadcasting) return TRUE + + +/// Returns a turf if it's not dense, else will find a neighbor. +/obj/machinery/quantum_server/proc/validate_turf(turf/chosen_turf) + if(!chosen_turf.is_blocked_turf()) + return chosen_turf + + for(var/turf/tile in get_adjacent_open_turfs(chosen_turf)) + if(!tile.is_blocked_turf()) + return chosen_turf + + +#undef MAX_DISTANCE diff --git a/code/modules/bitrunning/spawners.dm b/code/modules/bitrunning/spawners.dm index 0ad7e454e6f2..58e0ffb3ba51 100644 --- a/code/modules/bitrunning/spawners.dm +++ b/code/modules/bitrunning/spawners.dm @@ -3,12 +3,9 @@ prompt_name = "a virtual domain debug entity" flavour_text = "You probably shouldn't be seeing this, contact a coder!" you_are_text = "You are NOT supposed to be here. How did you let this happen?" - important_text = "You must eliminate any bitrunners from the domain." + important_text = "Bitrunning is a crime, and your primary threat." temp_body = TRUE -/obj/effect/mob_spawn/ghost_role/human/virtual_domain/Initialize(mapload) - . = ..() - notify_ghosts("The [name] has been created. The virtual world calls for aid!", src, "Virtual Insanity!") /obj/effect/mob_spawn/ghost_role/human/virtual_domain/special(mob/living/spawned_mob, mob/mob_possessor) var/datum/mind/ghost_mind = mob_possessor.mind @@ -20,6 +17,12 @@ spawned_mob.mind.add_antag_datum(/datum/antagonist/domain_ghost_actor) spawned_mob.mind.set_assigned_role(SSjob.get_job_type(/datum/job/bitrunning_glitch)) + +/// Simulates a ghost role spawn without calling special(), ie a bitrunner spawn instead of a ghost. +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/proc/artificial_spawn(mob/living/runner) + SEND_SIGNAL(src, COMSIG_BITRUNNER_SPAWNED, runner) + + /obj/effect/mob_spawn/ghost_role/human/virtual_domain/pirate name = "Virtual Pirate Remains" desc = "Some inanimate bones. They feel like they could spring to life at any moment!" @@ -28,22 +31,25 @@ icon_state = "remains" prompt_name = "a virtual skeleton pirate" you_are_text = "You are a virtual pirate. Yarrr!" - flavour_text = "You have awoken, without instruction. There's a LANDLUBBER after yer booty. Stop them!" + flavour_text = " There's a LANDLUBBER after yer booty. Stop them!" + /obj/effect/mob_spawn/ghost_role/human/virtual_domain/pirate/special(mob/living/spawned_mob, mob/mob_possessor) . = ..() spawned_mob.fully_replace_character_name(spawned_mob.real_name, "[pick(strings(PIRATE_NAMES_FILE, "generic_beginnings"))][pick(strings(PIRATE_NAMES_FILE, "generic_endings"))]") + /obj/effect/mob_spawn/ghost_role/human/virtual_domain/syndie name = "Virtual Syndicate Sleeper" icon = 'icons/obj/machines/sleeper.dmi' icon_state = "sleeper_s" prompt_name = "a virtual syndicate operative" you_are_text = "You are a virtual syndicate operative." - flavour_text = "You have awoken, without instruction. Alarms blare! We are being boarded!" + flavour_text = "Alarms blare! We are being boarded!" outfit = /datum/outfit/virtual_syndicate spawner_job_path = /datum/job/space_syndicate + /datum/outfit/virtual_syndicate name = "Virtual Syndie" id = /obj/item/card/id/advanced/chameleon @@ -54,5 +60,6 @@ shoes = /obj/item/clothing/shoes/combat implants = list(/obj/item/implant/weapons_auth) + /datum/outfit/virtual_syndicate/post_equip(mob/living/carbon/human/user, visualsOnly) user.faction |= ROLE_SYNDICATE diff --git a/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm b/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm new file mode 100644 index 000000000000..b745a4746aa2 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm @@ -0,0 +1,46 @@ +/datum/lazy_template/virtual_domain/island_brawl + name = "Island Brawl" + announce_to_ghosts = TRUE + cost = BITRUNNER_COST_HIGH + desc = "A 'peaceful' island tucked away in the middle of nowhere. This map will auto-complete after a number of deaths have occurred." + difficulty = BITRUNNER_DIFFICULTY_HIGH + forced_outfit = /datum/outfit/beachbum_combat + help_text = "There may be bounties laid out across the island, but the primary objective is to survive. Deaths on the island will count towards the final score." + key = "island_brawl" + map_name = "island_brawl" + reward_points = BITRUNNER_REWARD_HIGH + secondary_loot = list( + /obj/item/toy/beach_ball = 2, + /obj/item/clothing/shoes/sandal = 1, + /obj/item/clothing/glasses/sunglasses = 1, + /obj/item/gun/ballistic/automatic/mini_uzi = 1, + ) + + +/datum/lazy_template/virtual_domain/island_brawl/setup_domain(list/created_atoms) + for(var/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander/spawner in created_atoms) + custom_spawns += spawner + + RegisterSignals(spawner, list(COMSIG_GHOSTROLE_SPAWNED, COMSIG_BITRUNNER_SPAWNED), PROC_REF(on_spawn)) + + +/// Someone has spawned in, so we check for their death +/datum/lazy_template/virtual_domain/island_brawl/proc/on_spawn(datum/source, mob/living/spawned_mob) + SIGNAL_HANDLER + + RegisterSignals(spawned_mob, list(COMSIG_LIVING_DEATH), PROC_REF(on_death)) + + +/// Mob has died, so we add a point to the domain +/datum/lazy_template/virtual_domain/island_brawl/proc/on_death(datum/source, gibbed) + SIGNAL_HANDLER + + add_points(1) + + +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander + name = "Islander" + outfit = /datum/outfit/beachbum_combat + prompt_name = "a combat beach bum" + you_are_text = "You are a virtual islander." + flavour_text = "Don't let anyone ruin your idyllic vacation spot. Coordinate with others- or don't!" diff --git a/code/modules/bitrunning/virtual_domain/domains/pirates.dm b/code/modules/bitrunning/virtual_domain/domains/pirates.dm index 52d86a712118..b8bff6dbeb36 100644 --- a/code/modules/bitrunning/virtual_domain/domains/pirates.dm +++ b/code/modules/bitrunning/virtual_domain/domains/pirates.dm @@ -1,5 +1,6 @@ /datum/lazy_template/virtual_domain/pirates name = "Corsair Cove" + announce_to_ghosts = TRUE cost = BITRUNNER_COST_MEDIUM desc = "Battle your way to the hidden treasure, seize the booty, and make a swift escape before the pirates turn the tide." difficulty = BITRUNNER_DIFFICULTY_MEDIUM diff --git a/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm b/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm index 5f754dd433ad..f81bc6daae2c 100644 --- a/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm +++ b/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm @@ -1,5 +1,6 @@ /datum/lazy_template/virtual_domain/syndicate_assault name = "Syndicate Assault" + announce_to_ghosts = TRUE cost = BITRUNNER_COST_MEDIUM desc = "Board the enemy ship and recover the stolen cargo." difficulty = BITRUNNER_DIFFICULTY_MEDIUM diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm index b147e1514dec..472e7ba995a1 100644 --- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -47,6 +47,7 @@ /// An assoc list of typepath/amount to spawn on completion. Not weighted - the value is the amount var/list/completion_loot /// An assoc list of typepath/amount to spawn from secondary objectives. Not weighted - the value is the total number of items that can be obtained. + /// An assoc list of typepath/amount to spawn from secondary objectives. Not weighted - the value is the total number of items that can be obtained. var/list/secondary_loot = list() /// Number of secondary loot boxes generated. Resets when the domain is reloaded. var/secondary_loot_generated @@ -68,6 +69,7 @@ /datum/lazy_template/virtual_domain/proc/add_points(points_to_add) SEND_SIGNAL(src, COMSIG_BITRUNNER_GOAL_POINT, points_to_add) + /// Overridable proc to be called after the map is loaded. /datum/lazy_template/virtual_domain/proc/setup_domain(list/created_atoms) return diff --git a/icons/effects/bitrunning.dmi b/icons/effects/bitrunning.dmi index 8efa429389c3a50521ec4f2c366279a3505cf993..9a2e9c0228a5791d81f0fd976d272eca335b7654 100644 GIT binary patch delta 2562 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 bUi<$6*^|uoQT#~c00000NkvXXu0mjf_t;b? diff --git a/tgstation.dme b/tgstation.dme index 021b5d5ba611..d12029df7b70 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3538,6 +3538,13 @@ #include "code\modules\bitrunning\components\netpod_healing.dm" #include "code\modules\bitrunning\components\npc_friendly.dm" #include "code\modules\bitrunning\components\virtual_entity.dm" +#include "code\modules\bitrunning\netpod\_netpod.dm" +#include "code\modules\bitrunning\netpod\container.dm" +#include "code\modules\bitrunning\netpod\outfitting.dm" +#include "code\modules\bitrunning\netpod\signals.dm" +#include "code\modules\bitrunning\netpod\tools.dm" +#include "code\modules\bitrunning\netpod\ui.dm" +#include "code\modules\bitrunning\netpod\utils.dm" #include "code\modules\bitrunning\objects\byteforge.dm" #include "code\modules\bitrunning\objects\clothing.dm" #include "code\modules\bitrunning\objects\debug.dm" @@ -3547,7 +3554,6 @@ #include "code\modules\bitrunning\objects\landmarks.dm" #include "code\modules\bitrunning\objects\loot_box.dm" #include "code\modules\bitrunning\objects\loot_crate.dm" -#include "code\modules\bitrunning\objects\netpod.dm" #include "code\modules\bitrunning\objects\quantum_console.dm" #include "code\modules\bitrunning\objects\vendor.dm" #include "code\modules\bitrunning\objects\gimmick_disks\_gimmick_disk.dm" @@ -3582,6 +3588,7 @@ #include "code\modules\bitrunning\virtual_domain\domains\grassland_hunt.dm" #include "code\modules\bitrunning\virtual_domain\domains\heretic_hunt.dm" #include "code\modules\bitrunning\virtual_domain\domains\hierophant.dm" +#include "code\modules\bitrunning\virtual_domain\domains\island_brawl.dm" #include "code\modules\bitrunning\virtual_domain\domains\meta_central.dm" #include "code\modules\bitrunning\virtual_domain\domains\pipedream.dm" #include "code\modules\bitrunning\virtual_domain\domains\pirates.dm" diff --git a/tgui/packages/tgui/interfaces/QuantumConsole.tsx b/tgui/packages/tgui/interfaces/QuantumConsole.tsx index 2b08c4add218..d00f744f9a6d 100644 --- a/tgui/packages/tgui/interfaces/QuantumConsole.tsx +++ b/tgui/packages/tgui/interfaces/QuantumConsole.tsx @@ -46,6 +46,7 @@ type Avatar = { }; type Domain = { + announce_ghosts: BooleanLike; cost: number; desc: string; difficulty: number; @@ -73,10 +74,11 @@ enum Difficulty { High, } -const isConnected = (data: Data): data is Data & { connected: 1 } => - data.connected === 1; +function isConnected(data: Data): data is Data & { connected: 1 } { + return data.connected === 1; +} -const getColor = (difficulty: number) => { +function getColor(difficulty: number) { switch (difficulty) { case Difficulty.Low: return 'yellow'; @@ -87,9 +89,9 @@ const getColor = (difficulty: number) => { default: return 'green'; } -}; +} -export const QuantumConsole = (props) => { +export function QuantumConsole(props) { const { data } = useBackend(); return ( @@ -100,9 +102,9 @@ export const QuantumConsole = (props) => { ); -}; +} -const AccessView = (props) => { +function AccessView(props) { const { act, data } = useBackend(); const [tab, setTab] = useSharedState('tab', 0); @@ -246,11 +248,12 @@ const AccessView = (props) => { ); -}; +} -const DomainEntry = (props: DomainEntryProps) => { +function DomainEntry(props: DomainEntryProps) { const { domain: { + announce_ghosts, cost, desc, difficulty, @@ -282,6 +285,8 @@ const DomainEntry = (props: DomainEntryProps) => { buttonName = 'Deploy'; } + const canView = name !== '???'; + return ( { title={ <> {name} - {!!is_modular && name !== '???' && } - {!!has_secondary_objectives && name !== '???' && ( - - )} + {!!is_modular && canView && } + {!!has_secondary_objectives && canView && } + {!!announce_ghosts && canView && } } > @@ -311,6 +315,7 @@ const DomainEntry = (props: DomainEntryProps) => { {desc} {!!is_modular && ' (Modular)'} {!!has_secondary_objectives && ' (Secondary Objective Available)'} + {!!announce_ghosts && ' (Ghost Interaction)'} @@ -330,7 +335,7 @@ const DomainEntry = (props: DomainEntryProps) => { ); -}; +} const AvatarDisplay = (props) => { const { act, data } = useBackend(); From 3b79b1bbd199a817fa3d2449f1fecd928c5323f7 Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Wed, 14 Aug 2024 06:28:12 -0400 Subject: [PATCH 03/45] Fixes a hard del with island_brawl spawners (#85684) ## About The Pull Request ![yN86RRrD0h](https://github.com/user-attachments/assets/2aaa23c8-1fcb-4a40-ad54-c71306a2f430) Tin, just aims to fix this hard del by releasing the refs when the spawners get qdeleted. ## Why It's Good For The Game Fixes a hard del---also, there were some issues with this domain not clearing itself properly under certain cases and this may resolve that. ## Changelog Nothing player facing --- .../bitrunning/virtual_domain/domains/island_brawl.dm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm b/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm index b745a4746aa2..84eb53e026a2 100644 --- a/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm +++ b/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm @@ -21,9 +21,17 @@ for(var/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander/spawner in created_atoms) custom_spawns += spawner + RegisterSignal(spawner, COMSIG_QDELETING, PROC_REF(on_spawner_qdeleted)) RegisterSignals(spawner, list(COMSIG_GHOSTROLE_SPAWNED, COMSIG_BITRUNNER_SPAWNED), PROC_REF(on_spawn)) +/datum/lazy_template/virtual_domain/island_brawl/proc/on_spawner_qdeleted(obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander/source) + SIGNAL_HANDLER + + custom_spawns -= source + UnregisterSignal(source, COMSIG_QDELETING) + + /// Someone has spawned in, so we check for their death /datum/lazy_template/virtual_domain/island_brawl/proc/on_spawn(datum/source, mob/living/spawned_mob) SIGNAL_HANDLER From 55c8e24a2ec04f5ee9aaebf50819c8ca04d90e35 Mon Sep 17 00:00:00 2001 From: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:53:50 -0700 Subject: [PATCH 04/45] Adds two new BR maps, basic mobs, BR tweaks & fixes (#85292) Title. Adds two new maps: - Grasslands Hunt (peaceful) - Meta Central (easy) These maps add a new basic revolutionary mob and significantly upgrades the ai of basic deer. This fixes an issue where modular maps were not correctly spawning mobs or adding them to the "mutable candidates" for antagonists. There's also some balance changes to bitrunning vendor prices, which are generally now lower. This change is unrelated to the PR as a whole so I'm okay with removing it if there's concern
expand ![Screenshot 2024-07-26 151822](https://github.com/user-attachments/assets/61fd84f3-3768-4b7a-b421-a953fb8b8174) ![Screenshot 2024-07-26 151834](https://github.com/user-attachments/assets/27193237-858e-41ac-953c-6c30846c98c0)
- [x] Fix the revolutionary death anim - [x] Make deer run when injured New maps as a general positive for bitrunning Bug fixes Makes vendor choices for bitrunning-exclusive items generally less of a chore to get jlsnow301, MMMiracles, KikoWen0, Ben10Omintrix :cl: add: Added two new bitrunning maps: Grasslands Hunt and Meta Central. add: Deer are now more complex animals, granting them enhanced ability to run amok and chew your favorite plants. balance: Reduced the cost of most BR vendor items. fix: Fixes an issue where modular virtual domains spawned less mobs than intended. fix: These modular spawns are now valid mutation targets to become an antagonist. /:cl: --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- code/modules/bitrunning/orders/tech.dm | 28 ++++++++++---------------- tgstation.dme | 4 ++++ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/code/modules/bitrunning/orders/tech.dm b/code/modules/bitrunning/orders/tech.dm index 333e636f7eaa..3701d8fa989d 100644 --- a/code/modules/bitrunning/orders/tech.dm +++ b/code/modules/bitrunning/orders/tech.dm @@ -3,43 +3,37 @@ /datum/orderable_item/bitrunning_tech/item_tier1 cost_per_order = 750 - purchase_path = /obj/item/bitrunning_disk/item/tier1 + item_path = /obj/item/bitrunning_disk/item/tier1 desc = "This disk contains a program that lets you equip a medical beamgun, a C4 explosive, or a box of infinite pizza." /datum/orderable_item/bitrunning_tech/item_tier2 cost_per_order = 1250 - purchase_path = /obj/item/bitrunning_disk/item/tier2 + item_path = /obj/item/bitrunning_disk/item/tier2 desc = "This disk contains a program that lets you equip a luxury medipen, a pistol, or an armour vest." /datum/orderable_item/bitrunning_tech/item_tier3 cost_per_order = 2000 - purchase_path = /obj/item/bitrunning_disk/item/tier3 + item_path = /obj/item/bitrunning_disk/item/tier3 desc = "This disk contains a program that lets you equip an advanced energy gun, a dual bladed energy sword, or a minibomb." /datum/orderable_item/bitrunning_tech/ability_tier1 cost_per_order = 750 - purchase_path = /obj/item/bitrunning_disk/ability/tier1 + item_path = /obj/item/bitrunning_disk/ability/tier1 desc = "This disk contains a program that lets you cast Summon Cheese or Lesser Heal." /datum/orderable_item/bitrunning_tech/ability_tier2 cost_per_order = 1500 - purchase_path = /obj/item/bitrunning_disk/ability/tier2 + item_path = /obj/item/bitrunning_disk/ability/tier2 desc = "This disk contains a program that lets you cast Fireball, Lightning Bolt, or Forcewall." /datum/orderable_item/bitrunning_tech/ability_tier3 cost_per_order = 2500 - purchase_path = /obj/item/bitrunning_disk/ability/tier3 + item_path = /obj/item/bitrunning_disk/ability/tier3 desc = "This disk contains a program that lets you shapeshift into a lesser ashdrake, or a polar bear." -/datum/orderable_item/bitrunning_tech/gimmick_sports - cost_per_order = 750 - purchase_path = /obj/item/bitrunning_disk/gimmick/sports - desc = "This disk contains a program that lets you equip a sports-themed gimmick loadout." - -/datum/orderable_item/bitrunning_tech/gimmick_dungeon_crawling - cost_per_order = 1250 - purchase_path = /obj/item/bitrunning_disk/gimmick/dungeon - desc = "This disk contains a program that lets you equip a dungeon crawling-themed gimmick loadout." +/datum/orderable_item/bitrunning_tech/flip_skillchip + item_path = /obj/item/skillchip/matrix_taunt + cost_per_order = 1500 /datum/orderable_item/bitrunning_tech/pka_mod purchase_path = /obj/item/bitrunning_disk/item/pka_mods @@ -47,7 +41,7 @@ desc = "A decommissioned sleeper circuitboard, repurposed for recreational purposes." /datum/orderable_item/bitrunning_tech/pka_mod/premium - purchase_path = /obj/item/bitrunning_disk/item/pka_mods/premium + item_path = /obj/item/bitrunning_disk/item/pka_mods/premium cost_per_order = 1600 desc = "This disk contains a program that lets you equip stronger modkits for the proto-kinetic accelerator. Proto-kinetic accelerator not included." @@ -56,6 +50,6 @@ cost_per_order = 750 /datum/orderable_item/bitrunning_tech/pkc_mod/premium - purchase_path = /obj/item/bitrunning_disk/item/pkc_mods/premium + item_path = /obj/item/bitrunning_disk/item/pkc_mods/premium cost_per_order = 1600 desc = "This disk contains a program that lets you equip stronger trophies for the proto-kinetic crusher. Proto-kinetic crusher not included." diff --git a/tgstation.dme b/tgstation.dme index d12029df7b70..0b57e196662f 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3590,6 +3590,7 @@ #include "code\modules\bitrunning\virtual_domain\domains\hierophant.dm" #include "code\modules\bitrunning\virtual_domain\domains\island_brawl.dm" #include "code\modules\bitrunning\virtual_domain\domains\meta_central.dm" +#include "code\modules\bitrunning\virtual_domain\domains\meta_central.dm" #include "code\modules\bitrunning\virtual_domain\domains\pipedream.dm" #include "code\modules\bitrunning\virtual_domain\domains\pirates.dm" #include "code\modules\bitrunning\virtual_domain\domains\psyker_shuffle.dm" @@ -4685,6 +4686,7 @@ #include "code\modules\mob\living\basic\festivus_pole.dm" #include "code\modules\mob\living\basic\health_adjustment.dm" #include "code\modules\mob\living\basic\revolutionary.dm" +#include "code\modules\mob\living\basic\revolutionary.dm" #include "code\modules\mob\living\basic\tree.dm" #include "code\modules\mob\living\basic\blob_minions\blob_ai.dm" #include "code\modules\mob\living\basic\blob_minions\blob_mob.dm" @@ -4735,6 +4737,8 @@ #include "code\modules\mob\living\basic\farm_animals\cow\cow_wisdom.dm" #include "code\modules\mob\living\basic\farm_animals\deer\deer.dm" #include "code\modules\mob\living\basic\farm_animals\deer\deer_ai.dm" +#include "code\modules\mob\living\basic\farm_animals\deer\deer.dm" +#include "code\modules\mob\living\basic\farm_animals\deer\deer_ai.dm" #include "code\modules\mob\living\basic\farm_animals\goat\_goat.dm" #include "code\modules\mob\living\basic\farm_animals\goat\goat_ai.dm" #include "code\modules\mob\living\basic\farm_animals\goat\goat_subtypes.dm" From 989f7127014e398ac3bf9459afd1fd1a9c9ec3d3 Mon Sep 17 00:00:00 2001 From: Rhials <28870487+Rhials@users.noreply.github.com> Date: Sat, 21 Sep 2024 12:17:53 -0400 Subject: [PATCH 05/45] Meta Central Virtual Domain spawner fixes and announce_to_ghosts adjustment (#86797) ## About The Pull Request This swaps out the pirate spawner on the Meta Central VDOM with the subtype specifically meant for virtual domains. That thing wasn't actually a virtual domain spawner, it was just a regular one. This applies the necessary restrictions/roles being applied to pirates who spawn on that map. It will also make #86794 work on the Metastation Central map, rather than only affect the Corsair Cove map (which used the correct spawner type). This also adds the Announce to Ghosts flag to Meta Central, as is uniform for maps with ghost roles. Doing so also enables the VDOM selector UI indicator that ghost roles can spawn on the map. This whole thing started as an attempt to fix #86785 but I was too slow and someone beat me too it. Darn. ## Why It's Good For The Game Ensures #86785 will be closed properly. ## Changelog :cl: Rhials fix: The Meta Central Virtual Domain now uses the proper ghost role spawner, meaning you can't eavesdrop on syndie comms using their headset. /:cl: --- code/controllers/subsystem/bitrunning.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/controllers/subsystem/bitrunning.dm b/code/controllers/subsystem/bitrunning.dm index 2ea36ebcb390..51b7f329a4a6 100644 --- a/code/controllers/subsystem/bitrunning.dm +++ b/code/controllers/subsystem/bitrunning.dm @@ -25,7 +25,7 @@ SUBSYSTEM_DEF(bitrunning) var/can_view_reward = domain.difficulty < (scanner_tier + 1) && domain.cost <= points + 3 UNTYPED_LIST_ADD(levels, list( - "announce_ghosts"= domain.announce_to_ghosts, + "announce_ghosts" = domain.announce_to_ghosts, "cost" = domain.cost, "desc" = can_view ? domain.desc : "Limited scanning capabilities. Cannot infer domain details.", "difficulty" = domain.difficulty, From 8380547773bce004cf3581b861b7d2192701f6f9 Mon Sep 17 00:00:00 2001 From: carlarctg <53100513+carlarctg@users.noreply.github.com> Date: Thu, 21 Nov 2024 20:43:51 -0300 Subject: [PATCH 06/45] [IDED] Replaces UZI domain reward with ballistic chameleon gun (#87600) Replaces the mini-UZI beach battle domain reward with a ballistic chameleon gun. Also added a separate possible reward for an uzi bitrunning disk. --- .../virtual_domain/domains/island_brawl.dm | 4 +- .../chameleon/chameleon_action_subtypes.dm | 4 ++ .../clothing/chameleon/chameleon_gun.dm | 44 +++++++++++-------- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm b/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm index 84eb53e026a2..396990720575 100644 --- a/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm +++ b/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm @@ -13,10 +13,10 @@ /obj/item/toy/beach_ball = 2, /obj/item/clothing/shoes/sandal = 1, /obj/item/clothing/glasses/sunglasses = 1, - /obj/item/gun/ballistic/automatic/mini_uzi = 1, + /obj/item/gun/energy/laser/chameleon/ballistic_only = 1, + /obj/item/bitrunning_disk/item/mini_uzi = 1, ) - /datum/lazy_template/virtual_domain/island_brawl/setup_domain(list/created_atoms) for(var/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander/spawner in created_atoms) custom_spawns += spawner diff --git a/code/modules/clothing/chameleon/chameleon_action_subtypes.dm b/code/modules/clothing/chameleon/chameleon_action_subtypes.dm index 705d3a77b39f..46cb20f296b3 100644 --- a/code/modules/clothing/chameleon/chameleon_action_subtypes.dm +++ b/code/modules/clothing/chameleon/chameleon_action_subtypes.dm @@ -302,3 +302,7 @@ . = ..() for(var/other_type in other_cham_types) add_chameleon_items(other_type) + +/datum/action/item_action/chameleon/change/gun/ballistic + chameleon_type = /obj/item/gun/ballistic + chameleon_name = "Gun" diff --git a/code/modules/clothing/chameleon/chameleon_gun.dm b/code/modules/clothing/chameleon/chameleon_gun.dm index bf5af8e4d5fa..efb5913d571b 100644 --- a/code/modules/clothing/chameleon/chameleon_gun.dm +++ b/code/modules/clothing/chameleon/chameleon_gun.dm @@ -11,6 +11,8 @@ /// The badmin mode. Makes your projectiles act like the real deal. var/real_hits = FALSE + /// how it looks by default. + var/default_look = /obj/item/gun/energy/laser /obj/item/gun/energy/laser/chameleon/Initialize(mapload) . = ..() @@ -18,7 +20,7 @@ AddElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_CONTENTS|EMP_NO_EXAMINE) // Init order shenanigans dictate we have to do this last so we can't just use `active_type` var/datum/action/item_action/chameleon/change/gun/gun_action = locate() in actions - gun_action?.update_look(/obj/item/gun/energy/laser) + gun_action?.update_look(default_look) /** * Description: Resets the currently loaded chameleon variables, essentially resetting it to brand new. @@ -54,36 +56,34 @@ inhand_x_dimension = gun_to_set.inhand_x_dimension inhand_y_dimension = gun_to_set.inhand_y_dimension + // We dupe this casing and then delete it at the end, to grab the projectile. + var/obj/item/ammo_casing/casing_to_dupe + if(istype(gun_to_set, /obj/item/gun/ballistic)) var/obj/item/gun/ballistic/ball_gun = gun_to_set - var/obj/item/ammo_box/ball_ammo = new ball_gun.spawn_magazine_type(gun_to_set) - qdel(ball_gun) - - if(!istype(ball_ammo) || !ball_ammo.ammo_type) - qdel(ball_ammo) - return FALSE - - var/obj/item/ammo_casing/ball_cartridge = new ball_ammo.ammo_type(gun_to_set) - set_chameleon_ammo(ball_cartridge) + // We also need to copy the starting magazine for ballistics. + casing_to_dupe = initial(ball_gun.spawn_magazine_type.ammo_type) + casing_to_dupe = new casing_to_dupe(src) else if(istype(gun_to_set, /obj/item/gun/magic)) var/obj/item/gun/magic/magic_gun = gun_to_set - var/obj/item/ammo_casing/magic_cartridge = new magic_gun.ammo_type(gun_to_set) - set_chameleon_ammo(magic_cartridge) + casing_to_dupe = new magic_gun.ammo_type(src) else if(istype(gun_to_set, /obj/item/gun/energy)) var/obj/item/gun/energy/energy_gun = gun_to_set + // Even if the energy gun has multiple ammo types, we copy the first. Energy guns always (should) have a list in ammo_type. if(islist(energy_gun.ammo_type) && energy_gun.ammo_type.len) - var/obj/item/ammo_casing/energy_cartridge = energy_gun.ammo_type[1] - set_chameleon_ammo(energy_cartridge) + var/obj/item/first_casing = energy_gun.ammo_type[1] + casing_to_dupe = new first_casing.type(src) else if(istype(gun_to_set, /obj/item/gun/syringe)) - var/obj/item/ammo_casing/syringe_cartridge = new /obj/item/ammo_casing/syringegun(src) - set_chameleon_ammo(syringe_cartridge) + casing_to_dupe = new /obj/item/ammo_casing/syringegun(src) else - var/obj/item/ammo_casing/default_cartridge = new /obj/item/ammo_casing(src) - set_chameleon_ammo(default_cartridge) + casing_to_dupe = new /obj/item/ammo_casing(src) + + set_chameleon_ammo(casing_to_dupe) + qdel(casing_to_dupe) /** * Description: Sets the ammo type our gun should have. @@ -155,3 +155,11 @@ var/obj/item/gun/new_gun = new guntype(src) set_chameleon_gun(new_gun) qdel(new_gun) + +/obj/item/gun/energy/laser/chameleon/ballistic_only + actions_types = list(/datum/action/item_action/chameleon/change/gun/ballistic) + default_look = /obj/item/gun/ballistic/automatic/mini_uzi + +/obj/item/gun/energy/laser/chameleon/ballistic_only/Initialize(mapload) + . = ..() + set_chameleon_disguise(default_look) From 3c591871c1a132aa48264f6eb8eef8e082a06df6 Mon Sep 17 00:00:00 2001 From: _0Steven <42909981+00-Steven@users.noreply.github.com> Date: Thu, 30 Jan 2025 13:17:08 +0100 Subject: [PATCH 07/45] Bitrunning Gimmick Loadout Disks (feat. minor disk load refactor) (#88716) 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. 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:tm: 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. :cl: 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. /:cl: --- code/modules/bitrunning/objects/disks.dm | 1 + .../objects/gimmick_disks/_gimmick_disk.dm | 18 +++++++++--------- .../objects/gimmick_disks/dungeon_disk.dm | 8 ++++---- .../objects/gimmick_disks/sports_disk.dm | 6 +++--- code/modules/bitrunning/orders/tech.dm | 10 ++++++++++ .../bitrunning/server/obj_generation.dm | 2 +- .../virtual_domain/virtual_domain.dm | 2 ++ 7 files changed, 30 insertions(+), 17 deletions(-) diff --git a/code/modules/bitrunning/objects/disks.dm b/code/modules/bitrunning/objects/disks.dm index f91aeaab0438..db470325dd9a 100644 --- a/code/modules/bitrunning/objects/disks.dm +++ b/code/modules/bitrunning/objects/disks.dm @@ -111,6 +111,7 @@ ) + /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 diff --git a/code/modules/bitrunning/objects/gimmick_disks/_gimmick_disk.dm b/code/modules/bitrunning/objects/gimmick_disks/_gimmick_disk.dm index bb37a30abf5e..2580d8620c76 100644 --- a/code/modules/bitrunning/objects/gimmick_disks/_gimmick_disk.dm +++ b/code/modules/bitrunning/objects/gimmick_disks/_gimmick_disk.dm @@ -13,10 +13,10 @@ QDEL_NULL(granted_loadout) return ..() -/obj/item/bitrunning_disk/gimmick/load_onto_avatar(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, domain_flags) +/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, domain_flags) + return granted_loadout.grant_loadout(neo, avatar, external_load_flags) /obj/item/bitrunning_disk/gimmick/attack_self(mob/user, modifiers) . = ..() @@ -57,17 +57,17 @@ var/prefix_container_name = TRUE /// Grants out loadout. -/datum/bitrunning_gimmick/proc/grant_loadout(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, domain_flags) +/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, domain_flags) - return_flags |= grant_abilities(neo, avatar, domain_flags) + 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, domain_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(domain_flags & DOMAIN_FORBIDS_ITEMS) + if(external_load_flags & DOMAIN_FORBIDS_ITEMS) return BITRUNNER_GEAR_LOAD_BLOCKED var/obj/item/container_item = new container_item_type() @@ -81,11 +81,11 @@ return NONE -/datum/bitrunning_gimmick/proc/grant_abilities(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, domain_flags) +/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(domain_flags & DOMAIN_FORBIDS_ABILITIES) + if(external_load_flags & DOMAIN_FORBIDS_ABILITIES) return BITRUNNER_GEAR_LOAD_BLOCKED var/return_flags = NONE diff --git a/code/modules/bitrunning/objects/gimmick_disks/dungeon_disk.dm b/code/modules/bitrunning/objects/gimmick_disks/dungeon_disk.dm index 66ceca2246f6..559c7f022dfa 100644 --- a/code/modules/bitrunning/objects/gimmick_disks/dungeon_disk.dm +++ b/code/modules/bitrunning/objects/gimmick_disks/dungeon_disk.dm @@ -148,22 +148,22 @@ granted_items = list( /obj/item/clothing/under/color/black, - /obj/item/clothing/shoes/sneakers/black/rogue, + /obj/item/clothing/shoes/sneakers/black, /obj/item/clothing/mask/facescarf/rogue, - /obj/item/clothing/glasses/eyepatch/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/rogue +/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/rogue +/obj/item/clothing/glasses/eyepatch name = "eyepatch of SEALING" /obj/item/bedsheet/black/rogue_cape diff --git a/code/modules/bitrunning/objects/gimmick_disks/sports_disk.dm b/code/modules/bitrunning/objects/gimmick_disks/sports_disk.dm index be7b4bccff8c..7ad20aad9025 100644 --- a/code/modules/bitrunning/objects/gimmick_disks/sports_disk.dm +++ b/code/modules/bitrunning/objects/gimmick_disks/sports_disk.dm @@ -63,8 +63,8 @@ name = "Archer" granted_items = list( - /obj/item/clothing/under/kimono, - /obj/item/clothing/shoes/sandal/black, + /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, @@ -73,11 +73,11 @@ /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)) - atom_storage.max_slots = 1 /obj/item/storage/bag/quiver/endless/PopulateContents() . = ..() diff --git a/code/modules/bitrunning/orders/tech.dm b/code/modules/bitrunning/orders/tech.dm index 3701d8fa989d..be8a43892ea6 100644 --- a/code/modules/bitrunning/orders/tech.dm +++ b/code/modules/bitrunning/orders/tech.dm @@ -31,6 +31,16 @@ item_path = /obj/item/bitrunning_disk/ability/tier3 desc = "This disk contains a program that lets you shapeshift into a lesser ashdrake, or a polar bear." +/datum/orderable_item/bitrunning_tech/gimmick_sports + cost_per_order = 750 + purchase_path = /obj/item/bitrunning_disk/gimmick/sports + desc = "This disk contains a program that lets you equip a sports-themed gimmick loadout." + +/datum/orderable_item/bitrunning_tech/gimmick_dungeon_crawling + cost_per_order = 1250 + purchase_path = /obj/item/bitrunning_disk/gimmick/dungeon + desc = "This disk contains a program that lets you equip a dungeon crawling-themed gimmick loadout." + /datum/orderable_item/bitrunning_tech/flip_skillchip item_path = /obj/item/skillchip/matrix_taunt cost_per_order = 1500 diff --git a/code/modules/bitrunning/server/obj_generation.dm b/code/modules/bitrunning/server/obj_generation.dm index 0e88ff2c5a16..8f767de76732 100644 --- a/code/modules/bitrunning/server/obj_generation.dm +++ b/code/modules/bitrunning/server/obj_generation.dm @@ -135,7 +135,7 @@ /// Scans over neo's contents for bitrunning tech disks. Loads the items or abilities onto the avatar. /obj/machinery/quantum_server/proc/stock_gear(mob/living/carbon/human/avatar, mob/living/carbon/human/neo, datum/lazy_template/virtual_domain/generated_domain) - var/domain_forbids_flags = generated_domain.domain_flags + var/domain_forbids_flags = generated_domain.external_load_flags var/import_ban = list() var/disk_ban = list() diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm index 472e7ba995a1..f7d8e553f847 100644 --- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -37,6 +37,8 @@ * Player customization */ + /// Any restrictions this domain has on what external sources can load in + var/external_load_flags = NONE /// Any outfit that you wish to force on avatars. Overrides preferences var/datum/outfit/forced_outfit From ed99a35042f86a88d971274166130f9886915823 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 29 May 2025 15:11:49 -0500 Subject: [PATCH 08/45] Replace hardcoded atmos values for virtual domain turfs (#91195) ## About The Pull Request This replaces the hardcoded atmos strings for these turfs with the actual icebox atmos defines. ## Why It's Good For The Game Hardcoding values is bad. --- .../bitrunning/virtual_domain/domains/stairs_and_cliffs.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/bitrunning/virtual_domain/domains/stairs_and_cliffs.dm b/code/modules/bitrunning/virtual_domain/domains/stairs_and_cliffs.dm index ba18653a2c23..a4b8c87fb955 100644 --- a/code/modules/bitrunning/virtual_domain/domains/stairs_and_cliffs.dm +++ b/code/modules/bitrunning/virtual_domain/domains/stairs_and_cliffs.dm @@ -16,11 +16,11 @@ /turf/open/cliff/snowrock/virtual_domain name = "icy cliff" - initial_gas_mix = "o2=22;n2=82;TEMP=180" + initial_gas_mix = FROZEN_ATMOS /turf/open/lava/plasma/virtual_domain name = "plasma lake" - initial_gas_mix = "o2=22;n2=82;TEMP=180" + initial_gas_mix = FROZEN_ATMOS /datum/outfit/job/virtual_domain_iceclimber name = "Ice Climber" From 7e3dd9a38d24c90a5da7d78f900fc1c3e2bce010 Mon Sep 17 00:00:00 2001 From: carlarctg <53100513+carlarctg@users.noreply.github.com> Date: Sat, 31 May 2025 10:33:47 -0300 Subject: [PATCH 09/45] Adds support for PVP Bitrunning Domains (#89798) Adds support for PVP bitrunning domains. If min/max candidates are set above 0, the virtual domain will search for any ghost spawners inside and cause a randomly-selected ghost that signed up via poll to spawn there. If no ghosts are signed up the process is cancelled. Also adds content for them: An outfit wardrobe that lets you select one of several possible outfits, once. ![image](https://github.com/user-attachments/assets/485d854d-d0c4-43b8-9dc4-75fa9dd2f17f) Not confident in my code, it's been in the works (as part of a now-canned library heretic domain thing) for over a year PvP domains are an underexplored concept. The beach domain is cloying and overly simple. This adds support for newer and cooler domains as long as there are ghosts for them. :cl: code: Adds support for PVP Bitrunning Domains /:cl: --- .../objects/structures/outfit_wardrobe.dm | 87 ++++++++++++++++++ code/modules/bitrunning/server/_parent.dm | 2 + .../modules/bitrunning/server/map_handling.dm | 46 +++++++-- .../virtual_domain/virtual_domain.dm | 39 +++++++- icons/obj/storage/closet.dmi | Bin 45972 -> 48778 bytes tgstation.dme | 1 + 6 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 code/game/objects/structures/outfit_wardrobe.dm diff --git a/code/game/objects/structures/outfit_wardrobe.dm b/code/game/objects/structures/outfit_wardrobe.dm new file mode 100644 index 000000000000..f204559c3a9a --- /dev/null +++ b/code/game/objects/structures/outfit_wardrobe.dm @@ -0,0 +1,87 @@ +// if you need to access this elsewhere you already fucked up +#define TRAIT_WARDROBE_USED "wardrobe_used" + +/obj/structure/outfit_wardrobe + name = "outfit wardrobe" + desc = "Peek in and select one of several snazzy outfits. Narnia not included." + icon = 'icons/obj/storage/closet.dmi' + icon_state = "fullcabinet" + base_icon_state = "fullcabinet" + obj_flags = INDESTRUCTIBLE + density = TRUE + anchored = TRUE + /** + * Assoc list of outfits to how many charges left. + * If value is INFINITY, the amount is infinite. Otherwise, it decreases by one per use. + * At zero it can't be picked. + * Value is unique per wardrobe. + */ + var/list/selectable_outfits_to_amount = list( + /datum/outfit/cat_butcher = INFINITY, + /datum/outfit/job/clown = 2, + ) + /// Adds a trait with the source of wardrobe_id if this is TRUE, which is checked to prevent reuse. + var/one_use = TRUE + /// All wardrobes that share this id, share the one use restriction. If one_use is FALSE, it effectively does nothing. + var/wardrobe_id = "asparagus" + /// Humanize species that need unique environments to survive. + var/humanize_plasmamen = TRUE + +/obj/structure/outfit_wardrobe/attack_hand(mob/living/user, list/modifiers) + . = ..() + if(.) + return + + var/mob/living/carbon/human/human_user = user + if(!ishuman(human_user)) + return + + if(one_use && HAS_TRAIT_FROM(human_user, TRAIT_WARDROBE_USED, wardrobe_id)) + to_chat(human_user, span_notice("You already picked an outfit!")) + return + + var/list/display_classes = list() + var/datum/outfit/chosen_class + + for(var/datum/outfit/dressup as anything in selectable_outfits_to_amount) + if(selectable_outfits_to_amount[dressup] == 0) + continue + var/datum/radial_menu_choice/option = new(src) + // We take the type of either the suit storage or head item (Likely to be relevant) or barring that the uniform + var/obj/item/sprite_path = initial(dressup.suit_store) || initial(dressup.head) || initial(dressup.uniform) + if(!sprite_path) + stack_trace("[dressup] outfit got no god damn items to use for sprite") + sprite_path = /obj/item/food/grown/citrus/orange_3d + option.image = image(icon = initial(sprite_path.icon), icon_state = initial(sprite_path.icon_state)) + option.info = span_boldnotice("[initial(dressup.name)]") // no desc.. + display_classes[dressup] = option + + if(!length(display_classes)) + to_chat(human_user, span_warning("There are no available outfits!")) + return + + sort_list(display_classes) + var/choice = show_radial_menu(human_user, src, display_classes, radius = 38, require_near = TRUE) + if(!choice) + return + + chosen_class = choice + + human_user.balloon_alert(human_user, LOWER_TEXT(chosen_class.name)) + playsound(human_user, 'sound/items/zip/un_zip.ogg', 33) + playsound(src, 'sound/machines/closet/wooden_closet_open.ogg', 25) + icon_state = "fullcabinet_open" + if(!do_after(human_user, 3 SECONDS) || selectable_outfits_to_amount[choice] == 0) + playsound(src, 'sound/machines/closet/wooden_closet_close.ogg', 50) + icon_state = base_icon_state + return + selectable_outfits_to_amount[choice]-- + playsound(src, 'sound/machines/closet/wooden_closet_close.ogg', 50) + icon_state = base_icon_state + playsound(human_user, 'sound/items/zip/zip_up.ogg', 33) + + human_user.drop_everything() + human_user.equipOutfit(chosen_class) + ADD_TRAIT(human_user, TRAIT_WARDROBE_USED, wardrobe_id) + +#undef TRAIT_WARDROBE_USED diff --git a/code/modules/bitrunning/server/_parent.dm b/code/modules/bitrunning/server/_parent.dm index c299192147f5..3e5735c68e1a 100644 --- a/code/modules/bitrunning/server/_parent.dm +++ b/code/modules/bitrunning/server/_parent.dm @@ -52,6 +52,8 @@ var/broadcasting = FALSE /// Cooldown between being able to toggle broadcasting COOLDOWN_DECLARE(broadcast_toggle_cd) + /// Cooldown for how often you're allowed to harass deadchat for PVP domains + COOLDOWN_DECLARE(polling_cooldown) /obj/machinery/quantum_server/post_machine_initialize() . = ..() diff --git a/code/modules/bitrunning/server/map_handling.dm b/code/modules/bitrunning/server/map_handling.dm index d72316f02883..b6abd442968a 100644 --- a/code/modules/bitrunning/server/map_handling.dm +++ b/code/modules/bitrunning/server/map_handling.dm @@ -1,3 +1,5 @@ +#define POLLING_COOLDOWN_TIME 2 MINUTES + /// Gives all current occupants a notification that the server is going down /obj/machinery/quantum_server/proc/begin_shutdown(mob/user) if(isnull(generated_domain)) @@ -82,12 +84,40 @@ for(var/datum/lazy_template/virtual_domain/available in SSbitrunning.all_domains) if(map_key == available.key && points >= available.cost) generated_domain = available - RegisterSignal(generated_domain, COMSIG_LAZY_TEMPLATE_LOADED, PROC_REF(on_template_loaded)) - generated_domain.lazy_load() - return TRUE + break + + if(!generated_domain) + return FALSE + + if(generated_domain.mission_min_candidates && (!COOLDOWN_FINISHED(src, polling_cooldown))) + say("Advanced NPC algorithms resetting, please wait [DisplayTimeText(polling_cooldown)] or load a different domain.") + playsound(src, "sound/machines/buzz-[pick("sigh", "two")].ogg", 50, TRUE) + return FALSE - return FALSE + var/list/mob/lucky_ghosts + if(generated_domain.mission_min_candidates) + playsound(src, 'sound/machines/chime.ogg', 50, TRUE) + say("Loading advanced NPCs...") + var/list/mob/candidates = SSpolling.poll_ghost_candidates("Do you want to play as a virtual [generated_domain.spawner_role] in a bitrunner domain?", ROLE_GHOST_ROLE, ROLE_GHOST_ROLE, 15 SECONDS, POLL_IGNORE_SHUTTLE_DENIZENS, TRUE) + for(var/amount in 1 to generated_domain.mission_max_candidates) + if(length(candidates)) // If no candidates, fails in code below anyways + LAZYADD(lucky_ghosts, pick_n_take(candidates)) + + if(length(lucky_ghosts) < generated_domain.mission_min_candidates) + notify_ghosts("Not enough candidates for [generated_domain.spawner_role]! Aborting mission!") + playsound(src, "sound/machines/buzz-[pick("sigh", "two")].ogg", 50, TRUE) + say("Error! Unable to load advanced NPCs. Please try again or select different domain.") + COOLDOWN_START(src, polling_cooldown, POLLING_COOLDOWN_TIME) + return FALSE + + playsound(src, 'sound/machines/ping.ogg', 50, TRUE) + say("Success!") + + generated_domain.load_advanced_npcs(lucky_ghosts) + RegisterSignal(generated_domain, COMSIG_LAZY_TEMPLATE_LOADED, PROC_REF(on_template_loaded)) + generated_domain.lazy_load() + return TRUE /// Loads in necessary map items like hololadder spawns, caches, etc /obj/machinery/quantum_server/proc/load_map_items() @@ -114,6 +144,10 @@ qdel(thing) continue + if(istype(thing, /obj/effect/mob_spawn)) + generated_domain.ghost_spawners += thing + continue + if(istype(thing, /obj/effect/landmark/bitrunning/curiosity_spawn)) curiosity_turfs += get_turf(thing) qdel(thing) @@ -132,12 +166,10 @@ new /obj/structure/hololadder(tile) - if(!length(exit_turfs)) CRASH("Failed to find exit turfs on generated domain.") if(!length(goal_turfs)) CRASH("Failed to find send turfs on generated domain.") - if(!attempt_spawn_cache(cache_turfs)) return FALSE @@ -197,3 +229,5 @@ generated_domain = null mutation_candidate_refs.Cut() spawned_threat_refs.Cut() + +#undef POLLING_COOLDOWN_TIME diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm index f7d8e553f847..c79c68eedb84 100644 --- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -67,10 +67,47 @@ /// Forces all mob modules to only load once var/modular_unique_mobs = FALSE + /** + * Spawning + */ + + /// Looks for random landmarks to spawn on. + var/list/custom_spawns = list() + /// Set TRUE if you want reusable custom spawners + var/keep_custom_spawns = FALSE + /// The domain must have this many ghost candidates willing to join as entities, or else it will not load. + var/mission_min_candidates = 0 + /// Maximum amount possible of above. + var/mission_max_candidates = 0 + /// Ghosts that will be spawned as, presumably, an antagonist in the map. + var/list/chosen_ghosts + /// List of spawners used for candidates. + var/list/obj/effect/mob_spawn/ghost_role/ghost_spawners + /// Current domain mobs being held by ghosts + var/list/mob/living/ghost_mobs + /// The role that ghosts will get. Only used for poll text. + var/spawner_role = "Antagonist" + +/datum/lazy_template/virtual_domain/Destroy(force) + QDEL_NULL(ghost_spawners) + QDEL_NULL(ghost_mobs) + . = ..() + /// Sends a point to any loot signals on the map -/datum/lazy_template/virtual_domain/proc/add_points(points_to_add) +/datum/lazy_template/virtual_domain/proc/add_points(points_to_add = 1) SEND_SIGNAL(src, COMSIG_BITRUNNER_GOAL_POINT, points_to_add) +/// Loads the ghost candidates. +/datum/lazy_template/virtual_domain/proc/load_advanced_npcs(list/mob/lucky_ghosts) + for(var/mob/lucky_ghost as anything in lucky_ghosts) + var/obj/effect/mob_spawn/ghost_role/ghost_spawner = pick(ghost_spawners) + LAZYREMOVE(ghost_spawners, ghost_spawner) + + var/mob/new_mob = ghost_spawner.create(lucky_ghost, lucky_ghost.real_name) + LAZYADD(ghost_mobs, new_mob) + + var/ghostname = lucky_ghost.name + notify_ghosts("[ghostname] has been selected to be a [ghost_spawner.prompt_name]!", source = new_mob, header = "001010110") /// Overridable proc to be called after the map is loaded. /datum/lazy_template/virtual_domain/proc/setup_domain(list/created_atoms) diff --git a/icons/obj/storage/closet.dmi b/icons/obj/storage/closet.dmi index ec01f4b9a347e968bae2824f35a1fd2981fbea05..9392e4ccb4f10a67ef9dd495ee2032997f3f2d09 100644 GIT binary patch literal 48778 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 I!g_xH1E#~>!T74{oP!#FC3sR&???t66L3$6p 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 QL=6Bu(9l&cyK58iUz~%wwEzGB diff --git a/tgstation.dme b/tgstation.dme index 0b57e196662f..96bc8813533b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2729,6 +2729,7 @@ #include "code\game\objects\structures\mystery_box.dm" #include "code\game\objects\structures\noticeboard.dm" #include "code\game\objects\structures\ore_containers.dm" +#include "code\game\objects\structures\outfit_wardrobe.dm" #include "code\game\objects\structures\petrified_statue.dm" #include "code\game\objects\structures\pinatas.dm" #include "code\game\objects\structures\plasticflaps.dm" From 6fd639a0dc3c3f7e806ef606f22347eacaa144fc Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Tue, 14 Oct 2025 16:34:50 -0400 Subject: [PATCH 10/45] Adds the 'mob_possessor' argument to all the instances of special() (#93410) a spooky special possession pr for october appears~~ (This is just some code cleanup) Before, some procs had the `mob_possessor` arg, and some didn't. Which made it a nightmare to add more positional args to it down the line. This PR just ensures that all positional args are present in all the instances of the proc. Makes this proc chain slightly less of a mess to deal with. N/A --- .../bitrunning/virtual_domain/domains/wendigo.dm | 4 ++-- .../mapfluff/ruins/icemoonruin_code/mining_site.dm | 2 +- code/modules/mapfluff/ruins/spaceruin_code/meateor.dm | 2 +- code/modules/mob_spawn/corpses/job_corpses.dm | 6 +++--- code/modules/mob_spawn/corpses/mining_corpses.dm | 10 +++++----- code/modules/mob_spawn/corpses/nanotrasen_corpses.dm | 2 +- code/modules/mob_spawn/corpses/nonhuman_corpses.dm | 4 ++-- code/modules/mob_spawn/ghost_roles/away_roles.dm | 4 ++-- .../mob_spawn/ghost_roles/fugitive_hunter_roles.dm | 2 +- code/modules/mob_spawn/ghost_roles/mining_roles.dm | 4 ++-- code/modules/mob_spawn/ghost_roles/unused_roles.dm | 8 ++++---- code/modules/mob_spawn/mob_spawn.dm | 4 ++-- 12 files changed, 26 insertions(+), 26 deletions(-) diff --git a/code/modules/bitrunning/virtual_domain/domains/wendigo.dm b/code/modules/bitrunning/virtual_domain/domains/wendigo.dm index 6db7c3337b6b..469234f08197 100644 --- a/code/modules/bitrunning/virtual_domain/domains/wendigo.dm +++ b/code/modules/bitrunning/virtual_domain/domains/wendigo.dm @@ -8,10 +8,10 @@ map_name = "wendigo" reward_points = BITRUNNER_REWARD_HIGH -/obj/effect/mob_spawn/corpse/human/bitrunner/special(mob/living/spawned_mob) +/obj/effect/mob_spawn/corpse/human/bitrunner/special(mob/living/spawned_mob, mob/mob_possessor) . = ..() spawned_mob.apply_status_effect(/datum/status_effect/gutted) -/obj/effect/mob_spawn/corpse/human/cyber_police/special(mob/living/spawned_mob) +/obj/effect/mob_spawn/corpse/human/cyber_police/special(mob/living/spawned_mob, mob/mob_possessor) . = ..() spawned_mob.apply_status_effect(/datum/status_effect/gutted) diff --git a/code/modules/mapfluff/ruins/icemoonruin_code/mining_site.dm b/code/modules/mapfluff/ruins/icemoonruin_code/mining_site.dm index 8a37031b8184..7c1e4f7ddf2a 100644 --- a/code/modules/mapfluff/ruins/icemoonruin_code/mining_site.dm +++ b/code/modules/mapfluff/ruins/icemoonruin_code/mining_site.dm @@ -27,7 +27,7 @@ icon_state = "corpseminer" // Gives the minesite corpses the gutted effect so that the boss ignores them -/obj/effect/mob_spawn/corpse/human/minesite/special(mob/living/spawned_mob) +/obj/effect/mob_spawn/corpse/human/minesite/special(mob/living/spawned_mob, mob/mob_possessor) . = ..() spawned_mob.apply_status_effect(/datum/status_effect/gutted) diff --git a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm index 15e644cd1392..f127f4467c9c 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm @@ -11,7 +11,7 @@ /// Tiger cultist corpse but with an exit wound /obj/effect/mob_spawn/corpse/human/tigercultist/perforated -/obj/effect/mob_spawn/corpse/human/tigercultist/perforated/special(mob/living/carbon/human/spawned_human) +/obj/effect/mob_spawn/corpse/human/tigercultist/perforated/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) . = ..() var/obj/item/bodypart/chest/their_chest = spawned_human.get_bodypart(BODY_ZONE_CHEST) diff --git a/code/modules/mob_spawn/corpses/job_corpses.dm b/code/modules/mob_spawn/corpses/job_corpses.dm index 1b950e0f6137..b0014424037b 100644 --- a/code/modules/mob_spawn/corpses/job_corpses.dm +++ b/code/modules/mob_spawn/corpses/job_corpses.dm @@ -59,15 +59,15 @@ outfit = /datum/outfit/job/assistant icon_state = "corpsegreytider" -/obj/effect/mob_spawn/corpse/human/assistant/beesease_infection/special(mob/living/spawned_mob) +/obj/effect/mob_spawn/corpse/human/assistant/beesease_infection/special(mob/living/spawned_mob, mob/mob_possessor) . = ..() spawned_mob.ForceContractDisease(new /datum/disease/beesease) -/obj/effect/mob_spawn/corpse/human/assistant/brainrot_infection/special(mob/living/spawned_mob) +/obj/effect/mob_spawn/corpse/human/assistant/brainrot_infection/special(mob/living/spawned_mob, mob/mob_possessor) . = ..() spawned_mob.ForceContractDisease(new /datum/disease/brainrot) -/obj/effect/mob_spawn/corpse/human/assistant/spanishflu_infection/special(mob/living/spawned_mob) +/obj/effect/mob_spawn/corpse/human/assistant/spanishflu_infection/special(mob/living/spawned_mob, mob/mob_possessor) . = ..() spawned_mob.ForceContractDisease(new /datum/disease/fluspanish) diff --git a/code/modules/mob_spawn/corpses/mining_corpses.dm b/code/modules/mob_spawn/corpses/mining_corpses.dm index 98070267810a..add04088198d 100644 --- a/code/modules/mob_spawn/corpses/mining_corpses.dm +++ b/code/modules/mob_spawn/corpses/mining_corpses.dm @@ -8,7 +8,7 @@ burn_damage = 1000 mob_species = /datum/species/skeleton -/obj/effect/mob_spawn/corpse/human/charredskeleton/special(mob/living/carbon/human/spawned_human) +/obj/effect/mob_spawn/corpse/human/charredskeleton/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) . = ..() spawned_human.color = "#454545" spawned_human.gender = NEUTER @@ -24,7 +24,7 @@ outfit = select_outfit() return ..() -/obj/effect/mob_spawn/corpse/human/legioninfested/special(mob/living/carbon/human/spawned_human) +/obj/effect/mob_spawn/corpse/human/legioninfested/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) . = ..() var/obj/item/organ/legion_tumour/cancer = new() cancer.Insert(spawned_human, special = TRUE, movement_flags = DELETE_IF_REPLACED) @@ -65,7 +65,7 @@ /// Corpse spawner used by dwarf legions to make small corpses /obj/effect/mob_spawn/corpse/human/legioninfested/dwarf -/obj/effect/mob_spawn/corpse/human/legioninfested/dwarf/special(mob/living/carbon/human/spawned_human) +/obj/effect/mob_spawn/corpse/human/legioninfested/dwarf/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) . = ..() spawned_human.dna.add_mutation(/datum/mutation/human/dwarfism) @@ -113,7 +113,7 @@ /obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/select_outfit() return null -/obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/special(mob/living/carbon/human/spawned_human) +/obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) . = ..() spawned_human.gender = NEUTER @@ -124,7 +124,7 @@ brute_damage = 0 burn_damage = 1000 -/obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/charred/special(mob/living/carbon/human/spawned_human) +/obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/charred/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) . = ..() spawned_human.color = "#454545" diff --git a/code/modules/mob_spawn/corpses/nanotrasen_corpses.dm b/code/modules/mob_spawn/corpses/nanotrasen_corpses.dm index 0ed30e5a5215..c546173f5565 100644 --- a/code/modules/mob_spawn/corpses/nanotrasen_corpses.dm +++ b/code/modules/mob_spawn/corpses/nanotrasen_corpses.dm @@ -54,7 +54,7 @@ outfit = /datum/outfit/centcom/centcom_intern/unarmed mob_name = "Nameless Intern" -/obj/effect/mob_spawn/corpse/human/intern/special(mob/living/carbon/human/spawned_human) +/obj/effect/mob_spawn/corpse/human/intern/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) . = ..() spawned_human.gender = MALE //we're making it canon babies spawned_human.update_body() diff --git a/code/modules/mob_spawn/corpses/nonhuman_corpses.dm b/code/modules/mob_spawn/corpses/nonhuman_corpses.dm index 3a63a246035e..e33679017084 100644 --- a/code/modules/mob_spawn/corpses/nonhuman_corpses.dm +++ b/code/modules/mob_spawn/corpses/nonhuman_corpses.dm @@ -11,7 +11,7 @@ return . = ..() -/obj/effect/mob_spawn/corpse/ai/special(mob/living/silicon/ai/spawned/dead_ai) +/obj/effect/mob_spawn/corpse/ai/special(mob/living/silicon/ai/spawned/dead_ai, mob/mob_possessor) . = ..() dead_ai.name = src.name dead_ai.real_name = src.name @@ -24,7 +24,7 @@ ///the color of the slime you're spawning. var/slime_species = /datum/slime_type/grey -/obj/effect/mob_spawn/corpse/slime/special(mob/living/simple_animal/slime/spawned_slime) +/obj/effect/mob_spawn/corpse/slime/special(mob/living/simple_animal/slime/spawned_slime, mob/mob_possessor) . = ..() spawned_slime.set_slime_type(slime_species) diff --git a/code/modules/mob_spawn/ghost_roles/away_roles.dm b/code/modules/mob_spawn/ghost_roles/away_roles.dm index 4891cf1e0089..386e30ea88dc 100644 --- a/code/modules/mob_spawn/ghost_roles/away_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/away_roles.dm @@ -14,7 +14,7 @@ flavour_text = "Walk this mortal plane and terrorize all living adventurers who dare cross your path." spawner_job_path = /datum/job/skeleton -/obj/effect/mob_spawn/ghost_role/human/skeleton/special(mob/living/new_spawn) +/obj/effect/mob_spawn/ghost_role/human/skeleton/special(mob/living/new_spawn, mob/mob_possessor) . = ..() to_chat(new_spawn, "You have this horrible lurching feeling deep down that your binding to this world will fail if you abandon this zone... Were you reanimated to protect something?") new_spawn.AddComponent(/datum/component/stationstuck, PUNISHMENT_MURDER, "You experience a feeling like a stressed twine being pulled until it snaps. Then, merciful nothing.") @@ -30,7 +30,7 @@ you_are_text = "By unknown powers, your rotting remains have been resurrected!" flavour_text = "Walk this mortal plane and terrorize all living adventurers who dare cross your path." -/obj/effect/mob_spawn/ghost_role/human/zombie/special(mob/living/new_spawn) +/obj/effect/mob_spawn/ghost_role/human/zombie/special(mob/living/new_spawn, mob/mob_possessor) . = ..() to_chat(new_spawn, "You have this horrible lurching feeling deep down that your binding to this world will fail if you abandon this zone... Were you reanimated to protect something?") new_spawn.AddComponent(/datum/component/stationstuck, PUNISHMENT_MURDER, "You experience a feeling like a stressed twine being pulled until it snaps. Then, merciful nothing.") diff --git a/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm b/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm index 5b9e043349ea..4e32c9359349 100644 --- a/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm @@ -7,7 +7,7 @@ show_flavor = FALSE var/back_story = "error" -/obj/effect/mob_spawn/ghost_role/human/fugitive/special(mob/living/carbon/human/spawned_human) +/obj/effect/mob_spawn/ghost_role/human/fugitive/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) . = ..() var/datum/antagonist/fugitive_hunter/fughunter = new fughunter.backstory = back_story diff --git a/code/modules/mob_spawn/ghost_roles/mining_roles.dm b/code/modules/mob_spawn/ghost_roles/mining_roles.dm index 53fa00109703..cf8b1474eae0 100644 --- a/code/modules/mob_spawn/ghost_roles/mining_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/mining_roles.dm @@ -233,7 +233,7 @@ to_chat(user, span_warning("You have exhausted your usefulness to the Necropolis.")) return FALSE -/obj/effect/mob_spawn/ghost_role/human/ash_walker/special(mob/living/carbon/human/spawned_human) +/obj/effect/mob_spawn/ghost_role/human/ash_walker/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) . = ..() spawned_human.fully_replace_character_name(null, spawned_human.generate_random_mob_name(TRUE)) to_chat(spawned_human, "Drag the corpses of men and beasts to your nest. It will absorb them to create more of your kind. Invade the strange structure of the outsiders if you must. Do not cause unnecessary destruction, as littering the wastes with ugly wreckage is certain to not gain you favor. Glory to the Necropolis!") @@ -284,7 +284,7 @@ outfit = /datum/outfit/lavaland_syndicate spawner_job_path = /datum/job/lavaland_syndicate -/obj/effect/mob_spawn/ghost_role/human/lavaland_syndicate/special(mob/living/new_spawn) +/obj/effect/mob_spawn/ghost_role/human/lavaland_syndicate/special(mob/living/new_spawn, mob/mob_possessor) . = ..() new_spawn.grant_language(/datum/language/codespeak, source = LANGUAGE_MIND) diff --git a/code/modules/mob_spawn/ghost_roles/unused_roles.dm b/code/modules/mob_spawn/ghost_roles/unused_roles.dm index 4ca259fa47c3..3fc87f4c572c 100644 --- a/code/modules/mob_spawn/ghost_roles/unused_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/unused_roles.dm @@ -24,7 +24,7 @@ new /obj/structure/fluff/empty_sleeper/syndicate(get_turf(src)) return ..() -/obj/effect/mob_spawn/ghost_role/human/prisoner_transport/special(mob/living/carbon/human/spawned_human) +/obj/effect/mob_spawn/ghost_role/human/prisoner_transport/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) . = ..() spawned_human.fully_replace_character_name(null, "NTP #LL-0[rand(111,999)]") //Nanotrasen Prisoner #Lavaland-(numbers) @@ -153,7 +153,7 @@ new/obj/structure/fluff/empty_sleeper(get_turf(src)) return ..() -/obj/effect/mob_spawn/ghost_role/human/exile/special(mob/living/new_spawn) +/obj/effect/mob_spawn/ghost_role/human/exile/special(mob/living/new_spawn, mob/mob_possessor) . = ..() new_spawn.fully_replace_character_name(null,"Wish Granter's Victim ([rand(1,999)])") var/wish = rand(1,4) @@ -227,7 +227,7 @@ icon = 'icons/obj/machines/sleeper.dmi' icon_state = "sleeper" -/obj/effect/mob_spawn/cow/special(mob/living/spawned_mob) +/obj/effect/mob_spawn/cow/special(mob/living/spawned_mob, mob/mob_possessor) . = ..() gender = FEMALE @@ -270,7 +270,7 @@ outfit = /datum/outfit/syndicatespace/syndicrew spawner_job_path = /datum/job/syndicate_cybersun -/obj/effect/mob_spawn/ghost_role/human/syndicatespace/special(mob/living/new_spawn) +/obj/effect/mob_spawn/ghost_role/human/syndicatespace/special(mob/living/new_spawn, mob/mob_possessor) . = ..() new_spawn.grant_language(/datum/language/codespeak, source = LANGUAGE_MIND) var/datum/job/spawn_job = SSjob.get_job_type(spawner_job_path) diff --git a/code/modules/mob_spawn/mob_spawn.dm b/code/modules/mob_spawn/mob_spawn.dm index b94499907570..15e40bfa777e 100644 --- a/code/modules/mob_spawn/mob_spawn.dm +++ b/code/modules/mob_spawn/mob_spawn.dm @@ -53,7 +53,7 @@ spawned_mob_ref = WEAKREF(spawned_mob) return spawned_mob -/obj/effect/mob_spawn/proc/special(mob/living/spawned_mob) +/obj/effect/mob_spawn/proc/special(mob/living/spawned_mob, mob/mob_possessor) SHOULD_CALL_PARENT(TRUE) if(faction) spawned_mob.faction = faction @@ -296,7 +296,7 @@ if(mapload || (SSticker && SSticker.current_state > GAME_STATE_SETTING_UP)) INVOKE_ASYNC(src, PROC_REF(create)) -/obj/effect/mob_spawn/corpse/special(mob/living/spawned_mob) +/obj/effect/mob_spawn/corpse/special(mob/living/spawned_mob, mob/mob_possessor) . = ..() spawned_mob.death(TRUE) spawned_mob.adjustOxyLoss(oxy_damage) From 3c2d1135be7b2efe33dd8c40388a4db4bbae9442 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sat, 1 Nov 2025 07:39:08 -0500 Subject: [PATCH 11/45] Adds a rewards bonus for nohitting combat bitrunning domains (#93531) Completing a combat bitrunning domain grants 0.8 score bonus per player that escaped without being hit I figured it be a fun challenge to try to nohit these things, especially the megafauna ones. For a bonus. :cl: Melbert add: Nohitting a combat bitrunning domain rewards a higher score. /:cl: --- .../objects/gimmick_disks/_gimmick_disk.dm | 32 ++++++------------- code/modules/bitrunning/server/_parent.dm | 4 +-- code/modules/bitrunning/server/loot.dm | 8 ++--- .../bitrunning/server/obj_generation.dm | 2 +- .../virtual_domain/domains/grassland_hunt.dm | 1 + .../virtual_domain/domains/pipedream.dm | 1 - .../virtual_domain/virtual_domain.dm | 2 -- 7 files changed, 15 insertions(+), 35 deletions(-) diff --git a/code/modules/bitrunning/objects/gimmick_disks/_gimmick_disk.dm b/code/modules/bitrunning/objects/gimmick_disks/_gimmick_disk.dm index 2580d8620c76..24702d1254ad 100644 --- a/code/modules/bitrunning/objects/gimmick_disks/_gimmick_disk.dm +++ b/code/modules/bitrunning/objects/gimmick_disks/_gimmick_disk.dm @@ -13,10 +13,10 @@ 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) +/obj/item/bitrunning_disk/gimmick/load_onto_avatar(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, domain_flags) if(isnull(granted_loadout)) return BITRUNNER_GEAR_LOAD_FAILED - return granted_loadout.grant_loadout(neo, avatar, external_load_flags) + return granted_loadout.grant_loadout(neo, avatar, domain_flags) /obj/item/bitrunning_disk/gimmick/attack_self(mob/user, modifiers) . = ..() @@ -57,17 +57,17 @@ 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) +/datum/bitrunning_gimmick/proc/grant_loadout(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, domain_flags) var/return_flags = NONE - return_flags |= grant_items(neo, avatar, external_load_flags) - return_flags |= grant_abilities(neo, avatar, external_load_flags) + return_flags |= grant_items(neo, avatar, domain_flags) + return_flags |= grant_abilities(neo, avatar, domain_flags) return return_flags -/datum/bitrunning_gimmick/proc/grant_items(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, external_load_flags) +/datum/bitrunning_gimmick/proc/grant_items(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, domain_flags) if(!length(granted_items)) return NONE - if(external_load_flags & DOMAIN_FORBIDS_ITEMS) + if(domain_flags & DOMAIN_FORBIDS_ITEMS) return BITRUNNER_GEAR_LOAD_BLOCKED var/obj/item/container_item = new container_item_type() @@ -81,11 +81,11 @@ return NONE -/datum/bitrunning_gimmick/proc/grant_abilities(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, external_load_flags) +/datum/bitrunning_gimmick/proc/grant_abilities(mob/living/carbon/human/neo, mob/living/carbon/human/avatar, domain_flags) if(!length(granted_actions)) return NONE - if(external_load_flags & DOMAIN_FORBIDS_ABILITIES) + if(domain_flags & DOMAIN_FORBIDS_ABILITIES) return BITRUNNER_GEAR_LOAD_BLOCKED var/return_flags = NONE @@ -99,17 +99,3 @@ 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 diff --git a/code/modules/bitrunning/server/_parent.dm b/code/modules/bitrunning/server/_parent.dm index 3e5735c68e1a..620a0b8339ee 100644 --- a/code/modules/bitrunning/server/_parent.dm +++ b/code/modules/bitrunning/server/_parent.dm @@ -27,11 +27,9 @@ /// Any ghosts that have spawned in var/list/datum/weakref/spawned_threat_refs = list() /// Scales loot with extra players - var/multiplayer_bonus = 1.2 + var/multiplayer_bonus = 1.1 /// Extra bonus for every player that nohits the run var/nohit_bonus = 0.8 - ///The radio the console can speak into - var/obj/item/radio/radio /// The amount of points in the system, used to purchase maps var/points = 0 /// Keeps track of the number of times someone has built a hololadder diff --git a/code/modules/bitrunning/server/loot.dm b/code/modules/bitrunning/server/loot.dm index db99973c21d7..fde484407e71 100644 --- a/code/modules/bitrunning/server/loot.dm +++ b/code/modules/bitrunning/server/loot.dm @@ -19,7 +19,7 @@ /obj/machinery/quantum_server/proc/get_multiplayer_bonus() var/total = 0 var/multiplayer = FALSE - for(var/datum/weakref/connection_ref in avatar_connection_refs) + for(var/datum/weakref/connection_ref as anything in avatar_connection_refs) var/datum/component/avatar_connection/connection = connection_ref.resolve() if(isnull(connection)) continue @@ -34,11 +34,9 @@ return 0 var/total = 0 - for(var/datum/weakref/connection_ref in avatar_connection_refs) + for(var/datum/weakref/connection_ref as anything in avatar_connection_refs) var/datum/component/avatar_connection/connection = connection_ref.resolve() - if(isnull(connection)) - continue - if(connection.nohit) + if(connection?.nohit) total += nohit_bonus return total diff --git a/code/modules/bitrunning/server/obj_generation.dm b/code/modules/bitrunning/server/obj_generation.dm index 8f767de76732..0e88ff2c5a16 100644 --- a/code/modules/bitrunning/server/obj_generation.dm +++ b/code/modules/bitrunning/server/obj_generation.dm @@ -135,7 +135,7 @@ /// Scans over neo's contents for bitrunning tech disks. Loads the items or abilities onto the avatar. /obj/machinery/quantum_server/proc/stock_gear(mob/living/carbon/human/avatar, mob/living/carbon/human/neo, datum/lazy_template/virtual_domain/generated_domain) - var/domain_forbids_flags = generated_domain.external_load_flags + var/domain_forbids_flags = generated_domain.domain_flags var/import_ban = list() var/disk_ban = list() diff --git a/code/modules/bitrunning/virtual_domain/domains/grassland_hunt.dm b/code/modules/bitrunning/virtual_domain/domains/grassland_hunt.dm index b8840d6b952e..005587e9b7ef 100644 --- a/code/modules/bitrunning/virtual_domain/domains/grassland_hunt.dm +++ b/code/modules/bitrunning/virtual_domain/domains/grassland_hunt.dm @@ -8,6 +8,7 @@ mob_modules = list(/datum/modular_mob_segment/deer) domain_flags = DOMAIN_NO_NOHIT_BONUS + /datum/lazy_template/virtual_domain/grasslands_hunt/setup_domain(list/created_atoms) for(var/obj/effect/landmark/bitrunning/mob_segment/landmark in created_atoms) RegisterSignal(landmark, COMSIG_BITRUNNING_MOB_SEGMENT_SPAWNED, PROC_REF(on_spawned)) diff --git a/code/modules/bitrunning/virtual_domain/domains/pipedream.dm b/code/modules/bitrunning/virtual_domain/domains/pipedream.dm index 595600ce71c4..f32c984248a3 100644 --- a/code/modules/bitrunning/virtual_domain/domains/pipedream.dm +++ b/code/modules/bitrunning/virtual_domain/domains/pipedream.dm @@ -102,4 +102,3 @@ name = "Factory Quartermaster" outfit = /datum/outfit/factory/qm icon_state = "corpsecargotech" - diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm index c79c68eedb84..9e2e6c779d58 100644 --- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -37,8 +37,6 @@ * Player customization */ - /// Any restrictions this domain has on what external sources can load in - var/external_load_flags = NONE /// Any outfit that you wish to force on avatars. Overrides preferences var/datum/outfit/forced_outfit From 9d5f02a937e78cfa29c8e312f238915814cf9f4f Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Fri, 7 Nov 2025 19:20:41 -0600 Subject: [PATCH 12/45] Adds an easy bitrunning domain themed around sacrifing people, also makes digitital beings not care about death (#93546) 1. Adds `Heretical Hunt`, an easy difficulty domain that tasks you with killing and sacrificing simulated crew members. The domain has a few ways to conquer it, as you don't need to kill all the simulated crew members and there's some hidden loot to make use of It's an easy domain because - You have quite decent armor, - The mobs are pretty unintelligent, - You can always fall back and heal passively on the rust tiles - There are many ways to tackle it Though it's not a complete pushover, the mobs can still overwhelm you if you're not careful. image 2. Virtual beings are given a minor positive moodlet for killing people instead of a negative one 1. I thought it'd be kinda fun if we had antag-themed domains which soft-explains the gameplay loops or mechanics of certain antags, in this case, heretics sacrificing people 2. You don't feel remorse for killing people in video games, right anon? :cl: Melbert add: Added a new easy difficulty bitrunning domain, "Heretical Hunt" add: Digital mobs receive positive moodlets for killing people rather than negative fix: Madness Mask no longer affects corpses fix: Fixed some heretic runtimes /:cl: --- _maps/virtual_domains/heretic_hunt.dmm | 140 +++++++++--------- code/datums/elements/death_drops.dm | 13 +- .../antagonists/heretic/heretic_knowledge.dm | 5 + .../heretic/items/heretic_blades.dm | 70 +++++++++ .../antagonists/heretic/items/madness_mask.dm | 3 + code/modules/bitrunning/server/_parent.dm | 2 +- code/modules/bitrunning/spawners.dm | 6 +- .../virtual_domain/domains/heretic_hunt.dm | 2 +- 8 files changed, 162 insertions(+), 79 deletions(-) diff --git a/_maps/virtual_domains/heretic_hunt.dmm b/_maps/virtual_domains/heretic_hunt.dmm index e65d42cc1f92..80ef63e4466a 100644 --- a/_maps/virtual_domains/heretic_hunt.dmm +++ b/_maps/virtual_domains/heretic_hunt.dmm @@ -77,20 +77,21 @@ }, /turf/template_noop, /area/virtual_domain/safehouse) -"eB" = ( -/obj/machinery/defibrillator_mount/directional/west, -/obj/structure/table/glass, +"eT" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/virtual_domain) +"eV" = ( +/obj/machinery/stasis{ + dir = 1 + }, /obj/effect/turf_decal/tile/blue/fourcorners, -/obj/item/stack/medical/wrap/gauze/improvised, /obj/effect/turf_decal/siding/blue{ - dir = 4 + dir = 6 }, +/obj/structure/cable, /turf/open/floor/plastic, /area/virtual_domain) -"eT" = ( -/obj/effect/baseturf_helper/virtual_domain, -/turf/closed/indestructible/binary, -/area/virtual_domain) "fd" = ( /obj/structure/frame/computer{ dir = 1; @@ -378,6 +379,18 @@ /obj/item/storage/belt/unfathomable_curio, /turf/open/floor/iron, /area/virtual_domain) +"oD" = ( +/mob/living/basic/fake_crewman/assistant, +/obj/effect/mapping_helpers/broken_floor, +/obj/item/restraints/handcuffs/cable/yellow{ + pixel_x = 3; + pixel_y = 3 + }, +/obj/item/stack/medical/gauze/improvised{ + pixel_y = -2 + }, +/turf/open/floor/plating, +/area/virtual_domain) "oL" = ( /obj/effect/turf_decal/tile/yellow, /obj/structure/cable, @@ -425,17 +438,6 @@ /obj/effect/decal/cleanable/cobweb, /turf/open/floor/plating, /area/virtual_domain) -"qS" = ( -/obj/machinery/sleeper/stasis{ - dir = 4 - }, -/obj/effect/turf_decal/tile/blue/fourcorners, -/obj/effect/turf_decal/siding/blue{ - dir = 5 - }, -/obj/structure/cable, -/turf/open/floor/plastic, -/area/virtual_domain) "rq" = ( /obj/effect/turf_decal/tile/yellow/fourcorners, /obj/machinery/suit_storage_unit, @@ -567,6 +569,16 @@ /obj/structure/cable, /turf/open/floor/plating, /area/virtual_domain) +"vO" = ( +/obj/machinery/defibrillator_mount/directional/west, +/obj/structure/table/glass, +/obj/effect/turf_decal/tile/blue/fourcorners, +/obj/item/stack/medical/gauze/improvised, +/obj/effect/turf_decal/siding/blue{ + dir = 4 + }, +/turf/open/floor/plastic, +/area/virtual_domain) "vP" = ( /obj/effect/turf_decal/tile/yellow{ dir = 1 @@ -602,6 +614,17 @@ }, /turf/open/floor/plating/heretic_rust, /area/virtual_domain) +"xa" = ( +/obj/machinery/stasis{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue/fourcorners, +/obj/effect/turf_decal/siding/blue{ + dir = 5 + }, +/obj/structure/cable, +/turf/open/floor/plastic, +/area/virtual_domain) "xg" = ( /obj/effect/turf_decal/tile/red{ dir = 8 @@ -839,16 +862,6 @@ /obj/structure/extinguisher_cabinet/directional/south, /turf/open/floor/iron, /area/virtual_domain) -"GI" = ( -/obj/structure/table/glass, -/obj/effect/turf_decal/tile/blue/fourcorners, -/obj/effect/turf_decal/siding/blue/end{ - dir = 1 - }, -/obj/item/stack/medical/wrap/gauze/improvised, -/obj/structure/cable, -/turf/open/floor/plastic, -/area/virtual_domain) "GO" = ( /obj/structure/frame/computer{ pixel_y = 0; @@ -1096,6 +1109,17 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /turf/open/floor/plating/heretic_rust, /area/virtual_domain) +"NN" = ( +/obj/effect/turf_decal/tile/blue/fourcorners, +/obj/structure/table/glass, +/obj/item/stack/medical/gauze{ + pixel_y = 4 + }, +/obj/effect/turf_decal/siding/blue{ + dir = 8 + }, +/turf/open/floor/plastic, +/area/virtual_domain) "NO" = ( /mob/living/basic/fake_crewman/md, /turf/open/floor/plastic, @@ -1122,6 +1146,16 @@ "Pr" = ( /turf/closed/indestructible/binary, /area/virtual_domain) +"PB" = ( +/obj/structure/table/glass, +/obj/effect/turf_decal/tile/blue/fourcorners, +/obj/effect/turf_decal/siding/blue/end{ + dir = 1 + }, +/obj/item/stack/medical/gauze/improvised, +/obj/structure/cable, +/turf/open/floor/plastic, +/area/virtual_domain) "PJ" = ( /obj/effect/decal/cleanable/dirt/dust, /obj/effect/decal/cleanable/blood/old, @@ -1194,18 +1228,6 @@ /obj/effect/spawner/random/trash/garbage, /turf/open/floor/plating, /area/virtual_domain) -"RP" = ( -/mob/living/basic/fake_crewman/assistant, -/obj/effect/mapping_helpers/broken_floor, -/obj/item/restraints/handcuffs/cable/yellow{ - pixel_x = 3; - pixel_y = 3 - }, -/obj/item/stack/medical/wrap/gauze/improvised{ - pixel_y = -2 - }, -/turf/open/floor/plating, -/area/virtual_domain) "RQ" = ( /obj/effect/turf_decal/tile/blue, /obj/structure/cable, @@ -1308,17 +1330,6 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /turf/open/floor/plating/heretic_rust, /area/virtual_domain) -"VU" = ( -/obj/machinery/sleeper/stasis{ - dir = 4 - }, -/obj/effect/turf_decal/tile/blue/fourcorners, -/obj/effect/turf_decal/siding/blue{ - dir = 6 - }, -/obj/structure/cable, -/turf/open/floor/plastic, -/area/virtual_domain) "VW" = ( /obj/effect/turf_decal/tile/yellow/fourcorners, /obj/structure/tank_dispenser, @@ -1419,17 +1430,6 @@ }, /turf/open/floor/plastic, /area/virtual_domain) -"YC" = ( -/obj/effect/turf_decal/tile/blue/fourcorners, -/obj/structure/table/glass, -/obj/item/stack/medical/wrap/gauze{ - pixel_y = 4 - }, -/obj/effect/turf_decal/siding/blue{ - dir = 8 - }, -/turf/open/floor/plastic, -/area/virtual_domain) "YL" = ( /mob/living/basic/fake_crewman/sec, /obj/effect/turf_decal/tile/neutral/fourcorners, @@ -1825,7 +1825,7 @@ ge Vh rC rt -RP +oD Zu JG MF @@ -2062,10 +2062,10 @@ JG Zu Zu FH -VU +eV sT -qS -eB +xa +vO Zu iL oa @@ -2161,7 +2161,7 @@ dh uS uj uj -GI +PB vt JA KR @@ -2267,7 +2267,7 @@ Wo Zu VX Yy -YC +NN Zu Zt Pr diff --git a/code/datums/elements/death_drops.dm b/code/datums/elements/death_drops.dm index 70543260707b..13cb8938b8e6 100644 --- a/code/datums/elements/death_drops.dm +++ b/code/datums/elements/death_drops.dm @@ -41,15 +41,18 @@ if (SEND_SIGNAL(target, COMSIG_LIVING_DROP_LOOT, spawn_loot, gibbed) & COMPONENT_NO_LOOT_DROP) return + var/list/all_loot = list() for(var/thing_to_spawn in spawn_loot) for(var/i in 1 to (spawn_loot[thing_to_spawn] || 1)) - create_loot(thing_to_spawn, loot_loc, target, gibbed, spread_px = spawn_loot.len * 3) + all_loot += create_loot(thing_to_spawn, loot_loc, target, gibbed, spread_px = spawn_loot.len * 3) + + list_clear_nulls(all_loot) // in case of gibbed corpses + SEND_SIGNAL(target, COMSIG_LIVING_DROPPED_LOOT, all_loot, gibbed) /// Handles creating the loots /datum/element/death_drops/proc/create_loot(typepath, atom/loot_loc, mob/living/dead, gibbed, spread_px = 4) if(ispath(typepath, /obj/effect/mob_spawn/corpse)) - handle_corpse(typepath, loot_loc, dead, gibbed) - return + return handle_corpse(typepath, loot_loc, dead, gibbed) var/drop = new typepath(loot_loc) if(isitem(drop) && spread_px) @@ -57,6 +60,7 @@ var/clamped_px = clamp(spread_px, 0, 16) dropped_item.pixel_x = rand(-clamped_px, clamped_px) dropped_item.pixel_y = rand(-clamped_px, clamped_px) + return drop /// Handles snowflake case of mob corpses /datum/element/death_drops/proc/handle_corpse(typepath, atom/loot_loc, mob/living/dead, gibbed) @@ -68,7 +72,8 @@ // if gibbed, dispose of the body if(gibbed) body.gib(DROP_ALL_REMAINS) - return + return null // otherwise continue with the rest of the damage types body.setToxLoss(dead.getToxLoss()) body.setOxyLoss(dead.getOxyLoss()) + return body diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index 42b8b18bad69..4fb70bdebefe 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -238,6 +238,11 @@ return ..() /datum/heretic_knowledge/limited_amount/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) + var/datum/antagonist/heretic/our_heretic = GET_HERETIC(user) + if(our_heretic && our_heretic.unlimited_blades) + if(length(result_atoms & typesof(/obj/item/melee/sickly_blade))) + return TRUE + for(var/datum/weakref/ref as anything in created_items) var/atom/real_thing = ref.resolve() if(QDELETED(real_thing)) diff --git a/code/modules/antagonists/heretic/items/heretic_blades.dm b/code/modules/antagonists/heretic/items/heretic_blades.dm index 22212e944690..e13bda1ec861 100644 --- a/code/modules/antagonists/heretic/items/heretic_blades.dm +++ b/code/modules/antagonists/heretic/items/heretic_blades.dm @@ -39,6 +39,33 @@ SEND_SIGNAL(user, COMSIG_HERETIC_BLADE_ATTACK, target, src) /obj/item/melee/sickly_blade/attack_self(mob/user) + var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user) + if(heretic_datum?.unlimited_blades) + return + if(HAS_TRAIT(user, TRAIT_ELDRITCH_ARENA_PARTICIPANT)) + user.balloon_alert(user, "can't escape!") + if(escape_attempts > 2) + to_chat(user, span_hypnophrase(span_big("Cowardly sheep will be slaughtered!"))) + playsound(src, SFX_SHATTER, 70, TRUE) + var/obj/item/bodypart/to_remove = user.get_active_hand() + to_remove.dismember() + deltimer(escape_timer) + qdel(src) + return + escape_attempts++ + escape_timer = addtimer(CALLBACK(src, PROC_REF(reset_attempts)), 2 SECONDS, TIMER_STOPPABLE) + return + if(HAS_TRAIT(user, TRAIT_NO_TELEPORT)) + user.balloon_alert(user, "can't break!") + return + seek_safety(user) + +/obj/item/melee/sickly_blade/proc/reset_attempts() + escape_attempts = 0 + deltimer(escape_timer) + +/// Attempts to teleport the passed mob to somewhere safe on the station, if they can use the blade. +/obj/item/melee/sickly_blade/proc/seek_safety(mob/user) var/turf/safe_turf = find_safe_turf(zlevels = z, extended_safety_checks = TRUE) if(IS_HERETIC_OR_MONSTER(user)) if(do_teleport(user, safe_turf, channel = TELEPORT_CHANNEL_MAGIC)) @@ -123,6 +150,49 @@ icon_state = "dark_blade" inhand_icon_state = "dark_blade" after_use_message = "The Torn Champion hears your call..." + ///If our blade is currently infused with the mansus grasp + var/infused = FALSE + +/obj/item/melee/sickly_blade/dark/afterattack(atom/target, mob/user, list/modifiers, list/attack_modifiers) + . = ..() + if(!infused || target == user || !isliving(target)) + return + var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user) + var/mob/living/living_target = target + if(!heretic_datum) + return + + // Apply our heretic mark + var/datum/heretic_knowledge/limited_amount/starting/base_blade/mark_to_apply = heretic_datum.get_knowledge(/datum/heretic_knowledge/limited_amount/starting/base_blade) + if(!mark_to_apply) + return + mark_to_apply.create_mark(user, living_target) + infused = FALSE + update_appearance(UPDATE_ICON) + user.update_held_items() + + if(!check_behind(user, living_target)) + return + // We're officially behind them, apply effects + living_target.AdjustParalyzed(1.5 SECONDS) + living_target.apply_damage(10, BRUTE, wound_bonus = CANT_WOUND) + living_target.balloon_alert(user, "backstab!") + playsound(living_target, 'sound/items/weapons/guillotine.ogg', 100, TRUE) + +/obj/item/melee/sickly_blade/dark/dropped(mob/user, silent) + . = ..() + if(infused) + infused = FALSE + update_appearance(UPDATE_ICON) + +/obj/item/melee/sickly_blade/dark/update_icon_state() + . = ..() + if(infused) + icon_state = base_icon_state + "_infused" + inhand_icon_state = base_icon_state + "_infused" + else + icon_state = base_icon_state + inhand_icon_state = base_icon_state // Path of Cosmos's blade /obj/item/melee/sickly_blade/cosmic diff --git a/code/modules/antagonists/heretic/items/madness_mask.dm b/code/modules/antagonists/heretic/items/madness_mask.dm index f421aaefbc30..eee0cb231391 100644 --- a/code/modules/antagonists/heretic/items/madness_mask.dm +++ b/code/modules/antagonists/heretic/items/madness_mask.dm @@ -56,6 +56,9 @@ if(IS_HERETIC_OR_MONSTER(human_in_range) || human_in_range.stat > SOFT_CRIT || human_in_range.is_blind()) continue + if(human_in_range.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND)) + continue + human_in_range.mob_mood.direct_sanity_drain(rand(-2, -20) * seconds_per_tick) if(SPT_PROB(60, seconds_per_tick)) diff --git a/code/modules/bitrunning/server/_parent.dm b/code/modules/bitrunning/server/_parent.dm index 620a0b8339ee..29bfef53c0bc 100644 --- a/code/modules/bitrunning/server/_parent.dm +++ b/code/modules/bitrunning/server/_parent.dm @@ -128,7 +128,7 @@ return ..() -/obj/machinery/quantum_server/item_interaction(mob/living/user, obj/item/tool, list/modifiers, list/attack_modifiers) +/obj/machinery/quantum_server/item_interaction(mob/living/user, obj/item/tool, list/modifiers) if(!istype(tool, /obj/item/bitrunning_debug)) return NONE diff --git a/code/modules/bitrunning/spawners.dm b/code/modules/bitrunning/spawners.dm index 58e0ffb3ba51..31d4bf881c50 100644 --- a/code/modules/bitrunning/spawners.dm +++ b/code/modules/bitrunning/spawners.dm @@ -14,9 +14,9 @@ ..() - spawned_mob.mind.add_antag_datum(/datum/antagonist/domain_ghost_actor) - spawned_mob.mind.set_assigned_role(SSjob.get_job_type(/datum/job/bitrunning_glitch)) - + if(antag) + spawned_mob.mind.add_antag_datum(/datum/antagonist/domain_ghost_actor) + spawned_mob.mind.set_assigned_role(SSjob.get_job_type(/datum/job/bitrunning_glitch)) /// Simulates a ghost role spawn without calling special(), ie a bitrunner spawn instead of a ghost. /obj/effect/mob_spawn/ghost_role/human/virtual_domain/proc/artificial_spawn(mob/living/runner) diff --git a/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm b/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm index 2abd96e94c18..43225d18e38b 100644 --- a/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm +++ b/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm @@ -182,7 +182,7 @@ name = "engineer" death_spawner = /obj/effect/mob_spawn/corpse/human/engineer weapon = /obj/item/weldingtool - attack_sound = 'sound/items/welder.ogg' + attack_sound = 'sound/items/tools/welder.ogg' melee_damage_type = BURN damage_coeff = list(BRUTE = 1, BURN = 0.9, TOX = 1, STAMINA = 1, OXY = 1) From cbc0a538733bed3d195b508e6da5906bcf2144c2 Mon Sep 17 00:00:00 2001 From: Mike Long Date: Wed, 28 Jan 2026 12:40:33 -0500 Subject: [PATCH 13/45] Enhance bitrunner domain creation blackbox logging (#94913) Improves some blackbox logging for bitrunner domain creation, by also logging how much information they had when they chose it. Also some minor code improvements, by making methods for whether the name/reward of a domain is visible. Right now, if we just look at blackbox logs, we cannot easily tell if a domain is being deliberately avoided when possible, as we cannot tell if it's run unintentionally or intentionally. Now we will record that information, so we can act on it, and hopefully perform actions to improve the domains that people attempt to avoid. --- code/controllers/subsystem/bitrunning.dm | 4 ++-- code/controllers/subsystem/blackbox.dm | 1 + .../bitrunning/objects/quantum_console.dm | 4 ++-- code/modules/bitrunning/server/map_handling.dm | 16 +++++++++++++++- .../bitrunning/virtual_domain/virtual_domain.dm | 7 +++++++ 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/code/controllers/subsystem/bitrunning.dm b/code/controllers/subsystem/bitrunning.dm index 51b7f329a4a6..6f6d68a0b05c 100644 --- a/code/controllers/subsystem/bitrunning.dm +++ b/code/controllers/subsystem/bitrunning.dm @@ -21,8 +21,8 @@ SUBSYSTEM_DEF(bitrunning) for(var/datum/lazy_template/virtual_domain/domain as anything in all_domains) if(domain.domain_flags & DOMAIN_TEST_ONLY) continue - var/can_view = domain.difficulty < scanner_tier && domain.cost <= points + 5 - var/can_view_reward = domain.difficulty < (scanner_tier + 1) && domain.cost <= points + 3 + var/can_view = domain.can_view_name(scanner_tier, points) + var/can_view_reward = domain.can_view_reward(scanner_tier, points) UNTYPED_LIST_ADD(levels, list( "announce_ghosts" = domain.announce_to_ghosts, diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm index 844adc95f57b..66b9df501e02 100644 --- a/code/controllers/subsystem/blackbox.dm +++ b/code/controllers/subsystem/blackbox.dm @@ -15,6 +15,7 @@ SUBSYSTEM_DEF(blackbox) "round_end_stats" = 2, "testmerged_prs" = 2, "dynamic_threat" = 2, + "bitrunning_domain_loaded" = 2, ) //associative list of any feedback variables that have had their format changed since creation and their current version, remember to update this /datum/controller/subsystem/blackbox/Initialize() diff --git a/code/modules/bitrunning/objects/quantum_console.dm b/code/modules/bitrunning/objects/quantum_console.dm index 9a0f48ae80b8..d07a9339aa38 100644 --- a/code/modules/bitrunning/objects/quantum_console.dm +++ b/code/modules/bitrunning/objects/quantum_console.dm @@ -71,13 +71,13 @@ switch(action) if("random_domain") - server.cold_boot_map(server.get_random_domain_id()) + server.cold_boot_map(server.get_random_domain_id(), was_random_selection = TRUE) return TRUE if("refresh") ui.send_full_update() return TRUE if("set_domain") - server.cold_boot_map(params["id"]) + server.cold_boot_map(params["id"], was_random_selection = FALSE) return TRUE if("stop_domain") server.begin_shutdown(usr) diff --git a/code/modules/bitrunning/server/map_handling.dm b/code/modules/bitrunning/server/map_handling.dm index b6abd442968a..0753c8b49878 100644 --- a/code/modules/bitrunning/server/map_handling.dm +++ b/code/modules/bitrunning/server/map_handling.dm @@ -28,7 +28,7 @@ /// Links all the loading processes together - does validation for booting a map -/obj/machinery/quantum_server/proc/cold_boot_map(map_key) +/obj/machinery/quantum_server/proc/cold_boot_map(map_key, was_random_selection) if(!is_ready) return FALSE @@ -54,6 +54,20 @@ is_ready = TRUE return FALSE + + //We will want to record how the domain was selected. Either entirely randomly, with the name redacted, or with full information. + //Without this, it is difficult to determine what domains are selected more often intentionallly, vs unintentionally. + var/selection_type + + if(was_random_selection) + selection_type = "random selection" + else + if(generated_domain.can_view_name(scanner_tier, points)) + selection_type = "full information" + else + selection_type = "redacted information" + SSblackbox.record_feedback("nested tally", "bitrunning_domain_loaded", 1, list(selection_type, map_key)) + is_ready = TRUE var/spawn_chance = clamp((threat * glitch_chance), 5, threat_prob_max) diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm index 9e2e6c779d58..b65d19b03c7d 100644 --- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -86,6 +86,13 @@ /// The role that ghosts will get. Only used for poll text. var/spawner_role = "Antagonist" + +/datum/lazy_template/virtual_domain/proc/can_view_name(scanner_tier, server_points) + return difficulty < scanner_tier && cost <= server_points + 5 + +/datum/lazy_template/virtual_domain/proc/can_view_reward(scanner_tier, server_points) + return difficulty < (scanner_tier + 1) && cost <= server_points + 3 + /datum/lazy_template/virtual_domain/Destroy(force) QDEL_NULL(ghost_spawners) QDEL_NULL(ghost_mobs) From b03b9a35f3db7869256ef4c31dc862460a7e8d75 Mon Sep 17 00:00:00 2001 From: Leland Kemble <70413276+lelandkemble@users.noreply.github.com> Date: Tue, 24 Mar 2026 10:58:21 -0400 Subject: [PATCH 14/45] Vended glasses in the Beach Bar virtual domain count towards domain completion when drank (#95466) Previously, when one drank out of a glass of booze that the virtual bartender graciously offered, one would not get any closer to domain completion, because said bartender got his glasses new out of a vending machine. These glasses never were registered on domain creation, due to not existing on domain creation. This pr registers the vending machines within the domain with a new signal that allows newly created glasses to also be registered, and count towards domain completion. A bitrunner dying of liver failure due to the obtuse distinction between glasses that were in a vending machine and glasses that were on the table sounds very annoying. Also, you could very easily make the domain not completable within seconds by spawning as a ghostrole and breaking the table glasses. You can still do that, but you have to break an entire vending machine's worth of glasses too. :cl: fix: Glasses that were in the vending machine now still count for the completion of the Beach Bar virtual domain /:cl: --- .../bitrunning/virtual_domain/domains/beach_bar.dm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm b/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm index 5ae0793b6e92..12d6becc1367 100644 --- a/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm +++ b/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm @@ -13,6 +13,14 @@ for(var/obj/item/reagent_containers/cup/glass/drink in created_atoms) RegisterSignal(drink, COMSIG_GLASS_DRANK, PROC_REF(on_drink_drank)) + for(var/obj/machinery/vending/vending_machine in created_atoms) + RegisterSignal(vending_machine, COMSIG_VENDING_DISPENSED, PROC_REF(on_vended_item)) + + +/datum/lazy_template/virtual_domain/beach_bar/proc/on_vended_item(obj/machinery/vending/vending_machine, obj/item/vended_item) + if(istype(vended_item, /obj/item/reagent_containers/cup/glass)) + RegisterSignal(vended_item, COMSIG_GLASS_DRANK, PROC_REF(on_drink_drank)) + /// Eventually reveal the cache /datum/lazy_template/virtual_domain/beach_bar/proc/on_drink_drank(datum/source) From f3bad9ffff370169b26863b74079c4215c6b72e4 Mon Sep 17 00:00:00 2001 From: Leland Kemble <70413276+lelandkemble@users.noreply.github.com> Date: Fri, 29 May 2026 13:35:45 -0400 Subject: [PATCH 15/45] Bitrunning glitches take much longer to escape their simulation if there are remaining active bitrunners (#95934) For every living, connected bit avatar remaining in the simulation, the `do_after()` for leaving the simulation as a bitrunning glitch ghost role is multiplied by 5. The base timer remains at it was, two seconds. Additionally, there is an exception in place for the "Island Brawl" domain, which is the FFA with infinite respawns. It still multiplies by 5 for each active bitrunner, but that multiplier is reduced by 1 for each death, so you likely only have to kill one wave of bitrunners for normal escape time. Additionally, because of necessity for the exception and also basic good sense, bitrunning points are no longer component-based on specific turfs in the domain, and are held in the server. It never should have been a component, it had no reason to be one. This, as a consenquence, also fixes that you could completely softlock any domain using points by simply crowbarring the turf that the crate was yet to spawn on. Yesterday, I saw one of the lamest things I've ever seen in the game. A bitrunning glitch spawns in, walks literally right by two bitrunners as they ask who he is, and leaves the simulation to go on a killing spree unburdened. There was extenuating circumstances in that situation, but it still was a miserable breakdown of what I think the intended gameplay of a bitrunning glitch is, and shows how ridiculously easy it is to simply not engage with the intended hurdle in your path. The primary(and, really, only) job of a glitch is to kill bit avatars, and this PR makes it significantly more difficult to just... walk by them to go on your killing spree on station. Work, then play. :cl: balance: For every living, connected bitrunner remaining in a domain, bitrunning glitches will take five times longer to escape. fix: You can no longer softlock any virtual domain that uses points by crowbarring the reward turf before the crate spawns. /:cl: --- .../dcs/signals/signals_bitrunning.dm | 3 -- .../components/bitrunning_points.dm | 41 ------------------- .../modules/bitrunning/server/map_handling.dm | 3 +- code/modules/bitrunning/server/threats.dm | 16 ++++++++ .../virtual_domain/virtual_domain.dm | 20 ++++++++- tgstation.dme | 1 - 6 files changed, 36 insertions(+), 48 deletions(-) delete mode 100644 code/modules/bitrunning/components/bitrunning_points.dm diff --git a/code/__DEFINES/dcs/signals/signals_bitrunning.dm b/code/__DEFINES/dcs/signals/signals_bitrunning.dm index ccf47c226044..5e49840d344e 100644 --- a/code/__DEFINES/dcs/signals/signals_bitrunning.dm +++ b/code/__DEFINES/dcs/signals/signals_bitrunning.dm @@ -1,9 +1,6 @@ /// from /atom/movable/screen/alert/bitrunning/qserver_domain_complete #define COMSIG_BITRUNNER_ALERT_SEVER "bitrunner_alert_sever" -/// from /obj/effect/bitrunning/loot_signal: (points) -#define COMSIG_BITRUNNER_GOAL_POINT "bitrunner_goal_point" - // Netpods /// from /obj/machinery/netpod/sever_connection() diff --git a/code/modules/bitrunning/components/bitrunning_points.dm b/code/modules/bitrunning/components/bitrunning_points.dm deleted file mode 100644 index ea8f63f76d8d..000000000000 --- a/code/modules/bitrunning/components/bitrunning_points.dm +++ /dev/null @@ -1,41 +0,0 @@ -/// Attaches to a turf so it spawns a crate when a certain amount of points are added to it. -/datum/component/bitrunning_points - /// The amount required to spawn a crate - var/points_goal = 10 - /// A special condition limits this from spawning a crate - var/points_received = 0 - - -/datum/component/bitrunning_points/Initialize(datum/lazy_template/virtual_domain/domain) - . = ..() - if(!isturf(parent)) - return COMPONENT_INCOMPATIBLE - - RegisterSignal(domain, COMSIG_BITRUNNER_GOAL_POINT, PROC_REF(on_add_points)) - - -/// Listens for points to be added which will eventually spawn a crate. -/datum/component/bitrunning_points/proc/on_add_points(datum/source, points_to_add) - SIGNAL_HANDLER - - points_received += points_to_add - - if(points_received < points_goal) - return - - reveal() - - -/// Spawns the crate with some effects -/datum/component/bitrunning_points/proc/reveal() - playsound(src, 'sound/magic/blink.ogg', 50, TRUE) - - var/turf/tile = parent - var/obj/structure/closet/crate/secure/bitrunning/encrypted/crate = new() - crate.forceMove(tile) // Triggers any on-move effects on that turf - - var/datum/effect_system/spark_spread/quantum/sparks = new(tile) - sparks.set_up(number = 5, location = tile) - sparks.start() - - qdel(src) diff --git a/code/modules/bitrunning/server/map_handling.dm b/code/modules/bitrunning/server/map_handling.dm index 0753c8b49878..c672de619ead 100644 --- a/code/modules/bitrunning/server/map_handling.dm +++ b/code/modules/bitrunning/server/map_handling.dm @@ -168,8 +168,7 @@ continue if(istype(thing, /obj/effect/landmark/bitrunning/loot_signal)) - var/turf/signaler_turf = get_turf(thing) - signaler_turf.AddComponent(/datum/component/bitrunning_points, generated_domain) + generated_domain.main_crate_loc = thing.loc qdel(thing) continue diff --git a/code/modules/bitrunning/server/threats.dm b/code/modules/bitrunning/server/threats.dm index 025e60714a4e..f47dd34ff1a0 100644 --- a/code/modules/bitrunning/server/threats.dm +++ b/code/modules/bitrunning/server/threats.dm @@ -138,6 +138,22 @@ radio.talk_into(src, "Fabrication protocols have crashed unexpectedly. Please evacuate the area.", RADIO_CHANNEL_SUPPLY) timeout = 10 SECONDS + var/bitrunners_alive = 0 + var/island_brawl_exception = istype(generated_domain, /datum/lazy_template/virtual_domain/island_brawl) + for(var/datum/weakref/bitrunner_ref in avatar_connection_refs) + var/mob/living/bitrunner = astype(bitrunner_ref.resolve(), /datum/component/avatar_connection)?.parent + if(!bitrunner) + continue + if((bitrunner.stat > CONSCIOUS) || !bitrunner.client) + continue + if(island_brawl_exception) + timeout *= max(5 - generated_domain.main_crate_points, 1) + continue + bitrunners_alive++ + timeout *= 5 + if(bitrunners_alive) + to_chat(antag, span_warning("[bitrunners_alive] criminals still remain here, pilfering your domain. It will be more difficult to leave until they are handled.")) + if(!do_after(antag, timeout) || QDELETED(chosen_forge) || QDELETED(antag) || QDELETED(src) || !is_ready || !is_operational) chosen_forge.setup_particles() return diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm index b65d19b03c7d..fdd94c4871f7 100644 --- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -53,6 +53,12 @@ var/secondary_loot_generated /// Has this domain been beaten with high enough score to spawn a tech disk? var/disk_reward_spawned = FALSE + /// The amount of points towards the spawning of the main crate, on maps using points. + var/main_crate_points = 0 + /// The amount of points required to spawn the main crate, on maps using points. + var/main_crate_point_goal = 10 + /// The location the crate will spawn when enough points are accumulated, on maps using points. + var/main_crate_loc /** * Modularity @@ -100,7 +106,19 @@ /// Sends a point to any loot signals on the map /datum/lazy_template/virtual_domain/proc/add_points(points_to_add = 1) - SEND_SIGNAL(src, COMSIG_BITRUNNER_GOAL_POINT, points_to_add) + main_crate_points += points_to_add + if(main_crate_points >= main_crate_point_goal) + reveal() + +/datum/lazy_template/virtual_domain/proc/reveal() + if(!main_crate_loc) + return + var/turf/spawn_loc = get_turf(main_crate_loc) + playsound(spawn_loc, 'sound/effects/magic/blink.ogg', 50, TRUE) + var/obj/structure/closet/crate/secure/bitrunning/encrypted/crate = new() + crate.forceMove(spawn_loc) // Triggers any on-move effects on that turf + do_sparks(5, FALSE, spawn_loc, spark_type = /datum/effect_system/basic/spark_spread/quantum) + main_crate_loc = null /// Loads the ghost candidates. /datum/lazy_template/virtual_domain/proc/load_advanced_npcs(list/mob/lucky_ghosts) diff --git a/tgstation.dme b/tgstation.dme index 96bc8813533b..eefae6971ab0 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3534,7 +3534,6 @@ #include "code\modules\bitrunning\antagonists\netguardian.dm" #include "code\modules\bitrunning\components\avatar_connection.dm" #include "code\modules\bitrunning\components\avatar_gear.dm" -#include "code\modules\bitrunning\components\bitrunning_points.dm" #include "code\modules\bitrunning\components\glitch.dm" #include "code\modules\bitrunning\components\netpod_healing.dm" #include "code\modules\bitrunning\components\npc_friendly.dm" From a39a5b5825f071f89fe5c1f90fa5458e2f14619f Mon Sep 17 00:00:00 2001 From: Thunder12345 Date: Sat, 1 Aug 2026 01:33:32 +0100 Subject: [PATCH 16/45] Bitrunning the Gradening (#97209) Shows the best grade achieved for each domain in the quantum console. image Allows bitrunners to see what domains they've already finished without going back through a stack of paper they may have already trashed or just remembering everything. Also provides players with a sense of pride and accomplishment for scoring high on all the domains. --- code/__DEFINES/bitrunning.dm | 7 ++++ code/_globalvars/lists/bitrunning.dm | 9 ++++++ code/controllers/subsystem/bitrunning.dm | 3 ++ code/modules/bitrunning/server/loot.dm | 32 ++++++++++++++++--- .../virtual_domain/virtual_domain.dm | 10 ++++++ tgstation.dme | 1 + .../tgui/interfaces/QuantumConsole.tsx | 3 ++ 7 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 code/_globalvars/lists/bitrunning.dm diff --git a/code/__DEFINES/bitrunning.dm b/code/__DEFINES/bitrunning.dm index 900182eb43d7..379a302fac22 100644 --- a/code/__DEFINES/bitrunning.dm +++ b/code/__DEFINES/bitrunning.dm @@ -4,6 +4,13 @@ #define BITRUNNER_COST_HIGH 3 #define BITRUNNER_COST_EXTREME 20 +#define BITRUNNING_GRADE_NONE "None" +#define BITRUNNING_GRADE_D "D" +#define BITRUNNING_GRADE_C "C" +#define BITRUNNING_GRADE_B "B" +#define BITRUNNING_GRADE_A "A" +#define BITRUNNING_GRADE_S "S" + /// Yay you did it #define BITRUNNER_REWARD_MIN 1 /// You faced some decent odds diff --git a/code/_globalvars/lists/bitrunning.dm b/code/_globalvars/lists/bitrunning.dm new file mode 100644 index 000000000000..fe514c83f928 --- /dev/null +++ b/code/_globalvars/lists/bitrunning.dm @@ -0,0 +1,9 @@ +/// Assoc list of bitrunning grades in ascending order and their associated fontawesome icons. +GLOBAL_LIST_INIT(bitrunning_grades, list( + BITRUNNING_GRADE_NONE = FA_ICON_MINUS, + BITRUNNING_GRADE_D = FA_ICON_D, + BITRUNNING_GRADE_C = FA_ICON_C, + BITRUNNING_GRADE_B = FA_ICON_B, + BITRUNNING_GRADE_A = FA_ICON_A, + BITRUNNING_GRADE_S = FA_ICON_S +)) diff --git a/code/controllers/subsystem/bitrunning.dm b/code/controllers/subsystem/bitrunning.dm index 6f6d68a0b05c..d47085223ad4 100644 --- a/code/controllers/subsystem/bitrunning.dm +++ b/code/controllers/subsystem/bitrunning.dm @@ -24,6 +24,8 @@ SUBSYSTEM_DEF(bitrunning) var/can_view = domain.can_view_name(scanner_tier, points) var/can_view_reward = domain.can_view_reward(scanner_tier, points) + var/grade_symbol = GLOB.bitrunning_grades[domain.best_grade] + UNTYPED_LIST_ADD(levels, list( "announce_ghosts" = domain.announce_to_ghosts, "cost" = domain.cost, @@ -34,6 +36,7 @@ SUBSYSTEM_DEF(bitrunning) "has_secondary_objectives" = counterlist_sum(domain.secondary_loot) ? TRUE : FALSE, "name" = can_view ? domain.name : REDACTED, "reward" = can_view_reward ? domain.reward_points : REDACTED, + "grade_symbol" = grade_symbol, )) return levels diff --git a/code/modules/bitrunning/server/loot.dm b/code/modules/bitrunning/server/loot.dm index fde484407e71..957f195ae401 100644 --- a/code/modules/bitrunning/server/loot.dm +++ b/code/modules/bitrunning/server/loot.dm @@ -63,6 +63,13 @@ reward_cache.manifest = certificate reward_cache.update_appearance() + generated_domain.submit_grade(grade) + + if(can_generate_tech_disk(grade)) + SSblackbox.record_feedback("tally", "bitrunning_bepis_rewarded", 1, generated_domain.key) + new /obj/item/disk/design_disk/bepis/remove_tech(reward_cache) + generated_domain.disk_reward_spawned = TRUE + chosen_forge.start_to_spawn(reward_cache) return TRUE @@ -132,6 +139,21 @@ return text +/// Checks if the players should get a bepis reward +/obj/machinery/quantum_server/proc/can_generate_tech_disk(grade) + if(generated_domain.disk_reward_spawned) + return FALSE + + if(!LAZYLEN(SSresearch.techweb_nodes_experimental)) + return FALSE + + var/static/list/passing_grades = list() + if(!passing_grades.len) + passing_grades = list(BITRUNNING_GRADE_A,BITRUNNING_GRADE_S) + + return generated_domain.difficulty >= BITRUNNER_DIFFICULTY_MEDIUM && (grade in passing_grades) + + /// Grades the player's run based on several factors /obj/machinery/quantum_server/proc/grade_completion(completion_time) var/score = length(spawned_threat_refs) * 5 @@ -158,12 +180,12 @@ switch(score) if(1 to 4) - return "D" + return BITRUNNING_GRADE_D if(5 to 7) - return "C" + return BITRUNNING_GRADE_C if(8 to 10) - return "B" + return BITRUNNING_GRADE_B if(11 to 13) - return "A" + return BITRUNNING_GRADE_A else - return "S" + return BITRUNNING_GRADE_S diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm index fdd94c4871f7..edf63a46eb33 100644 --- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -92,6 +92,12 @@ /// The role that ghosts will get. Only used for poll text. var/spawner_role = "Antagonist" + /** + * Scorekeeping + */ + + /// The highest score grade achieved so far + var/best_grade = "None" /datum/lazy_template/virtual_domain/proc/can_view_name(scanner_tier, server_points) return difficulty < scanner_tier && cost <= server_points + 5 @@ -135,3 +141,7 @@ /// Overridable proc to be called after the map is loaded. /datum/lazy_template/virtual_domain/proc/setup_domain(list/created_atoms) return + +/datum/lazy_template/virtual_domain/proc/submit_grade(new_grade) + if(GLOB.bitrunning_grades.Find(new_grade) > GLOB.bitrunning_grades.Find(best_grade)) + best_grade = new_grade diff --git a/tgstation.dme b/tgstation.dme index eefae6971ab0..9265270080b3 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -530,6 +530,7 @@ #include "code\_globalvars\time_vars.dm" #include "code\_globalvars\lists\achievements.dm" #include "code\_globalvars\lists\ambience.dm" +#include "code\_globalvars\lists\bitrunning.dm" #include "code\_globalvars\lists\canisters.dm" #include "code\_globalvars\lists\cargo.dm" #include "code\_globalvars\lists\client.dm" diff --git a/tgui/packages/tgui/interfaces/QuantumConsole.tsx b/tgui/packages/tgui/interfaces/QuantumConsole.tsx index d00f744f9a6d..5969482b1adb 100644 --- a/tgui/packages/tgui/interfaces/QuantumConsole.tsx +++ b/tgui/packages/tgui/interfaces/QuantumConsole.tsx @@ -55,6 +55,7 @@ type Domain = { has_secondary_objectives: BooleanLike; name: string; reward: number | string; + grade_symbol: string; }; type DomainEntryProps = { @@ -262,6 +263,7 @@ function DomainEntry(props: DomainEntryProps) { has_secondary_objectives, name, reward, + grade_symbol, }, } = props; const { act, data } = useBackend(); @@ -309,6 +311,7 @@ function DomainEntry(props: DomainEntryProps) { {!!announce_ghosts && canView && } } + sideIcon={grade_symbol} > From a92bb4497a0510dff3fe5192295738f041cb6e47 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Mon, 3 Aug 2026 07:18:36 -0500 Subject: [PATCH 17/45] Bitrunner domain names are no longer hidden by upgrades (#97292) ## About The Pull Request All bitrunner domains no longer have their names hidden due to lacking upgraded stock parts. Domains that require a significant amount of stars are still hidden until unlocked. ## Why It's Good For The Game Three reasons: 1. At first I thought the names were obscured to require Bitrunners explore a variety of domains rather than play the same domain over and over again, but the actual domains don't randomize position in the list between rounds, making hidden names nothing but a memory check. 2. Even though the names were hidden the special icons weren't, meaning you could logic out which is which (at least, until the pool is expanded significantly, but that may not happen for. Some time.) 3. Some bitrunner domains now serve as pseudo-tutorials, so making them easier to pick out serves nicely towards that. ## Changelog :cl: Melbert qol: Bitrunner domain names are no longer hidden until you upgrade the server stock parts. Distantly unaffordable domains still have their names hidden. /:cl: --- code/modules/bitrunning/virtual_domain/virtual_domain.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm index edf63a46eb33..d2025596b184 100644 --- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -100,7 +100,7 @@ var/best_grade = "None" /datum/lazy_template/virtual_domain/proc/can_view_name(scanner_tier, server_points) - return difficulty < scanner_tier && cost <= server_points + 5 + return cost <= server_points + 5 /datum/lazy_template/virtual_domain/proc/can_view_reward(scanner_tier, server_points) return difficulty < (scanner_tier + 1) && cost <= server_points + 3 From 97cfe3ac850885bd01f2e21d3133436c7de7b1b1 Mon Sep 17 00:00:00 2001 From: Rhials <28870487+Rhials@users.noreply.github.com> Date: Tue, 15 Apr 2025 13:33:07 -0400 Subject: [PATCH 18/45] Bitrunner Glitch antagonists will not spawn on peaceful domains (#90611) --- code/modules/bitrunning/event.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/bitrunning/event.dm b/code/modules/bitrunning/event.dm index c50ef016a515..f3c3d0e0cc20 100644 --- a/code/modules/bitrunning/event.dm +++ b/code/modules/bitrunning/event.dm @@ -29,7 +29,7 @@ /datum/round_event_control/bitrunning_glitch/proc/validate_servers() active_servers.Cut() for(var/obj/machinery/quantum_server/server in SSmachines.get_machines_by_type(/obj/machinery/quantum_server)) - if(server.validate_mutation_candidates()) + if(server.validate_mutation_candidates() && server.generated_domain.difficulty != BITRUNNER_DIFFICULTY_NONE) active_servers.Add(WEAKREF(server)) return length(active_servers) > 0 From d63bb779473adbeafd0fbcda1b61d6fcd6ddc363 Mon Sep 17 00:00:00 2001 From: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Date: Tue, 5 Aug 2025 14:32:56 +0530 Subject: [PATCH 19/45] Adds some bit running construction qol (#92217) - Closes #92210 1) Bit Forge can be deconstructed with a screwdriver & crowbar like a normal machine 2) Adds examines & screentips for netpod, bitforge(icon for panel open state added) & quantum server on how to deconstruct them. New icon state for when bitforge has its panel open is shown below Screenshot (497) 3) Moved bitrunning machine designs into files that exist to hold that type of code :cl: code: moved designs & circuitboards for bitrunning into their correct files qol: adds examines & screentips for bitrunning netpod, bitforge & server on how to deconstruct them fix: bitforge can be deconstructed with a screwdriver & crowbar /:cl: --- .../machines/machine_circuitboards.dm | 32 ++++++++ code/modules/bitrunning/designs.dm | 74 ------------------ code/modules/bitrunning/netpod/_netpod.dm | 40 +++++++--- code/modules/bitrunning/objects/byteforge.dm | 34 +++++++- .../bitrunning/objects/quantum_console.dm | 5 ++ code/modules/bitrunning/server/_parent.dm | 40 +++++++--- .../research/designs/comp_board_designs.dm | 10 +++ .../research/designs/machine_designs.dm | 20 +++++ icons/obj/machines/bitrunning.dmi | Bin 27801 -> 27884 bytes tgstation.dme | 1 - 10 files changed, 156 insertions(+), 100 deletions(-) delete mode 100644 code/modules/bitrunning/designs.dm diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index 6ffd7b467d9a..1d6e75eb6097 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -1699,3 +1699,35 @@ /datum/stock_part/water_recycler = 1, /datum/stock_part/servo = 1, ) + +/obj/item/circuitboard/machine/quantum_server + name = "Quantum Server" + greyscale_colors = CIRCUIT_COLOR_SUPPLY + build_path = /obj/machinery/quantum_server + req_components = list( + /datum/stock_part/servo = 2, + /datum/stock_part/scanning_module = 1, + /datum/stock_part/capacitor = 1, + ) + +/obj/item/circuitboard/machine/netpod + name = "Netpod" + greyscale_colors = CIRCUIT_COLOR_SUPPLY + build_path = /obj/machinery/netpod + req_components = list( + /datum/stock_part/servo = 1, + /datum/stock_part/matter_bin = 2, + ) + +/obj/item/circuitboard/computer/quantum_console + name = "Quantum Console" + greyscale_colors = CIRCUIT_COLOR_SUPPLY + build_path = /obj/machinery/computer/quantum_console + +/obj/item/circuitboard/machine/byteforge + name = "Byteforge" + greyscale_colors = CIRCUIT_COLOR_SUPPLY + build_path = /obj/machinery/byteforge + req_components = list( + /datum/stock_part/micro_laser = 1, + ) diff --git a/code/modules/bitrunning/designs.dm b/code/modules/bitrunning/designs.dm deleted file mode 100644 index 96ae65d41e99..000000000000 --- a/code/modules/bitrunning/designs.dm +++ /dev/null @@ -1,74 +0,0 @@ -// Quantum server - -/obj/item/circuitboard/machine/quantum_server - name = "Quantum Server" - greyscale_colors = CIRCUIT_COLOR_SUPPLY - build_path = /obj/machinery/quantum_server - req_components = list( - /datum/stock_part/servo = 2, - /datum/stock_part/scanning_module = 1, - /datum/stock_part/capacitor = 1, - ) - -/** - * quantum server design - * are you absolutely sure?? - */ - -// Netpod - -/obj/item/circuitboard/machine/netpod - name = "Netpod" - greyscale_colors = CIRCUIT_COLOR_SUPPLY - build_path = /obj/machinery/netpod - req_components = list( - /datum/stock_part/servo = 1, - /datum/stock_part/matter_bin = 2, - ) - -/datum/design/board/netpod - name = "Netpod Board" - desc = "The circuit board for a netpod." - id = "netpod" - build_path = /obj/item/circuitboard/machine/netpod - category = list( - RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_CARGO - ) - departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING - -// Quantum console - -/obj/item/circuitboard/computer/quantum_console - name = "Quantum Console" - greyscale_colors = CIRCUIT_COLOR_SUPPLY - build_path = /obj/machinery/computer/quantum_console - -/datum/design/board/quantum_console - name = "Quantum Console Board" - desc = "Allows for the construction of circuit boards used to build a Quantum Console." - id = "quantum_console" - build_path = /obj/item/circuitboard/computer/quantum_console - category = list( - RND_CATEGORY_COMPUTER + RND_SUBCATEGORY_COMPUTER_CARGO - ) - departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING - -// Byteforge - -/obj/item/circuitboard/machine/byteforge - name = "Byteforge" - greyscale_colors = CIRCUIT_COLOR_SUPPLY - build_path = /obj/machinery/byteforge - req_components = list( - /datum/stock_part/micro_laser = 1, - ) - -/datum/design/board/byteforge - name = "Byteforge Board" - desc = "Allows for the construction of circuit boards used to build a Byteforge." - id = "byteforge" - build_path = /obj/item/circuitboard/machine/byteforge - category = list( - RND_CATEGORY_COMPUTER + RND_SUBCATEGORY_COMPUTER_CARGO - ) - departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING diff --git a/code/modules/bitrunning/netpod/_netpod.dm b/code/modules/bitrunning/netpod/_netpod.dm index 2208c56a7414..e372f004255f 100644 --- a/code/modules/bitrunning/netpod/_netpod.dm +++ b/code/modules/bitrunning/netpod/_netpod.dm @@ -48,9 +48,37 @@ QDEL_LIST(cached_outfits) +/obj/machinery/netpod/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + + if(isnull(held_item)) + context[SCREENTIP_CONTEXT_LMB] = "Select Outfit" + return CONTEXTUAL_SCREENTIP_SET + + if(held_item.tool_behaviour == TOOL_SCREWDRIVER && !occupant && !state_open) + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] Panel" + return CONTEXTUAL_SCREENTIP_SET + + if(held_item.tool_behaviour == TOOL_CROWBAR) + if(isnull(occupant)) + if(panel_open) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + else + context[SCREENTIP_CONTEXT_LMB] = "[state_open ? "Close" : "Open"] Cover" + else + context[SCREENTIP_CONTEXT_LMB] = "Break out" + return CONTEXTUAL_SCREENTIP_SET + /obj/machinery/netpod/examine(mob/user) . = ..() + . += span_notice("Its maintainance panel can be [EXAMINE_HINT("screwed")] [panel_open ? "close" : "open"].") + if(isnull(occupant)) + if(panel_open) + . += span_notice("It can be [EXAMINE_HINT("pried")] apart.") + else + . += span_notice("Its hatch can be [EXAMINE_HINT("pried")] [state_open ? "closed" : "open"]") + if(isnull(server_ref?.resolve())) . += span_infoplain("It's not connected to anything.") . += span_infoplain("Netpods must be built within 4 tiles of a server.") @@ -74,18 +102,6 @@ . += span_notice("It can be pried open with a crowbar, but its safety mechanisms will alert the occupant.") -/obj/machinery/netpod/add_context(atom/source, list/context, obj/item/held_item, mob/user) - . = ..() - - if(isnull(held_item)) - context[SCREENTIP_CONTEXT_LMB] = "Select Outfit" - return CONTEXTUAL_SCREENTIP_SET - - if(istype(held_item, /obj/item/crowbar) && occupant) - context[SCREENTIP_CONTEXT_LMB] = "Pry Open" - return CONTEXTUAL_SCREENTIP_SET - - /obj/machinery/netpod/update_icon_state() if(!is_operational) icon_state = base_icon_state diff --git a/code/modules/bitrunning/objects/byteforge.dm b/code/modules/bitrunning/objects/byteforge.dm index b971cdae75dd..d9a1c3b85ac2 100644 --- a/code/modules/bitrunning/objects/byteforge.dm +++ b/code/modules/bitrunning/objects/byteforge.dm @@ -5,6 +5,7 @@ desc = "A machine used by the quantum server. Quantum code converges here, materializing decrypted assets from the virtual abyss." icon = 'icons/obj/machines/bitrunning.dmi' icon_state = "byteforge" + base_icon_state = "byteforge" obj_flags = BLOCKS_CONSTRUCTION | CAN_BE_HIT /// Idle particles var/mutable_appearance/byteforge_particles @@ -12,18 +13,49 @@ /obj/machinery/byteforge/Initialize(mapload) . = ..() - return INITIALIZE_HINT_LATELOAD + register_context() /obj/machinery/byteforge/post_machine_initialize() . = ..() setup_particles() +/obj/machinery/byteforge/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = NONE + if(isnull(held_item)) + return + + if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] Panel" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_CROWBAR && panel_open) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/byteforge/examine(mob/user) + . = ..() + + . += span_notice("Make sure this is 4 tiles from the quantum server") + + . += span_notice("Its maintainance panel can be [EXAMINE_HINT("screwed")] [panel_open ? "close" : "open"].") + if(panel_open) + . += span_notice("It can be [EXAMINE_HINT("pried")] apart.") + /obj/machinery/byteforge/update_appearance(updates) . = ..() setup_particles() +/obj/machinery/byteforge/screwdriver_act(mob/living/user, obj/item/screwdriver) + . = ITEM_INTERACT_FAILURE + if(default_deconstruction_screwdriver(user, "[base_icon_state]_panel", base_icon_state, screwdriver)) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/byteforge/crowbar_act(mob/living/user, obj/item/crowbar) + . = ITEM_INTERACT_FAILURE + if(default_deconstruction_crowbar(crowbar)) + return ITEM_INTERACT_SUCCESS + /// Does some sparks after it's done /obj/machinery/byteforge/proc/flash(atom/movable/thing) playsound(src, 'sound/magic/blink.ogg', 50, TRUE) diff --git a/code/modules/bitrunning/objects/quantum_console.dm b/code/modules/bitrunning/objects/quantum_console.dm index d07a9339aa38..b87bf433cde1 100644 --- a/code/modules/bitrunning/objects/quantum_console.dm +++ b/code/modules/bitrunning/objects/quantum_console.dm @@ -16,6 +16,11 @@ . = ..() find_server() +/obj/machinery/computer/quantum_console/examine(mob/user) + . = ..() + if(!server_ref) + . += span_warning("Please install an quantum server in any tile next to this console.") + /obj/machinery/computer/quantum_console/ui_interact(mob/user, datum/tgui/ui) . = ..() diff --git a/code/modules/bitrunning/server/_parent.dm b/code/modules/bitrunning/server/_parent.dm index 29bfef53c0bc..c628cccc29d2 100644 --- a/code/modules/bitrunning/server/_parent.dm +++ b/code/modules/bitrunning/server/_parent.dm @@ -53,6 +53,10 @@ /// Cooldown for how often you're allowed to harass deadchat for PVP domains COOLDOWN_DECLARE(polling_cooldown) +/obj/machinery/quantum_server/Initialize(mapload) + . = ..() + register_context() + /obj/machinery/quantum_server/post_machine_initialize() . = ..() @@ -73,11 +77,27 @@ QDEL_NULL(radio) return ..() +/obj/machinery/quantum_server/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = NONE + if(isnull(held_item)) + return + + if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] Panel" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_CROWBAR && panel_open) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + return CONTEXTUAL_SCREENTIP_SET + /obj/machinery/quantum_server/examine(mob/user) . = ..() . += span_infoplain("Can be resource intensive to run. Ensure adequate power supply.") + . += span_notice("Its maintainance panel can be [EXAMINE_HINT("screwed")] [panel_open ? "close" : "open"].") + if(panel_open) + . += span_notice("It can be [EXAMINE_HINT("pried")] apart.") + var/upgraded = FALSE if(capacitor_coefficient < 1) . += span_infoplain("- Its coolant capacity reduces cooldown time by [(1 - capacitor_coefficient) * 100]%.") @@ -140,27 +160,23 @@ return ITEM_INTERACT_SUCCESS /obj/machinery/quantum_server/crowbar_act(mob/living/user, obj/item/crowbar) - . = ..() - + . = NONE if(!is_ready) balloon_alert(user, "it's scalding hot!") - return TRUE + return ITEM_INTERACT_FAILURE if(length(avatar_connection_refs)) balloon_alert(user, "all clients must disconnect!") - return TRUE + return ITEM_INTERACT_FAILURE if(default_deconstruction_crowbar(crowbar)) - return TRUE - return FALSE + return ITEM_INTERACT_SUCCESS /obj/machinery/quantum_server/screwdriver_act(mob/living/user, obj/item/screwdriver) - . = ..() - + . = NONE if(!is_ready) balloon_alert(user, "it's scalding hot!") - return TRUE - if(default_deconstruction_screwdriver(user, "[base_icon_state]_panel", icon_state, screwdriver)) - return TRUE - return FALSE + return ITEM_INTERACT_FAILURE + if(default_deconstruction_screwdriver(user, "[base_icon_state]_panel", base_icon_state, screwdriver)) + return ITEM_INTERACT_SUCCESS /obj/machinery/quantum_server/RefreshParts() var/capacitor_rating = 1.15 diff --git a/code/modules/research/designs/comp_board_designs.dm b/code/modules/research/designs/comp_board_designs.dm index fb82d0857e3c..f5d4cacafa4c 100644 --- a/code/modules/research/designs/comp_board_designs.dm +++ b/code/modules/research/designs/comp_board_designs.dm @@ -395,3 +395,13 @@ RND_CATEGORY_COMPUTER + RND_SUBCATEGORY_COMPUTER_COMMAND ) departmental_flags = DEPARTMENT_BITFLAG_SECURITY //Honestly should have a bridge techfab for this sometime. + +/datum/design/board/quantum_console + name = "Quantum Console Board" + desc = "Allows for the construction of circuit boards used to build a Quantum Console." + id = "quantum_console" + build_path = /obj/item/circuitboard/computer/quantum_console + category = list( + RND_CATEGORY_COMPUTER + RND_SUBCATEGORY_COMPUTER_CARGO + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index 938ceb574b2c..16da1d229eb1 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -1238,3 +1238,23 @@ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_SERVICE ) departmental_flags = DEPARTMENT_BITFLAG_SERVICE + +/datum/design/board/netpod + name = "Netpod Board" + desc = "The circuit board for a netpod." + id = "netpod" + build_path = /obj/item/circuitboard/machine/netpod + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_CARGO + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + +/datum/design/board/byteforge + name = "Byteforge Board" + desc = "Allows for the construction of circuit boards used to build a Byteforge." + id = "byteforge" + build_path = /obj/item/circuitboard/machine/byteforge + category = list( + RND_CATEGORY_COMPUTER + RND_SUBCATEGORY_COMPUTER_CARGO + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING diff --git a/icons/obj/machines/bitrunning.dmi b/icons/obj/machines/bitrunning.dmi index d61e910d195be367ca57799cc0fa353ab8b91684..626ab4ff4f39446a6e826609d2879df703e59f74 100644 GIT binary patch literal 27884 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 literal 27801 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 Om$rufjq+>uQU3!{gm()7 diff --git a/tgstation.dme b/tgstation.dme index 9265270080b3..fbe20f7190e0 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3522,7 +3522,6 @@ #include "code\modules\bitrunning\abilities.dm" #include "code\modules\bitrunning\alerts.dm" #include "code\modules\bitrunning\areas.dm" -#include "code\modules\bitrunning\designs.dm" #include "code\modules\bitrunning\event.dm" #include "code\modules\bitrunning\job.dm" #include "code\modules\bitrunning\outfits.dm" From 7c099326800857c6362cf866a64e1767ee5012a2 Mon Sep 17 00:00:00 2001 From: SimplyLogan <47579821+loganuk@users.noreply.github.com> Date: Tue, 26 Aug 2025 22:37:04 +0100 Subject: [PATCH 20/45] Bitrunner virtual area shuttle exploit (#92727) Fixes #92463 - Fixes an exploit to take loot out of the Virtual Domain via build a shuttle. - Explicitly prevents people taking items into the Virtual Domain. --- code/modules/bitrunning/netpod/utils.dm | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/code/modules/bitrunning/netpod/utils.dm b/code/modules/bitrunning/netpod/utils.dm index 6e0033fe02d9..364c1a038172 100644 --- a/code/modules/bitrunning/netpod/utils.dm +++ b/code/modules/bitrunning/netpod/utils.dm @@ -76,6 +76,20 @@ balloon_alert(neo, "nothing loaded!") return + balloon_alert(neo, "establishing connection...") + + // Prevent hand interactions during loading to stop smuggling exploits into virtual domain + ADD_TRAIT(neo, TRAIT_HANDS_BLOCKED, TRAIT_GENERIC) + + var/connection_successful = do_after(neo, 2 SECONDS, src) + + // Re-enable hand interactions after loading attempt + REMOVE_TRAIT(neo, TRAIT_HANDS_BLOCKED, TRAIT_GENERIC) + + if(!connection_successful) + open_machine() + return + var/mob/living/carbon/current_avatar = avatar_ref?.resolve() if(isnull(current_avatar) || current_avatar.stat != CONSCIOUS) // We need a viable avatar current_avatar = server.start_new_connection(neo, netsuit) From 957bd6abaf370dfafa86115b1915ad6d7e25bc57 Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Sat, 22 Nov 2025 14:24:41 -0500 Subject: [PATCH 21/45] Adds flag for virtual areas, fixes being able to send funds from virtualspace to real accounts (#94071) Fixes https://github.com/tgstation/tgstation/issues/90641 Fixes https://github.com/tgstation/tgstation/issues/88366 Eliminates worries over virtualspace currency being sent to real accounts. When I was looking into why there were no flags for bitrunning areas. Then I saw this mess: Code_2we2QjDyFp Not having enough space / being too lazy to refactor this is a silly reason to not include flags for something like these virtual areas where it can be quite helpful. Fortunately I am not too lazy ~~in this moment~~ so here we go: It was fairly logical to move over some of these to a separate flag, which I've called `area_flags_mapping` since they pertain to maploading things and terrain generation mostly. `area_flags` stays reserved for general properties and now has more room than it did before for you people to fill it with. In doing this it's also neatened up the code quit a bit, as UNIQUE_AREA was kind of everywhere and now that it's implied by default less areas need to have it defined (or explicitly un-defined).
Working as intended dreamseeker_p0Qts36tG1 dreamseeker_Ek7TXCcpbA
Fixes an exploit, improves the area flags situation slightly. :cl: fix: you can no longer send money from virtualspace to a real account code: adds a flag for virtual areas so they can easily be checked, as well as an easy helper proc, 'is_area_virtual(your_area)' /:cl: --- code/__DEFINES/_flags.dm | 60 ++++++++++++------- code/__DEFINES/is_helpers.dm | 1 + code/_globalvars/bitfields.dm | 29 ++++++--- code/controllers/subsystem/mapping.dm | 2 +- .../subsystem/persistence/engravings.dm | 2 +- code/datums/mapgen/CaveGenerator.dm | 10 ++-- code/game/area/areas.dm | 7 ++- code/game/area/areas/away_content.dm | 5 +- code/game/area/areas/centcom.dm | 13 ++-- code/game/area/areas/mining.dm | 46 +++++++------- code/game/area/areas/misc.dm | 2 +- code/game/area/areas/ruins/_ruins.dm | 2 +- code/game/area/areas/ruins/icemoon.dm | 50 ++++++++++++++++ code/game/area/areas/ruins/lavaland.dm | 1 + code/game/area/areas/ruins/space.dm | 16 ++--- code/game/area/areas/shuttles.dm | 13 +--- code/game/area/areas/station/common.dm | 2 +- code/game/area/areas/station/engineering.dm | 6 +- code/game/area/areas/station/hallway.dm | 2 +- code/game/area/areas/station/maintenance.dm | 4 +- code/game/area/areas/station/misc.dm | 2 +- code/game/area/areas/station/science.dm | 8 +-- code/game/area/areas/station/security.dm | 2 +- code/game/area/areas/station/service.dm | 4 +- code/game/area/areas/station/solars.dm | 12 +++- .../sacrifice_knowledge/sacrifice_map.dm | 2 +- code/modules/awaymissions/cordon.dm | 2 +- .../basketball/basketball_map_loading.dm | 2 +- code/modules/bitrunning/areas.dm | 33 +++++++--- .../bitrunning/components/virtual_entity.dm | 2 +- .../ruins/spaceruin_code/anomalyresearch.dm | 2 +- .../ruins/spaceruin_code/forgottenship.dm | 2 +- .../ruins/spaceruin_code/hilbertshotel.dm | 6 +- code/modules/mining/equipment/survival_pod.dm | 2 +- .../modules/mob/living/silicon/ai/multicam.dm | 2 +- .../file_system/programs/nt_pay.dm | 5 ++ 36 files changed, 233 insertions(+), 128 deletions(-) diff --git a/code/__DEFINES/_flags.dm b/code/__DEFINES/_flags.dm index b1abfc0b54fe..298018a99a80 100644 --- a/code/__DEFINES/_flags.dm +++ b/code/__DEFINES/_flags.dm @@ -99,42 +99,58 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define IS_SOLID (1<<6) /// This turf will never be cleared away by other objects on Initialize. #define NO_CLEARING (1<<7) +/// This atom is a pseudo-floor that blocks map generation's checkPlaceAtom() from placing things like trees ontop of it. +#define TURF_BLOCKS_POPULATE_TERRAIN_FLORAFEATURES (1<<8) ////////////////Area flags\\\\\\\\\\\\\\ /// If it's a valid territory for cult summoning or the CRAB-17 phone to spawn #define VALID_TERRITORY (1<<0) /// If blobs can spawn there and if it counts towards their score. #define BLOBS_ALLOWED (1<<1) -/// If mining tunnel generation is allowed in this area -#define CAVES_ALLOWED (1<<2) -/// If flora are allowed to spawn in this area randomly through tunnel generation -#define FLORA_ALLOWED (1<<3) -/// If mobs can be spawned by natural random generation -#define MOB_SPAWN_ALLOWED (1<<4) -/// If megafauna can be spawned by natural random generation -#define MEGAFAUNA_SPAWN_ALLOWED (1<<5) /// Are you forbidden from teleporting to the area? (centcom, mobs, wizard, hand teleporter) -#define NOTELEPORT (1<<6) +#define NOTELEPORT (1<<2) /// Hides area from player Teleport function. -#define HIDDEN_AREA (1<<7) -/// If false, loading multiple maps with this area type will create multiple instances. -#define UNIQUE_AREA (1<<8) +#define HIDDEN_AREA (1<<3) /// If people are allowed to suicide in it. Mostly for OOC stuff like minigames -#define BLOCK_SUICIDE (1<<9) +#define BLOCK_SUICIDE (1<<5) /// If set, this area will be innately traversable by Xenobiology camera consoles. -#define XENOBIOLOGY_COMPATIBLE (1<<10) +#define XENOBIOLOGY_COMPATIBLE (1<<6) /// If blood cultists can draw runes or build structures on this AREA. -#define CULT_PERMITTED (1<<11) -/// If engravings are persistent in this area -#define PERSISTENT_ENGRAVINGS (1<<12) +#define CULT_PERMITTED (1<<7) /// Mobs that die in this area don't produce a dead chat message -#define NO_DEATH_MESSAGE (1<<13) +#define NO_DEATH_MESSAGE (1<<8) /// This area should have extra shielding from certain event effects -#define EVENT_PROTECTED (1<<14) -/// This area does not allow virtual entities to enter. -#define VIRTUAL_SAFE_AREA (1<<16) +#define EVENT_PROTECTED (1<<9) +/// This Area Doesn't have Flood or Bomb Admin Messages, but will still log +#define QUIET_LOGS (1<<10) +/// This area does not allow the Binary channel +#define BINARY_JAMMING (1<<11) +/// This area prevents Bag of Holding rifts from being opened. +#define NO_BOH (1<<12) /// This area prevents fishing from removing unique/limited loot from sources that're also used outside of it. -#define UNLIMITED_FISHING (1<<19) +#define UNLIMITED_FISHING (1<<13) +/// This area is prevented from having gravity (ie. space, nearstation, or outside solars) +#define NO_GRAVITY (1<<14) +/// This area can be teleported in, but -only- to locations within that same area. +#define LOCAL_TELEPORT (1<<15) + +////////////////Area Mapping Flags\\\\\\\\\\\\\\ +/// If false, loading multiple maps with this area type will create multiple instances. +#define UNIQUE_AREA (1<<0) +/// If mining tunnel generation is allowed in this area +#define CAVES_ALLOWED (1<<1) +/// If flora are allowed to spawn in this area randomly through tunnel generation +#define FLORA_ALLOWED (1<<2) +/// If mobs can be spawned by natural random generation +#define MOB_SPAWN_ALLOWED (1<<3) +/// If megafauna can be spawned by natural random generation +#define MEGAFAUNA_SPAWN_ALLOWED (1<<4) +/// If engravings are persistent in this area +#define PERSISTENT_ENGRAVINGS (1<<5) +/// This is a virtual/bitrunning area +#define VIRTUAL_AREA (1<<6) +/// This area does not allow virtual entities to enter. +#define VIRTUAL_SAFE_AREA (1<<7) /* These defines are used specifically with the atom/pass_flags bitmask diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index e66cf5ffdb0e..4f9d5fb3647e 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -323,3 +323,4 @@ GLOBAL_LIST_INIT(book_types, typecacheof(list( #define isprojectilespell(thing) (istype(thing, /datum/action/cooldown/spell/pointed/projectile)) #define is_multi_tile_object(atom) (atom.bound_width > world.icon_size || atom.bound_height > world.icon_size) +#define is_area_virtual(checked_area) (GLOB.virtual_areas && GLOB.virtual_areas[(checked_area).type]) diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 867f22f4c773..18d355c07362 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -50,21 +50,32 @@ DEFINE_BITFIELD(appearance_flags, list( )) DEFINE_BITFIELD(area_flags, list( + "VALID_TERRITORY" = VALID_TERRITORY, "BLOBS_ALLOWED" = BLOBS_ALLOWED, + "NOTELEPORT" = NOTELEPORT, + "HIDDEN_AREA" = HIDDEN_AREA, "BLOCK_SUICIDE" = BLOCK_SUICIDE, - "CAVES_ALLOWED" = CAVES_ALLOWED, + "XENOBIOLOGY_COMPATIBLE" = XENOBIOLOGY_COMPATIBLE, "CULT_PERMITTED" = CULT_PERMITTED, + "NO_DEATH_MESSAGE" = NO_DEATH_MESSAGE, + "EVENT_PROTECTED" = EVENT_PROTECTED, + "QUIET_LOGS" = QUIET_LOGS, + "BINARY_JAMMING" = BINARY_JAMMING, + "NO_BOH" = NO_BOH, + "UNLIMITED_FISHING" = UNLIMITED_FISHING, + "NO_GRAVITY" = NO_GRAVITY, + "LOCAL_TELEPORT" = LOCAL_TELEPORT, +)) + +DEFINE_BITFIELD(area_flags_mapping, list( + "UNIQUE_AREA" = UNIQUE_AREA, + "CAVES_ALLOWED" = CAVES_ALLOWED, "FLORA_ALLOWED" = FLORA_ALLOWED, - "HIDDEN_AREA" = HIDDEN_AREA, - "MEGAFAUNA_SPAWN_ALLOWED" = MEGAFAUNA_SPAWN_ALLOWED, "MOB_SPAWN_ALLOWED" = MOB_SPAWN_ALLOWED, - "NOTELEPORT" = NOTELEPORT, - "NO_DEATH_MESSAGE" = NO_DEATH_MESSAGE, + "MEGAFAUNA_SPAWN_ALLOWED" = MEGAFAUNA_SPAWN_ALLOWED, "PERSISTENT_ENGRAVINGS" = PERSISTENT_ENGRAVINGS, - "UNIQUE_AREA" = UNIQUE_AREA, - "VALID_TERRITORY" = VALID_TERRITORY, - "XENOBIOLOGY_COMPATIBLE" = XENOBIOLOGY_COMPATIBLE, - "UNLIMITED_FISHING" = UNLIMITED_FISHING, + "VIRTUAL_AREA" = VIRTUAL_AREA, + "VIRTUAL_SAFE_AREA" = VIRTUAL_SAFE_AREA, )) DEFINE_BITFIELD(turf_flags, list( diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index 39bbe6cb6d2b..66954fad4cc1 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -491,7 +491,7 @@ GLOBAL_LIST_EMPTY(the_station_areas) /// Generates the global station area list, filling it with typepaths of unique areas found on the station Z. /datum/controller/subsystem/mapping/proc/generate_station_area_list() for(var/area/station/station_area in GLOB.areas) - if (!(station_area.area_flags & UNIQUE_AREA)) + if (!(station_area.area_flags_mapping & UNIQUE_AREA)) continue if (is_station_level(station_area.z)) GLOB.the_station_areas += station_area.type diff --git a/code/controllers/subsystem/persistence/engravings.dm b/code/controllers/subsystem/persistence/engravings.dm index f47fc7fbba12..309f595b8c85 100644 --- a/code/controllers/subsystem/persistence/engravings.dm +++ b/code/controllers/subsystem/persistence/engravings.dm @@ -62,7 +62,7 @@ if(!engraving.persistent_save) continue var/area/engraved_area = get_area(engraving.parent) - if(!(engraved_area.area_flags & PERSISTENT_ENGRAVINGS)) + if(!(engraved_area.area_flags_mapping & PERSISTENT_ENGRAVINGS)) continue saved_data["entries"] += engraving.save_persistent() diff --git a/code/datums/mapgen/CaveGenerator.dm b/code/datums/mapgen/CaveGenerator.dm index 4a15770b0c42..14929e31f378 100644 --- a/code/datums/mapgen/CaveGenerator.dm +++ b/code/datums/mapgen/CaveGenerator.dm @@ -81,7 +81,7 @@ /datum/map_generator/cave_generator/generate_terrain(list/turfs, area/generate_in) . = ..() - if(!(generate_in.area_flags & CAVES_ALLOWED)) + if(!(generate_in.area_flags_mapping & CAVES_ALLOWED)) return var/start_time = REALTIMEOFDAY @@ -105,10 +105,10 @@ /datum/map_generator/cave_generator/populate_terrain(list/turfs, area/generate_in) // Area var pullouts to make accessing in the loop faster - var/flora_allowed = (generate_in.area_flags & FLORA_ALLOWED) && length(flora_spawn_list) - var/feature_allowed = (generate_in.area_flags & FLORA_ALLOWED) && length(feature_spawn_list) - var/mobs_allowed = (generate_in.area_flags & MOB_SPAWN_ALLOWED) && length(mob_spawn_list) - var/megas_allowed = (generate_in.area_flags & MEGAFAUNA_SPAWN_ALLOWED) && length(megafauna_spawn_list) + var/flora_allowed = (generate_in.area_flags_mapping & FLORA_ALLOWED) && length(flora_spawn_list) + var/feature_allowed = (generate_in.area_flags_mapping & FLORA_ALLOWED) && length(feature_spawn_list) + var/mobs_allowed = (generate_in.area_flags_mapping & MOB_SPAWN_ALLOWED) && length(mob_spawn_list) + var/megas_allowed = (generate_in.area_flags_mapping & MEGAFAUNA_SPAWN_ALLOWED) && length(megafauna_spawn_list) SStitle.add_init_text("[type]fill", "> [name]: Population", "LOADING") var/start_time = REALTIMEOFDAY diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index c704bb187d35..914c912669b0 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -24,7 +24,10 @@ /// This uses the same nested list format as turfs_by_zlevel var/list/list/turf/turfs_to_uncontain_by_zlevel = list() - var/area_flags = VALID_TERRITORY | BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + /// General flag for area properties + var/area_flags = VALID_TERRITORY | BLOBS_ALLOWED | CULT_PERMITTED + /// Flag for mapping related area properties (such as cavegen) + var/area_flags_mapping = UNIQUE_AREA ///Do we have an active fire alarm? var/fire = FALSE @@ -160,7 +163,7 @@ GLOBAL_LIST_EMPTY(teleportlocs) /area/New() // This interacts with the map loader, so it needs to be set immediately // rather than waiting for atoms to initialize. - if (area_flags & UNIQUE_AREA) + if (area_flags_mapping & UNIQUE_AREA) GLOB.areas_by_type[type] = src GLOB.areas += src energy_usage = new /list(AREA_USAGE_LEN) // Some atoms would like to use power in Initialize() diff --git a/code/game/area/areas/away_content.dm b/code/game/area/areas/away_content.dm index 5e2219ef857e..44f6854e105d 100644 --- a/code/game/area/areas/away_content.dm +++ b/code/game/area/areas/away_content.dm @@ -11,7 +11,6 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30" has_gravity = STANDARD_GRAVITY ambience_index = AMBIENCE_AWAY sound_environment = SOUND_ENVIRONMENT_ROOM - area_flags = UNIQUE_AREA /area/awaymission/museum name = "Nanotrasen Museum" @@ -33,11 +32,11 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30" name = "Super Secret Room" static_lighting = FALSE base_lighting_alpha = 255 - area_flags = UNIQUE_AREA|NOTELEPORT + area_flags = NOTELEPORT has_gravity = STANDARD_GRAVITY /area/awaymission/secret - area_flags = UNIQUE_AREA|NOTELEPORT|HIDDEN_AREA + area_flags = NOTELEPORT|HIDDEN_AREA /area/awaymission/secret/unpowered always_unpowered = TRUE diff --git a/code/game/area/areas/centcom.dm b/code/game/area/areas/centcom.dm index 45abba7c1b2a..c67e45ca6f5e 100644 --- a/code/game/area/areas/centcom.dm +++ b/code/game/area/areas/centcom.dm @@ -7,8 +7,8 @@ icon_state = "centcom" static_lighting = TRUE requires_power = FALSE + area_flags = NOTELEPORT has_gravity = STANDARD_GRAVITY - area_flags = UNIQUE_AREA | NOTELEPORT flags_1 = NONE // This is just to define the category @@ -142,7 +142,6 @@ name = "Thunderdome Observation" icon_state = "thunder_observe" - // ENEMY // Wizard @@ -155,13 +154,12 @@ area_flags = UNIQUE_AREA | NOTELEPORT flags_1 = NONE - //Abductors /area/centcom/abductor_ship name = "Abductor Ship" icon_state = "abductor_ship" requires_power = FALSE - area_flags = UNIQUE_AREA | NOTELEPORT + area_flags = NOTELEPORT static_lighting = FALSE base_lighting_alpha = 255 has_gravity = STANDARD_GRAVITY @@ -214,12 +212,11 @@ name = "Mafia Minigame" icon_state = "mafia" static_lighting = FALSE - base_lighting_alpha = 255 requires_power = FALSE has_gravity = STANDARD_GRAVITY flags_1 = NONE - area_flags = BLOCK_SUICIDE | UNIQUE_AREA + area_flags = BLOCK_SUICIDE //CAPTURE THE FLAG /area/centcom/ctf @@ -230,7 +227,7 @@ base_lighting_alpha = 255 has_gravity = STANDARD_GRAVITY flags_1 = NONE - area_flags = UNIQUE_AREA | NOTELEPORT | NO_DEATH_MESSAGE | BLOCK_SUICIDE + area_flags = NOTELEPORT | NO_DEATH_MESSAGE | BLOCK_SUICIDE /area/centcom/ctf/control_room name = "Control Room A" @@ -284,7 +281,7 @@ ambience_index = AMBIENCE_RUINS always_unpowered = FALSE requires_power = TRUE - area_flags = UNIQUE_AREA | BLOBS_ALLOWED + area_flags = BLOBS_ALLOWED /area/centcom/asteroid/nearstation/bomb_site name = "\improper Bomb Testing Asteroid" diff --git a/code/game/area/areas/mining.dm b/code/game/area/areas/mining.dm index 16b78dcd5a73..176ffecef0df 100644 --- a/code/game/area/areas/mining.dm +++ b/code/game/area/areas/mining.dm @@ -3,7 +3,8 @@ icon = 'icons/area/areas_station.dmi' icon_state = "mining" has_gravity = STANDARD_GRAVITY - area_flags = VALID_TERRITORY | UNIQUE_AREA | FLORA_ALLOWED | CULT_PERMITTED + area_flags = VALID_TERRITORY | CULT_PERMITTED + area_flags_mapping = UNIQUE_AREA | FLORA_ALLOWED ambient_buzz = 'sound/ambience/magma.ogg' /area/mine/lobby @@ -132,7 +133,7 @@ icon_state = "mining" has_gravity = STANDARD_GRAVITY flags_1 = NONE - area_flags = VALID_TERRITORY | UNIQUE_AREA | FLORA_ALLOWED + area_flags = VALID_TERRITORY | FLORA_ALLOWED sound_environment = SOUND_AREA_LAVALAND ambient_buzz = 'sound/ambience/magma.ogg' @@ -145,7 +146,7 @@ power_light = FALSE requires_power = TRUE ambience_index = AMBIENCE_MINING - area_flags = VALID_TERRITORY | UNIQUE_AREA + area_flags = VALID_TERRITORY /area/lavaland/underground name = "Lavaland Caves" @@ -156,7 +157,6 @@ power_equip = FALSE power_light = FALSE ambience_index = AMBIENCE_MINING - area_flags = VALID_TERRITORY | UNIQUE_AREA | FLORA_ALLOWED /area/lavaland/surface/outdoors name = "Lavaland Wastes" @@ -164,12 +164,14 @@ /area/lavaland/surface/outdoors/unexplored //monsters and ruins spawn here icon_state = "unexplored" - area_flags = VALID_TERRITORY | UNIQUE_AREA | CAVES_ALLOWED | FLORA_ALLOWED | MOB_SPAWN_ALLOWED + area_flags = VALID_TERRITORY + area_flags_mapping = UNIQUE_AREA | CAVES_ALLOWED | FLORA_ALLOWED | MOB_SPAWN_ALLOWED map_generator = /datum/map_generator/cave_generator/lavaland /area/lavaland/surface/outdoors/unexplored/danger //megafauna will also spawn here icon_state = "danger" - area_flags = VALID_TERRITORY | UNIQUE_AREA | CAVES_ALLOWED | FLORA_ALLOWED | MOB_SPAWN_ALLOWED | MEGAFAUNA_SPAWN_ALLOWED + area_flags = VALID_TERRITORY + area_flags_mapping = parent_type::area_flags_mapping | MEGAFAUNA_SPAWN_ALLOWED /// Same thing as parent, but uses a different map generator for the icemoon ruin that needs it. /area/lavaland/surface/outdoors/unexplored/danger/no_ruins @@ -177,9 +179,7 @@ /area/lavaland/surface/outdoors/explored name = "Lavaland Labor Camp" - area_flags = VALID_TERRITORY | UNIQUE_AREA - - + area_flags = VALID_TERRITORY /**********************Ice Moon Areas**************************/ @@ -188,7 +188,7 @@ icon_state = "mining" has_gravity = STANDARD_GRAVITY flags_1 = NONE - area_flags = UNIQUE_AREA | FLORA_ALLOWED + area_flags_mapping = UNIQUE_AREA | FLORA_ALLOWED ambience_index = AMBIENCE_ICEMOON sound_environment = SOUND_AREA_ICEMOON ambient_buzz = 'sound/ambience/magma.ogg' @@ -201,7 +201,6 @@ power_equip = FALSE power_light = FALSE requires_power = TRUE - area_flags = UNIQUE_AREA | FLORA_ALLOWED /area/icemoon/surface/outdoors // parent that defines if something is on the exterior of the station. name = "Icemoon Wastes" @@ -216,20 +215,20 @@ area_flags = MOB_SPAWN_ALLOWED | FLORA_ALLOWED//flip this on, the generator has already disabled dangerous fauna /area/icemoon/surface/outdoors/noteleport // for places like the cursed spring water - area_flags = UNIQUE_AREA | FLORA_ALLOWED | NOTELEPORT + area_flags_mapping = parent_type::area_flags_mapping | NOTELEPORT /area/icemoon/surface/outdoors/noruins // when you want random generation without the chance of getting ruins icon_state = "noruins" - area_flags = UNIQUE_AREA | FLORA_ALLOWED | MOB_SPAWN_ALLOWED | CAVES_ALLOWED - map_generator = /datum/map_generator/cave_generator/icemoon/surface/noruins + area_flags_mapping = parent_type::area_flags_mapping | MOB_SPAWN_ALLOWED | CAVES_ALLOWED + map_generator = /datum/map_generator/cave_generator/icemoon/surface/noruins /area/icemoon/surface/outdoors/labor_camp name = "Icemoon Labor Camp" - area_flags = UNIQUE_AREA + area_flags_mapping = /area::area_flags_mapping /area/icemoon/surface/outdoors/unexplored //monsters and ruins spawn here icon_state = "unexplored" - area_flags = UNIQUE_AREA | FLORA_ALLOWED | MOB_SPAWN_ALLOWED | CAVES_ALLOWED + area_flags_mapping = parent_type::area_flags_mapping | MOB_SPAWN_ALLOWED | CAVES_ALLOWED /area/icemoon/surface/outdoors/unexplored/rivers // rivers spawn here icon_state = "danger" @@ -239,10 +238,10 @@ . = ..() if(HAS_TRAIT(SSstation, STATION_TRAIT_FORESTED)) map_generator = /datum/map_generator/cave_generator/icemoon/surface/forested - area_flags |= MOB_SPAWN_ALLOWED //flip this on, the generator has already disabled dangerous fauna + area_flags_mapping |= MOB_SPAWN_ALLOWED //flip this on, the generator has already disabled dangerous fauna /area/icemoon/surface/outdoors/unexplored/rivers/no_monsters - area_flags = UNIQUE_AREA | FLORA_ALLOWED | CAVES_ALLOWED + area_flags_mapping = /area/icemoon/::area_flags_mapping | CAVES_ALLOWED /area/icemoon/underground name = "Icemoon Caves" @@ -252,16 +251,15 @@ power_environ = FALSE power_equip = FALSE power_light = FALSE - area_flags = UNIQUE_AREA | FLORA_ALLOWED /area/icemoon/underground/unexplored // mobs and megafauna and ruins spawn here name = "Icemoon Caves" icon_state = "unexplored" - area_flags = CAVES_ALLOWED | FLORA_ALLOWED | MOB_SPAWN_ALLOWED | MEGAFAUNA_SPAWN_ALLOWED + area_flags_mapping = CAVES_ALLOWED | FLORA_ALLOWED | MOB_SPAWN_ALLOWED | MEGAFAUNA_SPAWN_ALLOWED /area/icemoon/underground/unexplored/no_rivers icon_state = "norivers" - area_flags = CAVES_ALLOWED | FLORA_ALLOWED // same rules as "shoreline" turfs since we might need this to pull double-duty + area_flags_mapping = CAVES_ALLOWED | FLORA_ALLOWED // same rules as "shoreline" turfs since we might need this to pull double-duty map_generator = /datum/map_generator/cave_generator/icemoon /area/icemoon/underground/unexplored/rivers // rivers spawn here @@ -273,15 +271,15 @@ /area/icemoon/underground/unexplored/rivers/deep/shoreline //use this for when you don't want mobs to spawn in certain areas in the "deep" portions. Think adjacent to rivers or station structures. icon_state = "shore" - area_flags = UNIQUE_AREA | CAVES_ALLOWED | FLORA_ALLOWED + area_flags_mapping = /area/icemoon/::area_flags_mapping | CAVES_ALLOWED /area/icemoon/underground/explored // ruins can't spawn here name = "Icemoon Underground" - area_flags = UNIQUE_AREA + area_flags_mapping = /area::area_flags_mapping /area/icemoon/underground/explored/graveyard name = "Graveyard" - area_flags = UNIQUE_AREA + area_flags_mapping = /area::area_flags_mapping ambience_index = AMBIENCE_SPOOKY icon = 'icons/area/areas_station.dmi' icon_state = "graveyard" diff --git a/code/game/area/areas/misc.dm b/code/game/area/areas/misc.dm index 75896f784618..6be58235d488 100644 --- a/code/game/area/areas/misc.dm +++ b/code/game/area/areas/misc.dm @@ -11,7 +11,7 @@ power_light = FALSE power_equip = FALSE power_environ = FALSE - area_flags = UNIQUE_AREA + area_flags = NO_GRAVITY outdoors = TRUE ambience_index = AMBIENCE_SPACE flags_1 = CAN_BE_DIRTY_1 diff --git a/code/game/area/areas/ruins/_ruins.dm b/code/game/area/areas/ruins/_ruins.dm index 69e17d365caf..0cbee32b4715 100644 --- a/code/game/area/areas/ruins/_ruins.dm +++ b/code/game/area/areas/ruins/_ruins.dm @@ -5,7 +5,7 @@ icon = 'icons/area/areas_ruins.dmi' icon_state = "ruins" has_gravity = STANDARD_GRAVITY - area_flags = HIDDEN_AREA | BLOBS_ALLOWED | UNIQUE_AREA + area_flags = HIDDEN_AREA | BLOBS_ALLOWED ambience_index = AMBIENCE_RUINS flags_1 = CAN_BE_DIRTY_1 sound_environment = SOUND_ENVIRONMENT_STONEROOM diff --git a/code/game/area/areas/ruins/icemoon.dm b/code/game/area/areas/ruins/icemoon.dm index 8afa3fae2d08..7ef689c61d91 100644 --- a/code/game/area/areas/ruins/icemoon.dm +++ b/code/game/area/areas/ruins/icemoon.dm @@ -45,6 +45,13 @@ /area/ruin/pizzeria/kitchen name = "\improper Moffuchi's Kitchen" +/area/ruin/syndibiodome + name = "\improper Syndicate Biodome" + sound_environment = SOUND_AREA_SMALL_ENCLOSED + ambience_index = AMBIENCE_DANGER + area_flags = NOTELEPORT + area_flags_mapping = NONE + /area/ruin/planetengi name = "\improper Engineering Outpost" @@ -66,3 +73,46 @@ /area/ruin/powered/hermit name = "\improper Hermit's Cabin" +/area/ruin/syndielab + name = "\improper Syndicate Lab" + ambience_index = AMBIENCE_DANGER + sound_environment = SOUND_ENVIRONMENT_CAVE + +/area/ruin/outpost31 + name = "\improper Outpost 31" + sound_environment = SOUND_AREA_SMALL_ENCLOSED + mood_bonus = -10 + mood_message = "Something very bad happened here..." + +/area/ruin/outpost31/medical + name = "\improper Outpost 31 Medical" + +/area/ruin/outpost31/kitchendiningroom + name = "\improper Outpost 31 Kitchen-Dining Room" + +/area/ruin/outpost31/kennel + name = "\improper Outpost 31 Kennel" + +/area/ruin/outpost31/radiomap + name = "\improper Outpost 31 Radio-Map Room" + +/area/ruin/outpost31/lab + name = "\improper Outpost 31 Lab" + area_flags = NOTELEPORT //megafauna arena + area_flags_mapping = NONE + requires_power = FALSE + +/area/ruin/outpost31/lootroom + name = "\improper Outpost 31 Secondary Storage" + area_flags = NOTELEPORT //megafauna loot room + area_flags_mapping = NONE + requires_power = FALSE + +/area/ruin/outpost31/recroom + name = "\improper Outpost 31 Rec Room" + +/area/ruin/outpost31/crewquarters + name = "\improper Outpost 31 Sleeping Quarters" + +/area/ruin/outpost31/commander_room + name = "\improper Outpost 31 Station Commander Office" diff --git a/code/game/area/areas/ruins/lavaland.dm b/code/game/area/areas/ruins/lavaland.dm index 8a95cf2ba006..047f5cdc9e65 100644 --- a/code/game/area/areas/ruins/lavaland.dm +++ b/code/game/area/areas/ruins/lavaland.dm @@ -43,6 +43,7 @@ /area/ruin/unpowered/cultaltar name = "\improper Cult Altar" area_flags = CULT_PERMITTED + area_flags_mapping = NONE ambience_index = AMBIENCE_SPOOKY //Syndicate lavaland base diff --git a/code/game/area/areas/ruins/space.dm b/code/game/area/areas/ruins/space.dm index f0bd865c2097..6da16019775a 100644 --- a/code/game/area/areas/ruins/space.dm +++ b/code/game/area/areas/ruins/space.dm @@ -2,7 +2,8 @@ /area/ruin/space has_gravity = FALSE - area_flags = UNIQUE_AREA + area_flags = NONE + area_flags_mapping = UNIQUE_AREA /area/ruin/space/unpowered always_unpowered = TRUE @@ -16,11 +17,10 @@ /area/ruin/space/has_grav/powered requires_power = FALSE - // Ruin solars define, /area/solars was moved to /area/station/solars, causing the solars specific areas to lose their properties /area/ruin/space/solars requires_power = FALSE - area_flags = UNIQUE_AREA + area_flags = NONE flags_1 = NONE ambience_index = AMBIENCE_ENGI airlock_wires = /datum/wires/airlock/engineering @@ -310,7 +310,7 @@ icon = 'icons/area/areas_ruins.dmi' // Solars inheriet areas_misc.dmi, not areas_ruin.dmi icon_state = "os_charlie_solars" requires_power = FALSE - area_flags = UNIQUE_AREA + area_flags = NONE sound_environment = SOUND_AREA_SPACE /area/ruin/space/ancientstation/charlie/storage @@ -534,8 +534,8 @@ /area/ruin/space/djstation/solars name = "\improper DJ Station Solars" icon_state = "DJ" - area_flags = UNIQUE_AREA - has_gravity = STANDARD_GRAVITY + area_flags = NONE + default_gravity = ZERO_GRAVITY /area/ruin/space/djstation/service name = "\improper DJ Station Service" @@ -582,7 +582,7 @@ /area/ruin/space/has_grav/hellfactoryoffice name = "\improper Hell Factory Office" - area_flags = VALID_TERRITORY | BLOBS_ALLOWED | UNIQUE_AREA | NOTELEPORT + area_flags = VALID_TERRITORY | BLOBS_ALLOWED | NOTELEPORT //Ruin of Spinward Smoothies @@ -600,7 +600,7 @@ /area/ruin/space/has_grav/powered/biooutpost name = "\improper Bioresearch Outpost" - area_flags = UNIQUE_AREA | NOTELEPORT + area_flags = NOTELEPORT /area/ruin/space/has_grav/powered/biooutpost/vault name = "\improper Bioresearch Outpost Secure Testing" diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm index 286398dd7acc..5854f6ebf045 100644 --- a/code/game/area/areas/shuttles.dm +++ b/code/game/area/areas/shuttles.dm @@ -10,6 +10,7 @@ always_unpowered = FALSE // Loading the same shuttle map at a different time will produce distinct area instances. area_flags = NONE + area_flags_mapping = NONE icon = 'icons/area/areas_station.dmi' icon_state = "shuttle" flags_1 = CAN_BE_DIRTY_1 @@ -108,11 +109,9 @@ static_lighting = FALSE base_lighting_alpha = 255 - /area/shuttle/arrival name = "Arrival Shuttle" - area_flags = UNIQUE_AREA// SSjob refers to this area for latejoiners - + area_flags_mapping = UNIQUE_AREA // SSjob refers to this area for latejoiners /area/shuttle/arrival/on_joining_game(mob/living/boarder) if(SSshuttle.arrivals?.mode == SHUTTLE_CALL) @@ -121,22 +120,17 @@ boarder.playsound_local(get_turf(boarder), 'sound/voice/ApproachingTG.ogg', 25) boarder.update_parallax_teleport() - /area/shuttle/pod_1 name = "Escape Pod One" - area_flags = NONE /area/shuttle/pod_2 name = "Escape Pod Two" - area_flags = NONE /area/shuttle/pod_3 name = "Escape Pod Three" - area_flags = NONE /area/shuttle/pod_4 name = "Escape Pod Four" - area_flags = NONE /area/shuttle/mining name = "Mining Shuttle" @@ -154,10 +148,9 @@ /area/shuttle/escape name = "Emergency Shuttle" - area_flags = BLOBS_ALLOWED + area_flags = CULT_PERMITTED area_limited_icon_smoothing = /area/shuttle/escape flags_1 = CAN_BE_DIRTY_1 - area_flags = CULT_PERMITTED /area/shuttle/escape/backup name = "Backup Emergency Shuttle" diff --git a/code/game/area/areas/station/common.dm b/code/game/area/areas/station/common.dm index 2280215ab6ee..1b6eba4dc25e 100644 --- a/code/game/area/areas/station/common.dm +++ b/code/game/area/areas/station/common.dm @@ -2,7 +2,7 @@ name = "\improper Crew Facilities" icon_state = "commons" sound_environment = SOUND_AREA_STANDARD_STATION - area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + area_flags = BLOBS_ALLOWED | CULT_PERMITTED /* * Dorm Areas diff --git a/code/game/area/areas/station/engineering.dm b/code/game/area/areas/station/engineering.dm index abd10c78f579..e34487a81823 100644 --- a/code/game/area/areas/station/engineering.dm +++ b/code/game/area/areas/station/engineering.dm @@ -54,7 +54,7 @@ /area/station/engineering/atmospherics_engine name = "\improper Atmospherics Engine" icon_state = "atmos_engine" - area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + area_flags = BLOBS_ALLOWED | CULT_PERMITTED /area/station/engineering/lobby name = "\improper Engineering Lobby" @@ -63,7 +63,7 @@ /area/station/engineering/supermatter name = "\improper Supermatter Engine" icon_state = "engine_sm" - area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + area_flags = BLOBS_ALLOWED | CULT_PERMITTED sound_environment = SOUND_AREA_SMALL_ENCLOSED /area/station/engineering/supermatter/waste @@ -110,7 +110,7 @@ /area/station/engineering/storage/tcomms name = "Telecomms Storage" icon_state = "tcom_storage" - area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + area_flags = BLOBS_ALLOWED | CULT_PERMITTED /* * Construction Areas diff --git a/code/game/area/areas/station/hallway.dm b/code/game/area/areas/station/hallway.dm index 2283614ade06..15b23b057618 100644 --- a/code/game/area/areas/station/hallway.dm +++ b/code/game/area/areas/station/hallway.dm @@ -84,7 +84,7 @@ /area/station/hallway/secondary/entry name = "\improper Arrival Shuttle Hallway" icon_state = "entry" - area_flags = UNIQUE_AREA | EVENT_PROTECTED + area_flags = EVENT_PROTECTED ambience_index = AMBIENCE_ARRIVALS /area/station/hallway/secondary/dock diff --git a/code/game/area/areas/station/maintenance.dm b/code/game/area/areas/station/maintenance.dm index 5e636719e7a0..93919536c58b 100644 --- a/code/game/area/areas/station/maintenance.dm +++ b/code/game/area/areas/station/maintenance.dm @@ -1,7 +1,7 @@ /area/station/maintenance name = "Generic Maintenance" ambience_index = AMBIENCE_MAINT - area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED | PERSISTENT_ENGRAVINGS + area_flags = BLOBS_ALLOWED | CULT_PERMITTED | PERSISTENT_ENGRAVINGS airlock_wires = /datum/wires/airlock/maint sound_environment = SOUND_AREA_TUNNEL_ENCLOSED forced_ambience = TRUE @@ -94,7 +94,7 @@ /area/station/maintenance/department/science/xenobiology name = "Xenobiology Maintenance" icon_state = "xenomaint" - area_flags = VALID_TERRITORY | BLOBS_ALLOWED | UNIQUE_AREA | XENOBIOLOGY_COMPATIBLE | CULT_PERMITTED + area_flags = VALID_TERRITORY | BLOBS_ALLOWED | XENOBIOLOGY_COMPATIBLE | CULT_PERMITTED /* * Generic Maintenance Tunnels diff --git a/code/game/area/areas/station/misc.dm b/code/game/area/areas/station/misc.dm index fbbe0e8a5120..6dae32737a51 100644 --- a/code/game/area/areas/station/misc.dm +++ b/code/game/area/areas/station/misc.dm @@ -30,5 +30,5 @@ power_light = FALSE requires_power = TRUE ambience_index = AMBIENCE_MINING - area_flags = UNIQUE_AREA + area_flags = NONE outdoors = TRUE diff --git a/code/game/area/areas/station/science.dm b/code/game/area/areas/station/science.dm index 2787a4ff87fe..74da9b6eccbe 100644 --- a/code/game/area/areas/station/science.dm +++ b/code/game/area/areas/station/science.dm @@ -110,20 +110,20 @@ /area/station/science/ordnance/burnchamber name = "\improper Ordnance Burn Chamber" icon_state = "ord_burn" - area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + area_flags = BLOBS_ALLOWED | CULT_PERMITTED /area/station/science/ordnance/freezerchamber name = "\improper Ordnance Freezer Chamber" icon_state = "ord_freeze" - area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + area_flags = BLOBS_ALLOWED | CULT_PERMITTED // Room for equipments and such /area/station/science/ordnance/testlab name = "\improper Ordnance Testing Lab" icon_state = "ord_test" - area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + area_flags = BLOBS_ALLOWED | CULT_PERMITTED /area/station/science/ordnance/bomb name = "\improper Ordnance Bomb Site" icon_state = "ord_boom" - area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + area_flags = BLOBS_ALLOWED| CULT_PERMITTED | NO_GRAVITY diff --git a/code/game/area/areas/station/security.dm b/code/game/area/areas/station/security.dm index 5a871e3e90c9..a1232e503f14 100644 --- a/code/game/area/areas/station/security.dm +++ b/code/game/area/areas/station/security.dm @@ -185,7 +185,7 @@ /area/station/security/prison name = "\improper Prison Wing" icon_state = "sec_prison" - area_flags = VALID_TERRITORY | BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED | PERSISTENT_ENGRAVINGS + area_flags = VALID_TERRITORY | BLOBS_ALLOWED | CULT_PERMITTED | PERSISTENT_ENGRAVINGS //Rad proof /area/station/security/prison/toilet diff --git a/code/game/area/areas/station/service.dm b/code/game/area/areas/station/service.dm index 23fe166616d4..355406563264 100644 --- a/code/game/area/areas/station/service.dm +++ b/code/game/area/areas/station/service.dm @@ -77,7 +77,7 @@ mood_bonus = 5 mood_message = "I love being in the library!" mood_trait = TRAIT_INTROVERT - area_flags = CULT_PERMITTED | BLOBS_ALLOWED | UNIQUE_AREA + area_flags = CULT_PERMITTED | BLOBS_ALLOWED sound_environment = SOUND_AREA_LARGE_SOFTFLOOR /area/station/service/library/garden @@ -180,7 +180,7 @@ /area/station/service/janitor name = "\improper Custodial Closet" icon_state = "janitor" - area_flags = CULT_PERMITTED | BLOBS_ALLOWED | UNIQUE_AREA + area_flags = CULT_PERMITTED | BLOBS_ALLOWED sound_environment = SOUND_AREA_SMALL_ENCLOSED /area/station/service/barber diff --git a/code/game/area/areas/station/solars.dm b/code/game/area/areas/station/solars.dm index 57376e2fb17b..2b0bffc8b503 100644 --- a/code/game/area/areas/station/solars.dm +++ b/code/game/area/areas/station/solars.dm @@ -5,7 +5,7 @@ /area/station/solars icon_state = "panels" requires_power = FALSE - area_flags = UNIQUE_AREA + area_flags = NO_GRAVITY flags_1 = NONE ambience_index = AMBIENCE_ENGI airlock_wires = /datum/wires/airlock/engineering @@ -40,10 +40,20 @@ name = "\improper Starboard Bow Solar Array" icon_state = "panelsFS" +/area/station/solars/starboard/fore/asteriod + name = "\improper Starboard Bow Asteriod Solar Array" + icon_state = "panelsFS" + area_flags = NONE // solar areas directly on asteriod have gravity + /area/station/solars/port name = "\improper Port Solar Array" icon_state = "panelsP" +/area/station/solars/port/asteriod + name = "\improper Port Asteriod Solar Array" + icon_state = "panelsP" + area_flags = NONE // solar areas directly on asteriod have gravity + /area/station/solars/port/aft name = "\improper Port Quarter Solar Array" icon_state = "panelsAP" diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm index bc6cb750b219..6782dba73af6 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm @@ -87,7 +87,7 @@ GLOBAL_LIST_EMPTY(heretic_sacrifice_landmarks) has_gravity = STANDARD_GRAVITY ambience_index = AMBIENCE_SPOOKY sound_environment = SOUND_ENVIRONMENT_CAVE - area_flags = UNIQUE_AREA | NOTELEPORT | HIDDEN_AREA | BLOCK_SUICIDE + area_flags = NOTELEPORT | HIDDEN_AREA | BLOCK_SUICIDE | NO_BOH /area/centcom/heretic_sacrifice/Initialize(mapload) if(!ambientsounds) diff --git a/code/modules/awaymissions/cordon.dm b/code/modules/awaymissions/cordon.dm index 285d0d49e105..15e418afbf2c 100644 --- a/code/modules/awaymissions/cordon.dm +++ b/code/modules/awaymissions/cordon.dm @@ -58,7 +58,7 @@ icon_state = "cordon" static_lighting = FALSE base_lighting_alpha = 255 - area_flags = UNIQUE_AREA|NOTELEPORT|HIDDEN_AREA + area_flags = NOTELEPORT|HIDDEN_AREA requires_power = FALSE /area/misc/cordon/Entered(atom/movable/arrived, area/old_area) diff --git a/code/modules/basketball/basketball_map_loading.dm b/code/modules/basketball/basketball_map_loading.dm index 469167367c0f..18022004f08b 100644 --- a/code/modules/basketball/basketball_map_loading.dm +++ b/code/modules/basketball/basketball_map_loading.dm @@ -33,7 +33,7 @@ base_lighting_alpha = 255 has_gravity = STANDARD_GRAVITY flags_1 = NONE - area_flags = UNIQUE_AREA | NOTELEPORT | NO_DEATH_MESSAGE | BLOCK_SUICIDE + area_flags = NOTELEPORT | NO_DEATH_MESSAGE | BLOCK_SUICIDE /datum/map_template/basketball should_place_on_top = FALSE diff --git a/code/modules/bitrunning/areas.dm b/code/modules/bitrunning/areas.dm index 0656f9d65b38..45c64d92389c 100644 --- a/code/modules/bitrunning/areas.dm +++ b/code/modules/bitrunning/areas.dm @@ -14,7 +14,8 @@ name = "Virtual Domain Ruins" icon_state = "bit_ruin" icon = 'icons/area/areas_station.dmi' - area_flags = UNIQUE_AREA | NOTELEPORT | EVENT_PROTECTED | HIDDEN_AREA | UNLIMITED_FISHING + area_flags = LOCAL_TELEPORT | EVENT_PROTECTED | HIDDEN_AREA | UNLIMITED_FISHING + area_flags_mapping = VIRTUAL_AREA has_gravity = STANDARD_GRAVITY requires_power = FALSE @@ -26,7 +27,8 @@ /area/virtual_domain/safehouse name = "Virtual Domain Safehouse" - area_flags = UNIQUE_AREA | NOTELEPORT | EVENT_PROTECTED | VIRTUAL_SAFE_AREA | UNLIMITED_FISHING + area_flags = LOCAL_TELEPORT | EVENT_PROTECTED | UNLIMITED_FISHING + area_flags_mapping = UNIQUE_AREA | VIRTUAL_AREA | VIRTUAL_SAFE_AREA icon_state = "bit_safe" requires_power = FALSE sound_environment = SOUND_ENVIRONMENT_ROOM @@ -36,32 +38,49 @@ /area/lavaland/surface/outdoors/virtual_domain name = "Virtual Domain Lava Ruins" icon_state = "bit_ruin" - area_flags = UNIQUE_AREA | NOTELEPORT | EVENT_PROTECTED | HIDDEN_AREA | UNLIMITED_FISHING + area_flags = /area/virtual_domain::area_flags + area_flags_mapping = /area/virtual_domain::area_flags_mapping /area/icemoon/underground/explored/virtual_domain name = "Virtual Domain Ice Ruins" icon_state = "bit_ice" - area_flags = UNIQUE_AREA | NOTELEPORT | EVENT_PROTECTED | HIDDEN_AREA | UNLIMITED_FISHING + area_flags = /area/virtual_domain::area_flags + area_flags_mapping = /area/virtual_domain::area_flags_mapping /area/ruin/space/virtual_domain name = "Virtual Domain Unexplored Location" icon = 'icons/area/areas_station.dmi' icon_state = "bit_ruin" - area_flags = UNIQUE_AREA | NOTELEPORT | EVENT_PROTECTED | HIDDEN_AREA | UNLIMITED_FISHING + area_flags = /area/virtual_domain::area_flags + area_flags_mapping = /area/virtual_domain::area_flags_mapping /area/space/virtual_domain name = "Virtual Domain Space" icon = 'icons/area/areas_station.dmi' icon_state = "bit_space" - area_flags = UNIQUE_AREA | NOTELEPORT | EVENT_PROTECTED | HIDDEN_AREA | UNLIMITED_FISHING + area_flags = /area/virtual_domain::area_flags + area_flags_mapping = /area/virtual_domain::area_flags_mapping ///Areas that virtual entities should not be in /area/virtual_domain/protected_space name = "Virtual Domain Safe Zone" - area_flags = UNIQUE_AREA | NOTELEPORT | EVENT_PROTECTED | VIRTUAL_SAFE_AREA | UNLIMITED_FISHING + area_flags = /area/virtual_domain/safehouse::area_flags + area_flags_mapping = /area/virtual_domain/safehouse::area_flags_mapping icon_state = "bit_safe" /area/virtual_domain/protected_space/fullbright static_lighting = FALSE base_lighting_alpha = 255 + +/// A hash lookup list of all the virtual area types +GLOBAL_LIST_INIT_TYPED(virtual_areas, /area, populate_virtual_areas()) + +/// Constructs the list of virtual areas +/proc/populate_virtual_areas() + RETURN_TYPE(/list/area) + var/list/area/virtual_areas = list() + for(var/area/area_type as anything in subtypesof(/area)) + if (area_type::area_flags_mapping & VIRTUAL_AREA) + virtual_areas[area_type] = TRUE + return virtual_areas diff --git a/code/modules/bitrunning/components/virtual_entity.dm b/code/modules/bitrunning/components/virtual_entity.dm index db81f376a809..83e8141db43e 100644 --- a/code/modules/bitrunning/components/virtual_entity.dm +++ b/code/modules/bitrunning/components/virtual_entity.dm @@ -22,7 +22,7 @@ stack_trace("Virtual entity entered a location with no area!") return - if(location_area.area_flags & VIRTUAL_SAFE_AREA) + if(location_area.area_flags_mapping & VIRTUAL_SAFE_AREA) source.balloon_alert(source, "out of bounds!") COOLDOWN_START(src, OOB_cooldown, 2 SECONDS) return COMPONENT_MOVABLE_BLOCK_PRE_MOVE diff --git a/code/modules/mapfluff/ruins/spaceruin_code/anomalyresearch.dm b/code/modules/mapfluff/ruins/spaceruin_code/anomalyresearch.dm index 43393b28d344..ae8a10f8217e 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/anomalyresearch.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/anomalyresearch.dm @@ -75,7 +75,7 @@ icon = 'icons/area/areas_ruins.dmi' icon_state = "anomaly_research" requires_power = FALSE - area_flags = HIDDEN_AREA | UNIQUE_AREA + area_flags = HIDDEN_AREA has_gravity = TRUE /obj/item/reagent_containers/cup/bottle/wittel diff --git a/code/modules/mapfluff/ruins/spaceruin_code/forgottenship.dm b/code/modules/mapfluff/ruins/spaceruin_code/forgottenship.dm index 6522fad5b29a..f3cef4a5cfd5 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/forgottenship.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/forgottenship.dm @@ -133,4 +133,4 @@ GLOBAL_VAR_INIT(fscpassword, generate_password()) name = "Syndicate Forgotten Vault" icon_state = "syndie-ship" ambientsounds = list('sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg') - area_flags = NOTELEPORT | UNIQUE_AREA + area_flags = NOTELEPORT diff --git a/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm index 0693cc1cdc4f..cd8ed00928a3 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm @@ -392,6 +392,7 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) requires_power = FALSE has_gravity = TRUE area_flags = NOTELEPORT | HIDDEN_AREA + area_flags_mapping = NONE static_lighting = TRUE ambientsounds = list('sound/ambience/servicebell.ogg') var/roomnumber = 0 @@ -480,7 +481,8 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) icon = 'icons/area/areas_ruins.dmi' icon_state = "hilbertshotel" requires_power = FALSE - area_flags = HIDDEN_AREA | NOTELEPORT | UNIQUE_AREA + area_flags = HIDDEN_AREA | NOTELEPORT + area_flags_mapping = UNIQUE_AREA has_gravity = TRUE /obj/item/abstracthotelstorage @@ -514,7 +516,7 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) name = "Hilbert Research Facility" /area/ruin/space/has_grav/powered/hilbertresearchfacility/secretroom - area_flags = UNIQUE_AREA | NOTELEPORT | HIDDEN_AREA + area_flags = NOTELEPORT | HIDDEN_AREA /obj/item/analyzer/hilbertsanalyzer name = "custom rigged analyzer" diff --git a/code/modules/mining/equipment/survival_pod.dm b/code/modules/mining/equipment/survival_pod.dm index f47d2cfd938b..d8a74804ec77 100644 --- a/code/modules/mining/equipment/survival_pod.dm +++ b/code/modules/mining/equipment/survival_pod.dm @@ -5,7 +5,7 @@ static_lighting = TRUE requires_power = FALSE has_gravity = STANDARD_GRAVITY - area_flags = BLOBS_ALLOWED | UNIQUE_AREA + area_flags = BLOBS_ALLOWED flags_1 = CAN_BE_DIRTY_1 //Survival Capsule diff --git a/code/modules/mob/living/silicon/ai/multicam.dm b/code/modules/mob/living/silicon/ai/multicam.dm index 1f001a4bf054..84703e0c6c60 100644 --- a/code/modules/mob/living/silicon/ai/multicam.dm +++ b/code/modules/mob/living/silicon/ai/multicam.dm @@ -103,7 +103,7 @@ static_lighting = FALSE base_lighting_alpha = 255 - area_flags = NOTELEPORT | HIDDEN_AREA | UNIQUE_AREA + area_flags = NOTELEPORT | HIDDEN_AREA ambientsounds = null flags_1 = NONE diff --git a/code/modules/modular_computers/file_system/programs/nt_pay.dm b/code/modules/modular_computers/file_system/programs/nt_pay.dm index 4e3fa5d3fb71..ba4b638598a2 100644 --- a/code/modules/modular_computers/file_system/programs/nt_pay.dm +++ b/code/modules/modular_computers/file_system/programs/nt_pay.dm @@ -60,6 +60,11 @@ SEND_SIGNAL(computer, COMSIG_MODULAR_COMPUTER_NT_PAY_RESULT, payment_result) /datum/computer_file/program/nt_pay/proc/_pay(token, money_to_send, mob/user) + var/area/user_area = get_area(user) + if(user_area && is_area_virtual(user_area)) + to_chat(user, span_notice("You cannot send virtual money to real accounts.")) + return NT_PAY_STATUS_NO_ACCOUNT + money_to_send = round(money_to_send) if(IS_DEPARTMENTAL_ACCOUNT(current_user)) From 9a92cdae1c4cb09c32575173cf6181d52f820d83 Mon Sep 17 00:00:00 2001 From: Fghj240 <43589874+Fghj240@users.noreply.github.com> Date: Tue, 25 Nov 2025 18:49:21 -0800 Subject: [PATCH 22/45] Netpods can scan the dna of their occupants to create a matching bit avatar (#94052) ## About The Pull Request Alt click a netpod to enable scanning, which scans your dna and makes newly created bit avatars look more like you. If the netpod already has an associated bit avatar it doesn't do anything. Purely visual/audial if you count tts, it doesn't let you switch races. ## Why It's Good For The Game Bitrunners can recognize their teammates. ## Changelog :cl: add: Netpods can now scan their occupants to create more accurate bit avatars /:cl: --------- Co-authored-by: Fghj240 --- .../components/avatar_connection.dm | 10 ++++ code/modules/bitrunning/netpod/_netpod.dm | 46 +++++++++++++------ code/modules/bitrunning/netpod/utils.dm | 1 + 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/code/modules/bitrunning/components/avatar_connection.dm b/code/modules/bitrunning/components/avatar_connection.dm index 1891a4689974..c4b6543f487b 100644 --- a/code/modules/bitrunning/components/avatar_connection.dm +++ b/code/modules/bitrunning/components/avatar_connection.dm @@ -20,6 +20,7 @@ obj/machinery/quantum_server/server, obj/machinery/netpod/pod, help_text, + copy_body, ) if(!isliving(parent) || !isliving(old_body) || !server.is_operational || !pod.is_operational) @@ -67,6 +68,15 @@ if(alias && avatar.real_name != alias) avatar.fully_replace_character_name(newname = alias) + avatar.voice = old_body.voice + avatar.voice_filter = old_body.voice_filter + if(ishuman(avatar) && ishuman(old_body) && copy_body) + var/mob/living/carbon/human/human_avatar = avatar + var/mob/living/carbon/human/human_old_body = old_body + human_avatar.dna.unique_identity = human_old_body.dna.unique_identity + human_avatar.physique = human_old_body.physique + human_avatar.updateappearance(mutcolor_update = TRUE) + update_avatar_id() avatar.mind.set_assigned_role(SSjob.get_job_type(/datum/job/bit_avatar)) diff --git a/code/modules/bitrunning/netpod/_netpod.dm b/code/modules/bitrunning/netpod/_netpod.dm index e372f004255f..80c3bfc5b211 100644 --- a/code/modules/bitrunning/netpod/_netpod.dm +++ b/code/modules/bitrunning/netpod/_netpod.dm @@ -1,5 +1,5 @@ #define BASE_DISCONNECT_DAMAGE 40 - +#define SCANNING_TOGGLE_COOLDOWN 5 /obj/machinery/netpod name = "netpod" @@ -26,6 +26,10 @@ var/disconnect_damage /// Static list of outfits to select from var/list/cached_outfits = list() + /// Whether bit avatars become visually similar to their bitrunner on first creation + var/copy_body = FALSE + /// The next time copy_body can be toggled + var/scanning_can_toggle = 0 /obj/machinery/netpod/post_machine_initialize() @@ -53,21 +57,21 @@ if(isnull(held_item)) context[SCREENTIP_CONTEXT_LMB] = "Select Outfit" - return CONTEXTUAL_SCREENTIP_SET - - if(held_item.tool_behaviour == TOOL_SCREWDRIVER && !occupant && !state_open) - context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] Panel" - return CONTEXTUAL_SCREENTIP_SET - - if(held_item.tool_behaviour == TOOL_CROWBAR) - if(isnull(occupant)) - if(panel_open) - context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + else + if(held_item.tool_behaviour == TOOL_SCREWDRIVER && !occupant && !state_open) + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] Panel" + + if(held_item.tool_behaviour == TOOL_CROWBAR) + if(isnull(occupant)) + if(panel_open) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + else + context[SCREENTIP_CONTEXT_LMB] = "[state_open ? "Close" : "Open"] Cover" else - context[SCREENTIP_CONTEXT_LMB] = "[state_open ? "Close" : "Open"] Cover" - else - context[SCREENTIP_CONTEXT_LMB] = "Break out" - return CONTEXTUAL_SCREENTIP_SET + context[SCREENTIP_CONTEXT_LMB] = "Break out" + + context[SCREENTIP_CONTEXT_ALT_LMB] = "[copy_body ? "Disable" : "Enable"] Scan" + return CONTEXTUAL_SCREENTIP_SET /obj/machinery/netpod/examine(mob/user) . = ..() @@ -88,6 +92,9 @@ . += span_infoplain("Drag yourself into the pod to engage the link.") . += span_infoplain("It has limited resuscitation capabilities. Remaining in the pod can heal some injuries.") . += span_infoplain("It has a security system that will alert the occupant if it is tampered with.") + if(copy_body) + . += span_infoplain("Occupant scanning is currently enabled, which will cause bit avatars to look like the occupant when first created.") + . += span_infoplain("Alt-click to [copy_body ? "disable" : "enable"] occupant scanning.") if(isnull(occupant)) . += span_infoplain("It's currently unoccupied.") @@ -149,5 +156,14 @@ disconnect_damage = BASE_DISCONNECT_DAMAGE * (1 - source.servo_bonus) +/obj/machinery/netpod/click_alt(mob/user) + if(world.time < scanning_can_toggle) + return CLICK_ACTION_BLOCKING + copy_body = !copy_body + scanning_can_toggle = world.time + SCANNING_TOGGLE_COOLDOWN + playsound(src, 'sound/machines/click.ogg', 50, TRUE) + user.balloon_alert_to_viewers(user, "scanning [copy_body ? "enabled" : "disabled"]") + return CLICK_ACTION_SUCCESS #undef BASE_DISCONNECT_DAMAGE +#undef SCANNING_TOGGLE_COOLDOWN diff --git a/code/modules/bitrunning/netpod/utils.dm b/code/modules/bitrunning/netpod/utils.dm index 364c1a038172..b918c93eb60f 100644 --- a/code/modules/bitrunning/netpod/utils.dm +++ b/code/modules/bitrunning/netpod/utils.dm @@ -111,6 +111,7 @@ server = server, \ pod = src, \ help_text = generated_domain.help_text, \ + copy_body = copy_body, \ ) connected = TRUE From 7d8f55af3597f441036e4fb14f3088c44d7ad251 Mon Sep 17 00:00:00 2001 From: Hatterhat <31829017+Hatterhat@users.noreply.github.com> Date: Wed, 3 Dec 2025 04:27:30 -0600 Subject: [PATCH 23/45] no more non-virtual blood-drunk caches (#94023) Co-authored-by: Hatterhat --- .../bitrunning/util/virtual_megafauna.dm | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/code/modules/bitrunning/util/virtual_megafauna.dm b/code/modules/bitrunning/util/virtual_megafauna.dm index 2707db9e2e74..4daf00141978 100644 --- a/code/modules/bitrunning/util/virtual_megafauna.dm +++ b/code/modules/bitrunning/util/virtual_megafauna.dm @@ -6,8 +6,23 @@ true_spawn = FALSE + // rebuild the achievement element's arguments to remove it appropriately + if (achievement_type || score_achievement_type) + var/list/achievements = list(/datum/award/achievement/boss/boss_killer, /datum/award/score/boss_score) + if (achievement_type) + achievements += achievement_type + if (score_achievement_type) + achievements += score_achievement_type + RemoveElement(/datum/element/kill_achievement, string_list(achievements), crusher_achievement_type, /datum/memory/megafauna_slayer) + + // remove the crusher loot element's arguments also to remove it appropriately + RemoveElement(\ + /datum/element/crusher_loot,\ + trophy_type = crusher_loot,\ + guaranteed_drop = 0.6,\ + replace_all = replace_crusher_drop,\ + drop_immediately = del_on_death,\ + ) + loot.Cut() loot += /obj/structure/closet/crate/secure/bitrunning/encrypted - - crusher_loot.Cut() - crusher_loot += /obj/structure/closet/crate/secure/bitrunning/encrypted From ee101ddc4194bcb035a99a1bd6b647eac026f903 Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Sat, 27 Dec 2025 10:38:28 +0000 Subject: [PATCH 24/45] Escaped glitches distort when interacted with (#94556) --- code/datums/components/holographic_nature.dm | 6 ++++ code/modules/bitrunning/components/glitch.dm | 37 ++++++++++---------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/code/datums/components/holographic_nature.dm b/code/datums/components/holographic_nature.dm index 9f9dace845be..0be1cc75aa28 100644 --- a/code/datums/components/holographic_nature.dm +++ b/code/datums/components/holographic_nature.dm @@ -19,6 +19,8 @@ /datum/component/holographic_nature/RegisterWithParent() AddComponent(/datum/component/connect_loc_behalf, parent, loc_connections) if(isliving(parent)) + RegisterSignal(parent, COMSIG_LIVING_MOB_BUMP, PROC_REF(on_bumped)) + RegisterSignal(parent, COMSIG_LIVING_MOB_BUMPED, PROC_REF(on_bumped)) RegisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_mob_damaged)) return @@ -44,6 +46,10 @@ if(isprojectile(thing) || thing.density) apply_effects() +/datum/component/holographic_nature/proc/on_bumped(atom/movable/source, mob/living/crossing_mob) + SIGNAL_HANDLER + apply_effects() + /datum/component/holographic_nature/proc/apply_effects() if(!COOLDOWN_FINISHED(src, glitch_cooldown)) return diff --git a/code/modules/bitrunning/components/glitch.dm b/code/modules/bitrunning/components/glitch.dm index f2e13c80b0d7..df42fb4eaf66 100644 --- a/code/modules/bitrunning/components/glitch.dm +++ b/code/modules/bitrunning/components/glitch.dm @@ -1,35 +1,36 @@ /datum/component/glitch /// Ref of the spawning forge var/datum/weakref/forge_ref + var/datum/weakref/distort_component /datum/component/glitch/Initialize(obj/machinery/quantum_server/server, obj/machinery/byteforge/forge) if(!isliving(parent)) return COMPONENT_INCOMPATIBLE - RegisterSignal(forge, COMSIG_MACHINERY_POWER_RESTORED, PROC_REF(on_forge_power_restored)) - RegisterSignals(forge, list(COMSIG_MACHINERY_BROKEN, COMSIG_MACHINERY_POWER_LOST), PROC_REF(on_forge_broken)) - forge_ref = WEAKREF(forge) - var/mob/living/owner = parent - server.remove_threat(owner) // so the server doesn't dust us + var/health_boost = 0 + + if (forge) + RegisterSignal(forge, COMSIG_MACHINERY_POWER_RESTORED, PROC_REF(on_forge_power_restored)) + RegisterSignals(forge, list(COMSIG_MACHINERY_BROKEN, COMSIG_MACHINERY_POWER_LOST), PROC_REF(on_forge_broken)) + forge_ref = WEAKREF(forge) + server.remove_threat(owner) // so the server doesn't dust us + health_boost = ROUND_UP(server.threat * 0.2) owner.faction.Cut() owner.faction += list(ROLE_GLITCH) - var/current_max = owner.maxHealth + ROUND_UP(server.threat * 0.2) - owner.maxHealth = clamp(current_max, 200, 500) + owner.maxHealth = clamp(owner.maxHealth + health_boost, 200, 500) owner.fully_heal() + owner.create_digital_aura() - var/atom/thing = owner - thing.create_digital_aura() - -/// Sakujo -/datum/component/glitch/proc/dust_mob() - if(QDELETED(parent)) - return +/datum/component/glitch/RegisterWithParent() + RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + distort_component = WEAKREF(parent.AddComponent(/datum/component/holographic_nature)) - var/mob/living/owner = parent - owner.dust() +/datum/component/glitch/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_LIVING_DEATH) + QDEL_NULL(distort_component) /// We don't want digital entities just lingering around as corpses. /datum/component/glitch/proc/on_death() @@ -41,10 +42,10 @@ var/mob/living/owner = parent to_chat(owner, span_userdanger("You feel a strange sensation...")) - var/obj/machinery/byteforge/forge = forge_ref.resolve() + var/obj/machinery/byteforge/forge = forge_ref?.resolve() forge?.setup_particles() - addtimer(CALLBACK(src, PROC_REF(dust_mob)), 2 SECONDS, TIMER_UNIQUE|TIMER_DELETE_ME|TIMER_STOPPABLE) + owner.fade_into_nothing(3 SECONDS, 2 SECONDS) /// If the forge breaks, we take a massive slowdown /datum/component/glitch/proc/on_forge_broken(datum/source) From 6f1e8c2956070b1b88af1763177e0cd8fb325750 Mon Sep 17 00:00:00 2001 From: Leland Kemble <70413276+lelandkemble@users.noreply.github.com> Date: Fri, 29 May 2026 13:26:27 -0400 Subject: [PATCH 25/45] Bitrunning actors no longer take double damage, their real bodies take damage (#96138) ## About The Pull Request `def_zone` can hold a zone or a bodypart. In the case of a simple stabbing or shooting, it sends a bodypart. That bodypart is the bodypart of the domain actor. When we call `apply_damage()` on the real-world body, we send the bodypart of the domain actor in `def_zone`, which somehow has no runtimes or checks and ends up successfully dealing damage... to the actor. Again. This doesn't always shield the original body, as `def_zone` is not guaranteed to be sent as a bodypart(or at all), so you could be killed in real life by certain attacks still, but for the grand majority of attacks this meant that the real body was invincible and the actor took double. ## Why It's Good For The Game fixes #93081 ## Changelog :cl: fix: Bitrunners no longer take double damage in-domain, and do take damage to their real bodies /:cl: --- code/modules/bitrunning/components/avatar_connection.dm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/code/modules/bitrunning/components/avatar_connection.dm b/code/modules/bitrunning/components/avatar_connection.dm index c4b6543f487b..3ea01cf8a0b0 100644 --- a/code/modules/bitrunning/components/avatar_connection.dm +++ b/code/modules/bitrunning/components/avatar_connection.dm @@ -184,7 +184,11 @@ if(damage > 30 && prob(30)) INVOKE_ASYNC(old_body, TYPE_PROC_REF(/mob/living, emote), "scream") - old_body.apply_damage(damage, damage_type, def_zone, blocked, wound_bonus = CANT_WOUND) + var/zone = def_zone + if(isbodypart(def_zone)) // If the defined zone is a bodypart, then it is the bodypart of the domain avatar. We don't want that bodypart, we want the real body's bodypart, so we go back to zone. + zone = astype(def_zone, /obj/item/bodypart).body_zone + + old_body.apply_damage(damage, damage_type, zone, blocked, wound_bonus = CANT_WOUND) if(old_body.stat > SOFT_CRIT) // KO! full_avatar_disconnect(cause_damage = TRUE) From 32f1b026a541200fac96425bae66db74a295a4a2 Mon Sep 17 00:00:00 2001 From: Leland Kemble <70413276+lelandkemble@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:23:46 -0400 Subject: [PATCH 26/45] Bitrunners can't leave domains while glitches remain in them (#96171) ## About The Pull Request When bitrunners attempt to leave their domain via the ladder, they will fail if there is an active, living bitrunning glitch antagonist within the domain. Other methods of leaving such as death or taking the red pill are unaffected. they can also leave if the crate's delivered sorta a sister PR to #95934 ## Why It's Good For The Game enter domain -> get alert about bitrunning glitch -> leave -> turn off domain What a fun gameplay loop, for everyone involved. You'll fight the glitch and you'll like it. (liking it [pending](https://github.com/tgstation/tgstation/pull/96138)) You can still leave if you REALLY, REALLY need to at the cost of a little brain damage, but otherwise the literal only obstacle that will ever be in your path should be something you have to engage with. ## Changelog :cl: balance: Bitrunners can't back out of domains via the ladder while bitrunning glitches remain in them /:cl: --- code/modules/bitrunning/objects/hololadder.dm | 8 ++++++++ code/modules/bitrunning/server/_parent.dm | 2 ++ code/modules/bitrunning/server/loot.dm | 2 ++ code/modules/bitrunning/server/map_handling.dm | 1 + 4 files changed, 13 insertions(+) diff --git a/code/modules/bitrunning/objects/hololadder.dm b/code/modules/bitrunning/objects/hololadder.dm index 3df41a403e73..1335ef8b848b 100644 --- a/code/modules/bitrunning/objects/hololadder.dm +++ b/code/modules/bitrunning/objects/hololadder.dm @@ -64,6 +64,14 @@ if(isnull(user.mind)) return + var/obj/machinery/quantum_server/our_server = server_ref.resolve() + if(!our_server.domain_complete) + for(var/datum/weakref/ghostrole_weakref as anything in our_server.spawned_threat_refs) + var/mob/living/ghostrole = ghostrole_weakref.resolve() + if(ghostrole?.stat == CONSCIOUS && ghostrole.client && ghostrole.mind.has_antag_datum(/datum/antagonist/bitrunning_glitch)) + to_chat(user, span_danger("A being in the simulation is preventing your retreat. You must either complete your mission or remove the obstacle before safe exit will be possible.")) + return + if(!HAS_TRAIT(user, TRAIT_TEMPORARY_BODY)) balloon_alert(user, "no connection detected") return diff --git a/code/modules/bitrunning/server/_parent.dm b/code/modules/bitrunning/server/_parent.dm index c628cccc29d2..3562fd10de2a 100644 --- a/code/modules/bitrunning/server/_parent.dm +++ b/code/modules/bitrunning/server/_parent.dm @@ -16,6 +16,8 @@ var/datum/lazy_template/virtual_domain/generated_domain /// If the current domain was a random selection var/domain_randomized = FALSE + /// Whether the domain is finished, so the bitrunners can leave despite glitches + var/domain_complete = FALSE /// Prevents multiple user actions. Handled by loading domains and cooldowns var/is_ready = TRUE /// Chance multipled by threat to spawn a glitch diff --git a/code/modules/bitrunning/server/loot.dm b/code/modules/bitrunning/server/loot.dm index 957f195ae401..b1575389caf8 100644 --- a/code/modules/bitrunning/server/loot.dm +++ b/code/modules/bitrunning/server/loot.dm @@ -71,6 +71,8 @@ generated_domain.disk_reward_spawned = TRUE chosen_forge.start_to_spawn(reward_cache) + + domain_complete = TRUE return TRUE diff --git a/code/modules/bitrunning/server/map_handling.dm b/code/modules/bitrunning/server/map_handling.dm index c672de619ead..5a69dde3fd22 100644 --- a/code/modules/bitrunning/server/map_handling.dm +++ b/code/modules/bitrunning/server/map_handling.dm @@ -199,6 +199,7 @@ /// Stops the current virtual domain and disconnects all users /obj/machinery/quantum_server/proc/reset(fast = FALSE) is_ready = FALSE + domain_complete = FALSE sever_connections() From 57cc69ac062f63bfa02ecb0641c4a0233629c4da Mon Sep 17 00:00:00 2001 From: Thunder12345 Date: Thu, 30 Jul 2026 14:46:59 +0100 Subject: [PATCH 27/45] Fixes point-based domain resetting (#97210) --- code/modules/bitrunning/server/map_handling.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/modules/bitrunning/server/map_handling.dm b/code/modules/bitrunning/server/map_handling.dm index 5a69dde3fd22..65c4c9c91d88 100644 --- a/code/modules/bitrunning/server/map_handling.dm +++ b/code/modules/bitrunning/server/map_handling.dm @@ -237,6 +237,7 @@ creature.dust(just_ash = TRUE, force = TRUE) // sometimes mobs just don't die generated_domain.secondary_loot_generated = 0 + generated_domain.main_crate_points = 0 avatar_connection_refs.Cut() exit_turfs = list() From 225010baf9948292322de180109af740c4584ddc Mon Sep 17 00:00:00 2001 From: larentoun <31931237+larentoun@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:07:58 +0300 Subject: [PATCH 28/45] Bitrunning: falling into chasms returns back to body (#87512) ## About The Pull Request fixes https://github.com/tgstation/tgstation/issues/87419 When the avatar is qdel'd, the player returns to original body ## Why It's Good For The Game Less bugs good ## Changelog :cl: fix: Bitrunning: falling into chasms and other ways of destroying your virtual body instead of killing it, WILL now return you to your body /:cl: --- code/modules/bitrunning/components/avatar_connection.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/bitrunning/components/avatar_connection.dm b/code/modules/bitrunning/components/avatar_connection.dm index 3ea01cf8a0b0..54e07bda7dd8 100644 --- a/code/modules/bitrunning/components/avatar_connection.dm +++ b/code/modules/bitrunning/components/avatar_connection.dm @@ -110,7 +110,7 @@ */ RegisterSignals(parent, list(COMSIG_BITRUNNER_ALERT_SEVER, COMSIG_BITRUNNER_CACHE_SEVER, COMSIG_BITRUNNER_LADDER_SEVER), PROC_REF(on_safe_disconnect)) RegisterSignal(parent, COMSIG_LIVING_PILL_CONSUMED, PROC_REF(disconnect_if_red_pill)) - RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(on_sever_connection)) + RegisterSignals(parent, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING), PROC_REF(on_sever_connection)) RegisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_linked_damage)) From 498352bcf85c3e13547f910737f99faeb740bdb5 Mon Sep 17 00:00:00 2001 From: Critawakets Date: Wed, 19 Aug 2026 04:49:29 -0400 Subject: [PATCH 29/45] this happened --- code/modules/bitrunning/objects/netpod.dm | 482 ---------------------- 1 file changed, 482 deletions(-) delete mode 100644 code/modules/bitrunning/objects/netpod.dm diff --git a/code/modules/bitrunning/objects/netpod.dm b/code/modules/bitrunning/objects/netpod.dm deleted file mode 100644 index f6edeadefddb..000000000000 --- a/code/modules/bitrunning/objects/netpod.dm +++ /dev/null @@ -1,482 +0,0 @@ -#define BASE_DISCONNECT_DAMAGE 40 - -/obj/machinery/netpod - name = "netpod" - - base_icon_state = "netpod" - circuit = /obj/item/circuitboard/machine/netpod - desc = "A link to the netverse. It has an assortment of cables to connect yourself to a virtual domain." - icon = 'icons/obj/machines/bitrunning.dmi' - icon_state = "netpod" - max_integrity = 300 - obj_flags = BLOCKS_CONSTRUCTION - state_open = TRUE - interaction_flags_mouse_drop = NEED_HANDS | NEED_DEXTERITY - - /// Whether we have an ongoing connection - var/connected = FALSE - /// A player selected outfit by clicking the netpod - var/datum/outfit/netsuit = /datum/outfit/job/bitrunner - /// Holds this to see if it needs to generate a new one - var/datum/weakref/avatar_ref - /// The linked quantum server - var/datum/weakref/server_ref - /// The amount of brain damage done from force disconnects - var/disconnect_damage - /// Static list of outfits to select from - var/list/cached_outfits = list() - -/obj/machinery/netpod/post_machine_initialize() - . = ..() - - disconnect_damage = BASE_DISCONNECT_DAMAGE - find_server() - - RegisterSignal(src, COMSIG_ATOM_TAKE_DAMAGE, PROC_REF(on_damage_taken)) - RegisterSignal(src, COMSIG_MACHINERY_POWER_LOST, PROC_REF(on_power_loss)) - RegisterSignals(src, list(COMSIG_QDELETING, COMSIG_MACHINERY_BROKEN),PROC_REF(on_broken)) - - register_context() - update_appearance() - -/obj/machinery/netpod/Destroy() - . = ..() - - QDEL_LIST(cached_outfits) - -/obj/machinery/netpod/examine(mob/user) - . = ..() - - if(isnull(server_ref?.resolve())) - . += span_infoplain("It's not connected to anything.") - . += span_infoplain("Netpods must be built within 4 tiles of a server.") - return - - if(!isobserver(user)) - . += span_infoplain("Drag yourself into the pod to engage the link.") - . += span_infoplain("It has limited resuscitation capabilities. Remaining in the pod can heal some injuries.") - . += span_infoplain("It has a security system that will alert the occupant if it is tampered with.") - - if(isnull(occupant)) - . += span_infoplain("It's currently unoccupied.") - return - - . += span_infoplain("It's currently occupied by [occupant].") - - if(isobserver(user)) - . += span_notice("As an observer, you can click this netpod to jump to its avatar.") - return - - . += span_notice("It can be pried open with a crowbar, but its safety mechanisms will alert the occupant.") - - - -/obj/machinery/netpod/add_context(atom/source, list/context, obj/item/held_item, mob/user) - . = ..() - - if(isnull(held_item)) - context[SCREENTIP_CONTEXT_LMB] = "Select Outfit" - return CONTEXTUAL_SCREENTIP_SET - - if(istype(held_item, /obj/item/crowbar) && occupant) - context[SCREENTIP_CONTEXT_LMB] = "Pry Open" - return CONTEXTUAL_SCREENTIP_SET - - -/obj/machinery/netpod/update_icon_state() - if(!is_operational) - icon_state = base_icon_state - return ..() - - if(state_open) - icon_state = base_icon_state + "_open_active" - return ..() - - if(panel_open) - icon_state = base_icon_state + "_panel" - return ..() - - icon_state = base_icon_state + "_closed" - if(occupant) - icon_state += "_active" - - return ..() - -/obj/machinery/netpod/mouse_drop_receive(mob/target, mob/user, params) - var/mob/living/carbon/player = user - - if(!iscarbon(player) || !is_operational || !state_open || player.buckled) - return - - close_machine(target) - -/obj/machinery/netpod/crowbar_act(mob/living/user, obj/item/tool) - if(user.combat_mode) - attack_hand(user) - return ITEM_INTERACT_SUCCESS - - if(default_pry_open(tool, user) || default_deconstruction_crowbar(tool)) - return ITEM_INTERACT_SUCCESS - -/obj/machinery/netpod/screwdriver_act(mob/living/user, obj/item/tool) - if(occupant) - balloon_alert(user, "in use!") - return ITEM_INTERACT_SUCCESS - - if(state_open) - balloon_alert(user, "close first.") - return ITEM_INTERACT_SUCCESS - - if(default_deconstruction_screwdriver(user, "[base_icon_state]_panel", "[base_icon_state]_closed", tool)) - update_appearance() // sometimes icon doesnt properly update during flick() - ui_close(user) - return ITEM_INTERACT_SUCCESS - -/obj/machinery/netpod/attack_hand(mob/living/user, list/modifiers) - . = ..() - if(!state_open && user == occupant) - container_resist_act(user) - -/obj/machinery/netpod/Exited(atom/movable/gone, direction) - . = ..() - if(!state_open && gone == occupant) - container_resist_act(gone) - -/obj/machinery/netpod/relaymove(mob/living/user, direction) - if(!state_open) - container_resist_act(user) - -/obj/machinery/netpod/container_resist_act(mob/living/user) - user.visible_message(span_notice("[occupant] emerges from [src]!"), - span_notice("You climb out of [src]!"), - span_notice("With a hiss, you hear a machine opening.")) - open_machine() - -/obj/machinery/netpod/open_machine(drop = TRUE, density_to_set = FALSE) - playsound(src, 'sound/machines/tramopen.ogg', 60, TRUE, frequency = 65000) - flick("[base_icon_state]_opening", src) - SEND_SIGNAL(src, COMSIG_BITRUNNER_NETPOD_OPENED) - update_use_power(IDLE_POWER_USE) - - return ..() - -/obj/machinery/netpod/close_machine(mob/user, density_to_set = TRUE) - if(!state_open || panel_open || !is_operational || !iscarbon(user)) - return - - playsound(src, 'sound/machines/tramclose.ogg', 60, TRUE, frequency = 65000) - flick("[base_icon_state]_closing", src) - ..() - - enter_matrix() - -/obj/machinery/netpod/default_pry_open(obj/item/crowbar, mob/living/pryer) - if(isnull(occupant) || !iscarbon(occupant)) - if(!state_open) - if(panel_open) - return FALSE - open_machine() - else - shut_pod() - - return TRUE - - pryer.visible_message( - span_danger("[pryer] starts prying open [src]!"), - span_notice("You start to pry open [src]."), - span_notice("You hear loud prying on metal.") - ) - playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) - - SEND_SIGNAL(src, COMSIG_BITRUNNER_CROWBAR_ALERT, pryer) - - if(do_after(pryer, 15 SECONDS, src)) - if(!state_open) - sever_connection() - open_machine() - - return TRUE - -/obj/machinery/netpod/ui_interact(mob/user, datum/tgui/ui) - if(!is_operational || occupant) - return - - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "NetpodOutfits") - ui.set_autoupdate(FALSE) - ui.open() - -/obj/machinery/netpod/ui_data() - var/list/data = list() - - data["netsuit"] = netsuit - return data - -/obj/machinery/netpod/ui_static_data() - var/list/data = list() - - if(!length(cached_outfits)) - cached_outfits += make_outfit_collection("Jobs", subtypesof(/datum/outfit/job)) - - data["collections"] = cached_outfits - - return data - -/obj/machinery/netpod/ui_act(action, params) - . = ..() - if(.) - return TRUE - switch(action) - if("select_outfit") - var/datum/outfit/new_suit = resolve_outfit(params["outfit"]) - if(new_suit) - netsuit = new_suit - return TRUE - - return FALSE - -/obj/machinery/netpod/attack_ghost(mob/dead/observer/our_observer) - var/our_target = avatar_ref?.resolve() - if(isnull(our_target) || !our_observer.orbit(our_target)) - return ..() - -/// Puts the occupant in netpod stasis, basically short-circuiting environmental conditions -/obj/machinery/netpod/proc/add_healing(mob/living/target) - if(target != occupant) - return - - target.AddComponent(/datum/component/netpod_healing, pod = src) - target.playsound_local(src, 'sound/effects/submerge.ogg', 20, vary = TRUE) - target.extinguish_mob() - update_use_power(ACTIVE_POWER_USE) - -/// Disconnects the occupant after a certain time so they aren't just hibernating in netpod stasis. A balance change -/obj/machinery/netpod/proc/auto_disconnect() - if(isnull(occupant) || state_open || connected) - return - - var/mob/player = occupant - player.playsound_local(src, 'sound/effects/splash.ogg', 60, TRUE) - to_chat(player, span_notice("The machine disconnects itself and begins to drain.")) - open_machine() - -/// Handles occupant post-disconnection effects like damage, sounds, etc -/obj/machinery/netpod/proc/disconnect_occupant(cause_damage = FALSE) - connected = FALSE - - var/mob/living/mob_occupant = occupant - if(isnull(occupant) || mob_occupant.stat == DEAD) - open_machine() - return - - mob_occupant.playsound_local(src, 'sound/magic/blink.ogg', 25, TRUE) - mob_occupant.set_static_vision(2 SECONDS) - mob_occupant.set_temp_blindness(1 SECONDS) - mob_occupant.Paralyze(2 SECONDS) - - if(!is_operational) - open_machine() - return - - var/heal_time = 1 - if(mob_occupant.health < mob_occupant.maxHealth) - heal_time = (mob_occupant.stat + 2) * 5 - addtimer(CALLBACK(src, PROC_REF(auto_disconnect)), heal_time SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME) - - if(!cause_damage) - return - - mob_occupant.flash_act(override_blindness_check = TRUE, visual = TRUE) - mob_occupant.adjustOrganLoss(ORGAN_SLOT_BRAIN, disconnect_damage) - INVOKE_ASYNC(mob_occupant, TYPE_PROC_REF(/mob/living, emote), "scream") - to_chat(mob_occupant, span_danger("You've been forcefully disconnected from your avatar! Your thoughts feel scrambled!")) - -/** - * ### Enter Matrix - * Finds any current avatars from this chair - or generates a new one - * - * New avatars cost 1 attempt, and this will eject if there's none left - * - * Connects the mind to the avatar if everything is ok - */ -/obj/machinery/netpod/proc/enter_matrix() - var/mob/living/carbon/human/neo = occupant - if(!ishuman(neo) || neo.stat == DEAD || isnull(neo.mind)) - balloon_alert(neo, "invalid occupant.") - return - - var/obj/machinery/quantum_server/server = find_server() - if(isnull(server)) - balloon_alert(neo, "no server connected!") - return - - var/datum/lazy_template/virtual_domain/generated_domain = server.generated_domain - if(isnull(generated_domain) || !server.is_ready) - balloon_alert(neo, "nothing loaded!") - return - - var/mob/living/carbon/current_avatar = avatar_ref?.resolve() - if(isnull(current_avatar) || current_avatar.stat != CONSCIOUS) // We need a viable avatar - var/obj/structure/hololadder/wayout = server.generate_hololadder() - if(isnull(wayout)) - balloon_alert(neo, "out of bandwidth!") - return - current_avatar = server.generate_avatar(wayout, netsuit) - avatar_ref = WEAKREF(current_avatar) - server.stock_gear(current_avatar, neo, generated_domain) - - neo.set_static_vision(3 SECONDS) - add_healing(occupant) - - if(!validate_entry(neo, current_avatar)) - open_machine() - return - - current_avatar.AddComponent( \ - /datum/component/avatar_connection, \ - old_mind = neo.mind, \ - old_body = neo, \ - server = server, \ - pod = src, \ - help_text = generated_domain.help_text, \ - ) - - connected = TRUE - -/// Finds a server and sets the server_ref -/obj/machinery/netpod/proc/find_server() - var/obj/machinery/quantum_server/server = server_ref?.resolve() - if(server) - return server - - server = locate(/obj/machinery/quantum_server) in oview(4, src) - if(isnull(server)) - return - - server_ref = WEAKREF(server) - RegisterSignal(server, COMSIG_MACHINERY_REFRESH_PARTS, PROC_REF(on_server_upgraded)) - RegisterSignal(server, COMSIG_BITRUNNER_DOMAIN_COMPLETE, PROC_REF(on_domain_complete)) - RegisterSignal(server, COMSIG_BITRUNNER_DOMAIN_SCRUBBED, PROC_REF(on_domain_scrubbed)) - - return server - -/// Creates a list of outfit entries for the UI. -/obj/machinery/netpod/proc/make_outfit_collection(identifier, list/outfit_list) - var/list/collection = list( - "name" = identifier, - "outfits" = list() - ) - - for(var/datum/outfit/outfit as anything in outfit_list) - var/outfit_name = initial(outfit.name) - if(findtext(outfit_name, "(") != 0 || findtext(outfit_name, "-") != 0) // No special variants please - continue - - collection["outfits"] += list(list("path" = outfit, "name" = outfit_name)) - - return list(collection) - -/// Machine has been broken - handles signals and reverting sprites -/obj/machinery/netpod/proc/on_broken(datum/source) - SIGNAL_HANDLER - - sever_connection() - -/// Checks the integrity, alerts occupants -/obj/machinery/netpod/proc/on_damage_taken(datum/source, damage_amount) - SIGNAL_HANDLER - - if(isnull(occupant) || !connected) - return - - var/total = max_integrity - damage_amount - var/integrity = (atom_integrity / total) * 100 - if(integrity > 50) - return - - SEND_SIGNAL(src, COMSIG_BITRUNNER_NETPOD_INTEGRITY) - -/// Puts points on the current occupant's card account -/obj/machinery/netpod/proc/on_domain_complete(datum/source, atom/movable/crate, reward_points) - SIGNAL_HANDLER - - if(isnull(occupant) || !connected) - return - - var/mob/living/player = occupant - - var/datum/bank_account/account = player.get_bank_account() - if(isnull(account)) - return - - account.bitrunning_points += reward_points * 100 - -/// The domain has been fully purged, so we should double check our avatar is deleted -/obj/machinery/netpod/proc/on_domain_scrubbed(datum/source) - SIGNAL_HANDLER - - var/mob/avatar = avatar_ref?.resolve() - if(isnull(avatar)) - return - - QDEL_NULL(avatar) - -/// Boots out anyone in the machine && opens it -/obj/machinery/netpod/proc/on_power_loss(datum/source) - SIGNAL_HANDLER - - if(state_open) - return - - if(isnull(occupant) || !connected) - connected = FALSE - open_machine() - return - - sever_connection() - -/// When the server is upgraded, drops brain damage a little -/obj/machinery/netpod/proc/on_server_upgraded(obj/machinery/quantum_server/source) - SIGNAL_HANDLER - - disconnect_damage = BASE_DISCONNECT_DAMAGE * (1 - source.servo_bonus) - -/// Resolves a path to an outfit. -/obj/machinery/netpod/proc/resolve_outfit(text) - var/path = text2path(text) - if(ispath(path, /datum/outfit)) - return path - -/// Severs the connection with the current avatar -/obj/machinery/netpod/proc/sever_connection() - if(isnull(occupant) || !connected) - return - - SEND_SIGNAL(src, COMSIG_BITRUNNER_NETPOD_SEVER) - -/// Closes the machine without shoving in an occupant -/obj/machinery/netpod/proc/shut_pod() - state_open = FALSE - playsound(src, 'sound/machines/tramclose.ogg', 60, TRUE, frequency = 65000) - flick("[base_icon_state]_closing", src) - set_density(TRUE) - - update_appearance() - -/// Checks for cases to eject/fail connecting an avatar -/obj/machinery/netpod/proc/validate_entry(mob/living/neo, mob/living/avatar) - if(!do_after(neo, 2 SECONDS, src)) - return FALSE - - // Very invalid - if(QDELETED(neo) || QDELETED(avatar) || QDELETED(src) || !is_operational) - return FALSE - - // Invalid - if(occupant != neo || isnull(neo.mind) || neo.stat > SOFT_CRIT || avatar.stat == DEAD) - return FALSE - - return TRUE - -#undef BASE_DISCONNECT_DAMAGE From 7914d84d98e2371f5ab7324483a4266063d61d30 Mon Sep 17 00:00:00 2001 From: Archemagus <32466328+Archemagus@users.noreply.github.com> Date: Mon, 17 Feb 2025 12:06:50 +0200 Subject: [PATCH 30/45] Automated Announcement System refactor (#89276) I standardized stuff in AASs code, and all current reference to it. Also added interactions for bounty cubes, weather reports and request consoles, all of it can be changed from AASs UI. Also it's easier now to add new config entries for AAS to proceed, and it's now downstream friendly. Well, because kind of order in code and because it's funny to make custom messages for... Almost everything? BTW any entry can be blocked from ingame changes, by default you can't change Broken Arrival shuttle and Security Officer arrival announcements, how it was before. But may be we should allow it - it's an open question. :cl: add: Many things now handles via AAS: Bounty Cubes, Request Consoles, Brig Cells, Vending Machines and Orion Trails alerts, Weather Reports, Cargo Order Console code: Now anyone can make their own entry for AAS refactor: AAS internals, also cleanup /:cl: --- code/__HELPERS/game.dm | 9 +- .../subsystem/modular_computers.dm | 4 +- code/datums/components/weatherannouncer.dm | 21 +- code/datums/request_message.dm | 6 +- code/game/machinery/announcement_system.dm | 357 +++++++++++++----- code/game/machinery/civilian_bounties.dm | 71 ++-- code/game/machinery/computer/arcade/orion.dm | 32 +- .../orders/order_computer/cook_order.dm | 3 +- .../orders/order_computer/mining_order.dm | 3 +- .../orders/order_computer/order_computer.dm | 30 +- code/game/machinery/doors/brigdoors.dm | 19 +- code/game/machinery/requests_console.dm | 90 +++-- .../components/unary_devices/cryo.dm | 43 ++- code/modules/bitrunning/objects/vendor.dm | 3 +- code/modules/bitrunning/server/_parent.dm | 13 +- code/modules/bitrunning/server/threats.dm | 11 +- code/modules/bitrunning/server/util.dm | 2 +- code/modules/cargo/orderconsole.dm | 23 +- code/modules/jobs/job_types/_job.dm | 3 +- .../jobs/job_types/security_officer.dm | 13 +- code/modules/mining/laborcamp/laborstacker.dm | 21 +- .../file_system/programs/dept_order.dm | 57 +-- code/modules/power/turbine/turbine.dm | 23 +- code/modules/shuttle/arrivals.dm | 5 +- code/modules/vending/_vending.dm | 27 +- .../tgui/interfaces/AutomatedAnnouncement.tsx | 220 +++++++---- 26 files changed, 718 insertions(+), 391 deletions(-) diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index d8f81afe1cfa..207627753137 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -252,13 +252,8 @@ return var/area/player_area = get_area(character) deadchat_broadcast(" has arrived at the station at [player_area.name].", "[character.real_name] ([job_title])", follow_target = character, message_type = DEADCHAT_ARRIVALRATTLE) - if(!length(GLOB.announcement_systems)) - return - if(!(character.mind?.assigned_role.job_flags & JOB_ANNOUNCE_ARRIVAL)) - return - - var/obj/machinery/announcement_system/announcer = pick(GLOB.announcement_systems) - announcer.announce("ARRIVAL", character.real_name, job_title, list()) //make the list empty to make it announce it in common + if(character.mind && (character.mind.assigned_role.job_flags & JOB_ANNOUNCE_ARRIVAL)) + aas_config_announce(/datum/aas_config_entry/arrival, list("PERSON" = character.real_name,"RANK" = rank)) ///Check if the turf pressure allows specialized equipment to work /proc/lavaland_equipment_pressure_check(turf/turf_to_check) diff --git a/code/controllers/subsystem/modular_computers.dm b/code/controllers/subsystem/modular_computers.dm index 4bb7f9f322fc..b08918d84cd0 100644 --- a/code/controllers/subsystem/modular_computers.dm +++ b/code/controllers/subsystem/modular_computers.dm @@ -41,9 +41,9 @@ SUBSYSTEM_DEF(modular_computers) ///Generate new coupon codes that can be redeemed with the Coupon Master App /datum/controller/subsystem/modular_computers/proc/announce_coupon() //If there's no way to announce the coupon, we may as well skip it. - if(!length(GLOB.announcement_systems)) + var/obj/machinery/announcement_system/announcement_system = get_announcement_system() + if(!announcement_system) return - var/obj/machinery/announcement_system/announcement_system = pick(GLOB.announcement_systems) var/static/list/discounts = list("0.10" = 7, "0.15" = 16, "0.20" = 20, "0.25" = 16, "0.50" = 8, "0.66" = 1) var/static/list/flash_discounts = list("0.30" = 3, "0.40" = 8, "0.50" = 8, "0.66" = 2, "0.75" = 1) diff --git a/code/datums/components/weatherannouncer.dm b/code/datums/components/weatherannouncer.dm index 0eda6c47a324..4ac103a7758d 100644 --- a/code/datums/components/weatherannouncer.dm +++ b/code/datums/components/weatherannouncer.dm @@ -80,7 +80,15 @@ if(previous_level == warning_level && previous_danger == is_weather_dangerous) return // No change var/atom/movable/speaker = parent - speaker.say(get_warning_message()) + var/msg = get_warning_message() + var/obj/machinery/announcement_system/aas = get_announcement_system(/datum/aas_config_entry/weather, speaker) + // Active AAS will override default announcement lines + if (aas) + msg = aas.compile_config_message(/datum/aas_config_entry/weather, list(), !is_weather_dangerous ? 4 : warning_level + 1) + // Stop toggling on radios for it, please! + aas.broadcast(msg, list(RADIO_CHANNEL_SUPPLY)) + // Still say it, because you can be not on our level + speaker.say(msg) speaker.update_appearance(UPDATE_ICON) update_light_color() @@ -170,6 +178,17 @@ .=..() UnregisterSignal(parent, COMSIG_ATOM_EXAMINE) +/datum/aas_config_entry/weather + name = "Cargo Alert: Weather Forecast" + general_tooltip = "Allows the radio to announce incoming weather." + announcement_lines_map = list( + "Clear" = "All clear, no weather alerts to report.", + "Incoming" = "Weather front incoming, begin to seek shelter.", + "Imminent or Active" = "Weather front imminent, find shelter immediately.", + "Safe" = "No risk expected from incoming weather front.", + ) + + #undef WEATHER_ALERT_CLEAR #undef WEATHER_ALERT_INCOMING #undef WEATHER_ALERT_IMMINENT_OR_ACTIVE diff --git a/code/datums/request_message.dm b/code/datums/request_message.dm index d9cf51bc5bed..18feada06cb9 100644 --- a/code/datums/request_message.dm +++ b/code/datums/request_message.dm @@ -12,8 +12,8 @@ var/message_stamped_by = "" /// The priority of the message var/priority = "" - /// The radio frequency the message should be broadcasted on - var/radio_freq = null + /// The radio channel the message should be broadcasted on + var/radio_channel = null /// The type of the request var/request_type = "" /// A list to be appended after the message, for example, list of ores @@ -26,7 +26,7 @@ message_verified_by = data["verified"] message_stamped_by = data["stamped"] priority = data["priority"] - radio_freq = data["notify_freq"] + radio_channel = data["notify_channel"] request_type = data["ore_update"] ? ORE_UPDATE_REQUEST : data["request_type"] var/list/data_appended_list = data["appended_list"] if(data_appended_list && data_appended_list.len) diff --git a/code/game/machinery/announcement_system.dm b/code/game/machinery/announcement_system.dm index ce2ff2a1d1ab..ca8d793ef18d 100644 --- a/code/game/machinery/announcement_system.dm +++ b/code/game/machinery/announcement_system.dm @@ -17,11 +17,13 @@ GLOBAL_LIST_EMPTY(announcement_systems) circuit = /obj/item/circuitboard/machine/announcement_system + ///All possible announcements and their local configurations + var/list/datum/aas_config_entry/config_entries = list() + + ///The headset that we use for broadcasting var/obj/item/radio/headset/radio - var/arrival = "%PERSON has signed up as %RANK" - var/arrivalToggle = 1 - var/newhead = "%PERSON, %RANK, is the department head." - var/newheadToggle = 1 + ///AIs headset support all stations channels, but it may require an override for away site or syndie AASs. + var/radio_type = /obj/item/radio/headset/silicon/ai var/greenlight = "Light_Green" var/pinklight = "Light_Pink" @@ -30,87 +32,76 @@ GLOBAL_LIST_EMPTY(announcement_systems) /obj/machinery/announcement_system/Initialize(mapload) . = ..() GLOB.announcement_systems += src - radio = new /obj/item/radio/headset/silicon/ai(src) + radio = new radio_type(src) + config_entries = init_subtypes(/datum/aas_config_entry, list()) update_appearance() /obj/machinery/announcement_system/randomize_language_if_on_station() return /obj/machinery/announcement_system/update_icon_state() - icon_state = "[base_icon_state]_[is_operational ? "On" : "Off"][panel_open ? "_Open" : null]" + icon_state = "[base_icon_state]_[is_operational && !(machine_stat & EMPED) ? "On" : "Off"][panel_open ? "_Open" : null]" return ..() /obj/machinery/announcement_system/update_overlays() . = ..() - if(arrivalToggle) + var/datum/aas_config_entry/entry = locate(/datum/aas_config_entry/arrival) in config_entries + if(entry && entry.enabled) . += greenlight - if(newheadToggle) + entry = locate(/datum/aas_config_entry/newhead) in config_entries + if(entry && entry.enabled) . += pinklight - if(machine_stat & BROKEN) + if(machine_stat & EMPED) . += errorlight /obj/machinery/announcement_system/Destroy() QDEL_NULL(radio) + QDEL_LAZYLIST(config_entries) GLOB.announcement_systems -= src //"OH GOD WHY ARE THERE 100,000 LISTED ANNOUNCEMENT SYSTEMS?!!" return ..() /obj/machinery/announcement_system/screwdriver_act(mob/living/user, obj/item/tool) - tool.play_tool_sound(src) - toggle_panel_open() - to_chat(user, span_notice("You [panel_open ? "open" : "close"] the maintenance hatch of [src].")) - update_appearance() - return TRUE + var/icon_state_assemble = "[base_icon_state]_[is_operational && !(machine_stat & EMPED) ? "On" : "Off"]" + if(default_deconstruction_screwdriver(user, "[icon_state_assemble]_Open", icon_state_assemble, tool)) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING /obj/machinery/announcement_system/crowbar_act(mob/living/user, obj/item/tool) + . = ..() if(default_deconstruction_crowbar(tool)) - return TRUE + return ITEM_INTERACT_SUCCESS /obj/machinery/announcement_system/multitool_act(mob/living/user, obj/item/tool) - if(!panel_open || !(machine_stat & BROKEN)) - return FALSE + if(!panel_open || !(machine_stat & EMPED)) + return ITEM_INTERACT_BLOCKING to_chat(user, span_notice("You reset [src]'s firmware.")) - set_machine_stat(machine_stat & ~BROKEN) + set_machine_stat(machine_stat & ~EMPED) update_appearance() + return ITEM_INTERACT_SUCCESS -/obj/machinery/announcement_system/proc/CompileText(str, user, rank) //replaces user-given variables with actual thingies. - str = replacetext(str, "%PERSON", "[user]") - str = replacetext(str, "%RANK", "[rank]") - return str - -/obj/machinery/announcement_system/proc/announce(message_type, user, rank, list/channels) - if(!is_operational) - return - - var/message - - if(message_type == "ARRIVAL" && arrivalToggle) - message = CompileText(arrival, user, rank) - else if(message_type == "NEWHEAD" && newheadToggle) - message = CompileText(newhead, user, rank) - else if(message_type == "ARRIVALS_BROKEN") - message = "The arrivals shuttle has been damaged. Docking for repairs..." - else if(message_type == "DESPAWN") - message = CompileText("%PERSON, %RANK, has entered long-term crew storage.", user, rank) - - broadcast(message, channels) - -/// Announces a new security officer joining over the radio -/obj/machinery/announcement_system/proc/announce_officer(mob/officer, department) - if (!is_operational) +/// Does funny breakage stuff +/obj/machinery/announcement_system/proc/act_up() + if (machine_stat & EMPED) return + set_machine_stat(machine_stat | EMPED) + update_appearance() + for (var/datum/aas_config_entry/config in config_entries) + config.act_up() - broadcast("Officer [officer.real_name] has been assigned to [department].", list(RADIO_CHANNEL_SECURITY)) +/obj/machinery/announcement_system/emp_act(severity) + . = ..() + if(!(machine_stat & (NOPOWER|EMPED|BROKEN)) && !(. & EMP_PROTECT_SELF)) + act_up() -/// Sends a message to the appropriate channels. -/obj/machinery/announcement_system/proc/broadcast(message, list/channels) - use_energy(active_power_usage) - if(channels.len == 0) - radio.talk_into(src, message, null) - else - for(var/channel in channels) - radio.talk_into(src, message, channel) +/obj/machinery/announcement_system/emag_act(mob/user, obj/item/card/emag/emag_card) + if(obj_flags & EMAGGED) + return FALSE + obj_flags |= EMAGGED + act_up() + balloon_alert(user, "announcement strings corrupted") + return TRUE /obj/machinery/announcement_system/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) @@ -119,12 +110,18 @@ GLOBAL_LIST_EMPTY(announcement_systems) ui.open() /obj/machinery/announcement_system/ui_data() - var/list/data = list() - data["arrival"] = arrival - data["arrivalToggle"] = arrivalToggle - data["newhead"] = newhead - data["newheadToggle"] = newheadToggle - return data + var/list/configs = list() + for(var/datum/aas_config_entry/config in config_entries) + configs += list(list( + name = config.name, + entryRef = REF(config), + enabled = config.enabled, + modifiable = config.modifiable, + announcementLinesMap = config.announcement_lines_map, + generalTooltip = config.general_tooltip, + varsAndTooltipsMap = config.vars_and_tooltips_map + )) + return list("config_entries" = configs) /obj/machinery/announcement_system/ui_act(action, param) . = ..() @@ -132,60 +129,218 @@ GLOBAL_LIST_EMPTY(announcement_systems) return if(!usr.can_perform_action(src, ALLOW_SILICON_REACH)) return - if(machine_stat & BROKEN) + if(machine_stat & EMPED) visible_message(span_warning("[src] buzzes."), span_hear("You hear a faint buzz.")) - playsound(src.loc, 'sound/machines/buzz-two.ogg', 50, TRUE) + playsound(src.loc, 'sound/machines/buzz/buzz-two.ogg', 50, TRUE) return - switch(action) - if("ArrivalText") - var/NewMessage = trim(html_encode(param["newText"]), MAX_MESSAGE_LEN) - if(!usr.can_perform_action(src, ALLOW_SILICON_REACH)) - return - if(NewMessage) - arrival = NewMessage - usr.log_message("updated the arrivals announcement to: [NewMessage]", LOG_GAME) - if("NewheadText") - var/NewMessage = trim(html_encode(param["newText"]), MAX_MESSAGE_LEN) - if(!usr.can_perform_action(src, ALLOW_SILICON_REACH)) - return - if(NewMessage) - newhead = NewMessage - usr.log_message("updated the head announcement to: [NewMessage]", LOG_GAME) - if("NewheadToggle") - newheadToggle = !newheadToggle - update_appearance() - if("ArrivalToggle") - arrivalToggle = !arrivalToggle - update_appearance() + add_fingerprint(usr) + var/datum/aas_config_entry/config = locate(params["entryRef"]) in config_entries + if(!config || !config.modifiable) + return -/obj/machinery/announcement_system/attack_robot(mob/living/silicon/user) - . = attack_ai(user) + switch(action) + if("Toggle") + config.enabled = !config.enabled + if (config.type in list(/datum/aas_config_entry/arrival, /datum/aas_config_entry/newhead)) + update_appearance() + if("Text") + if(!(params["lineKey"] in config.announcement_lines_map)) + message_admins("[ADMIN_LOOKUPFLW(usr)] tried to set announcement line for nonexisting line in the [config.name] for AAS. Probably href injection. Received line: [params["lineKey"]]") + log_game("[key_name(usr)] tried to mess with AAS. For [config.name] he tried to edit nonexistend [params["lineKey"]]") + return + var/new_message = trim(html_encode(params["newText"]), MAX_MESSAGE_LEN) + if(new_message) + config.announcement_lines_map[params["lineKey"]] = new_message + usr.log_message("updated [params["lineKey"]] line in the [config.name] to: [new_message]", LOG_GAME) -/obj/machinery/announcement_system/attack_ai(mob/user) - if(!user.can_perform_action(src, ALLOW_SILICON_REACH)) +/obj/machinery/announcement_system/can_interact(mob/user) + . = ..() + if (!.) return - if(machine_stat & BROKEN) + + if (machine_stat & EMPED) to_chat(user, span_warning("[src]'s firmware appears to be malfunctioning!")) + if (!isAI(user)) // Deus Ex Machina goes without multitool in his default complectation. + to_chat(user, span_warning("However, you can reset it with [EXAMINE_HINT("multitool")], while its [EXAMINE_HINT("panel is open")]!")) + return FALSE + +/// If AAS can't broadcast message, it shouldn't be picked by randomizer. +/obj/machinery/announcement_system/proc/has_supported_channels(list/channels) + if (!LAZYLEN(channels) || (RADIO_CHANNEL_COMMON in channels)) + // Okay, I am not proud of this, but I don't want CentCom or Syndie AASs to broadcast on Common. + return src.type == /obj/machinery/announcement_system + for(var/channel in channels) + if(radio.channels[channel]) + return TRUE + return FALSE + +/// Can AAS receive request for broadcast from you? +/obj/machinery/announcement_system/proc/can_be_reached_from(atom/source) + if(!source || !istype(source)) + return TRUE + var/turf/source_turf = get_turf(source) + if (!source_turf) + return TRUE + // Keep updated with broadcasting.dm (/datum/signal/subspace/vocal/New) + return z in SSmapping.get_connected_levels(source_turf) + +/// Compiles the announcement message with the provided variables. Announcement line is optional. +/obj/machinery/announcement_system/proc/compile_config_message(aas_config_entry_type, list/variables_map, announcement_line, fail_if_disabled=FALSE) + var/datum/aas_config_entry/config = locate(aas_config_entry_type) in config_entries + if (!config || (fail_if_disabled && !config.enabled)) return - interact(user) + return config.compile_announce(variables_map, announcement_line) -/obj/machinery/announcement_system/proc/act_up() //does funny breakage stuff - if(!atom_break()) // if badmins flag this unbreakable or its already broken +/// Sends a message to the appropriate channels. +/obj/machinery/announcement_system/proc/broadcast(message, list/channels, command_span = FALSE) + use_energy(active_power_usage) + if(!LAZYLEN(channels)) + radio.talk_into(src, message, null, command_span ? list(speech_span, SPAN_COMMAND) : null) + return + + // For some reasons, radio can't recognize RADIO_CHANNEL_COMMON in channels, so we need to handle it separately. + if (RADIO_CHANNEL_COMMON in channels) + radio.talk_into(src, message, null, command_span ? list(speech_span, SPAN_COMMAND) : null) + channels -= RADIO_CHANNEL_COMMON + for(var/channel in channels) + radio.talk_into(src, message, channel, command_span ? list(speech_span, SPAN_COMMAND) : null) + +/// Announces configs entry message with the provided variables. Channels and announcement_line are optional. +/obj/machinery/announcement_system/proc/announce(aas_config_entry_type, list/variables_map, list/channels, announcement_line, command_span) + var/msg = compile_config_message(aas_config_entry_type, variables_map, announcement_line, TRUE) + if (msg) + broadcast(msg, channels, command_span) + +/// Returns a random announcement system that is operational, has the specified config entry, signal can reach source and radio supports any channel in list. Config entry, source and channels are optional. +/proc/get_announcement_system(aas_config_entry_type, source, list/channels) + if (!GLOB.announcement_systems.len) + return null + var/list/intact_aass = list() + for(var/obj/machinery/announcement_system/announce as anything in GLOB.announcement_systems) + if(!QDELETED(announce) && announce.is_operational && announce.has_supported_channels(channels) && announce.can_be_reached_from(source)) + if(aas_config_entry_type) + var/datum/aas_config_entry/entry = locate(aas_config_entry_type) in announce.config_entries + if(!entry || !entry.enabled) + continue + intact_aass += announce + return intact_aass.len ? pick(intact_aass) : null + +/// Announces the provided message with the provided variables and config entry type. Channels, announcement_line, command_span and source are optional. +/proc/aas_config_announce(aas_config_entry_type, list/variables_map, source, list/channels, announcement_line, command_span) + var/obj/machinery/announcement_system/announcer = get_announcement_system(aas_config_entry_type, source, channels) + if (!announcer) + return + announcer.announce(aas_config_entry_type, variables_map, channels, announcement_line, command_span) + +/datum/aas_config_entry + var/name = "AAS configurable entry" + // Should we broadcast this announcement? + var/enabled = TRUE + // The announcement message. Key will be displayed in the UI. + var/list/announcement_lines_map = list("Message" = "This is a default announcement line.") + // Goes before tooltips for vars, mainly used if announcement has no replacable vars + var/general_tooltip + // Contains all replacable vars and their tooltips + var/list/vars_and_tooltips_map = list() + // Can be changed or disabled by players + var/modifiable = TRUE + +/// Compiles the announcement message with the provided variables. Announcement line is optional. +/datum/aas_config_entry/proc/compile_announce(list/variables_map, announcement_line) + var/announcement_message = LAZYACCESS(announcement_lines_map, announcement_line) + // If index was provided LAZYACCESS will return us a key, not value + if (isnum(announcement_line)) + announcement_message = announcement_lines_map[announcement_message] + // Fallback - first line + if (!announcement_message) + announcement_message = announcement_lines_map[announcement_lines_map[1]] + // Replace variables with their value + for(var/variable in vars_and_tooltips_map) + announcement_message = replacetext_char(announcement_message, "%[variable]", variables_map[variable] || "\[NO DATA\]") + return announcement_message + +/// Called when the announcement system is emagged or EMPed. +/datum/aas_config_entry/proc/act_up() + SHOULD_CALL_PARENT(TRUE) + + // Please do not mess with entries, that players can't fix. + if(!modifiable) + return TRUE + return FALSE + +/* + Global config entries for the announcement system. +*/ + +/datum/aas_config_entry/arrival + name = "Arrival Announcement" + announcement_lines_map = list( + "Message" = "%PERSON has signed up as %RANK") + vars_and_tooltips_map = list( + "PERSON" = "will be replaced with their name.", + "RANK" = "with their job." + ) + +/datum/aas_config_entry/arrival/act_up() + . = ..() + if (.) return - arrival = pick("#!@%ERR-34%2 CANNOT LOCAT@# JO# F*LE!", "CRITICAL ERROR 99.", "ERR)#: DA#AB@#E NOT F(*ND!") - newhead = pick("OV#RL()D: \[UNKNOWN??\] DET*#CT)D!", "ER)#R - B*@ TEXT F*O(ND!", "AAS.exe is not responding. NanoOS is searching for a solution to the problem.") + announcement_lines_map["Message"] = pick("#!@%ERR-34%2 CANNOT LOCAT@# JO# F*LE!", + "CRITICAL ERROR 99.", + "ERR)#: DA#AB@#E NOT F(*ND!") -/obj/machinery/announcement_system/emp_act(severity) +/datum/aas_config_entry/newhead + name = "Departmental Head Announcement" + announcement_lines_map = list( + "Message" = "%PERSON, %RANK, is the department head.") + vars_and_tooltips_map = list( + "PERSON" = "will be replaced with their name.", + "RANK" = "with their job." + ) + +/datum/aas_config_entry/newhead/act_up() . = ..() - if(!(machine_stat & (NOPOWER|BROKEN)) && !(. & EMP_PROTECT_SELF)) - act_up() + if (.) + return -/obj/machinery/announcement_system/emag_act(mob/user, obj/item/card/emag/emag_card) - if(obj_flags & EMAGGED) - return FALSE - obj_flags |= EMAGGED - act_up() - balloon_alert(user, "announcement strings corrupted") - return TRUE + announcement_lines_map["Message"] = pick("OV#RL()D: \[UNKNOWN??\] DET*#CT)D!", + "ER)#R - B*@ TEXT F*O(ND!", + "AAS.exe is not responding. NanoOS is searching for a solution to the problem.") + +/datum/aas_config_entry/researched_node + name = "Research Node Announcement" + announcement_lines_map = list( + "Message" = "The %NODE techweb node has been researched") + vars_and_tooltips_map = list( + "NODE" = "will be replaced with the researched node." + ) + +/datum/aas_config_entry/researched_node/act_up() + . = ..() + if (.) + return + + announcement_lines_map["Message"] = pick( + replacetext(/datum/aas_config_entry/researched_node::announcement_lines_map["Message"], "%NODE", /datum/techweb_node/mech_clown::display_name), + "R/NT1M3 A= ANNOUN-*#nt_SY!?EM.dm, LI%£ 86: N=0DE NULL!", + "BEPIS BEPIS BEPIS", + "ERR)#R - B*@ TEXT F*O(ND!") + +/datum/aas_config_entry/arrivals_broken + name = "Arrivals Shuttle Malfunction Announcement" + announcement_lines_map = list( + "Message" = "The arrivals shuttle has been damaged. Docking for repairs...") + general_tooltip = "Broadcasted, when arrivals shuttle docks for repairs. No replacable variables provided." + modifiable = FALSE + +/datum/aas_config_entry/announce_officer + name = "Security Alert: Officer Arrival Announcement" + announcement_lines_map = list( + "Message" = "Officer %OFFICER has been assigned to %DEPARTMENT.") + vars_and_tooltips_map = list( + "OFFICER" = "will be replaced with the officer's name.", + "DEPARTMENT" = "with the department they were assigned to." + ) + modifiable = FALSE diff --git a/code/game/machinery/civilian_bounties.dm b/code/game/machinery/civilian_bounties.dm index c1a73764e35f..4e24f05b055c 100644 --- a/code/game/machinery/civilian_bounties.dm +++ b/code/game/machinery/civilian_bounties.dm @@ -287,25 +287,10 @@ var/datum/bank_account/bounty_holder_account ///Bank account of the person who receives the handling tip. var/datum/bank_account/bounty_handler_account - ///Our internal radio. - var/obj/item/radio/radio - ///The key our internal radio uses. - var/radio_key = /obj/item/encryptionkey/headset_cargo /obj/item/bounty_cube/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_NO_BARCODES, INNATE_TRAIT) // Don't allow anyone to override our pricetag component with a barcode - radio = new(src) - radio.keyslot = new radio_key - radio.set_listening(FALSE) - radio.recalculateChannels() - RegisterSignal(radio, COMSIG_ITEM_PRE_EXPORT, PROC_REF(on_export)) - -/obj/item/bounty_cube/Destroy() - if(radio) - UnregisterSignal(radio, COMSIG_ITEM_PRE_EXPORT) - QDEL_NULL(radio) - return ..() /obj/item/bounty_cube/examine() . = ..() @@ -314,29 +299,20 @@ if(handler_tip && !bounty_handler_account) . += span_notice("Scan this in the cargo shuttle with an export scanner to register your bank account for the [bounty_value * handler_tip] credit handling tip.") -/* - * Signal proc for [COMSIG_ITEM_EXPORTED], registered on the internal radio. - * - * Deletes the internal radio before being exported, - * to stop it from bring counted as an export. - * - * No 4 free credits for you! - */ -/obj/item/bounty_cube/proc/on_export(datum/source) - SIGNAL_HANDLER - - QDEL_NULL(radio) - return COMPONENT_STOP_EXPORT // stops the radio from exporting, not the cube - /obj/item/bounty_cube/process(seconds_per_tick) //if our nag cooldown has finished and we aren't on Centcom or in transit, then nag if(COOLDOWN_FINISHED(src, next_nag_time) && !is_centcom_level(z) && !is_reserved_level(z)) - //set up our nag message + //set up our fallback message, in case of AAS being broken it will be sent to card holders var/nag_message = "[src] is unsent in [get_area(src)]." //nag on Supply channel and reduce the speed bonus multiplier to nothing - var/speed_bonus_lost = "[speed_bonus ? " Speedy delivery bonus of [bounty_value * speed_bonus] credit\s lost." : ""]" - radio.talk_into(src, "[nag_message][speed_bonus_lost]", RADIO_CHANNEL_SUPPLY) + var/obj/machinery/announcement_system/aas = get_announcement_system(/datum/aas_config_entry/bounty_cube_unsent, src) + if (aas) + nag_message = aas.compile_config_message(/datum/aas_config_entry/bounty_cube_unsent, list("LOCATION" = get_area_name(src), "COST" = bounty_value), "Regular Message") + if (speed_bonus) + aas.announce(/datum/aas_config_entry/bounty_cube_unsent, list("LOCATION" = get_area_name(src), "COST" = bounty_value, "BONUSLOST" = bounty_value * speed_bonus), list(RADIO_CHANNEL_SUPPLY), "When Bonus Lost") + else + aas.broadcast("[nag_message]", list(RADIO_CHANNEL_SUPPLY)) speed_bonus = 0 //alert the holder @@ -361,7 +337,13 @@ AddComponent(/datum/component/gps, "[src]") START_PROCESSING(SSobj, src) COOLDOWN_START(src, next_nag_time, nag_cooldown) - radio.talk_into(src,"Created in [get_area(src)] by [bounty_holder] ([bounty_holder_job]). Speedy delivery bonus lost in [time2text(next_nag_time - world.time,"mm:ss")].", RADIO_CHANNEL_SUPPLY) + aas_config_announce(/datum/aas_config_entry/bounty_cube_created, list( + "LOCATION" = get_area_name(src), + "PERSON" = bounty_holder, + "RANK" = bounty_holder_job, + "BONUSTIME" = time2text(next_nag_time - world.time,"mm:ss"), + "COST" = bounty_value + ), src, list(RADIO_CHANNEL_SUPPLY)) //for when you need a REAL bounty cube to test with and don't want to do a bounty each time your code changes /obj/item/bounty_cube/debug_cube @@ -407,4 +389,27 @@ qdel(src) uses-- +/datum/aas_config_entry/bounty_cube_created + name = "Cargo Alert: Bounty Cube Created" + announcement_lines_map = list( + "Message" = "A %COST cr bounty cube has been created in %LOCATION by %PERSON (%RANK). Speedy delivery bonus lost in %BONUSTIME.") + vars_and_tooltips_map = list( + "LOCATION" = "will be replaced with the location of the cube.", + "PERSON" = "with who created the cube.", + "RANK" = "with their job.", + "BONUSTIME" = "with the time left for speedy delivery tip.", + "COST" = "with the cost of the cube.", + ) + +/datum/aas_config_entry/bounty_cube_unsent + name = "Cargo Alert: Bounty Cube Unsent" + announcement_lines_map = list( + "Regular Message" = "The %COST cr bounty cube is unsent in %LOCATION.", + "When Bonus Lost" = "The %COST cr bounty cube is unsent in %LOCATION. Speedy delivery bonus of %BONUSLOST credits lost.") + vars_and_tooltips_map = list( + "LOCATION" = "will be replaced with the location of the cube.", + "COST" = "with the cost of the cube.", + "BONUSLOST" = "with the lost bonus tip, it will be sent just for When Bonus Lost message!", + ) + #undef CIV_BOUNTY_SPLIT diff --git a/code/game/machinery/computer/arcade/orion.dm b/code/game/machinery/computer/arcade/orion.dm index 002bbf578b31..3a270bbc099e 100644 --- a/code/game/machinery/computer/arcade/orion.dm +++ b/code/game/machinery/computer/arcade/orion.dm @@ -28,7 +28,6 @@ var/spaceport_raided = FALSE var/gameStatus = ORION_STATUS_START - var/obj/item/radio/radio var/list/gamers = list() var/killed_crew = 0 @@ -40,15 +39,12 @@ var/datum/orion_event/new_event = new path(src) events[new_event] = new_event.weight orion_events = events - radio = new /obj/item/radio(src) - radio.set_listening(FALSE) setup_events() /obj/machinery/computer/arcade/orion_trail/proc/setup_events() events = orion_events.Copy() /obj/machinery/computer/arcade/orion_trail/Destroy() - QDEL_NULL(radio) events = null return ..() @@ -123,14 +119,15 @@ gamers[gamer]++ // How many times the player has 'prestiged' (massacred their crew) if(gamers[gamer] > ORION_GAMER_REPORT_THRESHOLD && prob(20 * gamers[gamer])) + aas_config_announce(/datum/aas_config_entry/orion_violent_behavior_alert, list( + "PERSON" = gamer.name, + "LOCATION" = get_area_name(src), + "SOURCE" = name), src, list(RADIO_CHANNEL_SECURITY), RADIO_CHANNEL_SECURITY) - radio.set_frequency(FREQ_SECURITY) - radio.talk_into(src, "SECURITY ALERT: Crewmember [gamer] recorded displaying antisocial tendencies in [get_area(src)]. Please watch for violent behavior.", FREQ_SECURITY) - - radio.set_frequency(FREQ_MEDICAL) - radio.talk_into(src, "PSYCH ALERT: Crewmember [gamer] recorded displaying antisocial tendencies in [get_area(src)]. Please schedule psych evaluation.", FREQ_MEDICAL) - - remove_radio_all(radio)//so we dont keep transmitting sec and medical comms + aas_config_announce(/datum/aas_config_entry/orion_violent_behavior_alert, list( + "PERSON" = gamer.name, + "LOCATION" = get_area_name(src), + "SOURCE" = name), src, list(RADIO_CHANNEL_MEDICAL), RADIO_CHANNEL_MEDICAL) gamers[gamer] = ORION_GAMER_PAMPHLET //next report send a pamph @@ -547,4 +544,17 @@ dialogue_level++ addtimer(CALLBACK(src, PROC_REF(commit_explosion), dialogue_level), time_for_next_level) +/datum/aas_config_entry/orion_violent_behavior_alert + // Well we don't want to show that only Orion Trails reports violent behavior, eh-h? + name = "Violent Behavior Alert" + announcement_lines_map = list( + RADIO_CHANNEL_SECURITY = "SECURITY ALERT: Crewmember %PERSON recorded displaying antisocial tendencies in %LOCATION by %SOURCE. Please watch for violent behavior.", + RADIO_CHANNEL_MEDICAL = "PSYCH ALERT: Crewmember %PERSON recorded displaying antisocial tendencies in %LOCATION by %SOURCE. Please schedule psych evaluation.", + ) + vars_and_tooltips_map = list( + "PERSON" = "will be replaced with the crewmember reported", + "LOCATION" = "with the area of violent behavior", + "SOURCE" = "with the reporter", + ) + #undef ORION_TRAIL_WINTURN diff --git a/code/game/machinery/computer/orders/order_computer/cook_order.dm b/code/game/machinery/computer/orders/order_computer/cook_order.dm index 3ac23770d3e2..7518be414e82 100644 --- a/code/game/machinery/computer/orders/order_computer/cook_order.dm +++ b/code/game/machinery/computer/orders/order_computer/cook_order.dm @@ -8,10 +8,11 @@ CATEGORY_SAUCES_REAGENTS, ) blackbox_key = "chef" + announcement_line = "The kitchen has ordered groceries which will arrive on the cargo shuttle! Please make sure it gets to them as soon as possible!" /obj/machinery/computer/order_console/cook/order_groceries(mob/living/purchaser, obj/item/card/id/card, list/groceries) say("Thank you for your purchase! It will arrive on the next cargo shuttle!") - radio.talk_into(src, "The kitchen has ordered groceries which will arrive on the cargo shuttle! Please make sure it gets to them as soon as possible!", radio_channel) + aas_config_announce(/datum/aas_config_entry/order_console, list(), src, list(radio_channel), capitalize(blackbox_key)) for(var/datum/orderable_item/ordered_item in groceries) if(!(ordered_item.category_index in order_categories)) groceries.Remove(ordered_item) diff --git a/code/game/machinery/computer/orders/order_computer/mining_order.dm b/code/game/machinery/computer/orders/order_computer/mining_order.dm index 54946f8afeec..c83468eede70 100644 --- a/code/game/machinery/computer/orders/order_computer/mining_order.dm +++ b/code/game/machinery/computer/orders/order_computer/mining_order.dm @@ -24,6 +24,7 @@ CATEGORY_PKA, ) blackbox_key = "mining" + announcement_line = "A shaft miner has ordered equipment which will arrive on the cargo shuttle! Please make sure it gets to them as soon as possible!" /obj/machinery/computer/order_console/mining/subtract_points(final_cost, obj/item/card/id/card) if(final_cost <= card.registered_account.mining_points) @@ -56,7 +57,7 @@ can_be_cancelled = FALSE, ) say("Thank you for your purchase! It will arrive on the next cargo shuttle!") - radio.talk_into(src, "A shaft miner has ordered equipment which will arrive on the cargo shuttle! Please make sure it gets to them as soon as possible!", radio_channel) + aas_config_announce(/datum/aas_config_entry/order_console, list(), src, list(radio_channel), capitalize(blackbox_key)) SSshuttle.shopping_list += new_order /obj/machinery/computer/order_console/mining/retrieve_points(obj/item/card/id/id_card) diff --git a/code/game/machinery/computer/orders/order_computer/order_computer.dm b/code/game/machinery/computer/orders/order_computer/order_computer.dm index 7f675924e92c..687fa21f1b89 100644 --- a/code/game/machinery/computer/orders/order_computer/order_computer.dm +++ b/code/game/machinery/computer/orders/order_computer/order_computer.dm @@ -19,10 +19,10 @@ GLOBAL_LIST_EMPTY(order_console_products) COOLDOWN_DECLARE(order_cooldown) ///Cooldown time between uses, express console will have extra time depending on express_cost_multiplier. var/cooldown_time = 60 SECONDS - ///The radio the console can speak into - var/obj/item/radio/radio ///The channel we will attempt to speak into through our radio. var/radio_channel = RADIO_CHANNEL_SUPPLY + ///What line we should announce on ordering_groceries + var/announcement_line ///The kind of cash does the console use. var/credit_type = CREDIT_TYPE_CREDIT @@ -41,11 +41,6 @@ GLOBAL_LIST_EMPTY(order_console_products) /obj/machinery/computer/order_console/Initialize(mapload) . = ..() - radio = new(src) - radio.set_frequency(FREQ_SUPPLY) - radio.subspace_transmission = TRUE - radio.canhear_range = 0 - radio.recalculateChannels() if(GLOB.order_console_products.len) return @@ -54,10 +49,6 @@ GLOBAL_LIST_EMPTY(order_console_products) continue GLOB.order_console_products += new path -/obj/machinery/computer/order_console/Destroy() - QDEL_NULL(radio) - return ..() - /obj/machinery/computer/order_console/proc/get_total_cost() var/cost = 0 for(var/datum/orderable_item/item as anything in grocery_list) @@ -243,4 +234,21 @@ GLOBAL_LIST_EMPTY(order_console_products) /obj/machinery/computer/order_console/proc/order_groceries(mob/living/purchaser, obj/item/card/id/card, list/groceries) return +/datum/aas_config_entry/order_console + name = "Local Ordering Console Announcements" + announcement_lines_map = list() + general_tooltip = "Used to make announces, when consoles listed here placing new order" + +/datum/aas_config_entry/order_console/New() + . = ..() + for(var/obj/machinery/computer/order_console/subconsole as anything in subtypesof(/obj/machinery/computer/order_console)) + if(subconsole.blackbox_key) + announcement_lines_map[capitalize(subconsole.blackbox_key)] = subconsole.announcement_line + +/datum/aas_config_entry/order_console/compile_announce(list/variables_map, announcement_line) + if (!announcement_lines_map.len) + announcement_lines_map["Error"] = "Unknown Error happened, while we tried to procceed an order, please report this to Nanotrasen." + . = ..() + + #undef CREDIT_TYPE_CREDIT diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm index 6c1470279312..f3b69dab2cee 100644 --- a/code/game/machinery/doors/brigdoors.dm +++ b/code/game/machinery/doors/brigdoors.dm @@ -29,15 +29,12 @@ var/list/flashers = list() ///List of weakrefs to nearby closets var/list/closets = list() - ///needed to send messages to sec radio - var/obj/item/radio/sec_radio + ///Channel to report prisoneer's release + var/broadcast_channel = RADIO_CHANNEL_SECURITY /obj/machinery/status_display/door_timer/Initialize(mapload) . = ..() - sec_radio = new/obj/item/radio(src) - sec_radio.set_listening(FALSE) - if(id != null) for(var/obj/machinery/door/window/brigdoor/M in urange(20, src)) if (M.id == id) @@ -130,8 +127,7 @@ return 0 if(!forced) - sec_radio.set_frequency(FREQ_SECURITY) - sec_radio.talk_into(src, "Timer has expired. Releasing prisoner.", FREQ_SECURITY) + aas_config_announce(/datum/aas_config_entry/brig_cell_release_announcement, list("CELL" = name), src, list(broadcast_channel)) timing = FALSE activation_time = null @@ -274,6 +270,15 @@ continue timer_end(forced = TRUE) +/datum/aas_config_entry/brig_cell_release_announcement + name = "Security Alert: Cell Timer Expired" + announcement_lines_map = list( + "Message" = "Timer for %CELL has expired. Releasing prisoner.", + ) + vars_and_tooltips_map = list( + "CELL" = "will be replaced with the cell name.", + ) + #undef PRESET_SHORT #undef PRESET_MEDIUM #undef PRESET_LONG diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm index f4b9402e6ad5..a63802829d40 100644 --- a/code/game/machinery/requests_console.dm +++ b/code/game/machinery/requests_console.dm @@ -44,8 +44,6 @@ GLOBAL_LIST_EMPTY(req_console_ckey_departments) var/message_verified_by = "" /// If a message is stamped, this will contain the stamp name var/message_stamped_by = "" - /// Reference to the internal radio - var/obj/item/radio/radio ///If an emergency has been called by this device. Acts as both a cooldown and lets the responder know where it the emergency was triggered from var/emergency /// If ore redemption machines will send an update when it receives new ores. @@ -125,13 +123,9 @@ GLOBAL_LIST_EMPTY(req_console_ckey_departments) GLOB.req_console_all += src GLOB.req_console_ckey_departments[ckey(department)] = department // and then we set ourselves a listed name - - radio = new /obj/item/radio(src) - radio.set_listening(FALSE) find_and_hang_on_wall() /obj/machinery/requests_console/Destroy() - QDEL_NULL(radio) QDEL_LIST(messages) GLOB.req_console_all -= src return ..() @@ -169,20 +163,15 @@ GLOBAL_LIST_EMPTY(req_console_ckey_departments) if("set_emergency") if(emergency) return - var/radio_freq switch(params["emergency"]) if(REQ_EMERGENCY_SECURITY) //Security - radio_freq = FREQ_SECURITY + aas_config_announce(/datum/aas_config_entry/rc_emergency, list("LOCATION" = department), null, list(RADIO_CHANNEL_SECURITY), REQ_EMERGENCY_SECURITY) if(REQ_EMERGENCY_ENGINEERING) //Engineering - radio_freq = FREQ_ENGINEERING + aas_config_announce(/datum/aas_config_entry/rc_emergency, list("LOCATION" = department), null, list(RADIO_CHANNEL_ENGINEERING), REQ_EMERGENCY_ENGINEERING) if(REQ_EMERGENCY_MEDICAL) //Medical - radio_freq = FREQ_MEDICAL - if(radio_freq) - emergency = params["emergency"] - radio.set_frequency(radio_freq) - radio.talk_into(src,"[emergency] emergency in [department]!!",radio_freq) - update_appearance() - addtimer(CALLBACK(src, PROC_REF(clear_emergency)), 5 MINUTES) + aas_config_announce(/datum/aas_config_entry/rc_emergency, list("LOCATION" = department), null, list(RADIO_CHANNEL_MEDICAL), REQ_EMERGENCY_MEDICAL) + update_appearance() + addtimer(CALLBACK(src, PROC_REF(clear_emergency)), 5 MINUTES) return TRUE if("send_announcement") if(!COOLDOWN_FINISHED(src, announcement_cooldown)) @@ -246,20 +235,22 @@ GLOBAL_LIST_EMPTY(req_console_ckey_departments) ///Sends the message from the request console /obj/machinery/requests_console/proc/send_message(recipient, message, priority, request_type) - var/radio_freq + var/radio_channel + // They all naming them wrong, all the time... I'll probably rewrite this later in separate PR. + // Automatically from areas or via mapping helpers. (ther is no "Cargobay Request Console" in any map) switch(ckey(recipient)) if("bridge") - radio_freq = FREQ_COMMAND + radio_channel = RADIO_CHANNEL_COMMAND if("medbay") - radio_freq = FREQ_MEDICAL + radio_channel = RADIO_CHANNEL_MEDICAL if("science") - radio_freq = FREQ_SCIENCE + radio_channel = RADIO_CHANNEL_SCIENCE if("engineering") - radio_freq = FREQ_ENGINEERING + radio_channel = RADIO_CHANNEL_ENGINEERING if("security") - radio_freq = FREQ_SECURITY + radio_channel = RADIO_CHANNEL_SECURITY if("cargobay", "mining") - radio_freq = FREQ_SUPPLY + radio_channel = RADIO_CHANNEL_SUPPLY var/datum/signal/subspace/messaging/rc/signal = new(src, list( "sender_department" = department, @@ -268,7 +259,7 @@ GLOBAL_LIST_EMPTY(req_console_ckey_departments) "verified" = message_verified_by, "stamped" = message_stamped_by, "priority" = priority, - "notify_freq" = radio_freq, + "notify_channel" = radio_channel, "request_type" = request_type, )) signal.send_to_receivers() @@ -351,15 +342,26 @@ GLOBAL_LIST_EMPTY(req_console_ckey_departments) SStgui.update_uis(src) - var/alert = new_message.get_alert() - if(!silent) playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE) - say(alert) - - if(new_message.radio_freq) - radio.set_frequency(new_message.radio_freq) - radio.talk_into(src, "[alert]: [new_message.content]", new_message.radio_freq) + say(new_message.get_alert()) + + if(new_message.radio_channel) + var/authentication + var/announcement_line = "Unauthenticated" + if (new_message.message_verified_by) + authentication = new_message.message_verified_by + announcement_line = "Verified with ID" + else if (new_message.message_stamped_by) + authentication = new_message.message_stamped_by + announcement_line = "Stamped with stamp" + + aas_config_announce(/datum/aas_config_entry/rc_new_message, list( + "AUTHENTICATION" = authentication, + "SENDER" = new_message.sender_department, + "RECEIVER" = department, + "MESSAGE" = new_message.content + ), null, list(new_message.radio_channel), announcement_line, new_message.priority == REQ_EXTREME_MESSAGE_PRIORITY) /obj/machinery/requests_console/crowbar_act(mob/living/user, obj/item/tool) tool.play_tool_sound(src, 50) @@ -416,6 +418,32 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/requests_console/auto_name, 30) result_path = /obj/machinery/requests_console/auto_name pixel_shift = 30 +/datum/aas_config_entry/rc_emergency + name = "RC Alert: Emergency" + announcement_lines_map = list( + "Security" = "Security emergency in %LOCATION!!!", + "Engineering" = "Engineering emergency in %LOCATION!!!", + "Medical" = "Medical emergency in %LOCATION!!!", + ) + vars_and_tooltips_map = list( + "LOCATION" = "will be replaced with the department name", + ) + +/datum/aas_config_entry/rc_new_message + name = "RC Alert: New Message " + // Yes, players can't use html tags, however they can use speech mods like | or +, but sh-sh-sh, don't tell them! + announcement_lines_map = list( + "Unauthenticated" = "Message from %SENDER to %RECEIVER: %MESSAGE", + "Verified with ID" = "Message from %SENDER to %RECEIVER, Verified by %AUTHENTICATION (Authenticated): %MESSAGE", + "Stamped with stamp" = "Message from %SENDER to %RECEIVER, Stamped by %AUTHENTICATION (Authenticated): %MESSAGE", + ) + vars_and_tooltips_map = list( + "AUTHENTICATION" = "will be replaced with ID or stamp, if present", + "SENDER" = "with the sender department ", + "RECEIVER" = "with the receiver department", + "MESSAGE" = "with the message content", + ) + #undef REQ_EMERGENCY_SECURITY #undef REQ_EMERGENCY_ENGINEERING #undef REQ_EMERGENCY_MEDICAL diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index 57d2f91615be..28a6de3a1d8e 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -98,8 +98,6 @@ var/conduction_coefficient = 0.3 ///The beaker usually contains cryoxadone that is pumped into the mob var/obj/item/reagent_containers/cup/beaker = null - ///Inform medical about cryo status over this radio - var/obj/item/radio/radio /// Visual content - Occupant var/atom/movable/visual/cryo_occupant/occupant_vis /// tracks everything the machine is currently treating @@ -110,6 +108,8 @@ var/on = FALSE /// The sound loop that can be heard when the generator is processing. var/datum/looping_sound/cryo_cell/soundloop + /// For away sites, custom or admin events + var/broadcast_channel = RADIO_CHANNEL_MEDICAL /datum/armor/unary_cryo_cell energy = 100 @@ -119,12 +119,6 @@ /obj/machinery/cryo_cell/Initialize(mapload) . = ..() - radio = new(src) - radio.keyslot = new /obj/item/encryptionkey/headset_med - radio.subspace_transmission = TRUE - radio.canhear_range = 0 - radio.recalculateChannels() - occupant_vis = new(mapload, src) vis_contents += occupant_vis internal_connector = new(loc, src, dir, TANK_STANDARD_VOLUME * 4) @@ -137,7 +131,6 @@ vis_contents.Cut() QDEL_NULL(occupant_vis) - QDEL_NULL(radio) QDEL_NULL(beaker) QDEL_NULL(internal_connector) QDEL_NULL(soundloop) @@ -430,12 +423,10 @@ mob_occupant.extinguish_mob() if(mob_occupant.stat == DEAD) // Notify doctors and potentially eject if the patient is dead set_on(FALSE) - var/msg = "Patient is deceased." + aas_config_announce(/datum/aas_config_entry/medical_cryo_announcements, list("EJECTING" = autoeject), src, list(broadcast_channel), "Deceased") if(autoeject) // Eject if configured. - msg += " Auto ejecting patient now." open_machine() playsound(src, 'sound/machines/cryo_warning.ogg', 100) - radio.talk_into(src, msg, RADIO_CHANNEL_MEDICAL) return PROCESS_KILL // Don't bother with fully healed people. @@ -464,11 +455,9 @@ if(!ongoing_treatment) set_on(FALSE) playsound(src, 'sound/machines/cryo_warning.ogg', 100) // Bug the doctors. - var/msg = "Patient fully restored." + aas_config_announce(/datum/aas_config_entry/medical_cryo_announcements, list("EJECTING" = autoeject), src, list(broadcast_channel), "Fully Recovered") if(autoeject) // Eject if configured. - msg += " Auto ejecting patient now." open_machine() - radio.talk_into(src, msg, RADIO_CHANNEL_MEDICAL) return PROCESS_KILL var/datum/gas_mixture/air1 = internal_connector.gas_connector.airs[1] @@ -494,11 +483,9 @@ //check for workable conditions if(!internal_connector.gas_connector.nodes[1] || !air1 || !air1.gases.len || air1.total_moles() < CRYO_MIN_GAS_MOLES) // Turn off if the machine won't work. set_on(FALSE) - var/msg = "Insufficient cryogenic gas, shutting down." + aas_config_announce(/datum/aas_config_entry/medical_cryo_announcements, list("EJECTING" = autoeject), src, list(broadcast_channel), "Insufficient Gas") if(autoeject) // Eject if configured. - msg += " Auto ejecting patient now." open_machine() - radio.talk_into(src, msg, RADIO_CHANNEL_MEDICAL) return PROCESS_KILL //take damage from high temperatures @@ -707,6 +694,26 @@ /obj/machinery/cryo_cell/get_remote_view_fullscreens(mob/user) user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 1) +/datum/aas_config_entry/medical_cryo_announcements + name = "Medical Alert: Cryogenics Reports" + announcement_lines_map = list( + "Autoejecting" = "Auto ejecting patient now.", + "Deceased" = "Cryogenics report: Patient is deceased. %AUTOEJECTING", + "Fully Recovered" = "Cryogenics report: Patient fully restored. %AUTOEJECTING", + "Insufficient Gas" = "Cryogenics report: Insufficient cryogenic gas, shutting down. %AUTOEJECTING", + "Wound Treatment" = "Cryogenics report: Patient vitals fully recovered, continuing automated wound treatment." + ) + vars_and_tooltips_map = list( + "AUTOEJECTING" = "will be replaced with Autoejecting line, if system reports it's necessity" + ) + +/datum/aas_config_entry/medical_cryo_announcements/compile_announce(list/variables_map, announcement_line) + variables_map["AUTOEJECTING"] = variables_map["EJECTING"] ? announcement_lines_map["Autoejecting"] : "" + . = ..() + // Why double replacetext_char? Well, to handle cases where variable in the middle of sentence like "also %AUTOEJECTING this", so there will be no double spaces + // Yeah I am bad, at this, sorry (it should be a perfect place for regex usage, but I am weak) + . = trim(replacetext_char(replacetext_char(., "\[NO DATA\] ", ""), "\[NO DATA\]", "")) + #undef MAX_TEMPERATURE #undef CRYO_MULTIPLY_FACTOR #undef CRYO_TX_QTY diff --git a/code/modules/bitrunning/objects/vendor.dm b/code/modules/bitrunning/objects/vendor.dm index 6c66f7e379d8..2fab4b6e9024 100644 --- a/code/modules/bitrunning/objects/vendor.dm +++ b/code/modules/bitrunning/objects/vendor.dm @@ -23,6 +23,7 @@ CATEGORY_BEPIS, ) blackbox_key = "bitrunning" + announcement_line = "A bitrunner has ordered equipment which will arrive on the cargo shuttle! Please make sure it gets to them as soon as possible!" /obj/machinery/computer/order_console/bitrunning/subtract_points(final_cost, obj/item/card/id/card) if(final_cost <= card.registered_account.bitrunning_points) @@ -56,7 +57,7 @@ can_be_cancelled = FALSE, ) say("Thank you for your purchase! It will arrive on the next cargo shuttle!") - radio.talk_into(src, "A bitrunner has ordered equipment which will arrive on the cargo shuttle! Please make sure it gets to them as soon as possible!", radio_channel) + aas_config_announce(/datum/aas_config_entry/order_console, list(), src, list(radio_channel), capitalize(blackbox_key)) SSshuttle.shopping_list += new_order /obj/machinery/computer/order_console/bitrunning/retrieve_points(obj/item/card/id/id_card) diff --git a/code/modules/bitrunning/server/_parent.dm b/code/modules/bitrunning/server/_parent.dm index 3562fd10de2a..82adb01cacb5 100644 --- a/code/modules/bitrunning/server/_parent.dm +++ b/code/modules/bitrunning/server/_parent.dm @@ -62,11 +62,6 @@ /obj/machinery/quantum_server/post_machine_initialize() . = ..() - radio = new(src) - radio.keyslot = new /obj/item/encryptionkey/headset_cargo() - radio.set_listening(FALSE) - radio.recalculateChannels() - RegisterSignals(src, list(COMSIG_MACHINERY_BROKEN, COMSIG_MACHINERY_POWER_LOST), PROC_REF(on_broken)) RegisterSignal(src, COMSIG_QDELETING, PROC_REF(on_delete)) @@ -76,7 +71,6 @@ spawned_threat_refs.Cut() exit_turfs.Cut() QDEL_NULL(generated_domain) - QDEL_NULL(radio) return ..() /obj/machinery/quantum_server/add_context(atom/source, list/context, obj/item/held_item, mob/user) @@ -198,3 +192,10 @@ servo_bonus = servo_rating return ..() + +/datum/aas_config_entry/bitrunning_QS_ready_announcement + name = "Cargo Alert: Bitrunning QS Ready" + general_tooltip = "Announces when the quantum server is ready to be used. No variables provided" + announcement_lines_map = list( + "Message" = "Quantum Server report: Thermal systems within operational parameters. Proceeding to domain configuration." + ) diff --git a/code/modules/bitrunning/server/threats.dm b/code/modules/bitrunning/server/threats.dm index f47dd34ff1a0..22558f51925d 100644 --- a/code/modules/bitrunning/server/threats.dm +++ b/code/modules/bitrunning/server/threats.dm @@ -130,12 +130,15 @@ /obj/machinery/quantum_server/proc/station_spawn(mob/living/antag, obj/machinery/byteforge/chosen_forge) antag.balloon_alert(antag, "scanning...") chosen_forge.setup_particles(angry = TRUE) - radio.talk_into(src, "SECURITY BREACH: Unauthorized entry sequence detected.", RADIO_CHANNEL_SUPPLY) + var/obj/machinery/announcement_system/aas = get_announcement_system(source = src) + if (aas) + aas.broadcast("QUANTUM SERVER ALERT: Security breach detected. Unauthorized entry sequence in progress...", list(RADIO_CHANNEL_SUPPLY)) SEND_SIGNAL(src, COMSIG_BITRUNNER_STATION_SPAWN) var/timeout = 2 SECONDS if(!ishuman(antag)) - radio.talk_into(src, "Fabrication protocols have crashed unexpectedly. Please evacuate the area.", RADIO_CHANNEL_SUPPLY) + if (aas) + aas.broadcast("QUANTUM SERVER ALERT: Fabrication protocols have crashed unexpectedly. Please evacuate the area.", list(RADIO_CHANNEL_SUPPLY)) timeout = 10 SECONDS var/bitrunners_alive = 0 @@ -173,8 +176,8 @@ if(ishuman(antag)) reset_equipment(antag) - else - radio.talk_into(src, "CRITICAL ALERT: Unregistered mechanical entity deployed.") + else if (aas) + aas.broadcast("QUANTUM SERVER CRITICAL ALERT: Unregistered mechanical entity deployed.", list()) var/datum/antagonist/antag_datum = antag.mind?.has_antag_datum(/datum/antagonist/bitrunning_glitch) if(istype(antag_datum)) diff --git a/code/modules/bitrunning/server/util.dm b/code/modules/bitrunning/server/util.dm index 6a852706ab93..c8cbca42d250 100644 --- a/code/modules/bitrunning/server/util.dm +++ b/code/modules/bitrunning/server/util.dm @@ -5,7 +5,7 @@ /obj/machinery/quantum_server/proc/cool_off() is_ready = TRUE update_appearance() - radio.talk_into(src, "Thermal systems within operational parameters. Proceeding to domain configuration.", RADIO_CHANNEL_SUPPLY) + aas_config_announce(/datum/aas_config_entry/bitrunning_QS_ready_announcement, list(), src, list(RADIO_CHANNEL_SUPPLY)) /// If there are hosted minds, attempts to get a list of their current virtual bodies w/ vitals diff --git a/code/modules/cargo/orderconsole.dm b/code/modules/cargo/orderconsole.dm index 232bba1e00c3..bf137549f513 100644 --- a/code/modules/cargo/orderconsole.dm +++ b/code/modules/cargo/orderconsole.dm @@ -17,8 +17,6 @@ human remains, classified nuclear weaponry, mail, undelivered departmental order crates, syndicate bombs, \ homing beacons, unstable eigenstates, fax machines, or machinery housing any form of artificial intelligence." var/blockade_warning = "Bluespace instability detected. Shuttle movement impossible." - /// radio used by the console to send messages on supply channel - var/obj/item/radio/headset/radio /// var that tracks message cooldown var/message_cooldown var/list/loaded_coupons @@ -46,14 +44,6 @@ can_approve_requests = FALSE requestonly = TRUE -/obj/machinery/computer/cargo/Initialize(mapload) - . = ..() - radio = new /obj/item/radio/headset/headset_cargo(src) - -/obj/machinery/computer/cargo/Destroy() - QDEL_NULL(radio) - return ..() - /obj/machinery/computer/cargo/attacked_by(obj/item/I, mob/living/user) if(istype(I,/obj/item/trade_chip)) var/obj/item/trade_chip/contract = I @@ -311,8 +301,7 @@ if(self_paid) say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.") if(requestonly && message_cooldown < world.time) - var/message = amount == 1 ? "A new order has been requested." : "[amount] order has been requested." - radio.talk_into(src, message, RADIO_CHANNEL_SUPPLY) + aas_config_announce(/datum/aas_config_entry/cargo_orders_announcement, list("AMOUNT" = amount), src, list(RADIO_CHANNEL_SUPPLY), amount == 1 ? "Single Order" : "Multiple Orders") message_cooldown = world.time + 30 SECONDS . = TRUE @@ -487,3 +476,13 @@ var/datum/signal/status_signal = new(list("command" = command)) frequency.post_signal(src, status_signal) + +/datum/aas_config_entry/cargo_orders_announcement + name = "Cargo Alert: New Orders" + announcement_lines_map = list( + "Single Order" = "A new order has been requested.", + "Multiple Orders" = "%AMOUNT orders have been requested.", + ) + vars_and_tooltips_map = list( + "AMOUNT" = "will be replaced wuth number of orders.", + ) diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 0fd1e1f27fcc..4730fecedb2e 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -269,9 +269,8 @@ var/datum/record/crew/crew_rec = find_record(H.real_name) var/obj/item/card/id/crew_id = H.get_idcard(hand_first = FALSE) var/job_name = crew_rec?.rank || crew_id?.assignment || crew_id?.trim?.assignment || H.job - var/datum/callback/announce = CALLBACK(pick(GLOB.announcement_systems), TYPE_PROC_REF(/obj/machinery/announcement_system, announce), "NEWHEAD", H.real_name, job_name, channels) //timer because these should come after the captain announcement - SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(_addtimer), announce, 0.1 SECONDS)) + SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(_addtimer), CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(aas_config_announce), /datum/aas_config_entry/newhead, list("PERSON" = human.real_name, "RANK" = job_name), null, channels, null, TRUE), 1)) //If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1 /datum/job/proc/player_old_enough(client/player) diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm index a506c1485f58..b74fac87ecb1 100644 --- a/code/modules/jobs/job_types/security_officer.dm +++ b/code/modules/jobs/job_types/security_officer.dm @@ -169,11 +169,14 @@ GLOBAL_LIST_EMPTY(security_officer_distribution) department, distribution, ) - var/obj/machinery/announcement_system/announcement_system = pick(GLOB.announcement_systems) + var/obj/machinery/announcement_system/announcement_system = get_announcement_system(/datum/aas_config_entry/announce_officer) if (isnull(announcement_system)) return - announcement_system.announce_officer(officer, department) + announcement_system.announce(/datum/aas_config_entry/announce_officer, list( + "OFFICER" = officer.real_name, + "DEPARTMENT" = department, + ), list(RADIO_CHANNEL_SECURITY)) var/list/targets = list() @@ -194,10 +197,14 @@ GLOBAL_LIST_EMPTY(security_officer_distribution) if (!targets.len) return + // I thought it would be great, if AAS also modifies PDA messages. Especially because it's AASs message. var/datum/signal/subspace/messaging/tablet_message/signal = new(announcement_system, list( "fakename" = "Security Department Update", "fakejob" = "Automated Announcement System", - "message" = "Officer [officer.real_name] has been assigned to your department, [department].", + "message" = announcement_system.compile_config_message(/datum/aas_config_entry/announce_officer, list( + "OFFICER" = officer.real_name, + "DEPARTMENT" = department, + )), "targets" = targets, "automated" = TRUE, )) diff --git a/code/modules/mining/laborcamp/laborstacker.dm b/code/modules/mining/laborcamp/laborstacker.dm index d578116e6b95..7e068ba37060 100644 --- a/code/modules/mining/laborcamp/laborstacker.dm +++ b/code/modules/mining/laborcamp/laborstacker.dm @@ -10,13 +10,11 @@ GLOBAL_LIST(labor_sheet_values) density = FALSE /// Connected stacking machine var/obj/machinery/mineral/stacking_machine/laborstacker/stacking_machine - /// Needed to send messages to sec radio - var/obj/item/radio/security_radio + /// Cooldown for console says. + COOLDOWN_DECLARE(say_cooldown) /obj/machinery/mineral/labor_claim_console/Initialize(mapload) . = ..() - security_radio = new /obj/item/radio(src) - security_radio.set_listening(FALSE) locate_stacking_machine() //If we can't find a stacking machine end it all ok? if(!stacking_machine) @@ -31,7 +29,6 @@ GLOBAL_LIST(labor_sheet_values) GLOB.labor_sheet_values = sort_list(sheet_list, GLOBAL_PROC_REF(cmp_sheet_list)) /obj/machinery/mineral/labor_claim_console/Destroy() - QDEL_NULL(security_radio) if(stacking_machine) stacking_machine.labor_console = null stacking_machine = null @@ -118,8 +115,7 @@ GLOBAL_LIST(labor_sheet_values) to_chat(user_mob, span_alert("No permission to dock could be granted.")) else if(!(obj_flags & EMAGGED)) - security_radio.set_frequency(FREQ_SECURITY) - security_radio.talk_into(src, "A prisoner has returned to the station. Minerals and Prisoner ID card ready for retrieval.", FREQ_SECURITY) + aas_config_announce(/datum/aas_config_entry/security_labor_stacker, list("PERSON" = user_mob.real_name), src, list(RADIO_CHANNEL_SECURITY)) user_mob.log_message("has completed their labor points goal and is now sending the gulag shuttle back to the station.", LOG_GAME) to_chat(user_mob, span_notice("Shuttle received message and will be sent shortly.")) return TRUE @@ -190,3 +186,14 @@ GLOBAL_LIST(labor_sheet_values) say("ID: [prisoner_id.registered_name].") say("Points Collected: [prisoner_id.points] / [prisoner_id.goal].") say("Collect points by bringing smelted minerals to the Labor Shuttle stacking machine. Reach your quota to earn your release.") + +/datum/aas_config_entry/security_labor_stacker + name = "Security Alert: Labor Camp Release" + announcement_lines_map = list( + "Message" = "%PERSON returned to the station. Minerals and Prisoner ID card ready for retrieval." + ) + vars_and_tooltips_map = list( + "PERSON" = "will be replaced with the name of the prisoner." + ) + +#undef SHEET_POINT_VALUE diff --git a/code/modules/modular_computers/file_system/programs/dept_order.dm b/code/modules/modular_computers/file_system/programs/dept_order.dm index 951dcbae6d3b..bf264e48b3ae 100644 --- a/code/modules/modular_computers/file_system/programs/dept_order.dm +++ b/code/modules/modular_computers/file_system/programs/dept_order.dm @@ -29,18 +29,16 @@ ) /// Reference to the order we've made UNTIL it gets sent on the supply shuttle. this is so heads can cancel it VAR_PRIVATE/datum/supply_order/department_order - /// Our radio object we use to talk to our department. - VAR_PRIVATE/obj/item/radio/radio /// The radio channel we will speak into by default. VAR_PRIVATE/radio_channel - /// Maps what department gets what encryption key + /// Maps what department should report to what radio channel /// I could've put this on the job department datum but it felt unnecessary - VAR_PRIVATE/static/list/dept_to_radio = list( - /datum/job_department/engineering = /obj/item/encryptionkey/headset_eng, - /datum/job_department/medical = /obj/item/encryptionkey/headset_med, - /datum/job_department/science = /obj/item/encryptionkey/headset_sci, - /datum/job_department/security = /obj/item/encryptionkey/headset_sec, - /datum/job_department/service = /obj/item/encryptionkey/headset_service, + VAR_PRIVATE/static/list/dept_to_radio_channel = list( + /datum/job_department/engineering = RADIO_CHANNEL_ENGINEERING, + /datum/job_department/medical = RADIO_CHANNEL_MEDICAL, + /datum/job_department/science = RADIO_CHANNEL_SCIENCE, + /datum/job_department/security = RADIO_CHANNEL_SECURITY, + /datum/job_department/service = RADIO_CHANNEL_SERVICE, ) /// Sets the passed department type as the active department for this computer file. @@ -53,22 +51,10 @@ use_access |= linked_department_real.head_of_staff_access use_access |= linked_department_real.department_access // Also set up the radio - if(dept_to_radio[linked_department]) - if(!isnull(radio)) - QDEL_NULL(radio) - var/picked_key = dept_to_radio[linked_department] || /obj/item/encryptionkey/headset_cargo - radio = new(computer) - radio.keyslot = new picked_key() - radio.subspace_transmission = TRUE - radio.canhear_range = 0 - radio.recalculateChannels() - radio_channel = radio.keyslot.channels[1] + if(dept_to_radio_channel[linked_department]) + radio_channel = dept_to_radio_channel[linked_department] || RADIO_CHANNEL_SUPPLY computer.update_static_data_for_all_viewers() -/datum/computer_file/program/department_order/Destroy() - QDEL_NULL(radio) - return ..() - /datum/computer_file/program/department_order/ui_interact(mob/user, datum/tgui/ui) check_cooldown() @@ -242,6 +228,9 @@ SSshuttle.shopping_list += department_order if(!already_signalled) RegisterSignal(SSshuttle, COMSIG_SUPPLY_SHUTTLE_BUY, PROC_REF(finalize_department_order)) + if(!alert_silenced && alert_able) + aas_config_announce(/datum/aas_config_entry/department_orders, list("ORDER" = pack.name, "PERSON" = name), computer.physical, list(radio_channel), "Order Placed") + aas_config_announce(/datum/aas_config_entry/department_orders_cargo, list("DEPARTMENT" = linked_department.department_name), computer.physical, list(RADIO_CHANNEL_SUPPLY)) computer.physical.say("Order processed. Cargo will deliver the crate when it comes in on their shuttle. NOTICE: Heads of staff may override the order.") calculate_cooldown(pack.cost) @@ -265,7 +254,7 @@ /datum/computer_file/program/department_order/process_tick(seconds_per_tick) if(!check_cooldown() || alert_silenced || !alert_able) return - radio?.talk_into(computer, "Order cooldown has expired! A new order may now be placed!", radio_channel) + aas_config_announce(/datum/aas_config_entry/department_orders, list(), computer.physical, list(radio_channel), "Cooldown Reset") computer.alert_call(src, "Order cooldown expired!", 'sound/machines/ping.ogg') /// Checks if the cooldown is up and resets it if so. @@ -274,3 +263,23 @@ department_cooldowns[linked_department] = 0 return TRUE return FALSE + +/datum/aas_config_entry/department_orders + name = "Departmental Order Announcement" + announcement_lines_map = list( + "Order Placed" = "A department order has been placed by %PERSON for %ORDER.", + "Cooldown Reset" = "Department order cooldown has expired! A new order may now be placed!", + ) + vars_and_tooltips_map = list( + "ORDER" = "will be replaced with the package name", + "PERSON" = "with the orderer's name", + ) + +/datum/aas_config_entry/department_orders_cargo + name = "Cargo Alert: New Departmental Order" + announcement_lines_map = list( + "Message" = "New %DEPARTMENT departmental order has been placed" + ) + vars_and_tooltips_map = list( + "DEPARTMENT" = "will be replaced with orderer's department." + ) diff --git a/code/modules/power/turbine/turbine.dm b/code/modules/power/turbine/turbine.dm index 4dc04d6e6f49..5e010305b39b 100644 --- a/code/modules/power/turbine/turbine.dm +++ b/code/modules/power/turbine/turbine.dm @@ -412,8 +412,6 @@ var/damage = 0 ///Used to calculate the max damage received per tick and if the alarm should be called var/damage_archived = 0 - ///Our internal radio - var/obj/item/radio/radio COOLDOWN_DECLARE(turbine_damage_alert) @@ -421,17 +419,8 @@ //Volume of gas mixture is 3000 . = ..(mapload, gas_theoretical_volume = 3000) - radio = new(src) - radio.keyslot = new /obj/item/encryptionkey/headset_eng - radio.set_listening(FALSE) - radio.recalculateChannels() - new /obj/item/paper/guides/jobs/atmos/turbine(loc) -/obj/machinery/power/turbine/core_rotor/Destroy() - QDEL_NULL(radio) - return ..() - /obj/machinery/power/turbine/core_rotor/add_context(atom/source, list/context, obj/item/held_item, mob/user) . = ..() if(. == NONE) @@ -671,7 +660,7 @@ explosion(src, 2, 5, 7) return PROCESS_KILL - radio.talk_into(src, "Warning, turbine at [get_area_name(src)] taking damage, current integrity at [integrity]%!", RADIO_CHANNEL_ENGINEERING) + aas_config_announce(/datum/aas_config_entry/engineering_turbine_failure, list("INTEGRITY" = integrity, "LOCATION" = get_area_name(src)), src, list(RADIO_CHANNEL_ENGINEERING)) playsound(src, 'sound/machines/engine_alert1.ogg', 100, FALSE, 30, 30, falloff_distance = 10) //================ROTOR WORKING============// @@ -708,5 +697,15 @@ -Each tier increases the efficiency (more power), the max reachable RPM, and the max temperature that the machine can process without taking damage (up to fusion temperatures at the last tier!).
\ -A word of warning, the machine is very inefficient in its gas consumption and many unburnt gases will pass through. If you want to be cheap you can either pre-burn the gases or add a filtering system to collect the unburnt gases and reuse them." +/datum/aas_config_entry/engineering_turbine_failure + name = "Engineering Alert: Turbine Failure" + announcement_lines_map = list( + "Message" = "Warning, turbine at %LOCATION taking damage, current integrity at %INTEGRITY%!", + ) + vars_and_tooltips_map = list( + "LOCATION" = "will be replaced with location of the turbine.", + "INTEGRITY" = "with the current integrity of the turbine.", + ) + #undef PRESSURE_MAX #undef MINIMUM_TURBINE_PRESSURE diff --git a/code/modules/shuttle/arrivals.dm b/code/modules/shuttle/arrivals.dm index bd50a6b68958..3d3d291ad48b 100644 --- a/code/modules/shuttle/arrivals.dm +++ b/code/modules/shuttle/arrivals.dm @@ -78,10 +78,7 @@ damaged = TRUE if(console) console.say("Alert, hull breach detected!") - if (length(GLOB.announcement_systems)) - var/obj/machinery/announcement_system/announcer = pick(GLOB.announcement_systems) - if(!QDELETED(announcer)) - announcer.announce("ARRIVALS_BROKEN", channels = list()) + aas_config_announce(/datum/aas_config_entry/arrivals_broken, list(), command_span=TRUE) if(mode != SHUTTLE_CALL) sound_played = FALSE mode = SHUTTLE_IDLE diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 1f1ac9d9fdd0..26b7962e944e 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -218,9 +218,6 @@ GLOBAL_LIST_EMPTY(vending_machines_to_restock) ///Name of lighting mask for the vending machine var/light_mask - /// used for narcing on underages - var/obj/item/radio/sec_radio - ///Whether this vendor can be selected when building a custom vending machine var/allow_custom = FALSE @@ -296,7 +293,6 @@ GLOBAL_LIST_EMPTY(vending_machines_to_restock) /obj/machinery/vending/Destroy() QDEL_NULL(coin) QDEL_NULL(bill) - QDEL_NULL(sec_radio) GLOB.vending_machines_to_restock -= src return ..() @@ -1419,11 +1415,12 @@ GLOBAL_LIST_EMPTY(vending_machines_to_restock) if(age_restrictions && item_record.age_restricted && (!card_used.registered_age || card_used.registered_age < AGE_MINOR)) speak("You are not of legal age to purchase [item_record.name].") if(!(usr in GLOB.narcd_underages)) - if (isnull(sec_radio)) - sec_radio = new (src) - sec_radio.set_listening(FALSE) - sec_radio.set_frequency(FREQ_SECURITY) - sec_radio.talk_into(src, "SECURITY ALERT: Underaged crewmember [usr] recorded attempting to purchase [item_record.name] in [get_area(src)]. Please watch for substance abuse.", FREQ_SECURITY) + aas_config_announce(/datum/aas_config_entry/vendomat_age_control, list( + "PERSON" = usr.name, + "LOCATION" = get_area_name(src), + "VENDOR" = name, + "PRODUCT" = item_record.name + ), src, list(RADIO_CHANNEL_SECURITY)) GLOB.narcd_underages += usr flick(icon_deny,src) vend_ready = TRUE @@ -1871,4 +1868,16 @@ GLOBAL_LIST_EMPTY(vending_machines_to_restock) add_filter("vending_outline", 9, list("type" = "outline", "color" = COLOR_VERY_SOFT_YELLOW)) add_filter("vending_rays", 10, list("type" = "rays", "size" = 35, "color" = COLOR_VIVID_YELLOW)) +/datum/aas_config_entry/vendomat_age_control + name = "Security Alert: Underaged Substance Abuse" + announcement_lines_map = list( + "Message" = "SECURITY ALERT: Underaged crewmember %PERSON recorded attempting to purchase %PRODUCT in %LOCATION by %VENDOR. Please watch for substance abuse." + ) + vars_and_tooltips_map = list( + "PERSON" = "will be replaced with the name of the crewmember", + "PRODUCT" = "with the product, he attempted to purchase", + "LOCATION" = "with place of purchase", + "VENDOR" = "with the vending machine" + ) + #undef MAX_VENDING_INPUT_AMOUNT diff --git a/tgui/packages/tgui/interfaces/AutomatedAnnouncement.tsx b/tgui/packages/tgui/interfaces/AutomatedAnnouncement.tsx index d4fab9f73f4a..de864c056240 100644 --- a/tgui/packages/tgui/interfaces/AutomatedAnnouncement.tsx +++ b/tgui/packages/tgui/interfaces/AutomatedAnnouncement.tsx @@ -1,96 +1,158 @@ -import { Button, Input, LabeledList, Section } from 'tgui-core/components'; +import { filter, sortBy } from 'common/collections'; +import { useState } from 'react'; +import { + Button, + Input, + LabeledList, + NoticeBox, + Section, + Stack, + Table, +} from 'tgui-core/components'; import type { BooleanLike } from 'tgui-core/react'; import { useBackend } from '../backend'; import { Window } from '../layouts'; -const TOOLTIP_TEXT = ` - %PERSON will be replaced with their name. - %RANK with their job. -`; +type AASConfigEntry = { + name: string; + entryRef: string; + enabled: BooleanLike; + modifiable: BooleanLike; + announcementLinesMap: Record; + generalTooltip: string; + varsAndTooltipsMap: Record; +}; type Data = { - arrivalToggle: BooleanLike; - arrival: string; - newheadToggle: BooleanLike; - newhead: string; + config_entries: AASConfigEntry[]; }; export const AutomatedAnnouncement = (props) => { const { act, data } = useBackend(); - const { arrivalToggle, arrival, newheadToggle, newhead } = data; + const { config_entries = [] } = data; + + const [search, setSearch] = useState(''); + + const isEntryMatch = (entry: AASConfigEntry, search: string) => { + if (!search) return true; + const { name, announcementLinesMap = [], varsAndTooltipsMap = [] } = entry; + switch (true) { + case name?.toLowerCase().includes(search?.toLowerCase()): + case Object.keys(announcementLinesMap).some((lineKey) => + lineKey.toLowerCase().includes(search?.toLowerCase()), + ): + case Object.values(announcementLinesMap).some((line) => + line.toLowerCase().includes(search?.toLowerCase()), + ): + case Object.keys(varsAndTooltipsMap).some((varName) => + varName.toLowerCase().includes(search?.toLowerCase()), + ): + return true; + default: + return false; + } + }; + + const errorMessage = !config_entries.length + ? 'No configurable options detected! Please report this to Nanotrasen.' + : 'No match. Refine your search.'; + + const sorted = sortBy( + filter(config_entries, (entry) => isEntryMatch(entry, search)), + (entry) => entry.name, + ); return ( - + -
act('ArrivalToggle')} - /> - } - > - - - } - > - - act('ArrivalText', { - newText: value, - }) - } - /> - - -
-
act('NewheadToggle')} - /> - } - > - - + + + + setSearch(value)} /> - } - > - - act('NewheadText', { - newText: value, - }) - } - /> - - -
+ + +
+ + {!sorted.length ? ( + {errorMessage} + ) : ( +
+ {sorted.map((entry, index) => ( +
+ + + } + > + + {Object.entries(entry.announcementLinesMap).map( + ([lineKey, announcementLine]) => ( + + + {lineKey}: + + + + act('Text', { + entryRef: entry.entryRef, + lineKey, + newText: value, + }) + } + /> + + + ), + )} +
+
+ ))} +
+ )} +
+
); From fa01890d83b8a9c8403c8e27b52d8a22fb659f56 Mon Sep 17 00:00:00 2001 From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Wed, 1 May 2024 13:03:13 +0000 Subject: [PATCH 31/45] Fixes monkeys falling over through windoors (#82963) Monkey knockover is now on a new mob-bump signal instead of being on crossed, fixing some consistency issues and getting rid of our need for snowflake "can bump into mob" check. We should try to get rid of all our instances of Crossed and remove its use to prevent this bug from occurring in the future. Closes https://github.com/tgstation/tgstation/issues/66983 :cl: fix: Monkeys can no longer be knocked over by walking into a windoor on combat mode while they're behind it. /:cl: --- .../signals/signals_mob/signals_mob_living.dm | 2 ++ .../events/immovable_rod/immovable_rod.dm | 5 ----- .../carbon/human/species_types/monkeys.dm | 17 +++++------------ code/modules/mob/living/living.dm | 1 + 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index 8de0872a7ea9..8903e2940a29 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -151,6 +151,8 @@ #define COMPONENT_LIVING_BLOCK_PRE_MOB_BUMP (1<<0) ///From base of mob/living/MobBump() (mob/living) #define COMSIG_LIVING_MOB_BUMP "living_mob_bump" +///From base of mob/living/MobBump() (mob/living) +#define COMSIG_LIVING_MOB_BUMPED "living_mob_bumped" ///From base of mob/living/Bump() (turf/closed) #define COMSIG_LIVING_WALL_BUMP "living_wall_bump" ///From base of turf/closed/Exited() (turf/closed) diff --git a/code/modules/events/immovable_rod/immovable_rod.dm b/code/modules/events/immovable_rod/immovable_rod.dm index ace1093d2428..ad0c9715ed2b 100644 --- a/code/modules/events/immovable_rod/immovable_rod.dm +++ b/code/modules/events/immovable_rod/immovable_rod.dm @@ -76,11 +76,6 @@ if(istype(ghost)) ghost.ManualFollow(src) -/obj/effect/immovablerod/proc/on_entered_over_movable(datum/source, atom/movable/atom_crossed_over) - SIGNAL_HANDLER - if((atom_crossed_over.density || isliving(atom_crossed_over)) && !QDELETED(atom_crossed_over)) - Bump(atom_crossed_over) - /obj/effect/immovablerod/proc/on_entering_atom(datum/source, atom/destination, atom/old_loc, list/atom/old_locs) SIGNAL_HANDLER if(destination.density && isturf(destination)) diff --git a/code/modules/mob/living/carbon/human/species_types/monkeys.dm b/code/modules/mob/living/carbon/human/species_types/monkeys.dm index a82d3b89f4d7..93e960b31efb 100644 --- a/code/modules/mob/living/carbon/human/species_types/monkeys.dm +++ b/code/modules/mob/living/carbon/human/species_types/monkeys.dm @@ -160,24 +160,17 @@ /obj/item/organ/brain/primate/on_mob_insert(mob/living/carbon/primate) . = ..() - RegisterSignal(primate, COMSIG_MOVABLE_CROSS, PROC_REF(on_crossed), TRUE) + RegisterSignal(primate, COMSIG_LIVING_MOB_BUMPED, PROC_REF(on_mob_bump)) /obj/item/organ/brain/primate/on_mob_remove(mob/living/carbon/primate) . = ..() - UnregisterSignal(primate, COMSIG_MOVABLE_CROSS) + UnregisterSignal(primate, COMSIG_LIVING_MOB_BUMPED) -/obj/item/organ/brain/primate/proc/on_crossed(datum/source, atom/movable/crossed) +/obj/item/organ/internal/brain/primate/proc/on_mob_bump(mob/source, mob/living/crossing_mob) SIGNAL_HANDLER - if(!tripping) + if(!tripping || !crossing_mob.combat_mode) return - if(IS_DEAD_OR_INCAP(owner) || !isliving(crossed)) - return - var/mob/living/in_the_way_mob = crossed - if(iscarbon(in_the_way_mob) && !in_the_way_mob.combat_mode) - return - if(in_the_way_mob.pass_flags & PASSTABLE) - return - in_the_way_mob.knockOver(owner) + crossing_mob.knockOver(owner) /obj/item/organ/brain/primate/get_attacking_limb(mob/living/carbon/human/target) return owner.get_bodypart(BODY_ZONE_HEAD) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 68d0d03e8169..c8a2f5f931e8 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -178,6 +178,7 @@ return TRUE SEND_SIGNAL(src, COMSIG_LIVING_MOB_BUMP, M) + SEND_SIGNAL(M, COMSIG_LIVING_MOB_BUMPED, src) //Even if we don't push/swap places, we "touched" them, so spread fire spreadFire(M) From 05c51d3bb3de53b788c59a2e339ec001121efd1d Mon Sep 17 00:00:00 2001 From: Critawakets Date: Fri, 21 Aug 2026 02:35:47 -0400 Subject: [PATCH 32/45] Fixes up the tgstation dme --- tgstation.dme | 3 --- 1 file changed, 3 deletions(-) diff --git a/tgstation.dme b/tgstation.dme index fbe20f7190e0..d73f08814a18 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3590,7 +3590,6 @@ #include "code\modules\bitrunning\virtual_domain\domains\hierophant.dm" #include "code\modules\bitrunning\virtual_domain\domains\island_brawl.dm" #include "code\modules\bitrunning\virtual_domain\domains\meta_central.dm" -#include "code\modules\bitrunning\virtual_domain\domains\meta_central.dm" #include "code\modules\bitrunning\virtual_domain\domains\pipedream.dm" #include "code\modules\bitrunning\virtual_domain\domains\pirates.dm" #include "code\modules\bitrunning\virtual_domain\domains\psyker_shuffle.dm" @@ -4737,8 +4736,6 @@ #include "code\modules\mob\living\basic\farm_animals\cow\cow_wisdom.dm" #include "code\modules\mob\living\basic\farm_animals\deer\deer.dm" #include "code\modules\mob\living\basic\farm_animals\deer\deer_ai.dm" -#include "code\modules\mob\living\basic\farm_animals\deer\deer.dm" -#include "code\modules\mob\living\basic\farm_animals\deer\deer_ai.dm" #include "code\modules\mob\living\basic\farm_animals\goat\_goat.dm" #include "code\modules\mob\living\basic\farm_animals\goat\goat_ai.dm" #include "code\modules\mob\living\basic\farm_animals\goat\goat_subtypes.dm" From 60e20f425ca6011b5f88a863b63ddd0bcf205087 Mon Sep 17 00:00:00 2001 From: DATA <44149906+DATA-xPUNGED@users.noreply.github.com> Date: Wed, 12 Mar 2025 23:26:06 -0300 Subject: [PATCH 33/45] Adds a new smite: Retcon, also refactors the temporary_atom component into an atom level proc (#90016) ## About The Pull Request Adds a new Retcon smite, it makes the person fade out into nothingness with a configurable timer, deletes their records and reopens their job slot, as if they were never there at all. I was also annoyed that to play around with temporary_atom I had to slowy add a component, and it doesn't really have much of a reason to BE a component, so I refactored it into an atom level proc called fade_into_nothing ## Why It's Good For The Game The smite is useful for when you wanna get rid of someone who had to leave roundstart and whatnot, on top of just being funny. the refactor is also good because i can now put that proc on build mode and go to town. ## Changelog :cl: add: Added new mechanics or gameplay changes add: Added more things del: Removed old things qol: made something easier to use balance: rebalanced something fix: fixed a few things sound: added/modified/removed audio or sound effects image: added/modified/removed some icons or images map: added/modified/removed map content spellcheck: fixed a few typos code: changed some code refactor: refactored some code config: changed some config setting admin: messed with admin stuff server: something server ops should know /:cl: --------- Co-authored-by: Jacquerel --- code/__HELPERS/fade_into_nothing.dm | 9 +++++ code/datums/components/ranged_attacks.dm | 2 +- code/datums/components/storm_hating.dm | 2 +- code/datums/elements/temporary_atom.dm | 17 -------- code/datums/records/manifest.dm | 8 ++++ code/game/objects/effects/spiderwebs.dm | 2 +- .../admin/smites/{smite.dm => _smite.dm} | 0 code/modules/admin/smites/retcon.dm | 39 +++++++++++++++++++ .../basic/icemoon/ice_demon/ice_demon.dm | 2 +- .../file_system/programs/virtual_pet.dm | 2 +- tgstation.dme | 5 ++- 11 files changed, 64 insertions(+), 24 deletions(-) create mode 100644 code/__HELPERS/fade_into_nothing.dm delete mode 100644 code/datums/elements/temporary_atom.dm rename code/modules/admin/smites/{smite.dm => _smite.dm} (100%) create mode 100644 code/modules/admin/smites/retcon.dm diff --git a/code/__HELPERS/fade_into_nothing.dm b/code/__HELPERS/fade_into_nothing.dm new file mode 100644 index 000000000000..d8df8ced78eb --- /dev/null +++ b/code/__HELPERS/fade_into_nothing.dm @@ -0,0 +1,9 @@ +/// Deletes the atom with a little fading out animation after a specified time +/atom/proc/fade_into_nothing(life_time = 5 SECONDS, fade_time = 3 SECONDS) + QDEL_IN(src, life_time) + if (life_time > fade_time && fade_time > 0) + addtimer(CALLBACK(src, PROC_REF(fade_into_nothing_animate), fade_time), life_time - fade_time, TIMER_DELETE_ME) + +/// Actually does the fade out, used by fade_into_nothing() +/atom/proc/fade_into_nothing_animate(fade_time) + animate(src, alpha = 0, time = fade_time, flags = ANIMATION_PARALLEL) diff --git a/code/datums/components/ranged_attacks.dm b/code/datums/components/ranged_attacks.dm index 2f9c6cb822da..618d240ac67f 100644 --- a/code/datums/components/ranged_attacks.dm +++ b/code/datums/components/ranged_attacks.dm @@ -84,7 +84,7 @@ target_zone = ran_zone() casing.fire_casing(target, firer, null, null, null, target_zone, 0, firer) casing.update_appearance() - casing.AddElement(/datum/element/temporary_atom, 30 SECONDS) + casing.fade_into_nothing(30 SECONDS) SEND_SIGNAL(parent, COMSIG_BASICMOB_POST_ATTACK_RANGED, target, modifiers) return diff --git a/code/datums/components/storm_hating.dm b/code/datums/components/storm_hating.dm index ce43ba73276b..4060dc09a5e5 100644 --- a/code/datums/components/storm_hating.dm +++ b/code/datums/components/storm_hating.dm @@ -43,5 +43,5 @@ var/atom/parent_atom = parent if (!isturf(parent_atom.loc)) return - parent.AddElement(/datum/element/temporary_atom, life_time = 3 SECONDS, fade_time = 2 SECONDS) + parent_atom.fade_into_nothing(life_time = 3 SECONDS, fade_time = 2 SECONDS) qdel(src) diff --git a/code/datums/elements/temporary_atom.dm b/code/datums/elements/temporary_atom.dm deleted file mode 100644 index 6f91e8360b68..000000000000 --- a/code/datums/elements/temporary_atom.dm +++ /dev/null @@ -1,17 +0,0 @@ -/// Deletes the atom with a little fading out animation after a specified time -/datum/element/temporary_atom - -/datum/element/temporary_atom/Attach(datum/target, life_time = 5 SECONDS, fade_time = 3 SECONDS) - . = ..() - if (!isatom(target)) - return ELEMENT_INCOMPATIBLE - - addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), WEAKREF(target)), life_time, TIMER_DELETE_ME) - if (life_time > fade_time && fade_time > 0) - addtimer(CALLBACK(src, PROC_REF(fade_out), WEAKREF(target), fade_time), life_time - fade_time, TIMER_DELETE_ME) - -/datum/element/temporary_atom/proc/fade_out(datum/weakref/target_ref, fade_time) - var/atom/target = target_ref?.resolve() - if (isnull(target)) - return - animate(target, alpha = 0, time = fade_time, flags = ANIMATION_PARALLEL) diff --git a/code/datums/records/manifest.dm b/code/datums/records/manifest.dm index 21dc711109a3..f3a0b9707213 100644 --- a/code/datums/records/manifest.dm +++ b/code/datums/records/manifest.dm @@ -181,6 +181,14 @@ GLOBAL_DATUM_INIT(manifest, /datum/manifest, new) target.rank = assignment target.trim = trim +///Removes a record based on its name. +/datum/manifest/proc/remove(name) + var/datum/record/crew/target = find_record(name) + if(!target) + return + general -= target + qdel(target) + /** * Using the name to find the record, and person in reference to the body, we recreate photos for the manifest (and records). * Args: diff --git a/code/game/objects/effects/spiderwebs.dm b/code/game/objects/effects/spiderwebs.dm index 60190ce8014b..f543f19a75a7 100644 --- a/code/game/objects/effects/spiderwebs.dm +++ b/code/game/objects/effects/spiderwebs.dm @@ -276,6 +276,6 @@ /obj/structure/spider/effigy/Initialize(mapload) . = ..() - AddElement(/datum/element/temporary_atom, 1 MINUTES) + fade_into_nothing(1 MINUTES) #undef SPIDER_WEB_TINT diff --git a/code/modules/admin/smites/smite.dm b/code/modules/admin/smites/_smite.dm similarity index 100% rename from code/modules/admin/smites/smite.dm rename to code/modules/admin/smites/_smite.dm diff --git a/code/modules/admin/smites/retcon.dm b/code/modules/admin/smites/retcon.dm new file mode 100644 index 000000000000..461b7b330bbf --- /dev/null +++ b/code/modules/admin/smites/retcon.dm @@ -0,0 +1,39 @@ +/datum/smite/retcon + name = "Retcon" + /// how long until the thing attacked by this smite gets deleted + var/timer + /// the time it takes to actually do the fade out animation, the victim will always have some time still fully visible + var/fade_out_timer + +/datum/smite/retcon/configure(client/user) + timer = tgui_input_number(user, "How long should it take before the target disappears, in seconds?", "Retcon", 5) + + if (isnull(timer)) + return FALSE + + fade_out_timer = timer*3*0.2 + +/datum/smite/retcon/effect(client/user, mob/living/target) + . = ..() + target.fade_into_nothing(timer SECONDS,fade_out_timer SECONDS) + if(ishuman(target)) + delete_record(target) + delete_bank_account(target) + reopen_job_slot(target) + +/datum/smite/retcon/proc/delete_record(mob/living/target) + var/name = target.real_name + GLOB.manifest.remove(name) + +/datum/smite/retcon/proc/delete_bank_account(mob/living/target) + var/name = target.real_name + var/account_list = flatten_list(SSeconomy.bank_accounts_by_id) + for(var/datum/bank_account/account in account_list) + if(account.account_holder == name) + qdel(account) + return + +/datum/smite/retcon/proc/reopen_job_slot(mob/living/target) + var/datum/job/target_job = target.mind?.assigned_role + if(target_job) + target_job.total_positions += 1 diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm index 93045bf476b8..24efa68639ed 100644 --- a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm @@ -80,7 +80,7 @@ /mob/living/basic/mining/demon_afterimage/Initialize(mapload) . = ..() AddElement(/datum/element/simple_flying) - AddElement(/datum/element/temporary_atom, life_time = existence_period) + fade_into_nothing(life_time = existence_period) ///afterimage subtypes summoned by the crusher /mob/living/basic/mining/demon_afterimage/crusher diff --git a/code/modules/modular_computers/file_system/programs/virtual_pet.dm b/code/modules/modular_computers/file_system/programs/virtual_pet.dm index c4016e1e5625..35d80d3ce28c 100644 --- a/code/modules/modular_computers/file_system/programs/virtual_pet.dm +++ b/code/modules/modular_computers/file_system/programs/virtual_pet.dm @@ -535,7 +535,7 @@ GLOBAL_LIST_EMPTY(virtual_pets_list) announce_global_updates(message = "has found a chocolate at [selected_area.name]") selected_area = null var/obj/item/food/virtual_chocolate/chocolate = new(get_turf(computer)) - chocolate.AddElement(/datum/element/temporary_atom, life_time = 30 SECONDS) //we cant maintain its existence for too long! + chocolate.fade_into_nothing(life_time = 30 SECONDS) //we cant maintain its existence for too long! /datum/computer_file/program/virtual_pet/proc/recall_pet() animate(pet, transform = matrix().Scale(0.3, 0.3), time = 1.5 SECONDS) diff --git a/tgstation.dme b/tgstation.dme index d73f08814a18..4fdfb7fe2a9e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -470,6 +470,7 @@ #include "code\__HELPERS\string_assoc_nested_lists.dm" #include "code\__HELPERS\string_lists.dm" #include "code\__HELPERS\string_numbers_lists.dm" +#include "code\__HELPERS\fade_into_nothing.dm" #include "code\__HELPERS\text.dm" #include "code\__HELPERS\time.dm" #include "code\__HELPERS\traits.dm" @@ -1529,7 +1530,6 @@ #include "code\datums\elements\structure_repair.dm" #include "code\datums\elements\surgery_aid.dm" #include "code\datums\elements\swabbable.dm" -#include "code\datums\elements\temporary_atom.dm" #include "code\datums\elements\tenacious.dm" #include "code\datums\elements\tiny_mob_hunter.dm" #include "code\datums\elements\tool_flash.dm" @@ -2910,6 +2910,7 @@ #include "code\modules\admin\trophy_manager.dm" #include "code\modules\admin\whitelist.dm" #include "code\modules\admin\callproc\callproc.dm" +#include "code\modules\admin\smites\_smite.dm" #include "code\modules\admin\smites\bad_luck.dm" #include "code\modules\admin\smites\become_object.dm" #include "code\modules\admin\smites\berforate.dm" @@ -2933,9 +2934,9 @@ #include "code\modules\admin\smites\phobia_ocky_icky.dm" #include "code\modules\admin\smites\puzzgrid.dm" #include "code\modules\admin\smites\puzzle.dm" +#include "code\modules\admin\smites\retcon.dm" #include "code\modules\admin\smites\rod.dm" #include "code\modules\admin\smites\scarify.dm" -#include "code\modules\admin\smites\smite.dm" #include "code\modules\admin\smites\supply_pod.dm" #include "code\modules\admin\smites\supply_pod_quick.dm" #include "code\modules\admin\spawn_panel\admin_hotkeys.dm" From c4af19ddb771ece9a165ff70f14f9aec9f2010f8 Mon Sep 17 00:00:00 2001 From: DATA <44149906+DATA-xPUNGED@users.noreply.github.com> Date: Thu, 13 Mar 2025 04:28:34 -0300 Subject: [PATCH 34/45] Puts a file in the tgstation.dme into the right spot (#90018) ## About The Pull Request #90016 was speedmerged and it had a failed linter, this fixes that ## Why It's Good For The Game linters failing bad ## Changelog nothing player facing --- tgstation.dme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tgstation.dme b/tgstation.dme index 4fdfb7fe2a9e..e8d1812990bd 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -420,6 +420,7 @@ #include "code\__HELPERS\duplicating.dm" #include "code\__HELPERS\dynamic_human_icon_gen.dm" #include "code\__HELPERS\events.dm" +#include "code\__HELPERS\fade_into_nothing.dm" #include "code\__HELPERS\files.dm" #include "code\__HELPERS\filters.dm" #include "code\__HELPERS\forensics.dm" @@ -470,7 +471,6 @@ #include "code\__HELPERS\string_assoc_nested_lists.dm" #include "code\__HELPERS\string_lists.dm" #include "code\__HELPERS\string_numbers_lists.dm" -#include "code\__HELPERS\fade_into_nothing.dm" #include "code\__HELPERS\text.dm" #include "code\__HELPERS\time.dm" #include "code\__HELPERS\traits.dm" From 88bd17fa9291f26a239dbcf50c8657252cdac926 Mon Sep 17 00:00:00 2001 From: OrionTheFox <76465278+OrionTheFox@users.noreply.github.com> Date: Thu, 17 Jul 2025 23:00:12 -0600 Subject: [PATCH 35/45] Changes 'beach bar' bitdomain ghost-spawners to be virtual_domain subtypes (#92177) Simple enough. This domain used just plain ghost spawners from the lavaland ruin previously. Now, it has `temp_body = TRUE`, allowing ghosts to return to their original body after leaving the domain _Alongside this it has unique little descriptions to cement it as NPC actors_ This also adds a new `antag` variable to virtual_domain spawners, which decides if they get the hunt-bitrunners antag datum _(default True, in this case False)_
I did notice a few bugs while testing this, namely that temp_body doesn't always work? I'm not coder enough to hunt down why and fix it though... the two cases I noticed will be made into a bug report.
It feels weird that there's this temp_body ghost spawner stuff for bitdomains, but it's not used in **all** the bitdomains. They don't last that long, making them permanently kill old bodies feels odd. This also sets up an easy var for other bit ghostroles to use if they aren't explicitly antags hunting bitrunners, and don't need an antag datum. image :cl: qol: made the Beach Bar bitdomain ghostrole spawners keep your old body revivable (as other bitdomain spawners do) /:cl: --- _maps/virtual_domains/beach_bar.dmm | 6 +++--- code/modules/bitrunning/spawners.dm | 33 +++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/_maps/virtual_domains/beach_bar.dmm b/_maps/virtual_domains/beach_bar.dmm index 49ff295a0739..cc7b93806d99 100644 --- a/_maps/virtual_domains/beach_bar.dmm +++ b/_maps/virtual_domains/beach_bar.dmm @@ -688,7 +688,7 @@ /area/virtual_domain/fullbright) "CA" = ( /obj/structure/window/reinforced/spawner/directional/east, -/obj/effect/mob_spawn/ghost_role/human/beach/lifeguard, +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/beach/lifeguard, /turf/open/floor/wood, /area/virtual_domain/fullbright) "CO" = ( @@ -917,7 +917,7 @@ /turf/open/floor/light/colour_cycle/dancefloor_a, /area/virtual_domain/fullbright) "OE" = ( -/obj/effect/mob_spawn/ghost_role/human/beach{ +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/beach{ dir = 4 }, /turf/open/floor/wood, @@ -948,7 +948,7 @@ /area/virtual_domain/fullbright) "Pg" = ( /obj/structure/sign/poster/official/high_class_martini/directional/west, -/obj/effect/mob_spawn/ghost_role/human/bartender{ +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/beach/bartender{ dir = 4 }, /turf/open/floor/wood, diff --git a/code/modules/bitrunning/spawners.dm b/code/modules/bitrunning/spawners.dm index 31d4bf881c50..8758062bd21e 100644 --- a/code/modules/bitrunning/spawners.dm +++ b/code/modules/bitrunning/spawners.dm @@ -5,6 +5,8 @@ you_are_text = "You are NOT supposed to be here. How did you let this happen?" important_text = "Bitrunning is a crime, and your primary threat." temp_body = TRUE + ///Does this bit-entity get an antag datum with the goal of hunting bitrunners? TRUE by default + var/antag = TRUE /obj/effect/mob_spawn/ghost_role/human/virtual_domain/special(mob/living/spawned_mob, mob/mob_possessor) @@ -22,7 +24,35 @@ /obj/effect/mob_spawn/ghost_role/human/virtual_domain/proc/artificial_spawn(mob/living/runner) SEND_SIGNAL(src, COMSIG_BITRUNNER_SPAWNED, runner) +//Beach Bums (Friendly) +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/beach + prompt_name = "a virtual beach bum" + name = "virtual beach bum sleeper" + you_are_text = "You're, like, totally a virtual simulation of a dudebro, bruh." + flavour_text = "Ch'yea. You came here, like, on spring break, hopin' to pick up some bangin' hot e-chicks, y'knaw?" + important_text = "You have no qualms with Bitrunning: in fact, you aren't even aware you're in a simulation." + outfit = /datum/outfit/beachbum + spawner_job_path = /datum/job/beach_bum + antag = FALSE +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/beach/lifeguard + name = "virtual lifeguard sleeper" + you_are_text = "You are a spunky virtual lifeguard!" + flavour_text = "It's up to you to make sure nobody lags or gets eaten by malware and stuff." + outfit = /datum/outfit/beachbum/lifeguard + +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/beach/lifeguard/special(mob/living/carbon/human/lifeguard, mob/mob_possessor) + . = ..() + lifeguard.gender = FEMALE + lifeguard.update_body() + +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/beach/bartender + name = "virtual bartender sleeper" + you_are_text = "You are a virtual beach bartender!" + flavour_text = "Your job is to keep the virtually rendered drinks coming, and help the dudebros engage drunkness simulations." + outfit = /datum/outfit/spacebartender + +//Skeleton Pirates /obj/effect/mob_spawn/ghost_role/human/virtual_domain/pirate name = "Virtual Pirate Remains" desc = "Some inanimate bones. They feel like they could spring to life at any moment!" @@ -38,7 +68,7 @@ . = ..() spawned_mob.fully_replace_character_name(spawned_mob.real_name, "[pick(strings(PIRATE_NAMES_FILE, "generic_beginnings"))][pick(strings(PIRATE_NAMES_FILE, "generic_endings"))]") - +//Syndicate /obj/effect/mob_spawn/ghost_role/human/virtual_domain/syndie name = "Virtual Syndicate Sleeper" icon = 'icons/obj/machines/sleeper.dmi' @@ -49,7 +79,6 @@ outfit = /datum/outfit/virtual_syndicate spawner_job_path = /datum/job/space_syndicate - /datum/outfit/virtual_syndicate name = "Virtual Syndie" id = /obj/item/card/id/advanced/chameleon From 81dddbadf7eb1012e2d843cf40373dc4c160a522 Mon Sep 17 00:00:00 2001 From: Critawakets Date: Fri, 21 Aug 2026 03:02:46 -0400 Subject: [PATCH 36/45] Finishes it all up. --- code/__DEFINES/antagonists.dm | 3 + code/__HELPERS/game.dm | 2 +- code/game/area/areas/ruins/space.dm | 2 +- code/game/machinery/announcement_system.dm | 4 +- code/game/objects/items/storage/briefcase.dm | 11 ++++ .../objects/structures/outfit_wardrobe.dm | 10 +-- .../antagonists/heretic/heretic_knowledge.dm | 5 -- .../heretic/items/heretic_blades.dm | 63 ------------------- .../objects/gimmick_disks/sports_disk.dm | 6 +- code/modules/bitrunning/orders/tech.dm | 20 +++--- code/modules/bitrunning/server/loot.dm | 15 ++--- .../bitrunning/util/virtual_megafauna.dm | 11 +--- .../virtual_domain/domains/island_brawl.dm | 1 + .../virtual_domain/domains/pipedream.dm | 1 + .../virtual_domain/virtual_domain.dm | 4 +- code/modules/jobs/job_types/_job.dm | 2 +- .../carbon/human/species_types/monkeys.dm | 2 +- maplestation.dme | 18 ++++-- 18 files changed, 63 insertions(+), 117 deletions(-) diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm index b784f5de90b6..de4123eaff0c 100644 --- a/code/__DEFINES/antagonists.dm +++ b/code/__DEFINES/antagonists.dm @@ -222,8 +222,11 @@ GLOBAL_LIST_INIT(ai_employers, list( //Tells whether or not someone is a space ninja #define IS_SPACE_NINJA(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/ninja)) +// NON-MODULE CHANGE: original IS_HERETIC check wont be ported as it's error-prone, GET_HERETIC is therefore a copy /// Checks if the given mob is a heretic. #define IS_HERETIC(mob) (mob.mind?.has_antag_datum(/datum/antagonist/heretic)) +/// Checks if the given mob is a heretic and is guaranteed to return the datum if possible - added in order to satisfy compatibility with other PRs +#define GET_HERETIC(mob) (mob.mind?.has_antag_datum(/datum/antagonist/heretic)) /// Check if the given mob is a heretic monster. #define IS_HERETIC_MONSTER(mob) (mob.mind?.has_antag_datum(/datum/antagonist/heretic_monster)) /// Check if the given mob is a lunatic diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 207627753137..5d85e285c5dc 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -253,7 +253,7 @@ var/area/player_area = get_area(character) deadchat_broadcast(" has arrived at the station at [player_area.name].", "[character.real_name] ([job_title])", follow_target = character, message_type = DEADCHAT_ARRIVALRATTLE) if(character.mind && (character.mind.assigned_role.job_flags & JOB_ANNOUNCE_ARRIVAL)) - aas_config_announce(/datum/aas_config_entry/arrival, list("PERSON" = character.real_name,"RANK" = rank)) + aas_config_announce(/datum/aas_config_entry/arrival, list("PERSON" = character.real_name,"RANK" = job_title)) ///Check if the turf pressure allows specialized equipment to work /proc/lavaland_equipment_pressure_check(turf/turf_to_check) diff --git a/code/game/area/areas/ruins/space.dm b/code/game/area/areas/ruins/space.dm index 6da16019775a..db16c71940b2 100644 --- a/code/game/area/areas/ruins/space.dm +++ b/code/game/area/areas/ruins/space.dm @@ -535,7 +535,7 @@ name = "\improper DJ Station Solars" icon_state = "DJ" area_flags = NONE - default_gravity = ZERO_GRAVITY + has_gravity = FALSE /area/ruin/space/djstation/service name = "\improper DJ Station Service" diff --git a/code/game/machinery/announcement_system.dm b/code/game/machinery/announcement_system.dm index ca8d793ef18d..88c894933cac 100644 --- a/code/game/machinery/announcement_system.dm +++ b/code/game/machinery/announcement_system.dm @@ -123,7 +123,7 @@ GLOBAL_LIST_EMPTY(announcement_systems) )) return list("config_entries" = configs) -/obj/machinery/announcement_system/ui_act(action, param) +/obj/machinery/announcement_system/ui_act(action, params) . = ..() if(.) return @@ -131,7 +131,7 @@ GLOBAL_LIST_EMPTY(announcement_systems) return if(machine_stat & EMPED) visible_message(span_warning("[src] buzzes."), span_hear("You hear a faint buzz.")) - playsound(src.loc, 'sound/machines/buzz/buzz-two.ogg', 50, TRUE) + playsound(src.loc, 'sound/machines/buzz-two.ogg', 50, TRUE) return add_fingerprint(usr) diff --git a/code/game/objects/items/storage/briefcase.dm b/code/game/objects/items/storage/briefcase.dm index bc49f8ea781f..2a9e6d0d14ba 100644 --- a/code/game/objects/items/storage/briefcase.dm +++ b/code/game/objects/items/storage/briefcase.dm @@ -93,6 +93,17 @@ atom_storage.max_specific_storage = WEIGHT_CLASS_NORMAL AddComponent(/datum/component/lockable_storage) +/// 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 + ///Syndie variant of Secure Briefcase. Contains space cash, slightly more robust. /obj/item/storage/briefcase/secure/syndie force = 15 diff --git a/code/game/objects/structures/outfit_wardrobe.dm b/code/game/objects/structures/outfit_wardrobe.dm index f204559c3a9a..e6cedb2fdc94 100644 --- a/code/game/objects/structures/outfit_wardrobe.dm +++ b/code/game/objects/structures/outfit_wardrobe.dm @@ -68,17 +68,17 @@ chosen_class = choice human_user.balloon_alert(human_user, LOWER_TEXT(chosen_class.name)) - playsound(human_user, 'sound/items/zip/un_zip.ogg', 33) - playsound(src, 'sound/machines/closet/wooden_closet_open.ogg', 25) + playsound(human_user, 'sound/items/un_zip.ogg', 33) + playsound(src, 'sound/machines/wooden_closet_open.ogg', 25) icon_state = "fullcabinet_open" if(!do_after(human_user, 3 SECONDS) || selectable_outfits_to_amount[choice] == 0) - playsound(src, 'sound/machines/closet/wooden_closet_close.ogg', 50) + playsound(src, 'sound/machines/wooden_closet_close.ogg', 50) icon_state = base_icon_state return selectable_outfits_to_amount[choice]-- - playsound(src, 'sound/machines/closet/wooden_closet_close.ogg', 50) + playsound(src, 'sound/machines/wooden_closet_close.ogg', 50) icon_state = base_icon_state - playsound(human_user, 'sound/items/zip/zip_up.ogg', 33) + playsound(human_user, 'sound/items/zip_up.ogg', 33) human_user.drop_everything() human_user.equipOutfit(chosen_class) diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index 4fb70bdebefe..42b8b18bad69 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -238,11 +238,6 @@ return ..() /datum/heretic_knowledge/limited_amount/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) - var/datum/antagonist/heretic/our_heretic = GET_HERETIC(user) - if(our_heretic && our_heretic.unlimited_blades) - if(length(result_atoms & typesof(/obj/item/melee/sickly_blade))) - return TRUE - for(var/datum/weakref/ref as anything in created_items) var/atom/real_thing = ref.resolve() if(QDELETED(real_thing)) diff --git a/code/modules/antagonists/heretic/items/heretic_blades.dm b/code/modules/antagonists/heretic/items/heretic_blades.dm index e13bda1ec861..9062a0e081eb 100644 --- a/code/modules/antagonists/heretic/items/heretic_blades.dm +++ b/code/modules/antagonists/heretic/items/heretic_blades.dm @@ -39,31 +39,11 @@ SEND_SIGNAL(user, COMSIG_HERETIC_BLADE_ATTACK, target, src) /obj/item/melee/sickly_blade/attack_self(mob/user) - var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user) - if(heretic_datum?.unlimited_blades) - return - if(HAS_TRAIT(user, TRAIT_ELDRITCH_ARENA_PARTICIPANT)) - user.balloon_alert(user, "can't escape!") - if(escape_attempts > 2) - to_chat(user, span_hypnophrase(span_big("Cowardly sheep will be slaughtered!"))) - playsound(src, SFX_SHATTER, 70, TRUE) - var/obj/item/bodypart/to_remove = user.get_active_hand() - to_remove.dismember() - deltimer(escape_timer) - qdel(src) - return - escape_attempts++ - escape_timer = addtimer(CALLBACK(src, PROC_REF(reset_attempts)), 2 SECONDS, TIMER_STOPPABLE) - return if(HAS_TRAIT(user, TRAIT_NO_TELEPORT)) user.balloon_alert(user, "can't break!") return seek_safety(user) -/obj/item/melee/sickly_blade/proc/reset_attempts() - escape_attempts = 0 - deltimer(escape_timer) - /// Attempts to teleport the passed mob to somewhere safe on the station, if they can use the blade. /obj/item/melee/sickly_blade/proc/seek_safety(mob/user) var/turf/safe_turf = find_safe_turf(zlevels = z, extended_safety_checks = TRUE) @@ -150,49 +130,6 @@ icon_state = "dark_blade" inhand_icon_state = "dark_blade" after_use_message = "The Torn Champion hears your call..." - ///If our blade is currently infused with the mansus grasp - var/infused = FALSE - -/obj/item/melee/sickly_blade/dark/afterattack(atom/target, mob/user, list/modifiers, list/attack_modifiers) - . = ..() - if(!infused || target == user || !isliving(target)) - return - var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user) - var/mob/living/living_target = target - if(!heretic_datum) - return - - // Apply our heretic mark - var/datum/heretic_knowledge/limited_amount/starting/base_blade/mark_to_apply = heretic_datum.get_knowledge(/datum/heretic_knowledge/limited_amount/starting/base_blade) - if(!mark_to_apply) - return - mark_to_apply.create_mark(user, living_target) - infused = FALSE - update_appearance(UPDATE_ICON) - user.update_held_items() - - if(!check_behind(user, living_target)) - return - // We're officially behind them, apply effects - living_target.AdjustParalyzed(1.5 SECONDS) - living_target.apply_damage(10, BRUTE, wound_bonus = CANT_WOUND) - living_target.balloon_alert(user, "backstab!") - playsound(living_target, 'sound/items/weapons/guillotine.ogg', 100, TRUE) - -/obj/item/melee/sickly_blade/dark/dropped(mob/user, silent) - . = ..() - if(infused) - infused = FALSE - update_appearance(UPDATE_ICON) - -/obj/item/melee/sickly_blade/dark/update_icon_state() - . = ..() - if(infused) - icon_state = base_icon_state + "_infused" - inhand_icon_state = base_icon_state + "_infused" - else - icon_state = base_icon_state - inhand_icon_state = base_icon_state // Path of Cosmos's blade /obj/item/melee/sickly_blade/cosmic diff --git a/code/modules/bitrunning/objects/gimmick_disks/sports_disk.dm b/code/modules/bitrunning/objects/gimmick_disks/sports_disk.dm index 7ad20aad9025..be7b4bccff8c 100644 --- a/code/modules/bitrunning/objects/gimmick_disks/sports_disk.dm +++ b/code/modules/bitrunning/objects/gimmick_disks/sports_disk.dm @@ -63,8 +63,8 @@ name = "Archer" granted_items = list( - /obj/item/clothing/under/costume/kimono, - /obj/item/clothing/shoes/sandal/alt, + /obj/item/clothing/under/kimono, + /obj/item/clothing/shoes/sandal/black, /obj/item/storage/bag/quiver/endless, /obj/item/gun/ballistic/bow/longbow, /obj/item/ammo_casing/arrow/holy/blazing, @@ -73,11 +73,11 @@ /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)) + atom_storage.max_slots = 1 /obj/item/storage/bag/quiver/endless/PopulateContents() . = ..() diff --git a/code/modules/bitrunning/orders/tech.dm b/code/modules/bitrunning/orders/tech.dm index be8a43892ea6..333e636f7eaa 100644 --- a/code/modules/bitrunning/orders/tech.dm +++ b/code/modules/bitrunning/orders/tech.dm @@ -3,32 +3,32 @@ /datum/orderable_item/bitrunning_tech/item_tier1 cost_per_order = 750 - item_path = /obj/item/bitrunning_disk/item/tier1 + purchase_path = /obj/item/bitrunning_disk/item/tier1 desc = "This disk contains a program that lets you equip a medical beamgun, a C4 explosive, or a box of infinite pizza." /datum/orderable_item/bitrunning_tech/item_tier2 cost_per_order = 1250 - item_path = /obj/item/bitrunning_disk/item/tier2 + purchase_path = /obj/item/bitrunning_disk/item/tier2 desc = "This disk contains a program that lets you equip a luxury medipen, a pistol, or an armour vest." /datum/orderable_item/bitrunning_tech/item_tier3 cost_per_order = 2000 - item_path = /obj/item/bitrunning_disk/item/tier3 + purchase_path = /obj/item/bitrunning_disk/item/tier3 desc = "This disk contains a program that lets you equip an advanced energy gun, a dual bladed energy sword, or a minibomb." /datum/orderable_item/bitrunning_tech/ability_tier1 cost_per_order = 750 - item_path = /obj/item/bitrunning_disk/ability/tier1 + purchase_path = /obj/item/bitrunning_disk/ability/tier1 desc = "This disk contains a program that lets you cast Summon Cheese or Lesser Heal." /datum/orderable_item/bitrunning_tech/ability_tier2 cost_per_order = 1500 - item_path = /obj/item/bitrunning_disk/ability/tier2 + purchase_path = /obj/item/bitrunning_disk/ability/tier2 desc = "This disk contains a program that lets you cast Fireball, Lightning Bolt, or Forcewall." /datum/orderable_item/bitrunning_tech/ability_tier3 cost_per_order = 2500 - item_path = /obj/item/bitrunning_disk/ability/tier3 + purchase_path = /obj/item/bitrunning_disk/ability/tier3 desc = "This disk contains a program that lets you shapeshift into a lesser ashdrake, or a polar bear." /datum/orderable_item/bitrunning_tech/gimmick_sports @@ -41,17 +41,13 @@ purchase_path = /obj/item/bitrunning_disk/gimmick/dungeon desc = "This disk contains a program that lets you equip a dungeon crawling-themed gimmick loadout." -/datum/orderable_item/bitrunning_tech/flip_skillchip - item_path = /obj/item/skillchip/matrix_taunt - cost_per_order = 1500 - /datum/orderable_item/bitrunning_tech/pka_mod purchase_path = /obj/item/bitrunning_disk/item/pka_mods cost_per_order = 750 desc = "A decommissioned sleeper circuitboard, repurposed for recreational purposes." /datum/orderable_item/bitrunning_tech/pka_mod/premium - item_path = /obj/item/bitrunning_disk/item/pka_mods/premium + purchase_path = /obj/item/bitrunning_disk/item/pka_mods/premium cost_per_order = 1600 desc = "This disk contains a program that lets you equip stronger modkits for the proto-kinetic accelerator. Proto-kinetic accelerator not included." @@ -60,6 +56,6 @@ cost_per_order = 750 /datum/orderable_item/bitrunning_tech/pkc_mod/premium - item_path = /obj/item/bitrunning_disk/item/pkc_mods/premium + purchase_path = /obj/item/bitrunning_disk/item/pkc_mods/premium cost_per_order = 1600 desc = "This disk contains a program that lets you equip stronger trophies for the proto-kinetic crusher. Proto-kinetic crusher not included." diff --git a/code/modules/bitrunning/server/loot.dm b/code/modules/bitrunning/server/loot.dm index b1575389caf8..f46b39bede9b 100644 --- a/code/modules/bitrunning/server/loot.dm +++ b/code/modules/bitrunning/server/loot.dm @@ -54,8 +54,11 @@ var/bonus = calculate_rewards() + var/time_difference = world.time - generated_domain.start_time + var/grade = grade_completion(time_difference) + var/obj/item/paper/certificate = new() - certificate.add_raw_text(get_completion_certificate()) + certificate.add_raw_text(get_completion_certificate(time_difference, grade)) certificate.name = "certificate of domain completion" certificate.update_appearance() @@ -88,7 +91,7 @@ /// Returns the markdown text containing domain completion information -/obj/machinery/quantum_server/proc/get_completion_certificate() +/obj/machinery/quantum_server/proc/get_completion_certificate(time_difference, grade) var/base_points = generated_domain.reward_points if(domain_randomized) base_points -= 1 @@ -97,11 +100,9 @@ var/domain_threats = length(spawned_threat_refs) - var/time_difference = world.time - generated_domain.start_time - var/completion_time = "### Completion Time: [DisplayTimeText(time_difference)]\n" - var/grade = "\n---\n\n# Rating: [grade_completion(time_difference)]" + var/completion_grade = "\n---\n\n# Rating: [grade]" var/text = "# Certificate of Domain Completion\n\n---\n\n" @@ -113,7 +114,7 @@ if(bonuses <= 1) text += completion_time - text += grade + text += completion_grade return text text += "### Bonuses\n" @@ -137,7 +138,7 @@ text += "- **Components:** + [servo_rating]\n" text += completion_time - text += grade + text += completion_grade return text diff --git a/code/modules/bitrunning/util/virtual_megafauna.dm b/code/modules/bitrunning/util/virtual_megafauna.dm index 4daf00141978..6d2f2250c7bd 100644 --- a/code/modules/bitrunning/util/virtual_megafauna.dm +++ b/code/modules/bitrunning/util/virtual_megafauna.dm @@ -6,21 +6,14 @@ true_spawn = FALSE - // rebuild the achievement element's arguments to remove it appropriately - if (achievement_type || score_achievement_type) - var/list/achievements = list(/datum/award/achievement/boss/boss_killer, /datum/award/score/boss_score) - if (achievement_type) - achievements += achievement_type - if (score_achievement_type) - achievements += score_achievement_type - RemoveElement(/datum/element/kill_achievement, string_list(achievements), crusher_achievement_type, /datum/memory/megafauna_slayer) + // NON-MODULE CHANGE: its a proc here instead of an element so its just a singular var assignment + achievement_type = null // remove the crusher loot element's arguments also to remove it appropriately RemoveElement(\ /datum/element/crusher_loot,\ trophy_type = crusher_loot,\ guaranteed_drop = 0.6,\ - replace_all = replace_crusher_drop,\ drop_immediately = del_on_death,\ ) diff --git a/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm b/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm index 396990720575..6368cacd726e 100644 --- a/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm +++ b/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm @@ -16,6 +16,7 @@ /obj/item/gun/energy/laser/chameleon/ballistic_only = 1, /obj/item/bitrunning_disk/item/mini_uzi = 1, ) + domain_flags = DOMAIN_TEST_ONLY // NON-MODULE CHANGE : we don't want island brawl here /datum/lazy_template/virtual_domain/island_brawl/setup_domain(list/created_atoms) for(var/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander/spawner in created_atoms) diff --git a/code/modules/bitrunning/virtual_domain/domains/pipedream.dm b/code/modules/bitrunning/virtual_domain/domains/pipedream.dm index f32c984248a3..33938fe4630a 100644 --- a/code/modules/bitrunning/virtual_domain/domains/pipedream.dm +++ b/code/modules/bitrunning/virtual_domain/domains/pipedream.dm @@ -15,6 +15,7 @@ /datum/modular_mob_segment/hivebots_strong ) reward_points = BITRUNNER_REWARD_LOW + domain_flags = DOMAIN_TEST_ONLY // NON-MODULE CHANGE : currently impossible, you get mulched by the recycler // ID Trims /datum/id_trim/factory diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm index d2025596b184..ee3a18fc9f66 100644 --- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -120,10 +120,10 @@ if(!main_crate_loc) return var/turf/spawn_loc = get_turf(main_crate_loc) - playsound(spawn_loc, 'sound/effects/magic/blink.ogg', 50, TRUE) + playsound(spawn_loc, 'sound/magic/blink.ogg', 50, TRUE) var/obj/structure/closet/crate/secure/bitrunning/encrypted/crate = new() crate.forceMove(spawn_loc) // Triggers any on-move effects on that turf - do_sparks(5, FALSE, spawn_loc, spark_type = /datum/effect_system/basic/spark_spread/quantum) + do_sparks(5, FALSE, spawn_loc, spark_type = /datum/effect_system/spark_spread/quantum) main_crate_loc = null /// Loads the ghost candidates. diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 4730fecedb2e..60f919e724cc 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -270,7 +270,7 @@ var/obj/item/card/id/crew_id = H.get_idcard(hand_first = FALSE) var/job_name = crew_rec?.rank || crew_id?.assignment || crew_id?.trim?.assignment || H.job //timer because these should come after the captain announcement - SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(_addtimer), CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(aas_config_announce), /datum/aas_config_entry/newhead, list("PERSON" = human.real_name, "RANK" = job_name), null, channels, null, TRUE), 1)) + SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(_addtimer), CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(aas_config_announce), /datum/aas_config_entry/newhead, list("PERSON" = H.real_name, "RANK" = job_name), null, channels, null, TRUE), 1)) //If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1 /datum/job/proc/player_old_enough(client/player) diff --git a/code/modules/mob/living/carbon/human/species_types/monkeys.dm b/code/modules/mob/living/carbon/human/species_types/monkeys.dm index 93e960b31efb..038136e7fd73 100644 --- a/code/modules/mob/living/carbon/human/species_types/monkeys.dm +++ b/code/modules/mob/living/carbon/human/species_types/monkeys.dm @@ -166,7 +166,7 @@ . = ..() UnregisterSignal(primate, COMSIG_LIVING_MOB_BUMPED) -/obj/item/organ/internal/brain/primate/proc/on_mob_bump(mob/source, mob/living/crossing_mob) +/obj/item/organ/brain/primate/proc/on_mob_bump(mob/source, mob/living/crossing_mob) SIGNAL_HANDLER if(!tripping || !crossing_mob.combat_mode) return diff --git a/maplestation.dme b/maplestation.dme index 5d0fc3d1094b..effebf5e5eb9 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -420,6 +420,7 @@ #include "code\__HELPERS\duplicating.dm" #include "code\__HELPERS\dynamic_human_icon_gen.dm" #include "code\__HELPERS\events.dm" +#include "code\__HELPERS\fade_into_nothing.dm" #include "code\__HELPERS\files.dm" #include "code\__HELPERS\filters.dm" #include "code\__HELPERS\forensics.dm" @@ -530,6 +531,7 @@ #include "code\_globalvars\time_vars.dm" #include "code\_globalvars\lists\achievements.dm" #include "code\_globalvars\lists\ambience.dm" +#include "code\_globalvars\lists\bitrunning.dm" #include "code\_globalvars\lists\canisters.dm" #include "code\_globalvars\lists\cargo.dm" #include "code\_globalvars\lists\client.dm" @@ -1527,7 +1529,6 @@ #include "code\datums\elements\structure_repair.dm" #include "code\datums\elements\surgery_aid.dm" #include "code\datums\elements\swabbable.dm" -#include "code\datums\elements\temporary_atom.dm" #include "code\datums\elements\tenacious.dm" #include "code\datums\elements\tiny_mob_hunter.dm" #include "code\datums\elements\tool_flash.dm" @@ -2719,6 +2720,7 @@ #include "code\game\objects\structures\mystery_box.dm" #include "code\game\objects\structures\noticeboard.dm" #include "code\game\objects\structures\ore_containers.dm" +#include "code\game\objects\structures\outfit_wardrobe.dm" #include "code\game\objects\structures\petrified_statue.dm" #include "code\game\objects\structures\pinatas.dm" #include "code\game\objects\structures\plasticflaps.dm" @@ -2898,6 +2900,7 @@ #include "code\modules\admin\trophy_manager.dm" #include "code\modules\admin\whitelist.dm" #include "code\modules\admin\callproc\callproc.dm" +#include "code\modules\admin\smites\_smite.dm" #include "code\modules\admin\smites\bad_luck.dm" #include "code\modules\admin\smites\become_object.dm" #include "code\modules\admin\smites\berforate.dm" @@ -2923,7 +2926,6 @@ #include "code\modules\admin\smites\puzzle.dm" #include "code\modules\admin\smites\rod.dm" #include "code\modules\admin\smites\scarify.dm" -#include "code\modules\admin\smites\smite.dm" #include "code\modules\admin\smites\supply_pod.dm" #include "code\modules\admin\smites\supply_pod_quick.dm" #include "code\modules\admin\spawn_panel\admin_hotkeys.dm" @@ -3509,7 +3511,6 @@ #include "code\modules\bitrunning\abilities.dm" #include "code\modules\bitrunning\alerts.dm" #include "code\modules\bitrunning\areas.dm" -#include "code\modules\bitrunning\designs.dm" #include "code\modules\bitrunning\event.dm" #include "code\modules\bitrunning\job.dm" #include "code\modules\bitrunning\outfits.dm" @@ -3522,11 +3523,17 @@ #include "code\modules\bitrunning\antagonists\netguardian.dm" #include "code\modules\bitrunning\components\avatar_connection.dm" #include "code\modules\bitrunning\components\avatar_gear.dm" -#include "code\modules\bitrunning\components\bitrunning_points.dm" #include "code\modules\bitrunning\components\glitch.dm" #include "code\modules\bitrunning\components\netpod_healing.dm" #include "code\modules\bitrunning\components\npc_friendly.dm" #include "code\modules\bitrunning\components\virtual_entity.dm" +#include "code\modules\bitrunning\netpod\_netpod.dm" +#include "code\modules\bitrunning\netpod\container.dm" +#include "code\modules\bitrunning\netpod\outfitting.dm" +#include "code\modules\bitrunning\netpod\signals.dm" +#include "code\modules\bitrunning\netpod\tools.dm" +#include "code\modules\bitrunning\netpod\ui.dm" +#include "code\modules\bitrunning\netpod\utils.dm" #include "code\modules\bitrunning\objects\byteforge.dm" #include "code\modules\bitrunning\objects\clothing.dm" #include "code\modules\bitrunning\objects\debug.dm" @@ -3536,7 +3543,6 @@ #include "code\modules\bitrunning\objects\landmarks.dm" #include "code\modules\bitrunning\objects\loot_box.dm" #include "code\modules\bitrunning\objects\loot_crate.dm" -#include "code\modules\bitrunning\objects\netpod.dm" #include "code\modules\bitrunning\objects\quantum_console.dm" #include "code\modules\bitrunning\objects\vendor.dm" #include "code\modules\bitrunning\objects\gimmick_disks\_gimmick_disk.dm" @@ -3570,6 +3576,7 @@ #include "code\modules\bitrunning\virtual_domain\domains\gondola_asteroid.dm" #include "code\modules\bitrunning\virtual_domain\domains\grassland_hunt.dm" #include "code\modules\bitrunning\virtual_domain\domains\hierophant.dm" +#include "code\modules\bitrunning\virtual_domain\domains\island_brawl.dm" #include "code\modules\bitrunning\virtual_domain\domains\meta_central.dm" #include "code\modules\bitrunning\virtual_domain\domains\pipedream.dm" #include "code\modules\bitrunning\virtual_domain\domains\pirates.dm" @@ -3634,6 +3641,7 @@ #include "code\modules\cargo\universal_scanner.dm" #include "code\modules\cargo\bounties\assistant.dm" #include "code\modules\cargo\bounties\atmos.dm" +#include "code\modules\cargo\bounties\bitrunning.dm" #include "code\modules\cargo\bounties\botany.dm" #include "code\modules\cargo\bounties\chef.dm" #include "code\modules\cargo\bounties\engineering.dm" From 18a828b9d2d54002b3504131d25e6c1ee5625006 Mon Sep 17 00:00:00 2001 From: Waterpig <49160555+Majkl-J@users.noreply.github.com> Date: Tue, 23 Jul 2024 21:49:36 +0200 Subject: [PATCH 37/45] Fixes DDOSing bitrunners (#85194) Got a bug reported today on a downstream, turns out it's a thing here. If there's no linked avatar yet and you repeatedly climb in and out of the pod before the do_after finishes, you fill in all three bitrunning slots with unlinked avatars and fill up the bandwidth. I simply moved the do_after before any of the avatar spawning, making the pod "Try to connect" before it does anything avatar handling worthy. I will have one less bitrunner in my DMs reporting a bug, and you might too! Idk I guess bugfixes are good :cl: fix: fixes bitrunning breaking upon repeatedly climbing in and out the pod /:cl: --- code/modules/bitrunning/netpod/utils.dm | 3 --- 1 file changed, 3 deletions(-) diff --git a/code/modules/bitrunning/netpod/utils.dm b/code/modules/bitrunning/netpod/utils.dm index b918c93eb60f..39e476218ac9 100644 --- a/code/modules/bitrunning/netpod/utils.dm +++ b/code/modules/bitrunning/netpod/utils.dm @@ -145,9 +145,6 @@ /// Checks for cases to eject/fail connecting an avatar /obj/machinery/netpod/proc/validate_entry(mob/living/neo, mob/living/avatar) - if(!do_after(neo, 2 SECONDS, src)) - return FALSE - // Very invalid if(QDELETED(neo) || QDELETED(avatar) || QDELETED(src) || !is_operational) return FALSE From 05635682ed2cfc4a2b6a51c99fc44681108a7024 Mon Sep 17 00:00:00 2001 From: Critawakets Date: Fri, 21 Aug 2026 03:57:55 -0400 Subject: [PATCH 38/45] linter fixes --- code/modules/bitrunning/virtual_domain/virtual_domain.dm | 3 ++- code/modules/mining/laborcamp/laborstacker.dm | 2 -- tgstation.dme | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm index ee3a18fc9f66..9114c29d0ec7 100644 --- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -123,7 +123,8 @@ playsound(spawn_loc, 'sound/magic/blink.ogg', 50, TRUE) var/obj/structure/closet/crate/secure/bitrunning/encrypted/crate = new() crate.forceMove(spawn_loc) // Triggers any on-move effects on that turf - do_sparks(5, FALSE, spawn_loc, spark_type = /datum/effect_system/spark_spread/quantum) + var/datum/effect_system/spark_spread/quantum/sparks = new + sparks.set_up(5, FALSE, spawn_loc) main_crate_loc = null /// Loads the ghost candidates. diff --git a/code/modules/mining/laborcamp/laborstacker.dm b/code/modules/mining/laborcamp/laborstacker.dm index 7e068ba37060..d63c59c3b735 100644 --- a/code/modules/mining/laborcamp/laborstacker.dm +++ b/code/modules/mining/laborcamp/laborstacker.dm @@ -195,5 +195,3 @@ GLOBAL_LIST(labor_sheet_values) vars_and_tooltips_map = list( "PERSON" = "will be replaced with the name of the prisoner." ) - -#undef SHEET_POINT_VALUE diff --git a/tgstation.dme b/tgstation.dme index e8d1812990bd..cde06e9a327d 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4686,7 +4686,6 @@ #include "code\modules\mob\living\basic\festivus_pole.dm" #include "code\modules\mob\living\basic\health_adjustment.dm" #include "code\modules\mob\living\basic\revolutionary.dm" -#include "code\modules\mob\living\basic\revolutionary.dm" #include "code\modules\mob\living\basic\tree.dm" #include "code\modules\mob\living\basic\blob_minions\blob_ai.dm" #include "code\modules\mob\living\basic\blob_minions\blob_mob.dm" From c04dfd55028af6d618b24ccb79ff73dbed1a3487 Mon Sep 17 00:00:00 2001 From: Critawakets Date: Fri, 21 Aug 2026 04:11:39 -0400 Subject: [PATCH 39/45] attempts to fix the announcement tgui through unethical means --- code/game/machinery/announcement_system.dm | 19 ---------------- .../tgui/interfaces/AutomatedAnnouncement.tsx | 22 +++++++++---------- 2 files changed, 10 insertions(+), 31 deletions(-) diff --git a/code/game/machinery/announcement_system.dm b/code/game/machinery/announcement_system.dm index 88c894933cac..14db21b4642b 100644 --- a/code/game/machinery/announcement_system.dm +++ b/code/game/machinery/announcement_system.dm @@ -309,25 +309,6 @@ GLOBAL_LIST_EMPTY(announcement_systems) "ER)#R - B*@ TEXT F*O(ND!", "AAS.exe is not responding. NanoOS is searching for a solution to the problem.") -/datum/aas_config_entry/researched_node - name = "Research Node Announcement" - announcement_lines_map = list( - "Message" = "The %NODE techweb node has been researched") - vars_and_tooltips_map = list( - "NODE" = "will be replaced with the researched node." - ) - -/datum/aas_config_entry/researched_node/act_up() - . = ..() - if (.) - return - - announcement_lines_map["Message"] = pick( - replacetext(/datum/aas_config_entry/researched_node::announcement_lines_map["Message"], "%NODE", /datum/techweb_node/mech_clown::display_name), - "R/NT1M3 A= ANNOUN-*#nt_SY!?EM.dm, LI%£ 86: N=0DE NULL!", - "BEPIS BEPIS BEPIS", - "ERR)#R - B*@ TEXT F*O(ND!") - /datum/aas_config_entry/arrivals_broken name = "Arrivals Shuttle Malfunction Announcement" announcement_lines_map = list( diff --git a/tgui/packages/tgui/interfaces/AutomatedAnnouncement.tsx b/tgui/packages/tgui/interfaces/AutomatedAnnouncement.tsx index de864c056240..285d5ee01856 100644 --- a/tgui/packages/tgui/interfaces/AutomatedAnnouncement.tsx +++ b/tgui/packages/tgui/interfaces/AutomatedAnnouncement.tsx @@ -1,4 +1,5 @@ -import { filter, sortBy } from 'common/collections'; +import { sortBy } from 'es-toolkit'; +import { filter } from 'es-toolkit/compat'; import { useState } from 'react'; import { Button, @@ -26,11 +27,12 @@ type AASConfigEntry = { type Data = { config_entries: AASConfigEntry[]; + max_announcement_len: number; }; export const AutomatedAnnouncement = (props) => { const { act, data } = useBackend(); - const { config_entries = [] } = data; + const { config_entries = [], max_announcement_len } = data; const [search, setSearch] = useState(''); @@ -60,7 +62,7 @@ export const AutomatedAnnouncement = (props) => { const sorted = sortBy( filter(config_entries, (entry) => isEntryMatch(entry, search)), - (entry) => entry.name, + [(entry) => entry.name], ); return ( @@ -69,11 +71,7 @@ export const AutomatedAnnouncement = (props) => { - setSearch(value)} - /> + @@ -92,12 +90,12 @@ export const AutomatedAnnouncement = (props) => { icon="info" tooltip={ (entry.generalTooltip - ? entry.generalTooltip + '\n' + ? `${entry.generalTooltip}\n` : '') + Object.entries(entry.varsAndTooltipsMap) .map( ([varName, tooltip]) => - '%' + varName + ' ' + tooltip, + `%${varName} ${tooltip}`, ) .join('\n') } @@ -130,11 +128,11 @@ export const AutomatedAnnouncement = (props) => { + maxLength={max_announcement_len} + onBlur={(value) => act('Text', { entryRef: entry.entryRef, lineKey, From c6ce806043c7bf947600eea708c8556112d83885 Mon Sep 17 00:00:00 2001 From: Critawakets Date: Fri, 21 Aug 2026 04:26:52 -0400 Subject: [PATCH 40/45] I HATE TGUI ERRORS --- tgui/packages/tgui/interfaces/QuantumConsole.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/tgui/packages/tgui/interfaces/QuantumConsole.tsx b/tgui/packages/tgui/interfaces/QuantumConsole.tsx index 5969482b1adb..b191549a6ef0 100644 --- a/tgui/packages/tgui/interfaces/QuantumConsole.tsx +++ b/tgui/packages/tgui/interfaces/QuantumConsole.tsx @@ -11,7 +11,6 @@ import { Tooltip, } from 'tgui-core/components'; import type { BooleanLike } from 'tgui-core/react'; - import { useBackend, useSharedState } from '../backend'; import { Window } from '../layouts'; import { LoadingScreen } from './common/LoadingScreen'; From c2206bcec0a628954dc322d47af10654f7dc90c6 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:44:41 -0600 Subject: [PATCH 41/45] Caps custom AAS message length to a more reasonable value (#93829) Reduces the max length you can set an AAS message by 83% (1024 characters -> 168 characters) I think most people would agree it's fairly annoying when someone edits the AAS messages to be full paragraphs, filling common up with nonsense Ostensibly this can be handled by admins but I think allowing someone to enter 1000 characters here is a bit excessive in the first place - The longest default message we have is 144 characters Alternatively I can cap message length on a per-message-type basis so the most common messages (arrivals, weather) have a far shorter limit than the uncommon messages (security alerts) :cl: Melbert del: Caps "Automated Announcement System" messages to 168 characters (was: 1024 characters) /:cl: --- code/__DEFINES/say.dm | 1 + code/game/machinery/announcement_system.dm | 9 ++++++++- code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/aas_configs.dm | 10 ++++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 code/modules/unit_tests/aas_configs.dm diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm index 0262ab084376..ae42022467b7 100644 --- a/code/__DEFINES/say.dm +++ b/code/__DEFINES/say.dm @@ -110,6 +110,7 @@ #define MAX_PLAQUE_LEN 144 #define MAX_LABEL_LEN 64 #define MAX_DESC_LEN 280 +#define MAX_AAS_LENGTH 168 // Audio/Visual Flags. Used to determine what sense are required to notice a message. #define MSG_VISUAL (1<<0) diff --git a/code/game/machinery/announcement_system.dm b/code/game/machinery/announcement_system.dm index 14db21b4642b..550a26b7cd94 100644 --- a/code/game/machinery/announcement_system.dm +++ b/code/game/machinery/announcement_system.dm @@ -123,6 +123,13 @@ GLOBAL_LIST_EMPTY(announcement_systems) )) return list("config_entries" = configs) +/obj/machinery/announcement_system/ui_static_data(mob/user) + var/list/data = list() + + data["max_announcement_len"] = MAX_AAS_LENGTH + + return data + /obj/machinery/announcement_system/ui_act(action, params) . = ..() if(.) @@ -149,7 +156,7 @@ GLOBAL_LIST_EMPTY(announcement_systems) message_admins("[ADMIN_LOOKUPFLW(usr)] tried to set announcement line for nonexisting line in the [config.name] for AAS. Probably href injection. Received line: [params["lineKey"]]") log_game("[key_name(usr)] tried to mess with AAS. For [config.name] he tried to edit nonexistend [params["lineKey"]]") return - var/new_message = trim(html_encode(params["newText"]), MAX_MESSAGE_LEN) + var/new_message = trim(html_encode(params["newText"]), MAX_AAS_LENGTH) if(new_message) config.announcement_lines_map[params["lineKey"]] = new_message usr.log_message("updated [params["lineKey"]] line in the [config.name] to: [new_message]", LOG_GAME) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index ab28763bd28f..00658b8b8028 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -87,6 +87,7 @@ #define EASY_ALLOCATE(arguments...) allocate(__IMPLIED_TYPE__, run_loc_floor_bottom_left, ##arguments) // BEGIN_INCLUDE +#include "aas_configs.dm" #include "aaa_modular_vendors.dm" #include "aaa_pain.dm" #include "abductor_baton_spell.dm" diff --git a/code/modules/unit_tests/aas_configs.dm b/code/modules/unit_tests/aas_configs.dm new file mode 100644 index 000000000000..30b82619dd75 --- /dev/null +++ b/code/modules/unit_tests/aas_configs.dm @@ -0,0 +1,10 @@ +/datum/unit_test/aas_configs + +/datum/unit_test/aas_configs/Run() + var/expected_max_length = MAX_AAS_LENGTH + + for(var/config_type in valid_subtypesof(/datum/aas_config_entry)) + var/datum/aas_config_entry/entry = allocate(config_type) + for(var/key, line in entry.announcement_lines_map) + if(length_char(line) > expected_max_length) + TEST_FAIL("Announcement line '[key]' in config '[config_type]' exceeds max length of [expected_max_length] characters (actual length: [length(line)])") From 8a60210b71075378da6ba3d3ae27f1895f4ae4eb Mon Sep 17 00:00:00 2001 From: Critawakets Date: Fri, 21 Aug 2026 05:47:20 -0400 Subject: [PATCH 42/45] updates tgui-core --- bun.lock | 1 + tgui/bun.lock | 9 +++++---- tgui/packages/tgui-panel/package.json | 2 +- tgui/packages/tgui-say/package.json | 2 +- tgui/packages/tgui/package.json | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bun.lock b/bun.lock index d234ff9912a0..761ebb964bd3 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "devDependencies": { diff --git a/tgui/bun.lock b/tgui/bun.lock index acef62558fdd..b1c304ede32a 100644 --- a/tgui/bun.lock +++ b/tgui/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "tgui-workspace", @@ -49,7 +50,7 @@ "marked-smartypants": "^1.1.9", "react": "^19.1.0", "react-dom": "^19.1.0", - "tgui-core": "^5.3.1", + "tgui-core": "^5.10.0", "tgui-dev-server": "workspace:*", }, }, @@ -72,7 +73,7 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "tgui": "workspace:*", - "tgui-core": "^5.3.1", + "tgui-core": "^5.10.0", "tgui-dev-server": "workspace:*", }, }, @@ -84,7 +85,7 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "tgui": "workspace:*", - "tgui-core": "^5.3.1", + "tgui-core": "^5.10.0", }, }, "packages/tgui-setup": { @@ -991,7 +992,7 @@ "tgui": ["tgui@workspace:packages/tgui"], - "tgui-core": ["tgui-core@5.3.1", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@nozbe/microfuzz": "^1.0.0" }, "peerDependencies": { "react": "^19.1.0", "react-dom": "^19.1.0" } }, "sha512-+8dDM4R2G6T1SpzpkRXGYc7YD+9ohqYjTV/4Zgcqc1I4Ld8bJH9vKHCQVbEAsHTCIjHsTBW4xnOpKyAx1gRB5w=="], + "tgui-core": ["tgui-core@5.10.0", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@nozbe/microfuzz": "^1.0.0" }, "peerDependencies": { "react": "^19.1.0", "react-dom": "^19.1.0" } }, "sha512-sbw2zSdJM55M333CHP2HALoEh3LBF07HNPDEXIwPDTGu0TKEaBMvkRnQlLN81A5s9cLjS4DCIvYTTE1qUQENeQ=="], "tgui-dev-server": ["tgui-dev-server@workspace:packages/tgui-dev-server"], diff --git a/tgui/packages/tgui-panel/package.json b/tgui/packages/tgui-panel/package.json index b52f197f8699..792bb3ad4f3a 100644 --- a/tgui/packages/tgui-panel/package.json +++ b/tgui/packages/tgui-panel/package.json @@ -8,7 +8,7 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "tgui": "workspace:*", - "tgui-core": "^5.3.1", + "tgui-core": "^5.10.0", "tgui-dev-server": "workspace:*" }, "private": true diff --git a/tgui/packages/tgui-say/package.json b/tgui/packages/tgui-say/package.json index 47d64bc70e9c..b11aa26b1c8b 100644 --- a/tgui/packages/tgui-say/package.json +++ b/tgui/packages/tgui-say/package.json @@ -6,7 +6,7 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "tgui": "workspace:*", - "tgui-core": "^5.3.1" + "tgui-core": "^5.10.0" }, "private": true } diff --git a/tgui/packages/tgui/package.json b/tgui/packages/tgui/package.json index e4f1037775f9..b7f6fbcfa7bc 100644 --- a/tgui/packages/tgui/package.json +++ b/tgui/packages/tgui/package.json @@ -14,7 +14,7 @@ "marked-smartypants": "^1.1.9", "react": "^19.1.0", "react-dom": "^19.1.0", - "tgui-core": "^5.3.1", + "tgui-core": "^5.10.0", "tgui-dev-server": "workspace:*" }, "private": true From 04e3a9a4f339428cff24b7a866efd04dd9c6dce7 Mon Sep 17 00:00:00 2001 From: Critawakets Date: Fri, 21 Aug 2026 05:51:40 -0400 Subject: [PATCH 43/45] unit test include fix --- code/modules/unit_tests/_unit_tests.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 00658b8b8028..4dae9af8e731 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -87,9 +87,9 @@ #define EASY_ALLOCATE(arguments...) allocate(__IMPLIED_TYPE__, run_loc_floor_bottom_left, ##arguments) // BEGIN_INCLUDE -#include "aas_configs.dm" #include "aaa_modular_vendors.dm" #include "aaa_pain.dm" +#include "aas_configs.dm" #include "abductor_baton_spell.dm" #include "ablative_hud.dm" #include "achievements.dm" From fd1920791367b19a2480e370f7958067dfda124e Mon Sep 17 00:00:00 2001 From: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:12:58 -0700 Subject: [PATCH 44/45] Changes bitrunning hacker alias sanitization (#84518) ## About The Pull Request Bitrunning hacker names now permit most characters while staying within the IC filter and length requirements. Made some helper procs to keep it organized ## Why It's Good For The Game You can now name yourself things like An1me_Sn1p3r ## Changelog :cl: fix: Bitrunning hacker aliases are now much more permissive /:cl: --- code/__HELPERS/text.dm | 40 ++++++++++++++++++++---- code/modules/client/preferences/names.dm | 20 +++++++++++- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index b82120873c96..c6c51e899aaa 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -241,12 +241,7 @@ if(last_char_group == SPACES_DETECTED) t_out = copytext_char(t_out, 1, -1) //removes the last character (in this case a space) - for(var/bad_name in list("space","floor","wall","r-wall","monkey","unknown","inactive ai")) //prevents these common metagamey names - if(cmptext(t_out,bad_name)) - return //(not case sensitive) - - // Protects against names containing IC chat prohibited words. - if(is_ic_filtered(t_out) || is_soft_ic_filtered(t_out)) + if(!filter_name_ic(t_out)) return return t_out @@ -257,6 +252,39 @@ #undef LETTERS_DETECTED +/// Much more permissive version of reject_bad_name(). +/// Returns a trimmed string or null if the name is invalid. +/// Allows most characters except for IC chat prohibited words. +/proc/permissive_sanitize_name(value) + if(!istext(value)) // Not a string + return + + var/name_length = length(value) + if(name_length < 3) // Too short + return + + if(name_length > 3 * MAX_NAME_LEN) // Bad input + return + + var/trimmed = trim(value, MAX_NAME_LEN) + if(!filter_name_ic(trimmed)) // Contains IC chat prohibited words + return + + return trim_reduced(trimmed) + + +/// Helper proc to check if a name is valid for the IC filter +/proc/filter_name_ic(name) + for(var/bad_name in list("space", "floor", "wall", "r-wall", "monkey", "unknown", "inactive ai")) //prevents these common metagamey names + if(cmptext(name, bad_name)) + return FALSE //(not case sensitive) + + // Protects against names containing IC chat prohibited words. + if(is_ic_filtered(name) || is_soft_ic_filtered(name)) + return FALSE + + return TRUE + //html_encode helper proc that returns the smallest non null of two numbers //or 0 if they're both null (needed because of findtext returning 0 when a value is not present) diff --git a/code/modules/client/preferences/names.dm b/code/modules/client/preferences/names.dm index 8456eb9a6270..82d37eca0722 100644 --- a/code/modules/client/preferences/names.dm +++ b/code/modules/client/preferences/names.dm @@ -17,21 +17,26 @@ /// If the highest priority job matches this, will prioritize this name in the UI var/relevant_job + /datum/preference/name/apply_to_human(mob/living/carbon/human/target, value) // Only real_name applies directly, everything else is applied by something else return + /datum/preference/name/deserialize(input, datum/preferences/preferences) return reject_bad_name("[input]", allow_numbers) + /datum/preference/name/serialize(input) // `is_valid` should always be run before `serialize`, so it should not // be possible for this to return `null`. return reject_bad_name(input, allow_numbers) + /datum/preference/name/is_valid(value) return istext(value) && !isnull(reject_bad_name(value, allow_numbers)) + /// A character's real name /datum/preference/name/real_name explanation = "Name" @@ -181,8 +186,21 @@ explanation = "Hacker alias" group = "bitrunning" savefile_key = "hacker_alias" - allow_numbers = TRUE relevant_job = /datum/job/bitrunner + /datum/preference/name/hacker_alias/create_default_value() return pick(GLOB.hacker_aliases) + + +/datum/preference/name/hacker_alias/is_valid(value) + return !isnull(permissive_sanitize_name(value)) + + +/datum/preference/name/hacker_alias/deserialize(input, datum/preferences/preferences) + return permissive_sanitize_name(input) + + +/datum/preference/name/hacker_alias/serialize(input) + return permissive_sanitize_name(input) + From 0b452034268b19ac126da9cbf82610d0e0ae3efa Mon Sep 17 00:00:00 2001 From: Ical <86125936+Ical92@users.noreply.github.com> Date: Sun, 21 Jul 2024 16:27:49 -0400 Subject: [PATCH 45/45] Bitrunning Domain Fixes (#85111) --- .../virtual_domains/fredingtonfastingbear.dmm | 2459 +++++++---------- _maps/virtual_domains/island_brawl.dmm | 12 +- _maps/virtual_domains/psyker_shuffle.dmm | 2 +- code/modules/bitrunning/areas.dm | 6 +- 4 files changed, 941 insertions(+), 1538 deletions(-) diff --git a/_maps/virtual_domains/fredingtonfastingbear.dmm b/_maps/virtual_domains/fredingtonfastingbear.dmm index a9118edf7348..25b8e273a79f 100644 --- a/_maps/virtual_domains/fredingtonfastingbear.dmm +++ b/_maps/virtual_domains/fredingtonfastingbear.dmm @@ -1,11 +1,11 @@ //MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE "aV" = ( /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "bu" = ( /obj/machinery/oven/range, /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "bL" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, @@ -14,7 +14,7 @@ /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/decal/cleanable/greenglow, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "bP" = ( /obj/structure/sign/poster/official/report_crimes, /turf/closed/indestructible/reinforced, @@ -23,7 +23,7 @@ /obj/machinery/chem_dispenser/drinks/beer, /obj/structure/table/wood, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "bW" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, @@ -32,7 +32,7 @@ pixel_y = 6 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "cn" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, @@ -41,40 +41,41 @@ pixel_y = 2 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "cp" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, /mob/living/basic/bear/snow, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "cY" = ( /obj/structure/sign/poster/official/no_erp/directional/north, /obj/structure/sign/poster/official/no_erp/directional/north, /turf/closed/indestructible/reinforced, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "dl" = ( /obj/structure/sign/poster/contraband/blood_geometer/directional/south, /obj/effect/landmark/bitrunning/cache_spawn, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "dz" = ( /obj/structure/table/wood, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "dN" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/decal/cleanable/blood/xtracks, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "dV" = ( /obj/structure/table/glass, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "ex" = ( -/turf/closed/indestructible/binary, -/area/ruin/space/has_grav/powered/virtual_domain) +/obj/structure/sign/poster/contraband/bountyhunters/directional/south, +/turf/template_noop, +/area/virtual_domain) "eE" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, @@ -83,28 +84,28 @@ /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/decal/cleanable/blood/old, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "eW" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/decal/cleanable/confetti, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "fd" = ( /obj/structure/table/wood/poker, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "fo" = ( /obj/structure/table/wood, /obj/item/reagent_containers/cup/glass/shaker{ pixel_x = 5 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "fy" = ( /obj/machinery/stove, /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "fz" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, @@ -114,15 +115,15 @@ pixel_y = 6 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "fU" = ( /obj/effect/decal/cleanable/blood/footprints, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "gl" = ( /obj/structure/sign/poster/contraband/blasto_detergent, /turf/closed/indestructible/reinforced, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "gL" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, @@ -132,7 +133,7 @@ }, /obj/machinery/light/built/directional/east, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "hq" = ( /obj/structure/table/reinforced, /obj/item/flashlight/seclite{ @@ -147,11 +148,11 @@ "hI" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /turf/closed/indestructible/reinforced, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "hK" = ( /obj/item/trash/popcorn, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "hR" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, @@ -160,7 +161,7 @@ /obj/effect/decal/cleanable/confetti, /obj/effect/decal/cleanable/insectguts, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "jE" = ( /obj/structure/sign/poster/abductor/ayy_over_tizira, /obj/structure/sign/poster/official/the_owl, @@ -174,71 +175,71 @@ dir = 4 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "kf" = ( /obj/structure/chair/stool/bar/directional/east, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "km" = ( /obj/structure/chair/sofa/corp/left{ dir = 1 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "kL" = ( /obj/machinery/light/built/directional/west, /obj/item/light/tube/broken, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "lc" = ( /obj/machinery/vending/boozeomat, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "lL" = ( /obj/structure/table, /obj/item/kitchen/rollingpin/illegal, /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "lP" = ( /obj/machinery/vending/sovietsoda, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "lS" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/decal/cleanable/cobweb/cobweb2, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "ma" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/structure/table, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "ms" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/decal/cleanable/blood/tracks, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "mC" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/structure/table, /obj/item/food/pizza, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "mX" = ( /obj/machinery/computer/arcade/amputation{ dir = 1 }, /obj/effect/decal/cleanable/confetti, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "ng" = ( /obj/effect/decal/cleanable/blood/drip, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "nF" = ( /obj/effect/spawner/structure/window/reinforced/indestructible, /turf/open/floor/iron, @@ -251,11 +252,11 @@ /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/item/clothing/mask/animal/small/bear/cursed, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "nX" = ( /obj/structure/chair/sofa/corp/left, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "ob" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, @@ -263,12 +264,12 @@ /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/decal/cleanable/greenglow, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "of" = ( /obj/structure/railing, /obj/effect/decal/cleanable/fuel_pool/hivis, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "oO" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, @@ -276,23 +277,23 @@ dir = 4 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "pr" = ( /obj/machinery/light/built/directional/east, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "pK" = ( /obj/effect/decal/cleanable/blood/tracks{ dir = 4 }, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "pX" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/item/restraints/legcuffs/beartrap/prearmed, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "ql" = ( /obj/structure/table/reinforced, /obj/item/toy/plush/nukeplushie{ @@ -307,20 +308,20 @@ /area/virtual_domain/protected_space/fullbright) "qK" = ( /turf/closed/indestructible/fakedoor, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "rc" = ( /obj/structure/chair/comfy/black{ dir = 8 }, /obj/effect/decal/cleanable/blood/splatter, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "re" = ( /obj/structure/chair/comfy/black{ dir = 8 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "ri" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, @@ -329,14 +330,14 @@ }, /obj/effect/decal/cleanable/blood/tracks, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "rB" = ( /obj/item/trash/boritos/green{ pixel_x = 4; pixel_y = 11 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "rD" = ( /obj/machinery/door/poddoor{ id = "door2" @@ -350,21 +351,20 @@ dir = 4 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "sg" = ( -/obj/structure/sign/poster/contraband/bountyhunters/directional/south, -/turf/closed/indestructible/binary, -/area/ruin/space/has_grav/powered/virtual_domain) +/turf/template_noop, +/area/virtual_domain) "si" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/structure/table, /obj/item/balloon_mallet, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "sk" = ( /obj/machinery/computer/slot_machine, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "sl" = ( /obj/effect/spawner/structure/window/reinforced/indestructible, /obj/structure/sign/poster/contraband/space_cola/directional/north, @@ -375,28 +375,28 @@ dir = 1 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "td" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/decal/chempuff, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "to" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "tC" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/structure/table, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "tI" = ( /obj/item/trash/can, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "tN" = ( /obj/effect/baseturf_helper/virtual_domain, /turf/template_noop, @@ -405,7 +405,7 @@ /obj/structure/table/wood/poker, /obj/item/trash/ready_donk, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "ud" = ( /obj/effect/decal/cleanable/confetti, /turf/open/floor/iron/cafeteria, @@ -415,40 +415,40 @@ dir = 1 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "uZ" = ( /obj/effect/decal/cleanable/food/salt, /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "ve" = ( /turf/closed/indestructible/reinforced, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "vk" = ( /obj/structure/railing, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "vm" = ( /obj/effect/landmark/bitrunning/cache_spawn, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "vs" = ( /obj/structure/closet/secure_closet/freezer/cream_pie, /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "vw" = ( /mob/living/basic/bear/butter, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "vy" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/decal/cleanable/blood/gibs/down, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "vz" = ( /obj/effect/spawner/structure/window/reinforced/plasma, /turf/open/floor/iron, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "vF" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, @@ -458,18 +458,18 @@ id = "door2" }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "vG" = ( /obj/structure/closet/secure_closet/freezer/kitchen, /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "vR" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/structure/table, /obj/item/trash/tray, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "vT" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, @@ -478,7 +478,7 @@ }, /obj/effect/decal/cleanable/insectguts, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "wM" = ( /obj/item/toy/balloon/heart, /obj/machinery/light/built/directional/west, @@ -486,47 +486,47 @@ dir = 1 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "xa" = ( /obj/structure/railing, /obj/structure/curtain/cloth/fancy/mechanical/start_closed, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "xk" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/structure/table, /obj/effect/decal/cleanable/confetti, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "xv" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/structure/sign/poster/official/moth_hardhat/directional/west, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "xY" = ( /obj/effect/decal/cleanable/insectguts, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "yz" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "yB" = ( /obj/structure/railing, /obj/effect/decal/cleanable/blood/tracks, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "zU" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/decal/cleanable/blood/old, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Ag" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, @@ -534,7 +534,11 @@ dir = 4 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) +"Ak" = ( +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/floor/iron/kitchen, +/area/virtual_domain) "AF" = ( /obj/structure/table/wood, /obj/item/reagent_containers/cup/glass/bottle/goldschlager{ @@ -545,23 +549,23 @@ pixel_x = -4 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Bc" = ( /turf/template_noop, /area/virtual_domain/safehouse) "Bf" = ( /obj/effect/spawner/random/trash/graffiti, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Bj" = ( /obj/machinery/computer/arcade/amputation, /obj/effect/decal/cleanable/confetti, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Bo" = ( /obj/machinery/door/airlock, /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "BU" = ( /obj/effect/decal/cleanable/blood/old, /turf/open/floor/iron/cafeteria, @@ -574,16 +578,16 @@ }, /obj/effect/decal/cleanable/blood/gibs/up, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Cm" = ( /turf/template_noop, -/area/space) +/area/template_noop) "Ct" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/machinery/light/built/directional/east, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Dx" = ( /obj/effect/decal/cleanable/fuel_pool, /obj/effect/decal/cleanable/confetti, @@ -592,7 +596,7 @@ "Dz" = ( /mob/living/basic/bear/fightpit, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "DV" = ( /obj/machinery/door, /turf/open/floor/iron/cafeteria, @@ -605,14 +609,14 @@ }, /obj/effect/decal/cleanable/confetti, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "EP" = ( /obj/structure/railing{ dir = 4 }, /obj/effect/decal/cleanable/leaper_sludge, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Fc" = ( /turf/closed/indestructible/reinforced, /area/virtual_domain/protected_space/fullbright) @@ -621,28 +625,28 @@ /obj/machinery/light/built/directional/west, /obj/structure/fake_stairs/wood, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "FA" = ( /obj/effect/decal/cleanable/food/flour, /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Gb" = ( /obj/item/trash/peanuts, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Gn" = ( /obj/machinery/light/built/directional/east, /obj/effect/decal/cleanable/food/tomato_smudge, /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "GD" = ( /obj/effect/decal/cleanable/blood, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "GK" = ( /obj/structure/closet/crate/trashcart/filled, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Hi" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, @@ -650,12 +654,12 @@ /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Ho" = ( /obj/effect/decal/cleanable/fuel_pool/hivis, /mob/living/basic/bear/fightpit, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "HE" = ( /obj/structure/table/reinforced, /obj/item/gun/energy/laser{ @@ -680,11 +684,11 @@ "HG" = ( /obj/structure/curtain/cloth/fancy/mechanical, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "If" = ( /mob/living/basic/bear/russian, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Io" = ( /obj/structure/closet/crate/trashcart/filled, /turf/open/misc/dirt/jungle, @@ -692,12 +696,12 @@ "IX" = ( /obj/machinery/computer/arcade/amputation, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "IY" = ( /obj/structure/table/wood/poker, /obj/item/toy/cards/deck/syndicate, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Jh" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, @@ -707,13 +711,13 @@ pixel_y = 8 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "JD" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/decal/cleanable/blood/tracks, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "JF" = ( /obj/modular_map_root/safehouse{ key = "wood" @@ -730,11 +734,11 @@ }, /obj/item/trash/pistachios, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "KJ" = ( /mob/living/basic/bear/fightpit, /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Ln" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, @@ -743,11 +747,11 @@ }, /obj/effect/decal/cleanable/confetti, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "LP" = ( /obj/item/trash/shrimp_chips, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "LU" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, @@ -757,7 +761,7 @@ id = "door1" }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Mc" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, @@ -769,24 +773,24 @@ dir = 8 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Mm" = ( /obj/structure/chair/stool/bar/directional/north, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Mq" = ( /obj/structure/sign/poster/contraband/ambrosia_vulgaris/directional/west, /turf/closed/indestructible/reinforced, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "ML" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "MO" = ( /obj/effect/decal/cleanable/cobweb, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "MS" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, @@ -795,84 +799,84 @@ pixel_y = 2 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Ne" = ( /turf/closed/indestructible/binary, -/area/space) +/area/virtual_domain/fullbright) "Nz" = ( /obj/structure/sign/poster/official/pda_ad/directional/north, /turf/closed/indestructible/reinforced, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "NA" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "NE" = ( /obj/structure/sign/poster/abductor/ayy_cops/directional/north, /turf/closed/indestructible/reinforced, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Od" = ( /obj/structure/table/wood/poker, /obj/item/toy/cards/deck, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Ox" = ( /obj/structure/sign/poster/official/the_owl, /turf/closed/indestructible/reinforced, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "OB" = ( /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "OD" = ( /mob/living/basic/bear/russian, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "OM" = ( /obj/structure/table, /obj/effect/decal/cleanable/food/salt, /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Pm" = ( /obj/structure/chair/sofa/corp/right, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "PK" = ( /obj/structure/curtain/cloth/fancy/mechanical, /obj/effect/decal/cleanable/blood/tracks, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Qg" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/item/toy/balloon/heart, /obj/machinery/light/built/directional/west, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "RB" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/machinery/light/built/directional/west, /obj/effect/decal/cleanable/glass/plastitanium, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Sp" = ( /obj/structure/chair/sofa/corp, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Sr" = ( /obj/effect/decal/cleanable/blood/footprints{ dir = 4 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "SE" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/machinery/light/built/directional/east, /obj/effect/decal/cleanable/glass/plastitanium, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "SW" = ( /obj/machinery/door/poddoor{ id = "door1" @@ -886,11 +890,11 @@ pixel_y = 6 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Tp" = ( /obj/structure/sign/poster/official/soft_cap_pop_art, /turf/closed/indestructible/reinforced, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Tq" = ( /turf/open/misc/dirt/jungle, /area/virtual_domain/fullbright) @@ -902,34 +906,34 @@ }, /obj/effect/decal/cleanable/insectguts, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "TR" = ( /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/turf_decal/tile/bar/opposingcorners, /obj/effect/decal/cleanable/ants/fire, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "TW" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, /mob/living/basic/bear/snow, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Uj" = ( /obj/structure/curtain/bounty/start_closed, /turf/closed/indestructible/binary, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain/fullbright) "Ux" = ( /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "UR" = ( /obj/effect/decal/cleanable/confetti, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "UW" = ( /obj/structure/sign/poster/official/no_erp/directional/north, /turf/closed/indestructible/reinforced, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Vd" = ( /turf/open/floor/iron/cafeteria, /area/virtual_domain/protected_space/fullbright) @@ -937,14 +941,14 @@ /obj/structure/table, /obj/item/kitchen/tongs, /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Vs" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/decal/cleanable/blood/gibs/old, /obj/machinery/light/built/directional/west, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "VS" = ( /obj/structure/table/reinforced, /obj/machinery/computer/camera_advanced, @@ -953,7 +957,7 @@ "VW" = ( /obj/structure/reagent_dispensers/water_cooler, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Wj" = ( /obj/structure/sign/poster/contraband/free_key/directional, /turf/closed/indestructible/reinforced, @@ -961,7 +965,7 @@ "Wy" = ( /obj/effect/decal/cleanable/molten_object/large, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "WG" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, @@ -970,11 +974,11 @@ }, /obj/effect/decal/cleanable/confetti, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "XG" = ( /obj/machinery/roulette, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Yd" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, @@ -982,26 +986,26 @@ dir = 8 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Ye" = ( /obj/structure/table/glass, /obj/item/reagent_containers/cup/soda_cans/grey_bull, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Yy" = ( /obj/machinery/computer/arcade/amputation{ dir = 1 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "YK" = ( /obj/effect/spawner/random/trash, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "YL" = ( /obj/item/trash/raisins, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "YT" = ( /obj/machinery/button/door/directional/west{ id = "door1" @@ -1011,30 +1015,30 @@ "Zf" = ( /obj/machinery/deepfryer, /turf/open/floor/iron/kitchen/small, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Zj" = ( /mob/living/basic/bear/fightpit, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "Zp" = ( /obj/structure/chair/comfy/black{ dir = 8 }, /obj/effect/decal/cleanable/blood/old, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "ZL" = ( /obj/structure/railing{ dir = 4 }, /turf/open/floor/wood, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "ZU" = ( /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/turf_decal/tile/blue/opposingcorners, /obj/effect/decal/cleanable/confetti, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) "ZZ" = ( /obj/effect/turf_decal/tile/dark_red/opposingcorners, /obj/effect/turf_decal/tile/dark_red/opposingcorners, @@ -1042,7 +1046,7 @@ dir = 8 }, /turf/open/floor/iron/kitchen, -/area/ruin/space/has_grav/powered/virtual_domain) +/area/virtual_domain) (1,1,1) = {" Cm @@ -1090,9 +1094,6 @@ Cm Cm Cm Cm -Cm -Cm -Cm "} (2,1,1) = {" Cm @@ -1110,6 +1111,13 @@ Cm Cm Cm Cm +Uj +Uj +Uj +Uj +Uj +Uj +Uj Cm Cm Cm @@ -1133,6 +1141,8 @@ Cm Cm Cm Cm +"} +(3,1,1) = {" Cm Cm Cm @@ -1143,13 +1153,18 @@ Cm Cm Cm Cm -"} -(3,1,1) = {" Cm Cm Cm Cm Cm +Uj +MO +OB +OB +OB +OB +Uj Cm Cm Cm @@ -1173,6 +1188,8 @@ Cm Cm Cm Cm +"} +(4,1,1) = {" Cm Cm Cm @@ -1188,13 +1205,18 @@ Cm Cm Cm Cm +Uj +OB +OB +OB +OB +Zj +Uj Cm Cm Cm Cm Cm -"} -(4,1,1) = {" Cm Cm Cm @@ -1213,12 +1235,10 @@ Cm Cm Cm Cm +"} +(5,1,1) = {" Cm Cm -Ne -Ne -Ne -Ne Cm Cm Cm @@ -1232,6 +1252,13 @@ Cm Cm Cm Cm +Uj +vm +OB +OB +OB +vm +Uj Cm Cm Cm @@ -1243,8 +1270,6 @@ Cm Cm Cm Cm -"} -(5,1,1) = {" Cm Cm Cm @@ -1257,19 +1282,30 @@ Cm Cm Cm Cm +"} +(6,1,1) = {" Cm Cm Cm -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne +Cm +Cm +ve +ve +ve +ve +ve +ve +Ox +ve +ve +ve +ve +ve +OB +OB +OB +ve +ve Cm Cm Cm @@ -1294,12 +1330,29 @@ Cm Cm Cm "} -(6,1,1) = {" +(7,1,1) = {" Cm Cm Cm Cm Cm +ve +sk +Ux +Ux +kL +Ux +Ux +UR +Ux +Ux +Ux +Fy +EP +ZL +ZL +wM +ve Cm Cm Cm @@ -1310,16 +1363,6 @@ Cm Cm Cm Cm -Ne -Uj -Uj -Uj -Uj -Uj -Uj -Uj -Ne -Ne Cm Cm Cm @@ -1333,18 +1376,41 @@ Cm Cm Cm Cm +"} +(8,1,1) = {" Cm Cm Cm Cm Cm +ve +GK +Gb +Ux +IX +rB +YL +mX +UR +Ux +Ux +Ux +UR +UR +Ux +Ux +ve Cm Cm +ve +ve +ve +ve +ve +ve Cm Cm Cm -"} -(7,1,1) = {" Cm Cm Cm @@ -1357,23 +1423,38 @@ Cm Cm Cm Cm +"} +(9,1,1) = {" Cm Cm -Ne -Ne -Uj -MO -OB -OB -OB -OB -Uj -Ne Cm Cm Cm +gl +GK +Ux +Ux +Bj +Ux +Ux +Yy +Bf +Ux +Ux +Ux +Zp +rc +re +Ux +ve Cm Cm +ve +Ux +Ux +Ux +Ux +ve Cm Cm Cm @@ -1389,15 +1470,38 @@ Cm Cm Cm Cm +"} +(10,1,1) = {" Cm Cm Cm Cm -"} -(8,1,1) = {" Cm +ve +GK +Ux +Ux +Bj +UR +Ux +Yy +Ux +Ux +Ux +Ux +Ke +re +re +Ux +Tp Cm Cm +ve +Ux +vw +Ux +Ux +ve Cm Cm Cm @@ -1409,29 +1513,42 @@ Cm Cm Cm Cm -Ne -Ne -Uj -OB -OB -OB -OB -Zj -Uj -Ne -Ne -Ne Cm Cm Cm Cm +"} +(11,1,1) = {" Cm Cm Cm Cm Cm +ve +sk +Ux +Ux +Ux +Ux +Ux +Ux +Ux +Ux +Ux +Ux +re +re +re +Ux +ve Cm Cm +ve +Ux +Ux +Ux +Ux +ve Cm Cm Cm @@ -1443,98 +1560,49 @@ Cm Cm Cm Cm -"} -(9,1,1) = {" Cm Cm Cm Cm +"} +(12,1,1) = {" Cm -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne Uj -vm -OB -OB -OB -vm Uj -Ne -Ne -Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -"} -(10,1,1) = {" -Cm -Cm -Cm -Cm -Cm -Ne +Uj +Uj ve ve ve ve +Ux +Ux +Ux +Wy ve ve -Ox +NE +Ux +Ux +Ux +Ux +Ho ve ve ve ve ve -OB -OB -OB ve ve -Ne -Ne -Ne -Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm +ve +ve +ve +UW +UW +cY +UW +ve +ve Cm Cm Cm @@ -1544,29 +1612,43 @@ Cm Cm Cm "} -(11,1,1) = {" -Cm -Cm -Cm -Cm +(13,1,1) = {" Cm -Ne +Uj +OB +OB +dl +hI +to +Vs +dN +vT +oO +ML +EO +WG +Qg ve -sk -Ux -Ux -kL -Ux -Ux -UR Ux +xY Ux Ux -Fy -EP -ZL -ZL -wM +NA +NA +NA +NA +RB +NA +NA +zU +NA +NA +NA +yz +yz +yz +yz +nP ve Ne Ne @@ -1576,1092 +1658,373 @@ Ne Ne Ne Ne -Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm "} -(12,1,1) = {" -Cm -Cm -Cm -Cm +(14,1,1) = {" Cm -Ne +Uj +OB +OB +OB +xa +jZ +to +eW +bW +ma +to +hR +tC +MS ve -GK -Gb -Ux -IX -rB -YL -mX -UR Ux +XG +XG Ux +NA +pX +NA +NA +NA +NA +NA +NA +NA +NA +td +yz +LU +yz +ob +bL +ve +Ne +Bc +Bc +Bc +Bc +Bc +JF +Ne +"} +(15,1,1) = {" +Cm +Uj +OB +OB +OB +HG +of +ML +ZU +mC +vR +ML +tC +Jh +to Ux -UR -UR Ux +Od +tZ Ux +TR +ve +ve +ve ve -Ne -Ne ve ve ve +Fc +Fc +sl +SW +Fc +Wj +Fc ve ve ve +Bc +Bc +Bc +Bc +Bc +Bc Ne +"} +(16,1,1) = {" Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -"} -(13,1,1) = {" -Cm -Cm -Cm -Cm -Cm -Ne -gl -GK -Ux -Ux -Bj -Ux -Ux -Yy -Bf -Ux -Ux -Ux -Zp -rc -re -Ux -ve -Ne -Ne -ve -Ux -Ux -Ux -Ux -ve -Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -"} -(14,1,1) = {" -Cm -Cm -Cm -Ne -Ne -Ne -ve -GK -Ux -Ux -Bj -UR -Ux -Yy -Ux -Ux -Ux -Ux -Ke -re -re -Ux -Tp -Ne -Ne -ve -Ux -vw -Ux -Ux -ve -Ne -Ne -Ne -Cm -Cm -Cm -Cm -Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -"} -(15,1,1) = {" -Cm -Ne -Ne -Ne -Ne -Ne -ve -sk -Ux -Ux -Ux -Ux -Ux -Ux -Ux -Ux -Ux -Ux -re -re -re -Ux -ve -Ne -Ne -ve -Ux -Ux -Ux -Ux -ve -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -"} -(16,1,1) = {" -Cm -Ne -Uj -Uj -Uj -Uj -ve -ve -ve -ve -Ux -Ux -Ux -Wy -ve -ve -NE -Ux -Ux -Ux -Ux -Ho -ve -ve -ve -ve -ve -ve -ve -ve -ve -ve -UW -UW -cY -UW -ve -ve -Ne -Ne -Cm -Cm -Cm -Cm -Ne -Ne -Cm -Cm -"} -(17,1,1) = {" -Cm -Ne Uj OB OB -dl -hI +OB +HG +vk to -Vs -dN -vT -oO ML -EO -WG -Qg -ve +Ln +ZZ +to +ZZ +Yd +eW Ux -xY +hK +fd Ux Ux NA -NA -NA -NA -RB -NA -NA -zU -NA -NA -NA -yz -yz -yz -yz -nP +vz +tI +VW ve +sg +sg +sg +Fc +hq +ud +Dx +YT +Vd +Fc +Tq +Tq +Tq +Bc +Bc +Bc +Bc +Bc +Bc Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Cm -"} -(18,1,1) = {" -Cm -Ne -Uj -OB -OB -OB -xa -jZ -to -eW -bW -ma -to -hR -tC -MS -ve -Ux -XG -XG -Ux -NA -pX -NA -NA -NA -NA -NA -NA -NA -NA -td -yz -LU -yz -ob -bL -ve -Ne -Bc -Bc -Bc -Bc -Bc -JF -Ne -Cm -Cm -"} -(19,1,1) = {" -Cm -Ne -Uj -OB -OB -OB -HG -of -ML -ZU -mC -vR -ML -tC -Jh -to -Ux -Ux -Od -tZ -Ux -TR -ve -ve -ve -ve -ve -ve -ve -Fc -Fc -sl -SW -Fc -Wj -Fc -ve -ve -ve -Bc -Bc -Bc -Bc -Bc -Bc -Ne -Cm -Cm -"} -(20,1,1) = {" -Cm -Ne -Uj -OB -OB -OB -HG -vk -to -ML -Ln -ZZ -to -ZZ -Yd -eW -Ux -hK -fd -Ux -Ux -NA -vz -tI -VW -ve -ex -ex -ex -Fc -hq -ud -Dx -YT -Vd -Fc -Tq -Tq -Tq -Bc -Bc -Bc -Bc -Bc -Bc -Ne -Cm -Cm -"} -(21,1,1) = {" -Cm -Ne -Uj -OB -OB -OB -HG -vk -ML -to -ML -cn -ML -to -ML -ZU -Ux -Ux -fd -Ux -Ux -NA -Ux -Ux -lP -qK -ex -ex -sg -jE -VS -Vd -Vd -Vd -Vd -DV -Tq -Tq -Tq -Bc -Bc -Bc -Bc -Bc -Bc -Ne -Cm -Cm -"} -(22,1,1) = {" -Cm -Ne -Uj -OB -OB -pK -PK -yB -to -ML -EO -Ag -to -Ag -EO -eW -Ux -Ux -fd -Ux -Ux -NA -vz -LP -lc -ve -ex -ex -ex -JW -ql -BU -ud -HE -Vd -Fc -Io -Tq -Tq -Bc -Bc -Bc -Bc -Bc -Bc -Ne -Cm -Cm -"} -(23,1,1) = {" -Cm -Ne -Uj -OB -OB -pK -xa -Mc -JD -ZU -xk -tC -ML -si -ma -ZU -YK -Ux -fd -IY -Ux -NA -ve -ve -ve -ve -ve -ve -ve -Fc -Fc -nF -rD -Fc -bP -Fc -ve -ve -ve -Bc -Bc -Bc -Bc -Bc -Bc -Ne -Cm -Cm -"} -(24,1,1) = {" -Cm -Ne -Uj -OB -OB -If -ve -ML -rY -ML -tC -Jh -to -ma -fz -TW -ve -Ux -XG -XG -Ux -NA -NA -NA -NA -NA -NA -NA -xv -NA -NA -NA -yz -vF -yz -yz -Hi -ve -Ne -Bc -Bc -Bc -Bc -Bc -tN -Ne -Cm -Cm -"} -(25,1,1) = {" -Cm -Ne -Uj -OB -OB -vm -ve -lS -gL -ms -ri -TG -eW -Yd -Ce -Ct -ve -Ux -Ux -Ux -Ux -cp -NA -NA -NA -SE -NA -NA -vy -NA -NA -NA -yz -yz -yz -yz -eE -ve -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Cm -Cm -"} -(26,1,1) = {" -Cm -Ne -Uj -Uj -Uj -Uj -ve -ve -ve -ve -GD -Ux -Ux -Ux -ve -ve -ve -Ux -Ux -Ux -Ux -Ux -ve -ve -ve -ve -ve -ve -qK -ve -ve -Nz -ve -ve -ve -ve -ve -ve -Ne -Ne -Ne -Ne -Cm -Cm -Ne -Cm -Cm -Cm -"} -(27,1,1) = {" -Cm -Ne -Ne -Ne -Ne -Ne -ve -OD -Ux -Ux -Ux -Ux -Ux -Ux -Ux -Ux -Ux -Ux -Ux -Ux -Ux -Ux -ve -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -"} -(28,1,1) = {" -Cm -Cm -Ne -Ne -Ne -Ne -ve -kf -kf -kf -kf -kf -kf -Ux -Ux -Ux -Ux -Ux -Pm -Ye -km -Ux -ve -ve -ve -ve -ve -ve -ve -Ne -Ne -Ne -Cm -Cm -Cm -Cm -Ne -Ne -Ne -Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -"} -(29,1,1) = {" -Cm -Cm -Cm -Cm -Ne -Ne -ve -dz -dz -dz -dz -dz -dz -Mm -Ux -Ux -Ux -Ux -Sp -dV -ur -Ux -Bo -aV -uZ -KJ -Ve -lL -ve -Ne -Ne -Cm -Cm -Cm -Cm -Cm -Cm -Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm "} -(30,1,1) = {" -Cm -Cm -Cm -Cm +(17,1,1) = {" Cm -Ne -Mq -bR -Ux -Ux +Uj +OB +OB +OB +HG +vk +ML +to +ML +cn +ML +to +ML +ZU Ux Ux -dz -Mm +fd Ux Ux +NA Ux -Dz -nX -Ti -sR Ux -Bo -aV -aV -FA -aV -OM -ve -Ne +lP +qK +sg +sg +ex +jE +VS +Vd +Vd +Vd +Vd +DV +Tq +Tq +Tq +Bc +Bc +Bc +Bc +Bc +Bc Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm "} -(31,1,1) = {" -Cm -Cm -Cm -Cm +(18,1,1) = {" Cm -Ne -ve -lc -Ux -fo -AF -pr -dz -Mm +Uj +OB +OB +pK +PK +yB +to +ML +EO +Ag +to +Ag +EO +eW Ux Ux -ng -fU -fU -pr +fd Ux Ux +NA +vz +LP +lc ve -ve -ve -vG -aV -Zf -ve -Ne +sg +sg +sg +JW +ql +BU +ud +HE +Vd +Fc +Io +Tq +Tq +Bc +Bc +Bc +Bc +Bc +Bc Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm "} -(32,1,1) = {" -Cm -Cm -Cm -Cm +(19,1,1) = {" Cm -Ne -ve -ve -qK -ve -ve +Uj +OB +OB +pK +xa +Mc +JD +ZU +xk +tC +ML +si +ma +ZU +YK +Ux +fd +IY +Ux +NA ve ve ve ve -Ux -Sr ve ve ve +Fc +Fc +nF +rD +Fc +bP +Fc ve ve ve +Bc +Bc +Bc +Bc +Bc +Bc Ne +"} +(20,1,1) = {" +Cm +Uj +OB +OB +If ve -vG -aV -fy +ML +rY +ML +tC +Jh +to +ma +fz +TW +ve +Ux +XG +XG +Ux +NA +NA +NA +NA +NA +NA +NA +xv +NA +NA +NA +yz +vF +yz +yz +Hi ve Ne +Bc +Bc +Bc +Bc +Bc +tN Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm "} -(33,1,1) = {" -Cm -Cm -Cm +(21,1,1) = {" Cm -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne +Uj +OB +OB +vm +ve +lS +gL +ms +ri +TG +eW +Yd +Ce +Ct ve Ux -Sr +Ux +Ux +Ux +cp +NA +NA +NA +SE +NA +NA +vy +NA +NA +NA +yz +yz +yz +yz +eE ve Ne Ne @@ -2669,122 +2032,47 @@ Ne Ne Ne Ne -ve -vs -Gn -bu -ve Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm +Ne "} -(34,1,1) = {" -Cm -Cm -Cm +(22,1,1) = {" Cm -Ne -Ne -ve -ve +Uj +Uj +Uj +Uj ve ve ve ve +GD +Ux +Ux +Ux ve ve ve Ux Ux +Ux +Ux +Ux ve ve ve ve ve ve -Ne +qK ve ve +Nz +ve ve ve ve -Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -"} -(35,1,1) = {" -Cm -Cm -Cm -Cm -Ne -Ne ve -Ux -Ux -Ux -Ux -Ux -Dz -Ux -Ux -Ux -Ux -Ux -Ux -Ux -Ux -Ux ve -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm Cm Cm Cm @@ -2794,14 +2082,18 @@ Cm Cm Cm "} -(36,1,1) = {" +(23,1,1) = {" +Cm Cm Cm Cm Cm -Ne -Ne ve +OD +Ux +Ux +Ux +Ux Ux Ux Ux @@ -2812,64 +2104,7 @@ Ux Ux Ux Ux -Ux -Ux -Ux -Ux -Ux -ve -Ne -Ne -Ne -Ne -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -Cm -"} -(37,1,1) = {" -Cm -Cm -Cm -Cm -Ne -Ne -ve -ve -ve -qK -qK -ve -ve -ve -ve -ve -ve -ve -ve -qK -qK -ve ve -Ne -Ne Cm Cm Cm @@ -2894,36 +2129,35 @@ Cm Cm Cm "} -(38,1,1) = {" -Cm -Cm -Cm +(24,1,1) = {" Cm -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne -Ne Cm Cm Cm Cm +ve +kf +kf +kf +kf +kf +kf +Ux +Ux +Ux +Ux +Ux +Pm +Ye +km +Ux +ve +ve +ve +ve +ve +ve +ve Cm Cm Cm @@ -2941,19 +2175,36 @@ Cm Cm Cm Cm +"} +(25,1,1) = {" Cm Cm -"} -(39,1,1) = {" Cm Cm Cm -Ne -Ne -Ne -Ne -Ne -Ne +ve +dz +dz +dz +dz +dz +dz +Mm +Ux +Ux +Ux +Ux +Sp +dV +ur +Ux +Bo +aV +uZ +KJ +Ve +lL +ve Cm Cm Cm @@ -2971,11 +2222,36 @@ Cm Cm Cm Cm +"} +(26,1,1) = {" Cm Cm Cm Cm Cm +Mq +bR +Ux +Ux +Ux +Ux +dz +Mm +Ux +Ux +Ux +Dz +nX +Ti +sR +Ux +Bo +aV +aV +FA +aV +OM +ve Cm Cm Cm @@ -2994,15 +2270,35 @@ Cm Cm Cm "} -(40,1,1) = {" +(27,1,1) = {" Cm Cm Cm Cm -Ne -Ne -Ne Cm +ve +lc +Ux +fo +AF +pr +dz +Mm +Ux +Ux +ng +fU +fU +pr +Ux +Ux +ve +ve +ve +vG +aV +Zf +ve Cm Cm Cm @@ -3020,12 +2316,36 @@ Cm Cm Cm Cm +"} +(28,1,1) = {" Cm Cm Cm Cm Cm +ve +ve +qK +ve +ve +ve +ve +ve +ve +Ux +Sr +ve +ve +ve +ve +ve +ve Cm +ve +vG +aV +fy +ve Cm Cm Cm @@ -3044,13 +2364,12 @@ Cm Cm Cm "} -(41,1,1) = {" +(29,1,1) = {" Cm Cm Cm Cm Cm -Ne Cm Cm Cm @@ -3059,12 +2378,21 @@ Cm Cm Cm Cm +ve +Ux +Sr +ve Cm Cm Cm Cm Cm Cm +ve +vs +Gn +bu +ve Cm Cm Cm @@ -3082,19 +2410,41 @@ Cm Cm Cm Cm +"} +(30,1,1) = {" Cm Cm Cm Cm Cm +ve +ve +ve +ve +ve +ve +ve +ve +ve +Ux +Ux +ve +ve +ve +ve +ve +ve Cm +ve +ve +ve +ve +ve Cm Cm Cm Cm Cm -"} -(42,1,1) = {" Cm Cm Cm @@ -3107,11 +2457,30 @@ Cm Cm Cm Cm +"} +(31,1,1) = {" Cm Cm Cm Cm Cm +ve +Ux +Ux +Ux +Ux +Ux +Dz +Ux +Ux +Ux +Ux +Ux +Ux +Ux +Ux +Ux +ve Cm Cm Cm @@ -3135,16 +2504,33 @@ Cm Cm Cm Cm +"} +(32,1,1) = {" Cm Cm Cm Cm Cm +ve +Ux +Ux +Ux +Ux +Ux +Ux +Ux +Ux +Ux +Ux +Ux +Ux +Ux +Ux +Ux +ve Cm Cm Cm -"} -(43,1,1) = {" Cm Cm Cm @@ -3165,11 +2551,30 @@ Cm Cm Cm Cm +"} +(33,1,1) = {" Cm Cm Cm Cm Cm +ve +ve +ve +qK +qK +ve +ve +ve +ve +ve +ve +ve +ve +qK +qK +ve +ve Cm Cm Cm diff --git a/_maps/virtual_domains/island_brawl.dmm b/_maps/virtual_domains/island_brawl.dmm index dd2ed14f2f3c..560ccee99672 100644 --- a/_maps/virtual_domains/island_brawl.dmm +++ b/_maps/virtual_domains/island_brawl.dmm @@ -442,6 +442,10 @@ /obj/machinery/food_cart, /turf/open/floor/plating, /area/virtual_domain) +"eZ" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/water/beach, +/area/virtual_domain/fullbright) "fe" = ( /obj/effect/turf_decal/siding/wood{ dir = 8 @@ -1147,10 +1151,6 @@ }, /turf/open/floor/wood/large, /area/virtual_domain) -"oX" = ( -/obj/effect/baseturf_helper/virtual_domain, -/turf/closed/indestructible/binary, -/area/virtual_domain/fullbright) "oZ" = ( /obj/effect/turf_decal/sand, /obj/effect/turf_decal/siding/wood{ @@ -5698,7 +5698,7 @@ SR SR SR SR -oX +SR "} (19,1,1) = {" SR @@ -5779,7 +5779,7 @@ cO cO cO cO -cO +eZ SR "} (20,1,1) = {" diff --git a/_maps/virtual_domains/psyker_shuffle.dmm b/_maps/virtual_domains/psyker_shuffle.dmm index c744cecf0b43..d792c9382f21 100644 --- a/_maps/virtual_domains/psyker_shuffle.dmm +++ b/_maps/virtual_domains/psyker_shuffle.dmm @@ -246,7 +246,7 @@ Y Q Q Q -Q +i Y o o diff --git a/code/modules/bitrunning/areas.dm b/code/modules/bitrunning/areas.dm index 45c64d92389c..aa196e95be16 100644 --- a/code/modules/bitrunning/areas.dm +++ b/code/modules/bitrunning/areas.dm @@ -51,15 +51,13 @@ name = "Virtual Domain Unexplored Location" icon = 'icons/area/areas_station.dmi' icon_state = "bit_ruin" - area_flags = /area/virtual_domain::area_flags - area_flags_mapping = /area/virtual_domain::area_flags_mapping + area_flags = UNIQUE_AREA | NOTELEPORT | EVENT_PROTECTED | HIDDEN_AREA /area/space/virtual_domain name = "Virtual Domain Space" icon = 'icons/area/areas_station.dmi' icon_state = "bit_space" - area_flags = /area/virtual_domain::area_flags - area_flags_mapping = /area/virtual_domain::area_flags_mapping + area_flags = UNIQUE_AREA | NOTELEPORT | EVENT_PROTECTED | HIDDEN_AREA ///Areas that virtual entities should not be in