diff --git a/.github/images/README.md b/.github/images/README.md new file mode 100644 index 00000000000..a3c0c24ade3 --- /dev/null +++ b/.github/images/README.md @@ -0,0 +1,8 @@ +# Attributions + +## Badges +`built-with-resentment.svg` and `contains-technical-debt.svg` were originally sourced from https://forthebadge.com/, with the repository located at https://github.com/BraveUX/for-the-badge. `made-in-byond.gif` is a user-generated modification of one of these badges provided by this service. + +## Comics + +Both comics are sourced from https://www.monkeyuser.com/, which gives permission for use in non-profit usage via https://www.monkeyuser.com/about/index.html. `bug_free.png` can be found at https://www.monkeyuser.com/2019/bug-free/, and the original version of `technical_debt.png` can be found at https://www.monkeyuser.com/2018/tech-debt/ (the version found in the folder has been modified). diff --git a/.github/images/badges/built-with-resentment.svg b/.github/images/badges/built-with-resentment.svg new file mode 100644 index 00000000000..702b62d7214 --- /dev/null +++ b/.github/images/badges/built-with-resentment.svg @@ -0,0 +1,30 @@ + + built-with-resentment + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/images/badges/contains-technical-debt.svg b/.github/images/badges/contains-technical-debt.svg new file mode 100644 index 00000000000..051cec71170 --- /dev/null +++ b/.github/images/badges/contains-technical-debt.svg @@ -0,0 +1,32 @@ + + contains-technical-debts + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/images/badges/made-in-byond.gif b/.github/images/badges/made-in-byond.gif new file mode 100644 index 00000000000..aed1b7ca243 Binary files /dev/null and b/.github/images/badges/made-in-byond.gif differ diff --git a/.github/images/comics/106-tech-debt-modified.png b/.github/images/comics/106-tech-debt-modified.png new file mode 100644 index 00000000000..d88ea1f72b2 Binary files /dev/null and b/.github/images/comics/106-tech-debt-modified.png differ diff --git a/.github/images/comics/131-bug-free.png b/.github/images/comics/131-bug-free.png new file mode 100644 index 00000000000..fd3da8561e6 Binary files /dev/null and b/.github/images/comics/131-bug-free.png differ diff --git a/SQL/skyrat_schema.sql b/SQL/skyrat_schema.sql new file mode 100644 index 00000000000..4eb4ac4f68d --- /dev/null +++ b/SQL/skyrat_schema.sql @@ -0,0 +1,82 @@ +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + + +-- +-- Table structure for table `player_rank`. +-- +DROP TABLE IF EXISTS `player_rank`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `player_rank` ( + `ckey` VARCHAR(32) NOT NULL, + `rank` VARCHAR(12) NOT NULL, + `admin_ckey` VARCHAR(32) NOT NULL, + `date_added` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `deleted` BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY (`ckey`, `rank`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + + +-- +-- Table structure for table `player_rank_log`. +-- +DROP TABLE IF EXISTS `player_rank_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `player_rank_log` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `ckey` VARCHAR(32) NOT NULL, + `rank` VARCHAR(12) NOT NULL, + `admin_ckey` VARCHAR(32) NOT NULL, + `action` ENUM('ADDED', 'REMOVED') NOT NULL DEFAULT 'ADDED', + `date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + + +-- +-- Trigger structure for trigger `log_player_rank_additions`. +-- +DROP TRIGGER IF EXISTS `log_player_rank_additions`; +CREATE TRIGGER `log_player_rank_additions` +AFTER INSERT ON `player_rank` +FOR EACH ROW +INSERT INTO player_rank_log (ckey, rank, admin_ckey, `action`) VALUES (NEW.ckey, NEW.rank, NEW.admin_ckey, 'ADDED'); + + +-- +-- Trigger structure for trigger `log_player_rank_changes`. +-- +DROP TRIGGER IF EXISTS `log_player_rank_changes`; +DELIMITER // +CREATE TRIGGER `log_player_rank_changes` +AFTER UPDATE ON `player_rank` +FOR EACH ROW +BEGIN + IF NEW.deleted = 1 THEN + INSERT INTO player_rank_log (ckey, rank, admin_ckey, `action`) VALUES (NEW.ckey, NEW.rank, NEW.admin_ckey, 'REMOVED'); + ELSE + INSERT INTO player_rank_log (ckey, rank, admin_ckey, `action`) VALUES (NEW.ckey, NEW.rank, NEW.admin_ckey, 'ADDED'); + END IF; +END; // +DELIMITER ; + + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; diff --git a/_maps/RandomRuins/LavaRuins/lavaland_battle_site.dmm b/_maps/RandomRuins/LavaRuins/lavaland_battle_site.dmm new file mode 100644 index 00000000000..7dbc17d40f9 --- /dev/null +++ b/_maps/RandomRuins/LavaRuins/lavaland_battle_site.dmm @@ -0,0 +1,464 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/closed/mineral/strong/wasteland, +/area/lavaland/surface/outdoors) +"f" = ( +/obj/effect/mob_spawn/corpse/human/skeleton, +/obj/item/clothing/head/costume/crown{ + pixel_x = 15; + pixel_y = 6; + desc = "A crown that was fit for a king, looks like it didn't get them very far."; + name = "dented crown" + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"g" = ( +/obj/structure/water_source/puddle, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"h" = ( +/obj/effect/mob_spawn/corpse/human/skeleton, +/obj/item/shield/buckler, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"j" = ( +/turf/template_noop, +/area/template_noop) +"m" = ( +/obj/effect/decal/cleanable/shreds, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"n" = ( +/obj/structure/stone_tile/block/burnt, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"o" = ( +/obj/structure/stone_tile/burnt{ + dir = 1 + }, +/obj/structure/stone_tile/burnt{ + dir = 8 + }, +/obj/effect/decal/cleanable/blood/drip, +/mob/living/basic/mining/goliath, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"p" = ( +/obj/item/stack/rods{ + pixel_x = 10 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"q" = ( +/obj/structure/flora/rock, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"s" = ( +/mob/living/basic/mining/goliath, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"t" = ( +/obj/effect/decal/cleanable/blood/gibs/old, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"u" = ( +/obj/effect/decal/cleanable/blood/old, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"x" = ( +/obj/effect/decal/cleanable/blood/drip, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"D" = ( +/obj/structure/statue/bone/rib{ + name = "colossal tailbone" + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"F" = ( +/obj/effect/decal/cleanable/blood/drip, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/statue/bone/rib, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"G" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"H" = ( +/obj/structure/firepit, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"I" = ( +/obj/structure/closet/crate/wooden, +/obj/item/stack/sheet/animalhide/goliath_hide, +/obj/item/flashlight/flare/torch, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"J" = ( +/obj/structure/flora/ash/cap_shroom, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"K" = ( +/obj/structure/closet/crate/wooden, +/obj/item/stack/sheet/sinew, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"L" = ( +/obj/effect/decal/cleanable/blood/hitsplatter{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"M" = ( +/obj/structure/statue/bone/skull, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"O" = ( +/obj/structure/statue/bone/rib{ + dir = 1 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"P" = ( +/obj/effect/mob_spawn/corpse/human/skeleton, +/obj/item/shovel/spade, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"S" = ( +/obj/structure/chair/wood/wings{ + color = "#ffff00" + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"T" = ( +/obj/effect/mob_spawn/corpse/human/skeleton, +/obj/item/spear/bonespear{ + pixel_x = 13; + pixel_y = 12 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"U" = ( +/obj/structure/statue/bone/rib, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"V" = ( +/obj/effect/mob_spawn/corpse/human/skeleton, +/obj/item/stack/rods{ + pixel_x = -1; + pixel_y = -7 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"X" = ( +/obj/structure/stone_tile/burnt, +/obj/structure/stone_tile/burnt{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"Y" = ( +/obj/effect/mob_spawn/corpse/human/skeleton, +/obj/item/stack/rods{ + pixel_y = -12; + pixel_x = 2 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) + +(1,1,1) = {" +j +j +j +j +j +j +j +j +j +j +j +j +j +j +j +j +"} +(2,1,1) = {" +j +j +a +j +j +j +j +j +j +j +j +j +a +j +j +j +"} +(3,1,1) = {" +j +a +a +a +j +j +J +G +G +G +j +j +a +a +a +j +"} +(4,1,1) = {" +j +j +a +a +G +K +G +u +T +x +J +a +a +a +j +j +"} +(5,1,1) = {" +j +j +j +J +I +G +G +q +L +G +s +g +a +G +j +j +"} +(6,1,1) = {" +j +j +j +G +s +x +x +G +x +G +x +G +G +D +j +j +"} +(7,1,1) = {" +j +j +V +q +G +p +t +J +G +q +J +O +q +G +j +j +"} +(8,1,1) = {" +j +j +m +u +G +q +f +O +J +O +G +n +J +x +j +j +"} +(9,1,1) = {" +j +j +G +x +G +M +S +n +o +n +X +F +u +G +j +j +"} +(10,1,1) = {" +j +j +J +G +G +u +J +U +G +U +J +G +G +G +j +j +"} +(11,1,1) = {" +j +j +j +q +h +G +G +G +q +G +G +J +m +q +j +j +"} +(12,1,1) = {" +j +j +j +G +G +x +H +x +G +G +q +Y +G +a +j +j +"} +(13,1,1) = {" +j +j +a +G +J +G +G +P +G +u +G +a +a +a +a +j +"} +(14,1,1) = {" +j +a +a +a +j +j +G +G +q +G +j +j +a +a +j +j +"} +(15,1,1) = {" +j +a +a +j +j +j +j +j +j +j +j +a +a +a +j +j +"} +(16,1,1) = {" +j +j +j +j +j +j +j +j +j +j +j +j +j +j +j +j +"} diff --git a/_maps/RandomRuins/SpaceRuins/derelict9.dmm b/_maps/RandomRuins/SpaceRuins/derelict9.dmm new file mode 100644 index 00000000000..2fa948a60fa --- /dev/null +++ b/_maps/RandomRuins/SpaceRuins/derelict9.dmm @@ -0,0 +1,900 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/closed/mineral/random, +/area/ruin/space) +"b" = ( +/turf/closed/indestructible/riveted, +/area/ruin/space/has_grav) +"c" = ( +/obj/item/storage/toolbox/mechanical/old, +/obj/structure/rack, +/obj/effect/decal/cleanable/dirt, +/obj/effect/spawner/random/maintenance/three, +/turf/open/floor/plating/airless, +/area/ruin/space) +"h" = ( +/obj/structure/closet/crate, +/obj/item/weldingtool, +/obj/item/clothing/glasses/welding, +/turf/open/misc/asteroid/airless, +/area/ruin/space) +"j" = ( +/obj/structure/closet/crate/secure/loot, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav) +"l" = ( +/turf/template_noop, +/area/template_noop) +"m" = ( +/turf/closed/wall/rock, +/area/ruin/space) +"q" = ( +/obj/effect/mob_spawn/corpse/human/pirate/melee/space, +/obj/effect/decal/cleanable/blood, +/turf/open/misc/asteroid/airless, +/area/ruin/space) +"r" = ( +/obj/effect/decal/cleanable/blood, +/turf/open/misc/asteroid/airless, +/area/ruin/space) +"s" = ( +/obj/effect/mapping_helpers/bombable_wall, +/turf/closed/indestructible/riveted, +/area/ruin/space/has_grav) +"t" = ( +/obj/effect/gibspawner/human/bodypartless, +/obj/effect/mob_spawn/corpse/human/charredskeleton, +/turf/open/misc/asteroid/airless, +/area/ruin/space) +"u" = ( +/obj/structure/rack, +/obj/effect/spawner/random/clothing/beret_or_rabbitears, +/obj/effect/spawner/random/clothing/mafia_outfit, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav) +"x" = ( +/turf/open/floor/pod, +/area/ruin/space/has_grav) +"B" = ( +/obj/structure/rack, +/obj/effect/spawner/random/clothing/funny_hats, +/obj/effect/spawner/random/clothing/gloves, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav) +"C" = ( +/obj/structure/rack, +/obj/effect/spawner/random/clothing/mafia_outfit, +/obj/effect/spawner/random/clothing/pirate_or_bandana, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav) +"E" = ( +/obj/structure/mineral_door/wood, +/turf/open/misc/asteroid/airless, +/area/ruin/space) +"G" = ( +/obj/structure/safe, +/obj/item/clothing/head/collectable/petehat, +/turf/open/floor/pod/light, +/area/ruin/space/has_grav) +"I" = ( +/obj/machinery/porta_turret/syndicate/pod, +/turf/closed/wall/r_wall, +/area/ruin/space) +"J" = ( +/obj/item/gun/energy/laser/musket, +/obj/effect/decal/cleanable/blood, +/turf/open/misc/asteroid/airless, +/area/ruin/space) +"K" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/plating/airless, +/area/ruin/space) +"M" = ( +/obj/structure/reagent_dispensers/fueltank, +/turf/open/misc/asteroid/airless, +/area/ruin/space) +"O" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/spawner/random/maintenance, +/turf/open/floor/plating/airless, +/area/ruin/space) +"Q" = ( +/turf/open/floor/pod/light, +/area/ruin/space/has_grav) +"S" = ( +/obj/item/flashlight/lantern, +/turf/open/misc/asteroid/airless, +/area/ruin/space) +"T" = ( +/turf/open/misc/asteroid/airless, +/area/ruin/space) +"U" = ( +/obj/structure/rack, +/obj/effect/spawner/random/clothing/kittyears_or_rabbitears, +/obj/effect/spawner/random/clothing/lizardboots, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav) +"X" = ( +/obj/structure/rack, +/obj/effect/spawner/random/clothing/twentyfive_percent_cyborg_mask, +/obj/effect/spawner/random/clothing/costume, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav) +"Y" = ( +/obj/machinery/light/dim/directional/north, +/turf/open/floor/pod, +/area/ruin/space/has_grav) +"Z" = ( +/turf/closed/indestructible/fakedoor, +/area/ruin/space/has_grav) + +(1,1,1) = {" +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +a +a +a +l +l +l +l +"} +(2,1,1) = {" +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +T +l +l +a +l +"} +(3,1,1) = {" +l +l +l +l +l +l +l +a +l +l +l +l +l +l +l +l +l +l +l +l +l +l +a +a +l +l +l +l +l +l +l +l +l +"} +(4,1,1) = {" +l +l +l +l +T +a +a +a +T +l +l +I +l +l +l +l +l +l +l +l +l +l +T +l +l +l +l +l +l +l +l +l +a +"} +(5,1,1) = {" +T +T +T +T +T +a +a +m +r +T +r +a +l +a +l +l +l +l +l +l +l +l +l +l +l +l +l +T +a +a +l +l +T +"} +(6,1,1) = {" +l +T +T +a +a +a +a +T +q +T +m +a +a +a +a +l +l +l +l +l +l +l +l +l +l +l +T +a +a +T +l +l +l +"} +(7,1,1) = {" +l +T +a +a +a +a +a +J +a +a +a +a +a +a +a +a +a +a +l +l +l +l +l +l +l +l +l +a +a +T +l +l +l +"} +(8,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +O +m +a +a +a +a +a +T +T +T +T +l +l +l +a +r +l +l +l +l +"} +(9,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +T +T +K +K +T +O +a +a +a +a +T +T +T +T +T +l +l +l +l +l +l +l +"} +(10,1,1) = {" +a +a +a +a +a +a +a +a +b +b +b +s +b +b +r +T +T +K +a +a +a +a +T +T +T +l +l +l +l +l +l +T +T +"} +(11,1,1) = {" +l +a +a +a +a +a +a +a +b +j +B +X +U +b +T +t +T +m +a +a +a +a +T +T +T +l +l +l +l +l +l +a +a +"} +(12,1,1) = {" +l +l +a +a +a +a +a +a +b +x +x +x +x +b +K +T +T +a +a +a +a +a +a +T +T +l +l +l +l +l +l +l +l +"} +(13,1,1) = {" +l +T +T +a +a +a +a +a +b +Y +G +Q +x +Z +K +K +r +a +a +a +a +a +a +T +T +l +l +l +l +l +l +l +l +"} +(14,1,1) = {" +l +l +T +T +a +a +a +a +b +x +x +x +x +b +c +T +S +m +a +a +a +a +T +T +l +l +l +l +l +T +l +l +l +"} +(15,1,1) = {" +l +l +l +l +l +a +a +a +b +u +j +C +j +b +r +r +T +T +r +m +a +a +T +l +l +l +l +T +T +T +l +l +l +"} +(16,1,1) = {" +l +l +l +l +a +a +a +a +b +b +b +b +b +b +K +r +m +a +r +E +T +T +T +l +l +T +T +T +T +T +a +l +l +"} +(17,1,1) = {" +l +l +l +l +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +m +T +r +T +T +T +a +T +T +a +a +a +l +l +"} +(18,1,1) = {" +l +l +l +l +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +M +h +a +a +a +a +a +a +a +a +l +l +"} +(19,1,1) = {" +l +l +l +l +l +a +a +a +a +a +a +T +T +T +T +T +T +a +a +a +a +a +a +a +a +a +a +a +a +a +a +T +l +"} +(20,1,1) = {" +l +l +l +l +l +l +T +T +a +a +T +T +l +l +l +T +T +T +a +a +a +a +a +a +a +a +a +a +a +a +l +l +l +"} +(21,1,1) = {" +l +l +l +l +l +l +l +l +T +T +T +l +l +l +l +l +l +T +T +a +a +a +a +a +a +a +a +T +T +T +l +l +l +"} +(22,1,1) = {" +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +l +a +a +T +T +T +l +l +l +l +"} diff --git a/_maps/gateway_test.json b/_maps/gateway_test.json new file mode 100644 index 00000000000..5f4f8eec8a9 --- /dev/null +++ b/_maps/gateway_test.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "map_name": "Gateway Test", + "map_path": "map_files/debug", + "map_file": "gateway_test.dmm", + "space_ruin_levels": 1, + "load_all_away_missions": true, + "ignored_unit_tests": [ + "/datum/unit_test/antag_moodlets", + "/datum/unit_test/job_roundstart_spawnpoints", + "/datum/unit_test/required_map_items", + "/datum/unit_test/space_dragon_expiration", + "/datum/unit_test/traitor" + ] +} diff --git a/_maps/map_files/debug/gateway_test.dmm b/_maps/map_files/debug/gateway_test.dmm new file mode 100644 index 00000000000..d3cc2563d47 --- /dev/null +++ b/_maps/map_files/debug/gateway_test.dmm @@ -0,0 +1,392 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/closed/indestructible/reinforced, +/area/misc/testroom/gateway_room) +"c" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/white/full, +/turf/open/indestructible/dark, +/area/misc/testroom/gateway_room) +"d" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/red/line{ + dir = 8 + }, +/obj/effect/turf_decal/stripes/red/line{ + dir = 1 + }, +/turf/open/indestructible/dark, +/area/misc/testroom/gateway_room) +"f" = ( +/obj/effect/turf_decal/stripes/red/line{ + dir = 9 + }, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"g" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/obj/structure/extinguisher_cabinet/directional/west, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"h" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/obj/machinery/vending/wallmed/directional/west, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"j" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/white/full, +/obj/effect/turf_decal/stripes/red/line{ + dir = 6 + }, +/obj/structure/sign/flag/ssc/directional/north, +/turf/open/indestructible/dark, +/area/misc/testroom/gateway_room) +"l" = ( +/obj/machinery/computer/communications{ + dir = 8 + }, +/obj/effect/turf_decal/bot{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"m" = ( +/obj/effect/turf_decal/stripes/red/line{ + dir = 4 + }, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"n" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/white/full, +/obj/effect/turf_decal/stripes/red/line{ + dir = 4 + }, +/turf/open/indestructible/dark, +/area/misc/testroom/gateway_room) +"p" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/white/full, +/obj/effect/turf_decal/stripes/red/line{ + dir = 10 + }, +/obj/structure/sign/flag/nanotrasen/directional/north, +/turf/open/indestructible/dark, +/area/misc/testroom/gateway_room) +"q" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/white/full, +/obj/effect/turf_decal/stripes/red/line{ + dir = 8 + }, +/turf/open/indestructible/dark, +/area/misc/testroom/gateway_room) +"r" = ( +/obj/effect/turf_decal/delivery, +/obj/machinery/door/poddoor/shutters/indestructible, +/turf/open/floor/iron, +/area/misc/testroom/gateway_room) +"t" = ( +/obj/effect/turf_decal/stripes/red/line{ + dir = 5 + }, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"u" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/white/full, +/obj/effect/turf_decal/stripes/red/line{ + dir = 1 + }, +/obj/structure/sign/flag/terragov/directional/north, +/turf/open/indestructible/dark, +/area/misc/testroom/gateway_room) +"v" = ( +/obj/machinery/gateway/centerstation, +/obj/effect/turf_decal/stripes/white/box, +/obj/effect/turf_decal/trimline/blue, +/turf/open/indestructible/dark, +/area/misc/testroom/gateway_room) +"y" = ( +/obj/machinery/light/directional/east, +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/red/line{ + dir = 4 + }, +/obj/effect/turf_decal/stripes/red/line{ + dir = 1 + }, +/turf/open/indestructible/dark, +/area/misc/testroom/gateway_room) +"z" = ( +/obj/effect/landmark/blobstart, +/obj/effect/turf_decal/stripes/red/line, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"B" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"C" = ( +/obj/effect/turf_decal/stripes/red/line{ + dir = 8 + }, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"D" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 6 + }, +/obj/structure/sign/warning/secure_area/directional/south, +/obj/effect/landmark/latejoin, +/obj/effect/landmark/observer_start, +/obj/effect/landmark/start, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"E" = ( +/obj/machinery/door/airlock/public/glass{ + name = "Gateway Chamber" + }, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"G" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/obj/structure/table, +/obj/item/toy/figure/curator{ + pixel_y = 5 + }, +/obj/item/card/id/advanced/debug, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"H" = ( +/turf/open/space/basic, +/area/space) +"I" = ( +/obj/machinery/computer/gateway_control{ + dir = 8 + }, +/obj/effect/turf_decal/stripes/full, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"J" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/red/line{ + dir = 1 + }, +/turf/open/indestructible/dark, +/area/misc/testroom/gateway_room) +"K" = ( +/obj/structure/closet/crate/preopen, +/obj/item/stack/sheet/rglass{ + amount = 50 + }, +/obj/item/stack/sheet/iron/fifty, +/obj/item/stack/rods/fifty, +/obj/item/toy/plush/lizard_plushie/space/green{ + name = "Travels-The-Stars"; + desc = "The greatest gateway explorer ever created." + }, +/obj/effect/spawner/random/engineering/flashlight, +/obj/item/storage/toolbox/emergency, +/obj/machinery/firealarm/directional/west, +/obj/effect/turf_decal/bot{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 9 + }, +/obj/structure/sign/calendar/directional/north, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"M" = ( +/obj/structure/chair/stool/directional/east, +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/obj/machinery/keycard_auth/directional/west{ + pixel_y = 7 + }, +/obj/machinery/keycard_auth/directional/west{ + pixel_y = -7 + }, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"N" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/white/full, +/obj/effect/turf_decal/stripes/red/line{ + dir = 4 + }, +/obj/structure/sign/warning/radiation/directional/east, +/turf/open/indestructible/dark, +/area/misc/testroom/gateway_room) +"O" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 10 + }, +/obj/machinery/suit_storage_unit/centcom, +/obj/structure/sign/warning/engine_safety/directional/south, +/obj/structure/sign/warning/no_smoking/circle/directional/west, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"P" = ( +/obj/machinery/light/directional/west, +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/obj/structure/sign/poster/official/corporate_perks_vacation/directional/west, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"Q" = ( +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"R" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 5 + }, +/obj/structure/table, +/obj/item/paper/pamphlet/gateway{ + desc = "The Iris and You: How Not To Smash Against Steel At The End of the Event Horizon" + }, +/obj/effect/turf_decal/bot{ + dir = 1 + }, +/obj/item/radio/intercom/directional/north, +/turf/open/indestructible, +/area/misc/testroom/gateway_room) +"W" = ( +/turf/closed/indestructible/fakeglass, +/area/misc/testroom/gateway_room) + +(1,1,1) = {" +H +H +H +H +H +H +H +H +H +H +"} +(2,1,1) = {" +H +a +a +a +a +a +a +a +a +H +"} +(3,1,1) = {" +H +a +K +M +g +P +h +O +a +H +"} +(4,1,1) = {" +H +a +R +I +l +G +B +D +a +H +"} +(5,1,1) = {" +H +a +W +W +W +W +E +W +a +H +"} +(6,1,1) = {" +H +a +j +q +q +d +C +t +r +H +"} +(7,1,1) = {" +H +a +u +v +c +J +Q +z +r +H +"} +(8,1,1) = {" +H +a +p +N +n +y +m +f +r +H +"} +(9,1,1) = {" +H +a +a +a +a +a +a +a +a +H +"} +(10,1,1) = {" +H +H +H +H +H +H +H +H +H +H +"} diff --git a/_maps/safehouses/README.md b/_maps/safehouses/README.md new file mode 100644 index 00000000000..8027ea38e21 --- /dev/null +++ b/_maps/safehouses/README.md @@ -0,0 +1,17 @@ +# Safe House + +## Creating a new safe house + +1. Create a new map inside the `_maps\safe_houses` folder using the TGM format. +2. Create a new dm file inside `modules\bitrunning\virtual_domain\safe_houses` folder.. +4. Place exit and goal landmarks (obj/effect/landmark/bitrunning/..). Generally, 3 exits and 2 goals are ok. +5. Ideally, leave 3 spaces for gear. This has usually been xy [1x1] [1x2] [1x3] + +## Notes + +- Safe houses are intended to be 7x6 in size. You're not technically limited to this, but consider maps other maps might be using this size if you want it to be modular. +- Consider that avatars are not invincible and still require air. If you're making a safe house, it should start with an area that accommodates for this. +- For compatibility, your safe house should have a route open from the top center xy [3x0] of the map. +- If you want a custom safehouse for a custom map with no modularity, no problem. Make whatever sizes you want, just ensure there are exit and goal effects placed. +- Some maps can alter what is spawned into the safehouse by placing objects in the safehouse area. I'm using the left corner, starting from the top, for things like space gear. + diff --git a/_maps/safehouses/TEMPLATES/TEMPLATE.dmm b/_maps/safehouses/TEMPLATES/TEMPLATE.dmm new file mode 100644 index 00000000000..c8e5059f0d0 --- /dev/null +++ b/_maps/safehouses/TEMPLATES/TEMPLATE.dmm @@ -0,0 +1,82 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/obj/effect/spawner/structure/window/reinforced, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"c" = ( +/obj/effect/mapping_helpers/airlock/access/all, +/obj/machinery/door/airlock/external/glass, +/obj/structure/fans/tiny, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"p" = ( +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"v" = ( +/obj/effect/bitrunning/exit_spawn, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"N" = ( +/obj/effect/bitrunning/goal_turf, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"R" = ( +/turf/closed/wall, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +R +R +a +a +R +R +"} +(2,1,1) = {" +a +p +p +p +p +R +"} +(3,1,1) = {" +R +p +p +p +v +a +"} +(4,1,1) = {" +c +p +p +p +v +R +"} +(5,1,1) = {" +R +p +p +p +v +a +"} +(6,1,1) = {" +a +p +N +N +p +R +"} +(7,1,1) = {" +R +R +a +a +R +R +"} diff --git a/_maps/safehouses/den.dmm b/_maps/safehouses/den.dmm new file mode 100644 index 00000000000..235d786d6e9 --- /dev/null +++ b/_maps/safehouses/den.dmm @@ -0,0 +1,224 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/obj/structure/chair/office{ + dir = 1 + }, +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/light/small/directional/south, +/turf/open/floor/pod, +/area/virtual_domain/safehouse) +"c" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/light/small/directional/east, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/obj/effect/turf_decal/loading_area{ + dir = 4 + }, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"e" = ( +/obj/structure/table/reinforced/plastitaniumglass, +/obj/effect/spawner/random/food_or_drink/snack{ + pixel_x = 4; + pixel_y = 2 + }, +/turf/open/floor/pod/dark, +/area/virtual_domain/safehouse) +"i" = ( +/obj/effect/spawner/structure/window/reinforced, +/obj/machinery/door/poddoor/shutters/preopen{ + dir = 1; + id = "safehouseshutter" + }, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"l" = ( +/obj/structure/railing/corner/end{ + dir = 4 + }, +/turf/open/floor/pod/dark, +/area/virtual_domain/safehouse) +"p" = ( +/turf/open/floor/pod, +/area/virtual_domain/safehouse) +"r" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 9 + }, +/turf/open/floor/pod/dark, +/area/virtual_domain/safehouse) +"u" = ( +/obj/structure/railing, +/obj/effect/turf_decal/siding/dark, +/obj/structure/sign/poster/contraband/hacking_guide/directional/east, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/obj/effect/turf_decal/loading_area{ + dir = 4 + }, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"z" = ( +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 4 + }, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/pod/light, +/area/virtual_domain/safehouse) +"C" = ( +/turf/closed/wall, +/area/virtual_domain/safehouse) +"D" = ( +/obj/effect/decal/cleanable/generic, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 4 + }, +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 1 + }, +/turf/open/floor/pod, +/area/virtual_domain/safehouse) +"E" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 8 + }, +/obj/effect/decal/cleanable/generic, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/pod/light, +/area/virtual_domain/safehouse) +"G" = ( +/obj/effect/spawner/structure/window/reinforced, +/obj/machinery/door/poddoor/shutters/preopen{ + dir = 4; + id = "safehouseshutter" + }, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"I" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 5 + }, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/pod/light, +/area/virtual_domain/safehouse) +"J" = ( +/obj/effect/spawner/structure/window/reinforced, +/obj/machinery/door/poddoor/shutters/preopen{ + dir = 8; + id = "safehouseshutter" + }, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"K" = ( +/obj/effect/spawner/random/vending/colavend, +/obj/machinery/light/small/directional/south, +/turf/open/floor/pod, +/area/virtual_domain/safehouse) +"M" = ( +/turf/open/floor/pod/dark, +/area/virtual_domain/safehouse) +"O" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 8 + }, +/turf/open/floor/pod/dark, +/area/virtual_domain/safehouse) +"R" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 4 + }, +/turf/open/floor/pod/dark, +/area/virtual_domain/safehouse) +"U" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/sign/departments/cargo/directional/west, +/obj/machinery/light/small/directional/west, +/turf/open/floor/pod, +/area/virtual_domain/safehouse) +"W" = ( +/obj/effect/spawner/structure/window/reinforced, +/obj/machinery/door/poddoor/shutters/preopen{ + id = "safehouseshutter" + }, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"X" = ( +/obj/machinery/door/airlock/grunge, +/obj/structure/fans/tiny, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"Y" = ( +/obj/structure/table/reinforced/plastitaniumglass, +/obj/machinery/button/door{ + pixel_x = 4; + pixel_y = 4; + id = "safehouseshutter" + }, +/obj/effect/spawner/random/food_or_drink/refreshing_beverage{ + pixel_y = 6; + pixel_x = -10 + }, +/turf/open/floor/pod/dark, +/area/virtual_domain/safehouse) +"Z" = ( +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 4 + }, +/turf/open/floor/pod/dark, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +C +C +C +G +G +C +"} +(2,1,1) = {" +W +U +p +M +K +C +"} +(3,1,1) = {" +C +r +O +O +E +i +"} +(4,1,1) = {" +X +D +Z +R +z +i +"} +(5,1,1) = {" +C +M +l +e +I +i +"} +(6,1,1) = {" +W +c +u +Y +a +C +"} +(7,1,1) = {" +C +C +C +J +J +C +"} diff --git a/_maps/safehouses/dig.dmm b/_maps/safehouses/dig.dmm new file mode 100644 index 00000000000..7fbbd3e5549 --- /dev/null +++ b/_maps/safehouses/dig.dmm @@ -0,0 +1,165 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/obj/effect/turf_decal/sand/plating, +/obj/effect/turf_decal/siding/yellow/corner, +/obj/effect/turf_decal/sand/plating, +/obj/item/flashlight/glowstick{ + on = 1 + }, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"c" = ( +/obj/effect/turf_decal/siding/yellow/corner{ + dir = 8 + }, +/turf/open/misc/asteroid, +/area/virtual_domain/safehouse) +"h" = ( +/obj/effect/turf_decal/loading_area, +/obj/effect/turf_decal/box/corners{ + dir = 8 + }, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"i" = ( +/obj/effect/turf_decal/siding/yellow{ + dir = 8 + }, +/obj/effect/decal/remains/xeno/larva, +/turf/open/misc/asteroid, +/area/virtual_domain/safehouse) +"l" = ( +/obj/structure/table, +/obj/item/coin/gold{ + pixel_x = -6; + pixel_y = 2 + }, +/obj/item/flashlight/lantern{ + pixel_y = 8; + pixel_x = 4; + on = 1 + }, +/turf/open/misc/asteroid, +/area/virtual_domain/safehouse) +"o" = ( +/obj/effect/turf_decal/siding/yellow{ + dir = 8 + }, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/misc/asteroid, +/area/virtual_domain/safehouse) +"u" = ( +/obj/effect/turf_decal/siding/yellow{ + dir = 8 + }, +/obj/effect/turf_decal/siding/yellow, +/obj/effect/decal/cleanable/oil/streak, +/turf/open/misc/asteroid, +/area/virtual_domain/safehouse) +"x" = ( +/obj/effect/turf_decal/sand/plating, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"A" = ( +/turf/closed/wall/rock, +/area/virtual_domain/safehouse) +"B" = ( +/turf/open/misc/asteroid, +/area/virtual_domain/safehouse) +"H" = ( +/turf/closed/mineral/asteroid, +/area/virtual_domain/safehouse) +"I" = ( +/obj/machinery/door/airlock/maintenance/glass, +/obj/structure/fans/tiny, +/obj/effect/turf_decal/sand/plating, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"M" = ( +/obj/effect/turf_decal/siding/yellow, +/obj/effect/decal/remains/xeno, +/turf/open/misc/asteroid, +/area/virtual_domain/safehouse) +"N" = ( +/obj/effect/turf_decal/sand/plating, +/obj/effect/turf_decal/sand/plating, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"S" = ( +/obj/structure/railing{ + dir = 4 + }, +/obj/effect/turf_decal/loading_area, +/obj/effect/turf_decal/box/corners, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"T" = ( +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/misc/asteroid, +/area/virtual_domain/safehouse) +"U" = ( +/obj/effect/turf_decal/siding/yellow{ + dir = 8 + }, +/turf/open/misc/asteroid, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +H +H +A +A +H +H +"} +(2,1,1) = {" +A +N +x +a +H +A +"} +(3,1,1) = {" +A +i +U +u +h +A +"} +(4,1,1) = {" +I +B +B +M +S +A +"} +(5,1,1) = {" +A +l +B +c +o +H +"} +(6,1,1) = {" +A +A +T +T +T +A +"} +(7,1,1) = {" +H +A +H +H +A +A +"} diff --git a/_maps/safehouses/ice.dmm b/_maps/safehouses/ice.dmm new file mode 100644 index 00000000000..a8293f9502a --- /dev/null +++ b/_maps/safehouses/ice.dmm @@ -0,0 +1,254 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/obj/effect/spawner/structure/window/ice, +/turf/open/floor/plating/snowed, +/area/virtual_domain/safehouse) +"c" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 6 + }, +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/light/small/directional/east, +/obj/structure/chair/wood{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"f" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/plating/snowed, +/area/virtual_domain/safehouse) +"g" = ( +/obj/effect/spawner/structure/window/ice, +/obj/structure/barricade/wooden/crude/snow, +/turf/open/floor/plating/snowed, +/area/virtual_domain/safehouse) +"i" = ( +/turf/closed/wall/ice, +/area/virtual_domain/safehouse) +"m" = ( +/obj/effect/turf_decal/weather/snow/corner, +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/turf_decal/trimline/dark_blue/line{ + dir = 1 + }, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/pod, +/area/virtual_domain/safehouse) +"n" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 8 + }, +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/rack, +/obj/item/grown/log, +/obj/item/grown/log, +/obj/item/grown/log, +/obj/item/hatchet/wooden, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"o" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/obj/effect/turf_decal/loading_area{ + dir = 4 + }, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"p" = ( +/obj/structure/railing, +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/obj/effect/turf_decal/loading_area{ + dir = 4 + }, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"u" = ( +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"v" = ( +/obj/effect/turf_decal/weather/snow/corner, +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/turf_decal/trimline/dark_blue/line{ + dir = 5 + }, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/pod, +/area/virtual_domain/safehouse) +"x" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 1 + }, +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/vending/coffee, +/turf/open/floor/plating/snowed, +/area/virtual_domain/safehouse) +"z" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/decal/cleanable/generic, +/turf/open/floor/plating/snowed, +/area/virtual_domain/safehouse) +"A" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/turf_decal/weather/snow/corner{ + dir = 4 + }, +/obj/structure/railing/corner/end{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/plating/snowed, +/area/virtual_domain/safehouse) +"B" = ( +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/plating/snowed, +/area/virtual_domain/safehouse) +"C" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/turf_decal/weather/snow/corner{ + dir = 1 + }, +/obj/structure/table/wood, +/obj/item/reagent_containers/cup/glass/coffee{ + pixel_x = 7; + pixel_y = 13 + }, +/obj/item/reagent_containers/cup/glass/coffee/no_lid{ + pixel_x = -4; + pixel_y = 14 + }, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"D" = ( +/obj/effect/turf_decal/weather/snow/corner, +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/decal/cleanable/generic, +/obj/effect/turf_decal/trimline/dark_blue/line{ + dir = 9 + }, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/pod, +/area/virtual_domain/safehouse) +"I" = ( +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"L" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 9 + }, +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/light/small/directional/west, +/obj/machinery/smartfridge/drying_rack, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"O" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 4 + }, +/obj/effect/turf_decal/weather/snow/corner{ + dir = 8 + }, +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/door/airlock/freezer, +/obj/structure/fans/tiny, +/turf/open/floor/plating/snowed, +/area/virtual_domain/safehouse) +"S" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 5 + }, +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/table/wood, +/obj/item/trash/chips{ + pixel_x = 8; + pixel_y = 15 + }, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"W" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 8 + }, +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/closet/crate/internals, +/obj/item/tank/internals/oxygen, +/obj/item/tank/internals/oxygen, +/obj/item/tank/internals/oxygen, +/obj/item/clothing/mask/breath, +/obj/item/clothing/mask/breath, +/obj/item/clothing/mask/breath, +/obj/item/clothing/suit/hooded/wintercoat, +/obj/item/clothing/suit/hooded/wintercoat, +/obj/item/clothing/suit/hooded/wintercoat, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"Z" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 10 + }, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +i +i +g +a +i +i +"} +(2,1,1) = {" +i +L +n +W +Z +i +"} +(3,1,1) = {" +a +x +u +I +D +i +"} +(4,1,1) = {" +O +z +B +I +m +i +"} +(5,1,1) = {" +a +f +A +C +v +i +"} +(6,1,1) = {" +i +o +p +S +c +i +"} +(7,1,1) = {" +i +i +g +g +i +i +"} diff --git a/_maps/safehouses/lavaland_boss.dmm b/_maps/safehouses/lavaland_boss.dmm new file mode 100644 index 00000000000..7482846e61f --- /dev/null +++ b/_maps/safehouses/lavaland_boss.dmm @@ -0,0 +1,243 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/obj/effect/turf_decal/trimline/brown/filled/line, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/iron/dark/smooth_edge{ + dir = 1 + }, +/area/virtual_domain/safehouse) +"f" = ( +/turf/closed/wall, +/area/virtual_domain/safehouse) +"p" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 5 + }, +/obj/structure/tank_dispenser/oxygen, +/turf/open/floor/iron/dark, +/area/virtual_domain/safehouse) +"v" = ( +/obj/structure/table, +/obj/item/borg/upgrade/modkit/damage{ + pixel_x = 8; + pixel_y = 8 + }, +/obj/item/borg/upgrade/modkit/damage{ + pixel_y = 4; + pixel_x = 8 + }, +/obj/item/borg/upgrade/modkit/damage{ + pixel_x = 8 + }, +/obj/item/borg/upgrade/modkit/range{ + pixel_y = 8 + }, +/obj/item/borg/upgrade/modkit/range{ + pixel_y = 4 + }, +/obj/item/borg/upgrade/modkit/range, +/obj/item/borg/upgrade/modkit/cooldown{ + pixel_x = -8; + pixel_y = 8 + }, +/obj/item/borg/upgrade/modkit/cooldown{ + pixel_x = -8; + pixel_y = 4 + }, +/obj/item/borg/upgrade/modkit/cooldown{ + pixel_x = -8 + }, +/obj/item/reagent_containers/hypospray/medipen/survival/luxury{ + pixel_x = 6; + pixel_y = 6 + }, +/obj/item/reagent_containers/hypospray/medipen/survival/luxury{ + pixel_x = 6 + }, +/turf/open/floor/iron/dark/textured_large, +/area/virtual_domain/safehouse) +"w" = ( +/obj/effect/turf_decal/loading_area{ + dir = 4 + }, +/obj/machinery/light/directional/east, +/obj/structure/railing, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"A" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 1 + }, +/obj/structure/extinguisher_cabinet/directional/north, +/turf/open/floor/iron/dark/smooth_edge, +/area/virtual_domain/safehouse) +"B" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 10 + }, +/obj/structure/table, +/obj/item/flashlight/lantern{ + pixel_x = 8; + pixel_y = null + }, +/obj/item/flashlight/lantern{ + pixel_y = 4 + }, +/obj/item/flashlight/lantern{ + pixel_x = -8; + pixel_y = 8 + }, +/obj/item/clothing/glasses/meson/night, +/obj/item/clothing/glasses/meson/night, +/obj/item/clothing/glasses/meson/night, +/turf/open/floor/iron/dark, +/area/virtual_domain/safehouse) +"C" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 4 + }, +/obj/structure/closet, +/obj/item/gun/ballistic/rocketlauncher/unrestricted, +/obj/item/ammo_casing/rocket, +/obj/item/ammo_casing/rocket, +/obj/item/ammo_casing/rocket, +/obj/item/energy_katana, +/obj/item/ammo_box/magazine/m7mm, +/turf/open/floor/iron/dark/smooth_edge{ + dir = 8 + }, +/area/virtual_domain/safehouse) +"H" = ( +/obj/machinery/door/airlock/external/glass{ + name = "Mining External Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all, +/obj/structure/fans/tiny, +/turf/open/floor/iron/dark/textured_large, +/area/virtual_domain/safehouse) +"K" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 1 + }, +/turf/open/floor/iron/dark/smooth_edge, +/area/virtual_domain/safehouse) +"O" = ( +/obj/item/gun/energy/recharge/kinetic_accelerator{ + pixel_x = -6; + pixel_y = 6 + }, +/obj/item/gun/energy/recharge/kinetic_accelerator{ + pixel_x = -1; + pixel_y = 1 + }, +/obj/item/gun/energy/recharge/kinetic_accelerator{ + pixel_x = 4; + pixel_y = -4 + }, +/obj/structure/closet, +/obj/item/kinetic_crusher, +/obj/item/kinetic_crusher, +/turf/open/floor/iron/dark/textured_large, +/area/virtual_domain/safehouse) +"P" = ( +/obj/effect/spawner/structure/window/reinforced, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"S" = ( +/turf/open/floor/iron/dark/textured_large, +/area/virtual_domain/safehouse) +"T" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 6 + }, +/obj/structure/sign/departments/cargo/directional/south, +/obj/structure/closet, +/obj/item/gun/ballistic/automatic/l6_saw/unrestricted, +/obj/item/ammo_box/magazine/sniper_rounds, +/obj/item/gun/ballistic/rifle/sniper_rifle, +/turf/open/floor/iron/dark, +/area/virtual_domain/safehouse) +"X" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 8 + }, +/obj/machinery/suit_storage_unit/mining, +/turf/open/floor/iron/dark/smooth_edge{ + dir = 4 + }, +/area/virtual_domain/safehouse) +"Y" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 8 + }, +/obj/machinery/suit_storage_unit/mining, +/obj/machinery/light/directional/west, +/turf/open/floor/iron/dark/smooth_edge{ + dir = 4 + }, +/area/virtual_domain/safehouse) +"Z" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 9 + }, +/obj/machinery/suit_storage_unit/mining, +/turf/open/floor/iron/dark, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +f +f +f +P +P +f +"} +(2,1,1) = {" +P +Z +Y +X +B +f +"} +(3,1,1) = {" +f +A +S +S +a +P +"} +(4,1,1) = {" +H +K +O +v +a +P +"} +(5,1,1) = {" +f +A +S +S +a +P +"} +(6,1,1) = {" +P +p +w +C +T +f +"} +(7,1,1) = {" +f +f +f +P +P +f +"} diff --git a/_maps/safehouses/mine.dmm b/_maps/safehouses/mine.dmm new file mode 100644 index 00000000000..551e2ca0c00 --- /dev/null +++ b/_maps/safehouses/mine.dmm @@ -0,0 +1,164 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/obj/effect/turf_decal/trimline/brown/filled/line, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/iron/dark/smooth_edge{ + dir = 1 + }, +/area/virtual_domain/safehouse) +"f" = ( +/turf/closed/wall, +/area/virtual_domain/safehouse) +"p" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 5 + }, +/obj/structure/tank_dispenser/oxygen, +/turf/open/floor/iron/dark, +/area/virtual_domain/safehouse) +"w" = ( +/obj/effect/turf_decal/loading_area{ + dir = 4 + }, +/obj/machinery/light/directional/east, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"B" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 10 + }, +/obj/structure/table, +/obj/item/flashlight/lantern{ + pixel_x = 8; + pixel_y = null + }, +/obj/item/flashlight/lantern{ + pixel_y = 4 + }, +/obj/item/flashlight/lantern{ + pixel_x = -8; + pixel_y = 8 + }, +/turf/open/floor/iron/dark, +/area/virtual_domain/safehouse) +"C" = ( +/obj/effect/turf_decal/loading_area{ + dir = 4 + }, +/obj/structure/railing, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"H" = ( +/obj/machinery/door/airlock/external/glass{ + name = "Mining External Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all, +/obj/structure/fans/tiny, +/turf/open/floor/iron/dark/textured_large, +/area/virtual_domain/safehouse) +"K" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 1 + }, +/turf/open/floor/iron/dark/smooth_edge, +/area/virtual_domain/safehouse) +"P" = ( +/obj/effect/spawner/structure/window/reinforced, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"S" = ( +/turf/open/floor/iron/dark/textured_large, +/area/virtual_domain/safehouse) +"T" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 6 + }, +/obj/item/kirbyplants/random, +/obj/structure/sign/departments/cargo/directional/south, +/turf/open/floor/iron/dark, +/area/virtual_domain/safehouse) +"X" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 8 + }, +/obj/machinery/suit_storage_unit/mining, +/turf/open/floor/iron/dark/smooth_edge{ + dir = 4 + }, +/area/virtual_domain/safehouse) +"Y" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 8 + }, +/obj/machinery/suit_storage_unit/mining, +/obj/machinery/light/directional/west, +/turf/open/floor/iron/dark/smooth_edge{ + dir = 4 + }, +/area/virtual_domain/safehouse) +"Z" = ( +/obj/effect/turf_decal/trimline/brown/filled/line{ + dir = 9 + }, +/obj/machinery/suit_storage_unit/mining, +/turf/open/floor/iron/dark, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +f +f +f +P +P +f +"} +(2,1,1) = {" +P +Z +Y +X +B +f +"} +(3,1,1) = {" +f +K +S +S +a +P +"} +(4,1,1) = {" +H +K +S +S +a +P +"} +(5,1,1) = {" +f +K +S +S +a +P +"} +(6,1,1) = {" +P +p +w +C +T +f +"} +(7,1,1) = {" +f +f +f +P +P +f +"} diff --git a/_maps/safehouses/shuttle.dmm b/_maps/safehouses/shuttle.dmm new file mode 100644 index 00000000000..92228c95bd3 --- /dev/null +++ b/_maps/safehouses/shuttle.dmm @@ -0,0 +1,228 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/closed/wall/mineral/titanium, +/area/virtual_domain/safehouse) +"e" = ( +/obj/effect/spawner/structure/window/reinforced/shuttle, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"f" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"g" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/obj/effect/turf_decal/loading_area, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"i" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"k" = ( +/obj/effect/turf_decal/tile/neutral, +/obj/effect/turf_decal/stripes/line{ + dir = 5 + }, +/obj/structure/table/reinforced, +/obj/effect/decal/cleanable/dirt, +/obj/item/storage/toolbox/emergency, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"l" = ( +/obj/machinery/light/small/directional/south, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/obj/effect/turf_decal/loading_area, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"q" = ( +/obj/effect/turf_decal/stripes/end, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"r" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"t" = ( +/obj/machinery/power/shuttle_engine/propulsion/burst{ + dir = 8 + }, +/obj/structure/window/reinforced/spawner/directional/east, +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/turf/open/floor/plating/airless, +/area/virtual_domain/safehouse) +"u" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/line, +/obj/effect/decal/cleanable/blood/old, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"x" = ( +/obj/effect/turf_decal/tile/neutral/half/contrasted, +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/obj/machinery/light/small/directional/north, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"y" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/obj/machinery/door/airlock/shuttle/glass, +/obj/effect/turf_decal/stripes/line, +/obj/effect/turf_decal/sand/volcanic, +/obj/structure/fans/tiny, +/turf/open/floor/iron/white, +/area/virtual_domain/safehouse) +"A" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/obj/machinery/computer{ + dir = 8; + name = "shuttle console"; + icon_screen = "shuttle" + }, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"E" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 10 + }, +/obj/effect/decal/cleanable/generic, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"G" = ( +/obj/effect/turf_decal/tile/neutral{ + dir = 4 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 6 + }, +/obj/machinery/light/small/directional/south, +/obj/effect/decal/cleanable/dirt, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"H" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/line, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"I" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/line{ + dir = 6 + }, +/obj/structure/table/reinforced, +/obj/item/tank/internals/emergency_oxygen{ + pixel_x = 3 + }, +/obj/item/clothing/mask/gas, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"L" = ( +/obj/effect/turf_decal/stripes/end{ + dir = 1 + }, +/obj/effect/turf_decal/sand/volcanic, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"M" = ( +/obj/effect/turf_decal/tile/neutral/half/contrasted, +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/obj/machinery/light/small/directional/north, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"T" = ( +/obj/effect/turf_decal/tile/neutral/half/contrasted{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/line, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"X" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/structure/chair/comfy/shuttle{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +a +t +e +e +t +a +"} +(2,1,1) = {" +e +L +i +q +l +a +"} +(3,1,1) = {" +a +x +r +u +g +e +"} +(4,1,1) = {" +y +f +r +H +E +a +"} +(5,1,1) = {" +a +M +X +H +T +e +"} +(6,1,1) = {" +e +k +A +I +G +a +"} +(7,1,1) = {" +a +a +e +e +a +a +"} diff --git a/_maps/safehouses/shuttle_space.dmm b/_maps/safehouses/shuttle_space.dmm new file mode 100644 index 00000000000..a5afaa475c6 --- /dev/null +++ b/_maps/safehouses/shuttle_space.dmm @@ -0,0 +1,231 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/obj/effect/spawner/structure/window/reinforced/shuttle, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"b" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/line, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"c" = ( +/obj/effect/turf_decal/tile/neutral/half/contrasted, +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/obj/machinery/light/small/directional/north, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"i" = ( +/turf/closed/wall/mineral/titanium/overspace, +/area/virtual_domain/safehouse) +"l" = ( +/obj/effect/turf_decal/tile/neutral/half/contrasted{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/line, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"n" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/obj/machinery/computer{ + dir = 8; + name = "shuttle console"; + icon_screen = "shuttle" + }, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"o" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"q" = ( +/obj/effect/turf_decal/tile/neutral, +/obj/effect/turf_decal/stripes/line{ + dir = 5 + }, +/obj/structure/table/reinforced, +/obj/effect/decal/cleanable/dirt, +/obj/item/storage/toolbox/emergency, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"r" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/obj/effect/turf_decal/loading_area, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"z" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"A" = ( +/obj/effect/turf_decal/stripes/end{ + dir = 1 + }, +/obj/effect/turf_decal/sand/volcanic, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"B" = ( +/obj/effect/turf_decal/tile/neutral{ + dir = 4 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 6 + }, +/obj/machinery/light/small/directional/south, +/obj/effect/decal/cleanable/dirt, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"D" = ( +/obj/machinery/light/small/directional/south, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/obj/effect/turf_decal/loading_area, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"E" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/obj/machinery/door/airlock/shuttle/glass, +/obj/effect/turf_decal/stripes/line, +/obj/effect/turf_decal/sand/volcanic, +/obj/structure/fans/tiny, +/turf/open/floor/iron/white, +/area/virtual_domain/safehouse) +"G" = ( +/turf/closed/wall/mineral/titanium, +/area/virtual_domain/safehouse) +"H" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"I" = ( +/obj/effect/turf_decal/tile/neutral/half/contrasted, +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/obj/machinery/light/small/directional/north, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"L" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/line{ + dir = 6 + }, +/obj/structure/table/reinforced, +/obj/item/tank/internals/emergency_oxygen{ + pixel_x = 3 + }, +/obj/item/clothing/mask/gas, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"N" = ( +/obj/machinery/power/shuttle_engine/propulsion/burst{ + dir = 8 + }, +/obj/structure/window/reinforced/spawner/directional/east, +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/turf/open/floor/plating/airless, +/area/virtual_domain/safehouse) +"O" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/structure/chair/comfy/shuttle{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"U" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/stripes/line, +/obj/effect/decal/cleanable/blood/old, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"W" = ( +/obj/effect/turf_decal/stripes/end, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) +"Y" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 10 + }, +/obj/effect/decal/cleanable/generic, +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/iron, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +i +N +a +a +N +i +"} +(2,1,1) = {" +a +A +o +W +D +G +"} +(3,1,1) = {" +G +I +H +U +r +a +"} +(4,1,1) = {" +E +z +H +b +Y +G +"} +(5,1,1) = {" +G +c +O +b +l +a +"} +(6,1,1) = {" +a +q +n +L +B +G +"} +(7,1,1) = {" +i +G +a +a +G +i +"} diff --git a/_maps/safehouses/test_only_safehouse.dmm b/_maps/safehouses/test_only_safehouse.dmm new file mode 100644 index 00000000000..c23f8c4a22b --- /dev/null +++ b/_maps/safehouses/test_only_safehouse.dmm @@ -0,0 +1,29 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/obj/effect/landmark/bitrunning/cache_goal_turf, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"d" = ( +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"u" = ( +/turf/open/floor/plating, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +u +d +"} +(2,1,1) = {" +u +d +"} +(3,1,1) = {" +u +d +"} +(4,1,1) = {" +u +a +"} diff --git a/_maps/safehouses/wood.dmm b/_maps/safehouses/wood.dmm new file mode 100644 index 00000000000..0bb6b273fce --- /dev/null +++ b/_maps/safehouses/wood.dmm @@ -0,0 +1,120 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/open/indestructible/hotelwood, +/area/virtual_domain/safehouse) +"i" = ( +/obj/effect/spawner/structure/window, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"o" = ( +/turf/open/floor/carpet/green, +/area/virtual_domain/safehouse) +"p" = ( +/obj/item/kirbyplants/random/fullysynthetic, +/turf/open/indestructible/hotelwood, +/area/virtual_domain/safehouse) +"s" = ( +/obj/machinery/light/small/directional/east, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/obj/effect/turf_decal/loading_area{ + dir = 4 + }, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"v" = ( +/obj/machinery/light/small/directional/east, +/obj/structure/table/wood, +/obj/item/newspaper, +/turf/open/indestructible/hotelwood, +/area/virtual_domain/safehouse) +"x" = ( +/obj/structure/railing/corner/end{ + dir = 4 + }, +/turf/open/floor/carpet/green, +/area/virtual_domain/safehouse) +"z" = ( +/obj/structure/sign/poster/random/directional/east, +/turf/open/indestructible/hotelwood, +/area/virtual_domain/safehouse) +"G" = ( +/turf/closed/wall/mineral/wood, +/area/virtual_domain/safehouse) +"J" = ( +/obj/structure/railing, +/obj/effect/landmark/bitrunning/cache_goal_turf, +/obj/effect/turf_decal/loading_area{ + dir = 4 + }, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"T" = ( +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/indestructible/hotelwood, +/area/virtual_domain/safehouse) +"X" = ( +/obj/machinery/door/airlock/wood/glass, +/obj/structure/fans/tiny, +/turf/open/floor/plating, +/area/virtual_domain/safehouse) +"Z" = ( +/obj/machinery/light/small/directional/west, +/turf/open/indestructible/hotelwood, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +G +G +i +G +G +G +"} +(2,1,1) = {" +i +Z +a +a +Z +i +"} +(3,1,1) = {" +G +p +o +o +T +G +"} +(4,1,1) = {" +X +a +o +o +T +G +"} +(5,1,1) = {" +G +a +x +o +T +G +"} +(6,1,1) = {" +i +s +J +z +v +i +"} +(7,1,1) = {" +G +G +i +G +G +G +"} diff --git a/_maps/shuttles/emergency_humpback.dmm b/_maps/shuttles/emergency_humpback.dmm new file mode 100644 index 00000000000..f4c50bf6b84 --- /dev/null +++ b/_maps/shuttles/emergency_humpback.dmm @@ -0,0 +1,1308 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"ao" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/visible, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape/brig) +"at" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 1 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"bL" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape/brig) +"bS" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/structure/chair/comfy/shuttle, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"bT" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"cK" = ( +/turf/open/floor/iron/smooth_half/airless, +/area/shuttle/escape) +"df" = ( +/obj/machinery/door/airlock/external, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/effect/turf_decal/siding/blue, +/obj/effect/mapping_helpers/airlock/access/all/command/general, +/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ + cycle_id = "evac3" + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"dl" = ( +/obj/structure/chair/sofa/right{ + dir = 4 + }, +/turf/open/floor/iron/smooth, +/area/shuttle/escape) +"dF" = ( +/obj/machinery/power/shuttle_engine/propulsion{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/turf/open/floor/plating/airless, +/area/shuttle/escape/brig) +"dP" = ( +/obj/structure/table/reinforced/plastitaniumglass, +/obj/effect/spawner/random/food_or_drink/booze, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"ed" = ( +/obj/structure/table, +/obj/item/storage/medkit/regular, +/obj/item/storage/medkit/toxin{ + pixel_x = 4; + pixel_y = 4 + }, +/turf/open/floor/wood/large, +/area/shuttle/escape) +"ez" = ( +/obj/machinery/door/airlock/public/glass{ + name = "Cyborg Bay" + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"eK" = ( +/obj/structure/window/reinforced/spawner/directional/south, +/obj/machinery/power/shuttle_engine/heater{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/turf/open/floor/plating/airless, +/area/shuttle/escape) +"eU" = ( +/obj/machinery/door/airlock/external/glass, +/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ + cycle_id = "evac3" + }, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"eW" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/machinery/status_display/evac/directional/east, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"fu" = ( +/obj/machinery/computer/emergency_shuttle{ + dir = 1 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"gL" = ( +/obj/machinery/door/airlock/security/glass{ + name = "Escape Shuttle Brig" + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/effect/mapping_helpers/airlock/access/all/security/general, +/obj/effect/mapping_helpers/airlock/cyclelink_helper, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape/brig) +"gW" = ( +/obj/structure/window/reinforced/plasma/spawner/directional/east, +/obj/machinery/vending/boozeomat/all_access, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"hd" = ( +/obj/machinery/atmospherics/components/binary/pump/on/supply{ + dir = 4 + }, +/obj/structure/extinguisher_cabinet/directional/south, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape/brig) +"he" = ( +/obj/machinery/vending/wallmed/directional/east, +/turf/open/floor/wood/large, +/area/shuttle/escape) +"hk" = ( +/obj/machinery/light/directional/north, +/obj/structure/table, +/obj/machinery/recharger, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape/brig) +"hp" = ( +/obj/structure/lattice/catwalk, +/turf/template_noop, +/area/shuttle/escape) +"hs" = ( +/obj/effect/turf_decal/siding/wood, +/turf/open/floor/iron/showroomfloor, +/area/shuttle/escape) +"hA" = ( +/turf/open/floor/iron/smooth_half/airless{ + dir = 1 + }, +/area/shuttle/escape) +"ig" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 8 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"iW" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/structure/chair/comfy/shuttle, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"ji" = ( +/obj/machinery/door/airlock/external, +/obj/effect/turf_decal/siding/blue, +/obj/effect/mapping_helpers/airlock/access/all/command/general, +/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ + cycle_id = "evac3" + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"jJ" = ( +/obj/machinery/computer/arcade/orion_trail{ + dir = 1 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"kB" = ( +/obj/machinery/door/airlock/external, +/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ + cycle_id = "evac3" + }, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"kZ" = ( +/obj/structure/table, +/obj/item/storage/medkit/fire, +/obj/item/storage/medkit/brute{ + pixel_x = 4; + pixel_y = 4 + }, +/turf/open/floor/wood/large, +/area/shuttle/escape) +"my" = ( +/obj/machinery/computer/security{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"mF" = ( +/obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, +/turf/open/floor/plating, +/area/shuttle/escape) +"mI" = ( +/obj/structure/lattice/catwalk, +/obj/structure/railing{ + dir = 8 + }, +/obj/structure/chair/plastic{ + dir = 8 + }, +/turf/template_noop, +/area/shuttle/escape) +"mO" = ( +/obj/structure/lattice/catwalk, +/obj/machinery/light/small/dim/directional/east, +/turf/template_noop, +/area/shuttle/escape) +"nm" = ( +/obj/structure/chair/stool/directional/south, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"ns" = ( +/obj/structure/table/reinforced/plastitaniumglass, +/obj/machinery/cell_charger, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"nG" = ( +/turf/open/floor/wood/large, +/area/shuttle/escape) +"nQ" = ( +/turf/closed/wall/mineral/plastitanium/nodiagonal, +/area/shuttle/escape/brig) +"oj" = ( +/obj/structure/table/reinforced/plastitaniumglass, +/obj/item/storage/fancy/donut_box, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"oO" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/smooth, +/area/shuttle/escape) +"oP" = ( +/obj/structure/window/reinforced/plasma/spawner/directional/west, +/obj/structure/table/reinforced/plastitaniumglass, +/obj/machinery/chem_dispenser/drinks{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"po" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/smooth_corner/airless, +/area/shuttle/escape) +"pE" = ( +/obj/machinery/door/window/brigdoor/left/directional/east, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"qp" = ( +/obj/machinery/recharge_station, +/obj/machinery/light/small/directional/south, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"qP" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/machinery/light/small/dim/directional/west, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"rr" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 4 + }, +/obj/machinery/status_display/evac/directional/west, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"rt" = ( +/obj/machinery/power/shuttle_engine/propulsion{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/turf/open/floor/plating/airless, +/area/shuttle/escape) +"rw" = ( +/obj/machinery/computer/crew{ + dir = 1 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"ry" = ( +/obj/structure/table, +/obj/item/trash/chips, +/turf/open/floor/iron/smooth, +/area/shuttle/escape) +"rA" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 4 + }, +/obj/machinery/vending/wallmed/directional/west, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"rL" = ( +/obj/structure/lattice/catwalk, +/obj/structure/marker_beacon/jade, +/turf/template_noop, +/area/shuttle/escape) +"sQ" = ( +/obj/structure/window/reinforced/plasma/spawner/directional/south, +/obj/structure/window/reinforced/plasma/spawner/directional/east, +/obj/structure/window/reinforced/plasma/spawner/directional/west, +/obj/structure/window/reinforced/plasma/spawner/directional/north, +/obj/structure/bonfire/prelit, +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 8 + }, +/turf/open/floor/iron/smooth_corner/airless{ + dir = 1 + }, +/area/shuttle/escape) +"tY" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/machinery/button/flasher{ + pixel_y = 26; + pixel_x = 26; + id = "evacflash" + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"uw" = ( +/obj/machinery/computer/atmos_control/air_tank{ + atmos_chambers = list("evacair" = "Mixed Air Supply") + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape/brig) +"uW" = ( +/obj/machinery/recharge_station, +/obj/structure/extinguisher_cabinet/directional/south, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"vk" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/smooth_half/airless, +/area/shuttle/escape) +"vn" = ( +/obj/structure/window/reinforced/spawner/directional/south, +/obj/machinery/power/shuttle_engine/heater{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/turf/open/floor/plating/airless, +/area/shuttle/escape/brig) +"vw" = ( +/turf/template_noop, +/area/template_noop) +"vF" = ( +/obj/structure/chair/stool/bar/directional/south{ + can_buckle = 1 + }, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"vO" = ( +/obj/structure/closet, +/obj/item/multitool, +/obj/effect/spawner/random/engineering/toolbox, +/obj/machinery/light/small/directional/north, +/obj/effect/spawner/random/contraband/narcotics, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"vR" = ( +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"wf" = ( +/obj/machinery/door/airlock/mining/glass{ + name = "Emergency Shuttle Cargo Hold" + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"xb" = ( +/obj/structure/sign/departments/medbay/alt, +/turf/closed/wall/mineral/plastitanium/nodiagonal, +/area/shuttle/escape/brig) +"xy" = ( +/obj/machinery/suit_storage_unit/standard_unit, +/obj/machinery/light/small/dim/directional/east, +/obj/effect/turf_decal/stripes/line, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"yc" = ( +/obj/structure/statue/gold/hos, +/obj/machinery/light/floor, +/turf/open/floor/mineral/diamond, +/area/shuttle/escape) +"yh" = ( +/obj/structure/lattice/catwalk, +/obj/structure/railing{ + dir = 8 + }, +/obj/structure/table, +/obj/item/binoculars, +/turf/template_noop, +/area/shuttle/escape) +"yH" = ( +/obj/structure/chair/sofa/middle{ + dir = 4 + }, +/obj/machinery/light/directional/west, +/turf/open/floor/iron/smooth, +/area/shuttle/escape) +"yN" = ( +/obj/machinery/door/airlock/grunge{ + name = "Emergency Shuttle Airlock" + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"zn" = ( +/obj/structure/table, +/obj/item/surgery_tray/full, +/turf/open/floor/iron/showroomfloor, +/area/shuttle/escape) +"zr" = ( +/obj/structure/railing{ + dir = 4 + }, +/turf/open/floor/iron/smooth_half/airless{ + dir = 1 + }, +/area/shuttle/escape) +"zG" = ( +/obj/machinery/chem_master, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"zH" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/on, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"zZ" = ( +/obj/structure/table/reinforced/plastitaniumglass, +/obj/machinery/recharger, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"Af" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 4 + }, +/obj/machinery/light/dim/directional/west, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"Ax" = ( +/obj/structure/railing/corner, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/smooth_half/airless{ + dir = 4 + }, +/area/shuttle/escape) +"AA" = ( +/obj/machinery/suit_storage_unit/standard_unit, +/obj/effect/turf_decal/stripes/line, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"Bu" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 8 + }, +/obj/structure/extinguisher_cabinet/directional/east, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"Ch" = ( +/obj/structure/chair/sofa/left{ + dir = 4 + }, +/turf/open/floor/iron/smooth, +/area/shuttle/escape) +"Cl" = ( +/obj/machinery/door/airlock/medical/glass{ + name = "Medbay" + }, +/obj/effect/turf_decal/siding/wood, +/turf/open/floor/wood/large, +/area/shuttle/escape) +"CA" = ( +/obj/effect/spawner/random/structure/crate_loot, +/obj/effect/spawner/random/maintenance/seven, +/obj/item/reagent_containers/pill/maintenance, +/obj/item/reagent_containers/pill/maintenance, +/obj/item/reagent_containers/pill/maintenance, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"CH" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 8 + }, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"CW" = ( +/obj/structure/railing{ + dir = 4 + }, +/obj/structure/table, +/obj/item/binoculars, +/turf/open/floor/iron/smooth_corner/airless{ + dir = 1 + }, +/area/shuttle/escape) +"Df" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 1 + }, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"Dj" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/high_volume/siphon/monitored/air_output{ + dir = 1; + chamber_id = "evacair" + }, +/obj/machinery/light/small/directional/east, +/turf/open/floor/engine/air, +/area/shuttle/escape/brig) +"Ef" = ( +/obj/machinery/smartfridge/chemistry/preloaded, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"EJ" = ( +/obj/effect/turf_decal/siding/wood, +/obj/structure/extinguisher_cabinet/directional/west, +/turf/open/floor/iron/showroomfloor, +/area/shuttle/escape) +"EO" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"Fw" = ( +/obj/machinery/door/airlock/external/glass, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ + cycle_id = "evac3" + }, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"FG" = ( +/obj/machinery/door/airlock/security/glass{ + name = "Escape Shuttle Brig" + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/effect/mapping_helpers/airlock/access/all/security/general, +/obj/effect/turf_decal/stripes/line, +/obj/effect/mapping_helpers/airlock/cyclelink_helper{ + dir = 1 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape/brig) +"FZ" = ( +/obj/structure/railing/corner, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/smooth_half/airless{ + dir = 1 + }, +/area/shuttle/escape) +"GR" = ( +/turf/open/floor/iron/smooth, +/area/shuttle/escape) +"IB" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/mineral/diamond, +/area/shuttle/escape) +"JB" = ( +/obj/machinery/suit_storage_unit/standard_unit, +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"JW" = ( +/obj/machinery/light/directional/north, +/obj/machinery/computer/operating, +/turf/open/floor/iron/showroomfloor, +/area/shuttle/escape) +"Kf" = ( +/obj/structure/table/reinforced/plastitaniumglass, +/obj/item/storage/box/drinkingglasses, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"Ko" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/machinery/airalarm/directional/east, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"Kv" = ( +/obj/structure/tank_dispenser/oxygen, +/obj/machinery/light/small/directional/west, +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"KP" = ( +/obj/structure/window/reinforced/plasma/spawner/directional/west, +/obj/structure/table/reinforced/plastitaniumglass, +/obj/machinery/reagentgrinder, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"KX" = ( +/obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, +/turf/open/floor/plating, +/area/shuttle/escape/brig) +"Lk" = ( +/obj/structure/statue/diamond/ai2, +/obj/machinery/light/floor, +/turf/open/floor/mineral/diamond, +/area/shuttle/escape) +"Lp" = ( +/obj/structure/closet/crate/secure/loot, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"LB" = ( +/obj/machinery/door/airlock/command{ + name = "Cockpit" + }, +/obj/effect/mapping_helpers/airlock/access/all/command/general, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/effect/turf_decal/siding/dark{ + dir = 1 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"LC" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"LN" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"LY" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/machinery/flasher/directional/east{ + id = "evacflash" + }, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"MP" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/machinery/status_display/evac/directional/west, +/turf/open/floor/wood/large, +/area/shuttle/escape) +"MU" = ( +/obj/machinery/door/airlock/external, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ + cycle_id = "evac3" + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"MY" = ( +/obj/structure/railing/corner/end/flip, +/obj/structure/railing/corner/end{ + dir = 1 + }, +/turf/open/floor/iron/smooth_half/airless{ + dir = 1 + }, +/area/shuttle/escape) +"NR" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape/brig) +"Oj" = ( +/turf/closed/wall/mineral/plastitanium, +/area/shuttle/escape) +"Oo" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 8 + }, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"Oq" = ( +/obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/visible, +/turf/open/floor/plating, +/area/shuttle/escape/brig) +"Ot" = ( +/obj/machinery/door/airlock/external, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ + cycle_id = "evac3" + }, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"OO" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/wood/large, +/area/shuttle/escape) +"Pt" = ( +/obj/machinery/stasis, +/turf/open/floor/iron/showroomfloor, +/area/shuttle/escape) +"PS" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 8 + }, +/turf/open/floor/wood/large, +/area/shuttle/escape) +"PX" = ( +/obj/structure/chair/comfy/shuttle, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"Qe" = ( +/obj/machinery/light/directional/north, +/obj/machinery/atmospherics/components/unary/vent_pump/on, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"Sl" = ( +/turf/closed/wall/mineral/plastitanium/nodiagonal, +/area/shuttle/escape) +"Sx" = ( +/obj/structure/table/optable, +/turf/open/floor/iron/showroomfloor, +/area/shuttle/escape) +"SC" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/structure/extinguisher_cabinet/directional/west, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"SJ" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/machinery/light/directional/west, +/obj/machinery/vending/cigarette, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"Ta" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 4 + }, +/turf/open/floor/mineral/diamond, +/area/shuttle/escape) +"Te" = ( +/turf/closed/wall/mineral/plastitanium, +/area/shuttle/escape/brig) +"Ty" = ( +/obj/machinery/door/airlock/grunge{ + name = "Emergency Shuttle Airlock" + }, +/obj/docking_port/mobile/emergency, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"UR" = ( +/obj/machinery/status_display/evac/directional/north, +/turf/open/floor/iron/smooth, +/area/shuttle/escape) +"VH" = ( +/obj/structure/railing, +/turf/open/floor/iron/smooth_corner/airless{ + dir = 1 + }, +/area/shuttle/escape) +"VL" = ( +/obj/machinery/door/airlock/external, +/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ + cycle_id = "evac3" + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"WG" = ( +/obj/structure/chair/comfy/shuttle, +/obj/item/restraints/handcuffs, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape/brig) +"Xh" = ( +/obj/structure/window/reinforced/plasma/spawner/directional/west, +/obj/structure/table/reinforced/plastitaniumglass, +/obj/machinery/chem_dispenser/drinks/beer{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"Xi" = ( +/obj/structure/window/reinforced/plasma/spawner/directional/east, +/obj/machinery/light/directional/south, +/obj/structure/closet/cabinet, +/obj/item/reagent_containers/cup/glass/shaker, +/obj/item/storage/fancy/cigarettes/cigars/havana, +/obj/item/instrument/guitar, +/turf/open/floor/mineral/plastitanium, +/area/shuttle/escape) +"Xv" = ( +/obj/machinery/door/airlock/external/glass, +/turf/open/floor/plating, +/area/shuttle/escape) +"XQ" = ( +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"Ys" = ( +/obj/machinery/computer/communications{ + dir = 1 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"YF" = ( +/obj/machinery/air_sensor/air_tank{ + chamber_id = "evacair" + }, +/turf/open/floor/engine/air, +/area/shuttle/escape/brig) +"Zs" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/structure/extinguisher_cabinet/directional/east, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"ZG" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) +"ZR" = ( +/obj/machinery/light/directional/west, +/obj/structure/chair/comfy/shuttle{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/shuttle/escape) + +(1,1,1) = {" +vw +Te +Te +Te +Te +KX +nQ +yN +Sl +Ty +Sl +mI +yh +Sl +Oj +yN +Sl +yN +Sl +Sl +Oj +vw +vw +vw +vw +vw +"} +(2,1,1) = {" +dF +vn +WG +NR +KX +YF +KX +XQ +Oj +XQ +mF +hp +hp +mF +SJ +LN +ez +LN +at +qp +Sl +vw +vw +vw +vw +vw +"} +(3,1,1) = {" +dF +vn +WG +ao +Oq +Dj +KX +XQ +ZR +XQ +mF +hp +mO +mF +iW +XQ +Sl +ns +XQ +uW +Sl +vw +vw +vw +vw +vw +"} +(4,1,1) = {" +Te +nQ +hk +hd +Te +Te +nQ +ZG +ZG +ZG +Sl +Xv +Oj +Sl +bS +ZG +Sl +Xh +oP +KP +Sl +Sl +mF +mF +mF +vw +"} +(5,1,1) = {" +dF +vn +uw +ao +gL +bL +FG +EO +vR +vR +rr +vR +Af +rA +EO +vF +dP +vR +vR +Kf +mF +oj +my +zZ +mF +mF +"} +(6,1,1) = {" +dF +vn +nQ +Te +Te +Te +xb +EO +EO +EO +EO +EO +EO +vR +EO +vF +dP +zH +EO +zG +mF +XQ +ig +XQ +rw +mF +"} +(7,1,1) = {" +rt +eK +Pt +EJ +MP +OO +Cl +EO +CH +vR +vR +vR +EO +EO +EO +Df +Sl +gW +pE +Xi +Sl +Qe +LN +PX +fu +mF +"} +(8,1,1) = {" +rt +eK +Pt +hs +OO +nG +mF +vR +vR +Oo +Bu +Oo +EO +Oo +Ko +EO +EO +eW +EO +LY +LB +tY +Zs +XQ +Ys +mF +"} +(9,1,1) = {" +Oj +Sl +JW +hs +PS +nG +Ef +bT +LC +Sl +Sl +Sl +wf +Sl +Sl +Oj +Oj +Oj +Oj +Oj +Sl +mF +Sl +mF +mF +mF +"} +(10,1,1) = {" +rt +eK +Sx +hs +nG +ed +Sl +nm +jJ +Sl +CA +SC +EO +Sl +ry +Ch +yH +dl +GR +cK +VL +XQ +ji +Ta +Lk +mF +"} +(11,1,1) = {" +rt +eK +zn +hs +he +kZ +Oj +Sl +Sl +Oj +vO +EO +Lp +Oj +UR +GR +oO +oO +oO +vk +MU +LN +df +IB +yc +mF +"} +(12,1,1) = {" +vw +Oj +Oj +Oj +Oj +Oj +Oj +JB +Kv +Oj +Oj +wf +Oj +Sl +GR +po +FZ +zr +MY +CW +Sl +mF +Sl +mF +mF +mF +"} +(13,1,1) = {" +vw +vw +vw +vw +rt +eK +AA +zH +EO +Fw +qP +EO +qP +Ot +oO +Ax +sQ +vw +rL +vw +vw +vw +vw +vw +vw +vw +"} +(14,1,1) = {" +vw +vw +vw +vw +rt +eK +xy +vR +vR +eU +vR +CH +vR +kB +hA +VH +vw +vw +hp +vw +vw +vw +vw +vw +vw +vw +"} +(15,1,1) = {" +vw +vw +vw +vw +vw +Oj +Sl +kB +kB +Sl +mF +mF +mF +Sl +Oj +mF +vw +vw +rL +vw +vw +vw +vw +vw +vw +vw +"} +(16,1,1) = {" +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +hp +vw +vw +vw +vw +vw +vw +vw +"} +(17,1,1) = {" +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +vw +rL +vw +vw +vw +vw +vw +vw +vw +"} diff --git a/_maps/shuttles/pirate_ex_interdyne.dmm b/_maps/shuttles/pirate_ex_interdyne.dmm new file mode 100644 index 00000000000..5d2149c049f --- /dev/null +++ b/_maps/shuttles/pirate_ex_interdyne.dmm @@ -0,0 +1,1155 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"aa" = ( +/obj/machinery/door/poddoor/shutters/preopen{ + dir = 8; + id = "interdynebridge" + }, +/obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, +/turf/open/floor/plating, +/area/shuttle/pirate) +"ab" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 8 + }, +/obj/machinery/turretid{ + icon_state = "control_kill"; + lethal = 1; + locked = 0; + pixel_y = -24; + req_access = null + }, +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 8 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ad" = ( +/obj/machinery/button/door/directional/south{ + id = "interdynebridge"; + name = "Bridge Bolt Control"; + specialfunctions = 4 + }, +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted, +/obj/structure/chair/comfy/shuttle{ + dir = 4 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ae" = ( +/obj/machinery/shuttle_scrambler, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"af" = ( +/turf/template_noop, +/area/template_noop) +"ag" = ( +/obj/machinery/door/airlock/hatch{ + id_tag = "piratebridgebolt"; + name = "Bridge"; + req_access = null + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/effect/mapping_helpers/airlock/cutaiwire, +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/tile/dark_blue/fourcorners, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ah" = ( +/obj/machinery/computer/shuttle/pirate, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aj" = ( +/turf/closed/wall/mineral/plastitanium/nodiagonal, +/area/shuttle/pirate) +"ak" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 8 + }, +/obj/structure/table/reinforced/plastitaniumglass, +/obj/machinery/reagentgrinder/constructed, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"al" = ( +/obj/machinery/loot_locator{ + dir = 8 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"an" = ( +/obj/effect/turf_decal/tile/dark_blue{ + dir = 8 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ao" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 1 + }, +/obj/machinery/light/floor, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ap" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 1 + }, +/obj/machinery/chem_master, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ar" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted, +/obj/item/stack/sheet/mineral/plasma/five, +/obj/item/stack/sheet/mineral/plasma/five, +/obj/structure/table/reinforced/plastitaniumglass, +/obj/item/storage/box/beakers/variety, +/obj/item/storage/box/beakers/variety, +/obj/item/storage/box/beakers/variety, +/obj/item/stack/sheet/iron/fifty, +/obj/item/stack/cable_coil, +/obj/item/stack/cable_coil, +/obj/item/screwdriver/nuke, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"av" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 4 + }, +/obj/machinery/door/window/brigdoor/right/directional/west{ + req_access = list("syndicate") + }, +/obj/structure/bed, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aw" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 8 + }, +/obj/machinery/chem_heater, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ax" = ( +/obj/structure/chair/office{ + dir = 1 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ay" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 4 + }, +/obj/structure/extinguisher_cabinet/directional/east, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"az" = ( +/obj/effect/turf_decal/tile/dark_blue, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aC" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted, +/obj/structure/extinguisher_cabinet/directional/north, +/obj/effect/mob_spawn/ghost_role/human/pirate/interdyne/junior{ + dir = 8 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aD" = ( +/obj/machinery/power/apc/auto_name/directional/north{ + cell_type = /obj/item/stock_parts/cell/bluespace; + start_charge = 100 + }, +/obj/effect/mapping_helpers/apc/cut_AI_wire, +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 1 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/machinery/power/smes/engineering{ + charge = 1e+600 + }, +/obj/structure/cable, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aF" = ( +/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate/pirate{ + dir = 4; + x_offset = -3; + y_offset = 7 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aG" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 8 + }, +/obj/machinery/piratepad, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/machinery/power/terminal{ + dir = 1 + }, +/obj/structure/cable, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aH" = ( +/obj/machinery/computer/crew/syndie{ + dir = 8 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aI" = ( +/obj/effect/turf_decal/tile/dark_blue/opposingcorners, +/obj/machinery/portable_atmospherics/canister/healium, +/obj/machinery/atmospherics/components/unary/portables_connector/visible, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aJ" = ( +/obj/effect/turf_decal/tile/dark_blue/opposingcorners, +/obj/machinery/computer/operating, +/obj/machinery/defibrillator_mount/charging{ + pixel_y = 28 + }, +/obj/item/disk/surgery/brainwashing{ + name = "Interdyne Brainwashing Protocol Surgery Disk"; + pixel_x = -8 + }, +/obj/item/disk/surgery/sleeper_protocol{ + name = "Interdyne Sleeper Protocol Surgery Disk"; + pixel_x = 1 + }, +/obj/item/disk/surgery/forgottenship{ + name = "Interdyne Advanced Surgery Procedures Disk"; + pixel_x = 9 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aK" = ( +/obj/machinery/door/airlock/external/glass/ruin{ + cyclelinkeddir = 666666; + id_tag = "pirateportexternal"; + req_access = null + }, +/obj/effect/mapping_helpers/airlock/cyclelink_helper{ + dir = 1 + }, +/obj/docking_port/mobile/pirate{ + launch_status = 0; + movement_force = list("KNOCKDOWN"=0,"THROW"=0); + name = "Pirate Ship"; + port_direction = 2 + }, +/obj/docking_port/stationary{ + dwidth = 11; + height = 100; + name = "Deep Space"; + shuttle_id = "pirate_home"; + width = 100 + }, +/obj/effect/mapping_helpers/airlock/cutaiwire, +/obj/effect/turf_decal/tile/dark_blue/fourcorners, +/obj/structure/fans/tiny, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aL" = ( +/obj/machinery/door/airlock/external/glass/ruin{ + cyclelinkeddir = 666666; + id_tag = "pirateportexternal"; + req_access = null + }, +/obj/effect/mapping_helpers/airlock/cyclelink_helper, +/obj/effect/turf_decal/tile/dark_blue/fourcorners, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/structure/fans/tiny, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aM" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 1 + }, +/obj/structure/table/reinforced/plastitaniumglass, +/obj/item/reagent_containers/cup/beaker/noreact, +/obj/item/reagent_containers/cup/beaker/noreact, +/obj/item/storage/box/syringes, +/obj/machinery/light/no_nightlight/directional/north, +/obj/item/storage/box/syringes, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aO" = ( +/obj/machinery/porta_turret/syndicate/energy/heavy{ + faction = list("pirate","Syndicate") + }, +/turf/closed/wall/mineral/plastitanium/nodiagonal, +/area/shuttle/pirate) +"aQ" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 4 + }, +/obj/machinery/chem_dispenser/fullupgrade, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aR" = ( +/obj/effect/turf_decal/tile/dark_blue/opposingcorners, +/obj/machinery/atmospherics/pipe/smart/manifold4w/dark/visible, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aS" = ( +/obj/effect/turf_decal/tile/dark_blue/opposingcorners, +/obj/structure/closet/crate/freezer/blood, +/obj/machinery/light/small/blacklight/directional/south, +/obj/machinery/iv_drip, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"aW" = ( +/obj/effect/turf_decal/tile/dark_blue/opposingcorners, +/obj/machinery/cryo_cell, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"be" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 4 + }, +/obj/machinery/computer/pandemic, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bf" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 8 + }, +/obj/structure/table/reinforced/plastitaniumglass, +/obj/item/storage/box/syndie_kit/chemical, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bk" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 4 + }, +/obj/machinery/suit_storage_unit/interdyne, +/obj/item/screwdriver/nuke, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bl" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted, +/obj/structure/table/reinforced/plastitaniumglass, +/obj/item/storage/box/syndie_kit/tuberculosisgrenade, +/obj/machinery/light/small/blacklight/directional/south, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bm" = ( +/obj/effect/turf_decal/tile/dark_blue/opposingcorners, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bo" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted, +/obj/structure/table/reinforced/plastitaniumglass, +/obj/item/gun/medbeam, +/obj/item/gun/syringe/rapidsyringe, +/obj/item/pen/sleepy{ + name = "Interdyne Chem Pen" + }, +/obj/item/defibrillator/compact/combat/loaded, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"br" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted, +/obj/machinery/suit_storage_unit/interdyne, +/obj/item/screwdriver/nuke, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bu" = ( +/obj/structure/window/reinforced, +/obj/item/storage/box/handcuffs, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bv" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 4 + }, +/obj/structure/window/reinforced, +/obj/structure/window/reinforced/unanchored/spawner/directional/north, +/obj/structure/bed, +/obj/machinery/door/window/brigdoor/right/directional/west{ + req_access = list("syndicate") + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bx" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 1 + }, +/obj/effect/turf_decal/tile/dark_blue, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bA" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bB" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 1 + }, +/obj/effect/turf_decal/tile/dark_blue{ + dir = 8 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bC" = ( +/obj/effect/turf_decal/tile/dark_blue{ + dir = 1 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bF" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 8 + }, +/obj/structure/table/reinforced/plastitaniumglass, +/obj/item/reagent_containers/cup/beaker/meta, +/obj/item/reagent_containers/cup/beaker/meta, +/obj/item/reagent_containers/cup/beaker/meta, +/obj/item/reagent_containers/cup/beaker/meta, +/obj/item/reagent_containers/dropper, +/obj/item/reagent_containers/dropper, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bH" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted, +/obj/machinery/vending/drugs{ + name = "\improper SyndicateDrug Plus" + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bK" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted, +/obj/machinery/smartfridge/chemistry/virology/preloaded, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bQ" = ( +/obj/machinery/airalarm/directional/north, +/obj/effect/mapping_helpers/airalarm/all_access, +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 4 + }, +/obj/machinery/suit_storage_unit/interdyne, +/obj/item/screwdriver/nuke, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"bX" = ( +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"dV" = ( +/obj/machinery/door/airlock/hatch{ + req_access = null + }, +/obj/effect/turf_decal/tile/dark_blue/opposingcorners, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ek" = ( +/obj/machinery/door/airlock/hatch{ + req_access = null + }, +/obj/effect/turf_decal/tile/dark_blue/opposingcorners, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ep" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 8 + }, +/obj/machinery/computer/piratepad_control{ + dir = 1 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ey" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 8 + }, +/obj/effect/mob_spawn/ghost_role/human/pirate/interdyne/junior{ + dir = 4 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"eE" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 8 + }, +/obj/machinery/door/window/brigdoor/right/directional/south{ + req_access = list("syndicate") + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"fO" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 4 + }, +/mob/living/simple_animal/bot/medbot/nukie{ + name = "Dr. Pax"; + desc = "A twitchy medibot. It can't seem to hold still. Slightly concerning." + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"fY" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 1 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"gY" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 1 + }, +/obj/structure/window/reinforced, +/obj/structure/bed{ + dir = 1 + }, +/obj/machinery/door/window/brigdoor/left/directional/east{ + req_access = list("syndicate") + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ic" = ( +/obj/machinery/power/shuttle_engine/heater{ + dir = 8 + }, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 1 + }, +/obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, +/obj/machinery/power/shuttle_engine/heater, +/turf/open/floor/plating/airless, +/area/shuttle/pirate) +"iD" = ( +/obj/machinery/light/small/directional/south, +/obj/machinery/button/door/directional/south{ + id = "pirateportexternal"; + name = "External Bolt Control"; + normaldoorcontrol = 1; + specialfunctions = 4 + }, +/obj/effect/turf_decal/tile/dark_blue/half/contrasted, +/obj/effect/mob_spawn/ghost_role/human/pirate/interdyne/senior{ + dir = 1 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"jv" = ( +/obj/effect/turf_decal/tile/dark_blue/opposingcorners, +/obj/machinery/atmospherics/components/unary/vent_pump/on, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"jY" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"lW" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 1 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"mD" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted, +/turf/closed/wall/mineral/plastitanium/nodiagonal, +/area/shuttle/pirate) +"mU" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ne" = ( +/obj/machinery/door/poddoor/shutters/preopen{ + id = "interdynebridge" + }, +/obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, +/turf/open/floor/plating, +/area/shuttle/pirate) +"qy" = ( +/obj/machinery/power/shuttle_engine/propulsion{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 5 + }, +/turf/open/floor/plating/airless, +/area/shuttle/pirate) +"sP" = ( +/obj/effect/turf_decal/tile/dark_blue/opposingcorners, +/obj/structure/table/optable, +/obj/item/defibrillator/loaded, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ua" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"vz" = ( +/obj/machinery/door/poddoor/shutters/preopen{ + dir = 1; + id = "interdynebridge" + }, +/obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, +/turf/open/floor/plating, +/area/shuttle/pirate) +"vB" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 4 + }, +/obj/structure/table/reinforced/plastitaniumglass, +/obj/item/scalpel/advanced, +/obj/item/scalpel/advanced, +/obj/item/scalpel/advanced, +/obj/item/retractor/advanced, +/obj/item/retractor/advanced, +/obj/item/retractor/advanced, +/obj/item/cautery/advanced, +/obj/item/cautery/advanced, +/obj/item/cautery/advanced, +/obj/item/surgical_drapes, +/obj/item/surgical_drapes, +/obj/item/surgical_drapes, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"wf" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted, +/obj/structure/table/reinforced/plastitaniumglass, +/obj/item/stack/sheet/mineral/plasma, +/obj/item/stack/sheet/mineral/plasma, +/obj/item/stack/sheet/mineral/plasma, +/obj/machinery/reagentgrinder/constructed, +/obj/item/storage/box/monkeycubes/syndicate, +/obj/item/storage/box/monkeycubes/syndicate, +/obj/item/reagent_containers/cup/bottle, +/obj/item/reagent_containers/cup/bottle, +/obj/item/reagent_containers/cup/bottle, +/obj/item/reagent_containers/cup/beaker/meta, +/obj/item/stack/sheet/mineral/plasma/thirty, +/obj/item/stack/sheet/mineral/uranium/five, +/obj/item/stack/sheet/mineral/uranium/five, +/obj/item/stack/sheet/mineral/uranium/five, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/stack/sheet/mineral/gold, +/obj/item/reagent_containers/condiment/milk, +/obj/item/reagent_containers/condiment/milk, +/obj/item/reagent_containers/condiment/milk, +/obj/item/reagent_containers/condiment/milk, +/obj/item/reagent_containers/condiment/milk, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"yi" = ( +/obj/machinery/door/airlock/hatch{ + id_tag = "piratebridgebolt"; + name = "Bridge"; + req_access = null + }, +/obj/effect/turf_decal/tile/dark_blue/fourcorners, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"BT" = ( +/obj/machinery/power/shuttle_engine/propulsion{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 10 + }, +/turf/open/floor/plating/airless, +/area/shuttle/pirate) +"Cb" = ( +/obj/machinery/power/shuttle_engine/propulsion, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 10 + }, +/turf/open/floor/plating/airless, +/area/shuttle/pirate) +"DD" = ( +/obj/machinery/door/poddoor/shutters/preopen{ + dir = 4; + id = "interdynebridge" + }, +/obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, +/turf/open/floor/plating, +/area/shuttle/pirate) +"En" = ( +/obj/effect/turf_decal/tile/dark_blue/opposingcorners, +/obj/machinery/atmospherics/pipe/smart/manifold4w/dark/visible, +/obj/machinery/light/small/blacklight/directional/south, +/obj/item/reagent_containers/cup/beaker/meta/salbutamol{ + list_reagents = list(/datum/reagent/medicine/c2/convermol=180) + }, +/obj/item/wrench/medical, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"ED" = ( +/obj/machinery/power/shuttle_engine/heater{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 1 + }, +/obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, +/turf/open/floor/plating/airless, +/area/shuttle/pirate) +"GS" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted, +/obj/machinery/atmospherics/components/unary/vent_pump/on, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"Hg" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted, +/obj/structure/closet/l3closet/virology, +/obj/structure/extinguisher_cabinet/directional/south, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"Iv" = ( +/obj/machinery/power/shuttle_engine/propulsion{ + dir = 4 + }, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 5 + }, +/turf/open/floor/plating/airless, +/area/shuttle/pirate) +"JT" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 8 + }, +/obj/structure/table/reinforced/plastitaniumglass, +/obj/item/reagent_containers/spray/chemsprayer/bioterror, +/obj/item/reagent_containers/spray/chemsprayer/bioterror, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"Ks" = ( +/obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, +/obj/machinery/door/poddoor/shutters/preopen{ + dir = 8; + id = "interdynebridge" + }, +/turf/open/floor/plating, +/area/shuttle/pirate) +"Oe" = ( +/obj/effect/turf_decal/tile/dark_blue{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"OL" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted, +/obj/machinery/vending/medical/syndicate_access, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"Po" = ( +/obj/machinery/power/shuttle_engine/propulsion{ + dir = 8 + }, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 10 + }, +/turf/open/floor/plating/airless, +/area/shuttle/pirate) +"Rq" = ( +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 1 + }, +/obj/machinery/light/no_nightlight/directional/north, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"SD" = ( +/obj/machinery/door/airlock/hatch{ + req_access = null + }, +/obj/effect/turf_decal/tile/dark_blue/fourcorners, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"Tr" = ( +/obj/machinery/porta_turret/syndicate/energy/heavy{ + faction = list("pirate","Syndicate"); + max_integrity = 10000 + }, +/turf/closed/wall/mineral/plastitanium/nodiagonal, +/area/shuttle/pirate) +"Ur" = ( +/obj/effect/turf_decal/tile/dark_blue/anticorner/contrasted{ + dir = 8 + }, +/obj/machinery/atmospherics/components/tank/air, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"UM" = ( +/obj/machinery/power/shuttle_engine/propulsion, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 5 + }, +/turf/open/floor/plating/airless, +/area/shuttle/pirate) +"VF" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 8 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"Wp" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 1 + }, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) +"Xk" = ( +/obj/effect/turf_decal/tile/dark_blue/opposingcorners, +/obj/machinery/light/small/directional/west, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/obj/structure/mirror/directional/east, +/turf/open/floor/iron/dark, +/area/shuttle/pirate) + +(1,1,1) = {" +af +af +af +af +af +aO +aa +aa +Ks +aa +aO +af +af +af +af +af +"} +(2,1,1) = {" +af +af +af +af +af +aj +ap +aw +ak +bF +aj +af +af +af +af +af +"} +(3,1,1) = {" +af +af +af +af +af +aj +aM +ax +fO +ar +aj +af +af +af +af +af +"} +(4,1,1) = {" +af +af +af +af +Po +aj +aQ +ay +Oe +OL +aj +af +af +af +af +af +"} +(5,1,1) = {" +af +af +af +BT +ED +aj +aj +aj +bx +bH +aj +Po +af +af +af +af +"} +(6,1,1) = {" +af +aO +Ks +aj +aj +aI +En +aj +yi +aj +aj +ic +Cb +af +af +af +"} +(7,1,1) = {" +aO +aj +ey +aF +aj +aW +aR +ek +lW +JT +bf +aj +aj +aa +aj +Tr +"} +(8,1,1) = {" +vz +ae +an +ab +aj +aj +aj +aj +fY +bX +iD +aj +aD +aG +ep +aj +"} +(9,1,1) = {" +vz +ah +ao +GS +ag +bm +Xk +bm +fY +ua +jY +aL +fY +Wp +mU +aK +"} +(10,1,1) = {" +vz +al +az +ad +aj +aj +aj +aj +fY +VF +bl +aj +bQ +bk +br +aj +"} +(11,1,1) = {" +aO +aj +aC +aH +aj +sP +jv +dV +bA +vB +bo +aj +aj +DD +aj +Tr +"} +(12,1,1) = {" +af +aO +ne +aj +aj +aJ +aS +aj +SD +aj +aj +ED +UM +af +af +af +"} +(13,1,1) = {" +af +af +af +qy +ED +aj +aj +mD +bB +Ur +aj +Iv +af +af +af +af +"} +(14,1,1) = {" +af +af +af +af +Iv +aj +gY +eE +bC +Hg +aj +af +af +af +af +af +"} +(15,1,1) = {" +af +af +af +af +af +aj +Rq +bu +VF +wf +aj +af +af +af +af +af +"} +(16,1,1) = {" +af +af +af +af +af +aj +av +bv +be +bK +aj +af +af +af +af +af +"} +(17,1,1) = {" +af +af +af +af +af +aO +DD +mD +DD +DD +aO +af +af +af +af +af +"} diff --git a/_maps/skyrat/automapper/templates/birdshot/birdshot_supermatter.dmm b/_maps/skyrat/automapper/templates/birdshot/birdshot_supermatter.dmm new file mode 100644 index 00000000000..fc0d26771fb --- /dev/null +++ b/_maps/skyrat/automapper/templates/birdshot/birdshot_supermatter.dmm @@ -0,0 +1,71 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/template_noop, +/area/template_noop) +"b" = ( +/obj/effect/turf_decal/stripes/red/box, +/obj/machinery/atmospherics/pipe/smart/simple/cyan/visible{ + dir = 8 + }, +/turf/open/floor/iron/smooth, +/area/station/engineering/supermatter/room) +"j" = ( +/obj/machinery/airalarm/directional/south, +/turf/template_noop, +/area/template_noop) +"v" = ( +/obj/structure/sign/delam_procedure, +/turf/template_noop, +/area/template_noop) +"B" = ( +/obj/machinery/button/delam_scram, +/turf/closed/wall, +/area/station/engineering/supermatter/room) +"D" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 10 + }, +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 4 + }, +/obj/structure/cable, +/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4, +/turf/open/floor/iron/smooth, +/area/station/engineering/supermatter/room) +"T" = ( +/obj/machinery/atmospherics/components/unary/delam_scram/directional/east, +/turf/template_noop, +/area/template_noop) + +(1,1,1) = {" +T +a +a +a +a +a +"} +(2,1,1) = {" +a +a +a +a +j +a +"} +(3,1,1) = {" +a +v +a +b +B +a +"} +(4,1,1) = {" +a +a +a +a +D +a +"} diff --git a/_maps/skyrat/automapper/templates/deltastation/deltastation_supermatter.dmm b/_maps/skyrat/automapper/templates/deltastation/deltastation_supermatter.dmm new file mode 100644 index 00000000000..3a91788fd6a --- /dev/null +++ b/_maps/skyrat/automapper/templates/deltastation/deltastation_supermatter.dmm @@ -0,0 +1,92 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/template_noop, +/area/template_noop) +"i" = ( +/obj/effect/turf_decal/stripes/red/box, +/turf/template_noop, +/area/template_noop) +"j" = ( +/obj/structure/sign/delam_procedure, +/turf/template_noop, +/area/template_noop) +"l" = ( +/obj/machinery/button/delam_scram, +/turf/closed/wall/r_wall, +/area/station/engineering/supermatter/room) +"t" = ( +/obj/machinery/atmospherics/components/unary/delam_scram{ + dir = 4 + }, +/turf/template_noop, +/area/template_noop) +"M" = ( +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/cyan/visible, +/obj/machinery/meter, +/turf/open/floor/iron, +/area/station/engineering/supermatter/room) + +(1,1,1) = {" +t +a +a +a +a +a +a +"} +(2,1,1) = {" +a +a +a +a +a +a +a +"} +(3,1,1) = {" +a +a +a +a +a +a +a +"} +(4,1,1) = {" +a +a +a +a +a +a +a +"} +(5,1,1) = {" +a +a +a +a +M +j +a +"} +(6,1,1) = {" +a +a +a +a +i +l +i +"} +(7,1,1) = {" +a +a +a +a +a +a +a +"} diff --git a/_maps/skyrat/automapper/templates/icebox/icebox_supermatter.dmm b/_maps/skyrat/automapper/templates/icebox/icebox_supermatter.dmm new file mode 100644 index 00000000000..07732061300 --- /dev/null +++ b/_maps/skyrat/automapper/templates/icebox/icebox_supermatter.dmm @@ -0,0 +1,118 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/template_noop, +/area/template_noop) +"c" = ( +/obj/machinery/button/delam_scram, +/turf/template_noop, +/area/template_noop) +"i" = ( +/obj/effect/turf_decal/stripes/red/box, +/turf/template_noop, +/area/template_noop) +"n" = ( +/obj/effect/spawner/structure/window/reinforced/plasma, +/turf/template_noop, +/area/station/engineering/supermatter/room) +"p" = ( +/obj/machinery/atmospherics/components/unary/delam_scram/directional/east, +/turf/template_noop, +/area/template_noop) +"s" = ( +/obj/structure/sign/delam_procedure, +/turf/closed/wall/r_wall, +/area/station/engineering/supermatter/room) +"y" = ( +/obj/effect/turf_decal/stripes/red/box, +/turf/open/floor/engine, +/area/station/engineering/supermatter/room) +"H" = ( +/obj/structure/table/reinforced, +/obj/item/clothing/suit/utility/radiation, +/obj/item/clothing/head/utility/radiation, +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/obj/item/geiger_counter, +/obj/item/clothing/glasses/meson, +/turf/open/floor/engine, +/area/station/engineering/supermatter/room) + +(1,1,1) = {" +a +a +a +a +a +a +a +a +a +a +p +"} +(2,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +"} +(3,1,1) = {" +i +c +y +a +a +a +a +a +a +a +a +"} +(4,1,1) = {" +a +s +H +a +a +a +a +a +a +a +a +"} +(5,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +"} +(6,1,1) = {" +a +n +a +a +a +a +a +a +a +a +a +"} diff --git a/_maps/skyrat/automapper/templates/metastation/metastation_supermatter.dmm b/_maps/skyrat/automapper/templates/metastation/metastation_supermatter.dmm new file mode 100644 index 00000000000..280a852105d --- /dev/null +++ b/_maps/skyrat/automapper/templates/metastation/metastation_supermatter.dmm @@ -0,0 +1,91 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/template_noop, +/area/template_noop) +"h" = ( +/obj/machinery/button/delam_scram, +/turf/closed/wall/r_wall, +/area/station/engineering/supermatter/room) +"x" = ( +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/effect/turf_decal/stripes/red/box, +/turf/open/floor/iron, +/area/station/engineering/main) +"C" = ( +/obj/effect/turf_decal/stripes/red/box, +/turf/open/floor/engine, +/area/station/engineering/supermatter/room) +"R" = ( +/obj/structure/sign/delam_procedure, +/turf/closed/wall/r_wall, +/area/station/engineering/supermatter/room) +"V" = ( +/obj/machinery/atmospherics/components/unary/delam_scram/directional/west, +/turf/template_noop, +/area/template_noop) + +(1,1,1) = {" +x +h +C +a +a +a +a +a +"} +(2,1,1) = {" +a +R +a +a +a +a +a +a +"} +(3,1,1) = {" +a +a +a +a +a +a +a +a +"} +(4,1,1) = {" +a +a +a +a +a +a +a +a +"} +(5,1,1) = {" +a +a +a +a +a +a +a +a +"} +(6,1,1) = {" +a +a +a +a +a +a +a +V +"} diff --git a/_maps/skyrat/automapper/templates/northstar/northstar_supermatter.dmm b/_maps/skyrat/automapper/templates/northstar/northstar_supermatter.dmm new file mode 100644 index 00000000000..a02542f376a --- /dev/null +++ b/_maps/skyrat/automapper/templates/northstar/northstar_supermatter.dmm @@ -0,0 +1,170 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/template_noop, +/area/template_noop) +"b" = ( +/obj/machinery/atmospherics/components/unary/delam_scram/directional/west, +/turf/template_noop, +/area/template_noop) +"c" = ( +/obj/effect/spawner/structure/window/reinforced/plasma, +/obj/structure/cable, +/turf/open/floor/plating, +/area/station/engineering/supermatter/room) +"e" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/machinery/incident_display/delam/directional/north, +/turf/open/floor/iron, +/area/station/engineering/lobby) +"n" = ( +/obj/machinery/button/door/directional/north{ + id = "Secure Storage"; + req_access = list("engineering") + }, +/turf/template_noop, +/area/template_noop) +"o" = ( +/obj/structure/sign/delam_procedure/directional/west, +/turf/template_noop, +/area/template_noop) +"q" = ( +/obj/effect/turf_decal/stripes/red/box, +/turf/template_noop, +/area/template_noop) +"v" = ( +/obj/structure/cable, +/turf/open/floor/iron/half{ + dir = 1 + }, +/area/station/engineering/lobby) +"y" = ( +/obj/effect/turf_decal/tile/red{ + dir = 8 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/turf/open/floor/iron, +/area/station/engineering/lobby) +"A" = ( +/obj/machinery/button/delam_scram, +/turf/closed/wall/r_wall, +/area/station/engineering/supermatter/room) +"C" = ( +/obj/effect/turf_decal/stripes/red/box, +/turf/open/floor/iron/half{ + dir = 1 + }, +/area/station/engineering/lobby) +"P" = ( +/obj/structure/sign/delam_procedure, +/turf/template_noop, +/area/template_noop) + +(1,1,1) = {" +n +a +a +a +a +a +a +"} +(2,1,1) = {" +a +a +a +a +a +a +a +"} +(3,1,1) = {" +a +a +a +a +a +a +a +"} +(4,1,1) = {" +e +a +P +v +C +a +a +"} +(5,1,1) = {" +y +a +a +c +A +a +a +"} +(6,1,1) = {" +a +a +a +a +q +o +a +"} +(7,1,1) = {" +a +a +a +a +a +a +a +"} +(8,1,1) = {" +a +a +a +a +a +a +a +"} +(9,1,1) = {" +a +a +a +a +a +a +a +"} +(10,1,1) = {" +a +a +a +a +a +a +a +"} +(11,1,1) = {" +a +a +a +a +a +a +b +"} diff --git a/_maps/skyrat/automapper/templates/tramstation/tramstation_supermatter.dmm b/_maps/skyrat/automapper/templates/tramstation/tramstation_supermatter.dmm new file mode 100644 index 00000000000..ca7e23d65df --- /dev/null +++ b/_maps/skyrat/automapper/templates/tramstation/tramstation_supermatter.dmm @@ -0,0 +1,85 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/template_noop, +/area/template_noop) +"k" = ( +/obj/effect/turf_decal/stripes/red/box, +/obj/structure/sign/delam_procedure, +/turf/closed/wall/r_wall, +/area/station/engineering/supermatter/room) +"l" = ( +/obj/machinery/power/apc/auto_name/directional/east, +/obj/structure/cable, +/obj/effect/turf_decal/stripes/red/box, +/turf/open/floor/iron, +/area/station/engineering/main) +"m" = ( +/obj/structure/table/reinforced, +/obj/item/clothing/head/utility/radiation, +/obj/item/clothing/glasses/meson, +/obj/item/geiger_counter, +/obj/item/geiger_counter, +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/turf/open/floor/engine, +/area/station/engineering/supermatter/room) +"q" = ( +/obj/effect/turf_decal/stripes/red/box, +/obj/machinery/button/delam_scram, +/turf/closed/wall/r_wall, +/area/station/engineering/supermatter/room) +"D" = ( +/obj/machinery/atmospherics/components/unary/delam_scram/directional/east, +/turf/template_noop, +/area/template_noop) +"E" = ( +/obj/structure/cable, +/obj/effect/turf_decal/stripes/red/box, +/turf/open/floor/engine, +/area/station/engineering/supermatter/room) + +(1,1,1) = {" +a +a +m +a +a +a +a +a +a +"} +(2,1,1) = {" +a +a +a +a +a +a +a +a +a +"} +(3,1,1) = {" +a +k +a +a +a +a +a +a +D +"} +(4,1,1) = {" +l +q +E +a +a +a +a +a +a +"} diff --git a/_maps/templates/lazy_templates/heretic_sacrifice.dmm b/_maps/templates/lazy_templates/heretic_sacrifice.dmm new file mode 100644 index 00000000000..cbabb4c6485 --- /dev/null +++ b/_maps/templates/lazy_templates/heretic_sacrifice.dmm @@ -0,0 +1,2026 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"ab" = ( +/turf/open/space/basic, +/area/space) +"cd" = ( +/obj/effect/decal/cleanable/ash{ + pixel_x = -8; + pixel_y = 8 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"cS" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/bonfire/prelit, +/turf/open/floor/stone, +/area/centcom/heretic_sacrifice/flesh) +"cW" = ( +/obj/structure/no_effect_signpost/void, +/turf/open/misc/asteroid, +/area/centcom/heretic_sacrifice/void) +"dX" = ( +/obj/effect/decal/cleanable/oil, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"dZ" = ( +/obj/effect/decal/fakelattice{ + density = 0 + }, +/turf/open/misc/ironsand, +/area/centcom/heretic_sacrifice/rust) +"fh" = ( +/obj/structure/cable, +/turf/open/floor/plating, +/area/centcom/heretic_sacrifice/rust) +"fL" = ( +/obj/effect/decal/cleanable/blood/old, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/indestructible/plating, +/area/centcom/heretic_sacrifice/knock) +"fO" = ( +/turf/open/indestructible, +/area/space) +"gJ" = ( +/obj/effect/decal/remains/human, +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"hE" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/item/storage/toolbox/mechanical/old, +/turf/open/indestructible/plating, +/area/centcom/heretic_sacrifice/knock) +"hZ" = ( +/obj/effect/decal/cleanable/blood/old, +/turf/open/indestructible/necropolis/air, +/area/centcom/heretic_sacrifice/flesh) +"jg" = ( +/obj/effect/decal/cleanable/blood/old, +/obj/structure/cable, +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"jt" = ( +/turf/closed/indestructible/reinforced, +/area/centcom/heretic_sacrifice/knock) +"jB" = ( +/obj/machinery/light/very_dim/directional/south, +/turf/open/misc/asteroid, +/area/centcom/heretic_sacrifice/void) +"lz" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"mb" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/remains/human, +/turf/open/misc/ironsand, +/area/centcom/heretic_sacrifice/rust) +"mG" = ( +/obj/effect/turf_decal/weather, +/obj/structure/cable, +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"mR" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/fakelattice{ + density = 0 + }, +/turf/open/floor/plating, +/area/centcom/heretic_sacrifice/rust) +"mW" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/item/spear, +/turf/open/indestructible/plating, +/area/centcom/heretic_sacrifice/knock) +"mZ" = ( +/obj/effect/decal/fakelattice{ + density = 0 + }, +/turf/open/floor/plating, +/area/centcom/heretic_sacrifice/rust) +"nG" = ( +/obj/structure/bonfire/prelit, +/turf/open/misc/dirt/jungle/wasteland{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"nL" = ( +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/block/cracked{ + dir = 10 + }, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"nP" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"oh" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"ps" = ( +/obj/effect/turf_decal/weather, +/turf/open/floor/plating, +/area/centcom/heretic_sacrifice/rust) +"pt" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/fakelattice{ + density = 0 + }, +/turf/open/misc/ironsand, +/area/centcom/heretic_sacrifice/rust) +"pN" = ( +/obj/effect/turf_decal/weather/dirt{ + dir = 9 + }, +/obj/effect/decal/cleanable/oil, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"qn" = ( +/obj/effect/decal/remains/human, +/turf/open/misc/asteroid, +/area/centcom/heretic_sacrifice/void) +"qo" = ( +/obj/effect/decal/cleanable/blood/old, +/obj/effect/decal/cleanable/dirt/dust, +/obj/item/storage/toolbox/mechanical/old, +/turf/open/indestructible/plating, +/area/centcom/heretic_sacrifice/knock) +"qu" = ( +/obj/effect/turf_decal/weather/dirt, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"qM" = ( +/obj/structure/stone_tile/block/cracked, +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/weather/dirt{ + dir = 5 + }, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"rP" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/cleanable/blood/old, +/turf/open/indestructible/necropolis/air, +/area/centcom/heretic_sacrifice/flesh) +"sb" = ( +/obj/effect/decal/cleanable/blood/old, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"tF" = ( +/obj/effect/turf_decal/weather/dirt{ + dir = 9 + }, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"ui" = ( +/turf/open/floor/stone, +/area/centcom/heretic_sacrifice/flesh) +"uu" = ( +/obj/effect/turf_decal/trimline/brown/corner{ + dir = 1 + }, +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"uM" = ( +/obj/effect/decal/cleanable/blood/old, +/obj/effect/decal/cleanable/dirt/dust, +/obj/item/clothing/under/color/grey/ancient, +/turf/open/indestructible/plating, +/area/centcom/heretic_sacrifice/knock) +"uT" = ( +/obj/structure/cable, +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"vs" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 1 + }, +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"vv" = ( +/obj/effect/turf_decal/weather/dirt{ + dir = 4 + }, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"wo" = ( +/turf/closed/indestructible/grille, +/area/centcom/heretic_sacrifice/knock) +"wt" = ( +/obj/structure/stone_tile/block{ + dir = 1 + }, +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"wE" = ( +/turf/open/misc/dirt/jungle/wasteland{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"wP" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/item/flashlight/flare{ + fuel = 1e+031; + randomize_fuel = 0; + icon_state = "flare-on"; + on = 1 + }, +/turf/open/indestructible/plating, +/area/centcom/heretic_sacrifice/knock) +"wS" = ( +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"wY" = ( +/turf/open/floor/glass{ + desc = "A peek into the other side."; + name = "void glass floor" + }, +/area/centcom/heretic_sacrifice/void) +"xc" = ( +/turf/open/indestructible/white, +/area/space) +"yC" = ( +/obj/effect/turf_decal/trimline/brown/line{ + dir = 1 + }, +/turf/open/floor/plating, +/area/centcom/heretic_sacrifice/rust) +"zb" = ( +/obj/structure/stone_tile/slab, +/obj/effect/decal/cleanable/dirt, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"zU" = ( +/obj/effect/decal/fakelattice{ + density = 0 + }, +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"Aw" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/item/clothing/mask/gas, +/turf/open/indestructible/plating, +/area/centcom/heretic_sacrifice/knock) +"AH" = ( +/obj/effect/turf_decal/trimline/brown/corner, +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"AN" = ( +/obj/machinery/light/floor, +/obj/effect/decal/fakelattice{ + density = 0 + }, +/turf/open/misc/ironsand, +/area/centcom/heretic_sacrifice/rust) +"AO" = ( +/turf/open/floor/fakespace, +/area/centcom/heretic_sacrifice/void) +"AW" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/item/clothing/mask/gas/tiki_mask/yalp_elor, +/turf/open/indestructible/plating, +/area/centcom/heretic_sacrifice/knock) +"AY" = ( +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/indestructible/plating, +/area/centcom/heretic_sacrifice/knock) +"Bv" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/trimline/brown/line, +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"Bw" = ( +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"By" = ( +/obj/effect/decal/cleanable/blood/old, +/turf/open/floor/plating, +/area/centcom/heretic_sacrifice/rust) +"Cf" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/weather, +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"Ck" = ( +/obj/effect/turf_decal/trimline/brown/corner{ + dir = 8 + }, +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"CB" = ( +/obj/effect/decal/cleanable/blood/old, +/turf/open/floor/stone, +/area/centcom/heretic_sacrifice/flesh) +"CG" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/stone, +/area/centcom/heretic_sacrifice/flesh) +"CV" = ( +/obj/effect/decal/cleanable/plasma, +/obj/effect/landmark/heretic/ash, +/turf/open/misc/dirt/jungle/wasteland{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"DL" = ( +/obj/structure/stone_tile/block/burnt{ + dir = 4 + }, +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"En" = ( +/turf/closed/indestructible/fakedoor/maintenance, +/area/centcom/heretic_sacrifice/knock) +"ER" = ( +/turf/open/misc/ironsand, +/area/centcom/heretic_sacrifice/rust) +"Fd" = ( +/turf/closed/indestructible/riveted, +/area/space) +"Gl" = ( +/obj/structure/stone_tile/block/burnt{ + dir = 1 + }, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"GX" = ( +/obj/effect/landmark/heretic/flesh, +/turf/open/indestructible/necropolis/air, +/area/centcom/heretic_sacrifice/flesh) +"HE" = ( +/obj/effect/turf_decal/weather, +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"HJ" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/gibspawner/human, +/turf/open/floor/stone, +/area/centcom/heretic_sacrifice/flesh) +"HQ" = ( +/turf/closed/indestructible/necropolis, +/area/centcom/heretic_sacrifice/flesh) +"Ie" = ( +/obj/effect/turf_decal/weather/dirt{ + dir = 1 + }, +/obj/effect/decal/cleanable/blood/old, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"Ii" = ( +/obj/effect/turf_decal/weather/dirt{ + dir = 5 + }, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"Je" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/misc/ironsand, +/area/centcom/heretic_sacrifice/rust) +"Jy" = ( +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"JJ" = ( +/obj/effect/decal/remains/human, +/turf/open/indestructible/necropolis/air, +/area/centcom/heretic_sacrifice/flesh) +"Ko" = ( +/obj/machinery/light/very_dim/directional/east, +/turf/open/floor/fakespace, +/area/centcom/heretic_sacrifice/void) +"Ku" = ( +/obj/structure/stone_tile/block{ + dir = 4 + }, +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"Kz" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"KO" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/gibspawner/generic/animal, +/obj/structure/bonfire/prelit, +/turf/open/floor/stone, +/area/centcom/heretic_sacrifice/flesh) +"KP" = ( +/obj/structure/stone_tile/surrounding_tile, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"La" = ( +/turf/closed/indestructible/riveted/plastinum, +/area/centcom/heretic_sacrifice/void) +"LA" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/trimline/brown/corner{ + dir = 4 + }, +/turf/open/floor/plating, +/area/centcom/heretic_sacrifice/rust) +"Mf" = ( +/obj/effect/decal/cleanable/ash/large, +/obj/effect/decal/cleanable/dirt, +/turf/open/misc/dirt/jungle/wasteland{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"Mw" = ( +/obj/machinery/light/very_dim/directional/west, +/turf/open/floor/fakespace, +/area/centcom/heretic_sacrifice/void) +"MZ" = ( +/obj/effect/decal/cleanable/blood/old, +/obj/effect/decal/cleanable/dirt/dust, +/obj/item/flashlight/flare{ + fuel = 1e+031; + randomize_fuel = 0; + icon_state = "flare-on"; + on = 1 + }, +/turf/open/indestructible/plating, +/area/centcom/heretic_sacrifice/knock) +"Nh" = ( +/obj/effect/turf_decal/trimline/brown/line{ + dir = 1 + }, +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"NA" = ( +/obj/structure/stone_tile/burnt, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"NQ" = ( +/obj/effect/turf_decal/weather/dirt{ + dir = 1 + }, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"OD" = ( +/obj/effect/landmark/heretic, +/turf/open/misc/dirt/jungle/wasteland{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"OG" = ( +/obj/effect/turf_decal/weather/dirt{ + dir = 10 + }, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"OW" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/item/clothing/under/color/grey/ancient, +/turf/open/indestructible/plating, +/area/centcom/heretic_sacrifice/knock) +"Pl" = ( +/obj/effect/turf_decal/weather/dirt{ + dir = 6 + }, +/turf/open/misc/ashplanet/wateryrock{ + initial_gas_mix = "o2=22;n2=82;TEMP=293.15"; + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"Qi" = ( +/obj/structure/stone_tile, +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"QL" = ( +/obj/effect/decal/cleanable/ash/large{ + pixel_x = -2; + pixel_y = -8 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"Rb" = ( +/obj/effect/landmark/heretic/void, +/turf/open/misc/asteroid, +/area/centcom/heretic_sacrifice/void) +"Rh" = ( +/obj/effect/turf_decal/trimline/brown/line{ + dir = 4 + }, +/turf/open/floor/plating, +/area/centcom/heretic_sacrifice/rust) +"RW" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/stone, +/area/centcom/heretic_sacrifice/flesh) +"Se" = ( +/turf/open/floor/plating, +/area/centcom/heretic_sacrifice/rust) +"St" = ( +/turf/open/indestructible/necropolis/air, +/area/centcom/heretic_sacrifice/flesh) +"Sy" = ( +/obj/structure/stone_tile/block, +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"Tf" = ( +/obj/effect/decal/cleanable/food/salt, +/turf/open/indestructible/necropolis/air, +/area/centcom/heretic_sacrifice/flesh) +"TC" = ( +/obj/effect/decal/cleanable/ash, +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"TS" = ( +/obj/effect/turf_decal/trimline/brown/line{ + dir = 8 + }, +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"Ue" = ( +/obj/machinery/light/very_dim/directional/north, +/turf/open/floor/fakespace, +/area/centcom/heretic_sacrifice/void) +"UO" = ( +/obj/effect/landmark/heretic/rust, +/turf/open/floor/plating, +/area/centcom/heretic_sacrifice/rust) +"Vd" = ( +/obj/structure/bonfire/prelit, +/turf/open/floor/stone, +/area/centcom/heretic_sacrifice/flesh) +"VK" = ( +/obj/effect/decal/cleanable/ash, +/obj/effect/decal/cleanable/ash{ + pixel_x = 4; + pixel_y = 4 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/misc/dirt/jungle/dark{ + planetary_atmos = 0; + slowdown = 0 + }, +/area/centcom/heretic_sacrifice/ash) +"Wb" = ( +/turf/open/misc/asteroid, +/area/centcom/heretic_sacrifice/void) +"WD" = ( +/turf/closed/indestructible/iron{ + opacity = 1 + }, +/area/centcom/heretic_sacrifice/rust) +"Xk" = ( +/obj/effect/decal/cleanable/blood/old, +/turf/open/misc/asteroid, +/area/centcom/heretic_sacrifice/void) +"Xr" = ( +/turf/open/floor/plating/rust, +/area/centcom/heretic_sacrifice/rust) +"Xt" = ( +/obj/effect/gibspawner/generic/animal, +/turf/open/floor/stone, +/area/centcom/heretic_sacrifice/flesh) +"Yp" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/indestructible/necropolis/air, +/area/centcom/heretic_sacrifice/flesh) +"Zw" = ( +/obj/effect/decal/cleanable/blood/old, +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/landmark/heretic/knock, +/turf/open/indestructible/plating, +/area/centcom/heretic_sacrifice/knock) +"ZA" = ( +/turf/closed/indestructible/riveted/boss, +/area/centcom/heretic_sacrifice/ash) + +(1,1,1) = {" +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +"} +(2,1,1) = {" +ab +ab +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +ab +"} +(3,1,1) = {" +ab +ab +Fd +jt +jt +wo +wo +jt +En +En +jt +wo +wo +jt +jt +Fd +ZA +ZA +ZA +ZA +ZA +ZA +ZA +ZA +ZA +ZA +ZA +ZA +Fd +La +La +La +La +La +La +La +La +La +La +La +La +Fd +ab +"} +(4,1,1) = {" +ab +ab +Fd +jt +AY +AY +AY +AY +AY +AY +AY +AY +fL +AY +jt +Fd +ZA +Pl +wS +Ii +Bw +Bw +Bw +Bw +Pl +wS +Ii +ZA +Fd +La +AO +AO +AO +AO +Mw +AO +AO +AO +wY +Wb +La +Fd +ab +"} +(5,1,1) = {" +ab +ab +Fd +wo +AY +fL +uM +AY +AY +qo +fL +AY +mW +MZ +wo +Fd +ZA +wS +nG +VK +sb +Bw +Bw +Bw +Qi +nG +Kz +ZA +Fd +La +AO +wY +AO +AO +AO +AO +AO +AO +AO +wY +La +Fd +ab +"} +(6,1,1) = {" +ab +ab +Fd +wo +AY +Aw +fL +AY +wP +fL +fL +AY +qo +AY +wo +Fd +ZA +OG +cd +pN +Bw +Bw +vv +Bw +OG +TC +tF +ZA +Fd +La +wY +cW +wY +AO +AO +AO +AO +AO +AO +AO +La +Fd +ab +"} +(7,1,1) = {" +ab +ab +Fd +jt +AY +fL +fL +AY +AY +fL +AY +AY +fL +Aw +jt +Fd +ZA +Bw +Bw +Bw +Pl +nP +nP +Ii +Bw +Bw +Bw +ZA +Fd +La +AO +wY +AO +AO +wY +wY +AO +AO +AO +wY +La +Fd +ab +"} +(8,1,1) = {" +ab +ab +Fd +En +AY +MZ +fL +AY +Zw +OW +AY +AY +AY +fL +En +Fd +ZA +Bw +Bw +qu +vs +wE +Mf +Sy +NQ +lz +lz +ZA +Fd +La +AO +AO +AO +wY +Wb +Rb +wY +AO +wY +jB +La +Fd +ab +"} +(9,1,1) = {" +ab +ab +Fd +En +fL +AY +AY +AY +fL +fL +AY +AY +fL +AY +En +Fd +ZA +Bw +Bw +Bw +wt +CV +OD +Sy +NQ +lz +Bw +ZA +Fd +La +Ue +AO +AO +wY +Xk +Wb +wY +AO +wY +Wb +La +Fd +ab +"} +(10,1,1) = {" +ab +ab +Fd +jt +AY +AY +fL +AY +fL +AY +fL +AY +AW +fL +jt +Fd +ZA +Bw +Bw +Bw +OG +DL +Ku +Ie +Bw +dX +Bw +ZA +Fd +La +AO +AO +AO +AO +wY +wY +AO +AO +wY +Wb +La +Fd +ab +"} +(11,1,1) = {" +ab +ab +Fd +wo +fL +AY +hE +MZ +AY +AY +hE +fL +wP +AY +wo +Fd +ZA +Pl +gJ +qM +Bw +Bw +nL +lz +Pl +Kz +Ii +ZA +Fd +La +wY +AO +AO +AO +AO +AO +AO +AO +AO +wY +La +Fd +ab +"} +(12,1,1) = {" +ab +ab +Fd +wo +AY +fL +AY +AY +AY +fL +AY +OW +fL +fL +wo +Fd +ZA +wS +nG +Sy +lz +Gl +zb +NA +QL +nG +wS +ZA +Fd +La +Wb +wY +AO +AO +AO +AO +AO +wY +AO +AO +La +Fd +ab +"} +(13,1,1) = {" +ab +ab +Fd +jt +AY +AY +fL +fL +AY +AY +AY +AY +AY +AY +jt +Fd +ZA +OG +wS +tF +Bw +Jy +KP +Bw +OG +wS +tF +ZA +Fd +La +Wb +Xk +wY +AO +AO +Ko +wY +qn +wY +AO +La +Fd +ab +"} +(14,1,1) = {" +ab +ab +Fd +jt +jt +wo +wo +jt +En +En +jt +wo +wo +jt +jt +Fd +ZA +ZA +ZA +ZA +ZA +ZA +ZA +ZA +ZA +ZA +ZA +ZA +Fd +La +La +La +La +La +La +La +La +La +La +La +La +Fd +ab +"} +(15,1,1) = {" +ab +ab +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +ab +"} +(16,1,1) = {" +ab +ab +Fd +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +Fd +WD +WD +WD +WD +WD +WD +WD +WD +WD +WD +WD +WD +Fd +HQ +HQ +HQ +HQ +HQ +HQ +HQ +HQ +HQ +HQ +HQ +HQ +Fd +ab +"} +(17,1,1) = {" +ab +ab +Fd +fO +fO +fO +fO +fO +fO +xc +xc +xc +xc +xc +fO +Fd +WD +dZ +dZ +dZ +ER +Je +fh +Xr +Xr +Xr +Xr +WD +Fd +HQ +St +ui +St +St +St +Yp +Yp +St +ui +St +HQ +Fd +ab +"} +(18,1,1) = {" +ab +ab +Fd +fO +fO +fO +fO +fO +fO +fO +fO +xc +fO +fO +fO +Fd +WD +dZ +AN +mb +HE +oh +fh +Xr +ps +AN +Xr +WD +Fd +HQ +Xt +cS +RW +ui +ui +ui +RW +Xt +Vd +ui +HQ +Fd +ab +"} +(19,1,1) = {" +ab +ab +Fd +fO +fO +fO +fO +fO +fO +xc +xc +xc +xc +xc +fO +Fd +WD +ER +mG +jg +fh +fh +uT +By +dZ +pt +ER +WD +Fd +HQ +St +CB +JJ +ui +St +St +ui +hZ +ui +St +HQ +Fd +ab +"} +(20,1,1) = {" +ab +ab +Fd +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +Fd +WD +Se +mR +zU +AH +Rh +Rh +LA +ER +ps +Je +WD +Fd +HQ +St +ui +ui +HJ +RW +RW +ui +RW +RW +St +HQ +Fd +ab +"} +(21,1,1) = {" +ab +ab +Fd +fO +fO +fO +fO +fO +fO +xc +fO +xc +xc +xc +fO +Fd +WD +Xr +mZ +Se +Bv +Xr +Xr +Nh +Xr +Se +Se +WD +Fd +HQ +St +RW +St +ui +hZ +Tf +ui +St +RW +St +HQ +Fd +ab +"} +(22,1,1) = {" +ab +ab +Fd +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +Fd +WD +Se +Se +Xr +Bv +UO +Se +yC +Se +zU +Xr +WD +Fd +HQ +St +RW +Yp +ui +GX +Yp +ui +JJ +ui +St +HQ +Fd +ab +"} +(23,1,1) = {" +ab +ab +Fd +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +Fd +WD +Xr +oh +Xr +Ck +TS +TS +uu +Xr +Xr +HE +WD +Fd +HQ +St +ui +ui +ui +ui +RW +CG +ui +ui +St +HQ +Fd +ab +"} +(24,1,1) = {" +ab +ab +Fd +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +Fd +WD +ps +Se +Se +By +Se +oh +Xr +ps +pt +ER +WD +Fd +HQ +hZ +ui +St +ui +St +St +ui +rP +ui +St +HQ +Fd +ab +"} +(25,1,1) = {" +ab +ab +Fd +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +Fd +WD +Je +AN +Cf +oh +mR +zU +ps +ER +AN +ER +WD +Fd +HQ +RW +KO +ui +ui +RW +ui +ui +RW +cS +ui +HQ +Fd +ab +"} +(26,1,1) = {" +ab +ab +Fd +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +Fd +WD +ER +pt +ER +HE +Xr +Se +Se +Je +ER +Xr +WD +Fd +HQ +St +ui +JJ +Yp +Yp +St +St +Yp +Xt +St +HQ +Fd +ab +"} +(27,1,1) = {" +ab +ab +Fd +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +fO +Fd +WD +WD +WD +WD +WD +WD +WD +WD +WD +WD +WD +WD +Fd +HQ +HQ +HQ +HQ +HQ +HQ +HQ +HQ +HQ +HQ +HQ +HQ +Fd +ab +"} +(28,1,1) = {" +ab +ab +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +Fd +ab +"} +(29,1,1) = {" +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +ab +"} diff --git a/_maps/virtual_domains/README.md b/_maps/virtual_domains/README.md new file mode 100644 index 00000000000..a02d43e1575 --- /dev/null +++ b/_maps/virtual_domains/README.md @@ -0,0 +1,32 @@ +# Making new virtual domains + +## From scratch + +1. Create a new map using TGM format. It can be any size, but please, consider limiting to 75x75 max. +2. Ensure that the map has ONE tile marked with the safehouse bottom left landmark. If you're using modular safehouses, it will need to be a 7x6 area. +4. Provide a way for players to enter your new map via the north door, which is 4th tile over. +5. Enclose your area with a single wall binary closed wall. + +## From 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. +2. Copy and paste the existing map into it. +3. Find an accessible area for a safehouse, 7x6 - or with a custom, just ensure the necessary landmarks are placed. +4. Place a bottom left safehouse landmark somewhere on the map to load the safehouse. + +## BOTH. +1. You need to have one (1) 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. +2. Make note of the size of the map. Make sure this is in the dm file. +3. Create the dm file that defines the map qualities. Examples are in the bitrunning file. + +### Notes + +You shouldn't need to fully enclose your map in 15 tiles of binary filler. Using one solid wall should do the trick. + +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. + +If you want to add prep gear, you can do so within the safehouse's area as long you don't overlap with goal turfs or exit spawners. The top left corner is a good spot for this, with respect for the walls, therefore [1, 1], [1, 2], [1, 3] + +You can also create safehouses if you find yourself needing the same gear over and over again. There is a readme for that as well. + +Boss zones should give players pretty ample space, I've been using a 23x23 minimum area. diff --git a/_maps/virtual_domains/ash_drake.dmm b/_maps/virtual_domains/ash_drake.dmm new file mode 100644 index 00000000000..50fbac8696a --- /dev/null +++ b/_maps/virtual_domains/ash_drake.dmm @@ -0,0 +1,1750 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"c" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"e" = ( +/obj/structure/marker_beacon/cerulean, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"f" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"g" = ( +/obj/structure/marker_beacon/lime, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"h" = ( +/obj/machinery/light/small/blacklight/directional/south, +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/virtual_domain/powered) +"i" = ( +/obj/structure/marker_beacon/jade, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"j" = ( +/obj/structure/marker_beacon/teal, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"l" = ( +/obj/structure/marker_beacon/bronze, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"q" = ( +/mob/living/simple_animal/hostile/megafauna/dragon/virtual_domain, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"s" = ( +/turf/closed/mineral/volcanic/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"u" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"v" = ( +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"G" = ( +/obj/structure/marker_beacon/purple, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"J" = ( +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"L" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"P" = ( +/obj/structure/marker_beacon/fuchsia, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Z" = ( +/obj/effect/mob_spawn/corpse/human/miner, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) + +(1,1,1) = {" +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +u +"} +(2,1,1) = {" +v +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +v +"} +(3,1,1) = {" +v +s +s +s +s +J +J +s +s +J +J +s +s +J +J +J +J +s +s +s +J +J +J +s +s +s +s +s +s +s +s +s +s +J +J +s +s +s +J +J +s +s +J +J +s +v +"} +(4,1,1) = {" +v +s +s +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +a +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +s +v +"} +(5,1,1) = {" +v +s +s +J +a +J +J +J +J +a +J +J +J +J +a +a +J +J +J +J +J +a +a +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +s +s +v +"} +(6,1,1) = {" +v +s +s +J +a +J +J +a +a +a +a +a +a +a +a +a +a +a +a +J +a +a +a +a +a +J +J +J +a +a +J +J +J +a +a +J +a +J +a +a +J +J +J +s +s +v +"} +(7,1,1) = {" +v +s +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +J +s +v +"} +(8,1,1) = {" +v +s +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +s +v +"} +(9,1,1) = {" +v +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +i +a +a +a +a +a +Z +a +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(10,1,1) = {" +v +s +J +J +J +a +a +a +a +a +a +G +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(11,1,1) = {" +v +s +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(12,1,1) = {" +v +s +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +J +s +v +"} +(13,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +a +a +a +a +g +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(14,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(15,1,1) = {" +v +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +h +c +c +c +c +c +L +a +a +J +J +s +v +"} +(16,1,1) = {" +v +s +J +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +c +c +c +c +c +c +a +a +J +J +s +v +"} +(17,1,1) = {" +v +s +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +l +a +a +c +c +c +c +c +c +a +a +J +J +s +v +"} +(18,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +q +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +c +c +c +c +c +c +a +J +J +s +s +v +"} +(19,1,1) = {" +v +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +c +c +c +c +c +c +a +a +J +J +s +v +"} +(20,1,1) = {" +v +s +J +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +c +c +c +c +c +c +a +a +J +J +s +v +"} +(21,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +e +a +a +a +a +a +a +a +a +a +a +a +a +h +c +c +c +c +c +f +a +a +J +s +s +v +"} +(22,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +s +s +v +"} +(23,1,1) = {" +v +s +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +s +s +v +"} +(24,1,1) = {" +v +s +J +J +a +a +a +a +j +a +a +a +a +a +P +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(25,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +J +s +v +"} +(26,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(27,1,1) = {" +v +s +s +J +J +J +a +a +a +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +j +a +a +a +a +a +a +a +J +J +s +v +"} +(28,1,1) = {" +v +s +J +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +J +s +v +"} +(29,1,1) = {" +v +s +J +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +s +v +"} +(30,1,1) = {" +v +s +s +J +J +J +J +a +a +J +J +J +a +a +a +a +J +J +J +a +a +a +J +J +J +a +a +a +J +J +a +a +a +a +a +a +J +J +a +a +a +J +J +s +s +v +"} +(31,1,1) = {" +v +s +s +a +J +J +J +J +J +J +J +J +J +a +a +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +a +a +a +J +J +J +J +J +J +J +J +J +s +v +"} +(32,1,1) = {" +v +s +s +a +J +J +J +J +J +J +J +J +J +J +J +J +J +a +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +s +v +"} +(33,1,1) = {" +v +s +s +s +s +s +J +J +s +s +s +s +J +J +s +s +s +s +s +s +J +J +s +s +s +s +J +J +s +s +s +s +J +J +J +s +s +s +s +s +s +J +J +J +s +v +"} +(34,1,1) = {" +v +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +v +"} +(35,1,1) = {" +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +"} diff --git a/_maps/virtual_domains/beach_bar.dmm b/_maps/virtual_domains/beach_bar.dmm new file mode 100644 index 00000000000..408d3c0cda1 --- /dev/null +++ b/_maps/virtual_domains/beach_bar.dmm @@ -0,0 +1,2932 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"ab" = ( +/obj/machinery/vending/cigarette/beach, +/obj/effect/turf_decal/sand, +/obj/structure/sign/poster/contraband/have_a_puff/directional/west, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"ag" = ( +/turf/open/floor/carpet/red, +/area/virtual_domain/powered) +"as" = ( +/obj/structure/closet/crate/bin, +/obj/item/tank/internals/emergency_oxygen, +/obj/item/trash/candy, +/obj/item/toy/talking/owl, +/obj/effect/turf_decal/sand, +/obj/machinery/light/directional/west, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"aw" = ( +/obj/machinery/grill, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"aE" = ( +/turf/open/floor/pod/light, +/area/virtual_domain/powered) +"aZ" = ( +/obj/machinery/light/small/directional/east, +/obj/structure/closet/crate{ + name = "fuel crate" + }, +/obj/item/stack/sheet/mineral/coal/ten, +/obj/item/stack/sheet/mineral/coal/ten, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"bf" = ( +/mob/living/basic/crab{ + name = "Jonny" + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"bC" = ( +/obj/effect/turf_decal/sand, +/mob/living/basic/crab{ + name = "James" + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"bM" = ( +/mob/living/basic/crab{ + name = "Jon" + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"bQ" = ( +/obj/structure/fluff/beach_umbrella/cap, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"bS" = ( +/obj/machinery/chem_master/condimaster{ + name = "CondiMaster Neo"; + pixel_x = -4 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"cb" = ( +/obj/structure/table/wood, +/obj/item/reagent_containers/pill/lsd, +/obj/item/reagent_containers/pill/lsd, +/obj/item/reagent_containers/pill/lsd, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"cv" = ( +/turf/open/floor/carpet/royalblue, +/area/virtual_domain/powered) +"cz" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/jukebox, +/obj/item/coin/gold, +/turf/open/floor/sepia, +/area/virtual_domain/powered) +"cG" = ( +/obj/structure/flora/bush/sparsegrass/style_random, +/turf/open/water/beach, +/area/virtual_domain/powered) +"db" = ( +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/vending_refill/cigarette, +/obj/item/vending_refill/boozeomat, +/obj/structure/closet/secure_closet{ + icon_state = "cabinet"; + name = "booze storage"; + req_access = list("bar") + }, +/obj/item/storage/backpack/duffelbag, +/obj/item/etherealballdeployer, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/colocup, +/obj/item/reagent_containers/cup/glass/colocup, +/obj/item/reagent_containers/cup/glass/colocup, +/obj/item/reagent_containers/cup/glass/colocup, +/obj/item/reagent_containers/cup/glass/colocup, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"di" = ( +/obj/machinery/vending/boozeomat, +/obj/effect/mapping_helpers/atom_injector/obj_flag{ + inject_flags = 1; + target_type = /obj/machinery/vending/boozeomat + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"dj" = ( +/turf/open/misc/beach/coast{ + dir = 1 + }, +/area/virtual_domain/powered) +"dx" = ( +/obj/effect/turf_decal/sand, +/obj/effect/turf_decal/stripes/asteroid/line{ + dir = 8 + }, +/turf/open/floor/sepia, +/area/virtual_domain/powered) +"dI" = ( +/obj/machinery/light/directional/south, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"dZ" = ( +/obj/structure/bookcase/random/reference, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"ed" = ( +/obj/machinery/atmospherics/components/tank/air{ + dir = 1 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"er" = ( +/obj/structure/noticeboard/staff, +/turf/closed/wall/mineral/wood/nonmetal, +/area/virtual_domain/powered) +"fc" = ( +/obj/structure/table/wood, +/obj/item/reagent_containers/pill/happy, +/obj/item/toy/figure/bartender{ + pixel_x = -8; + pixel_y = -1 + }, +/obj/item/reagent_containers/cup/glass/drinkingglass/virtual_domain{ + pixel_y = 8; + pixel_x = 5 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"fr" = ( +/obj/item/melee/skateboard/hoverboard, +/obj/machinery/light/directional/west, +/turf/open/floor/pod/light, +/area/virtual_domain/powered) +"gh" = ( +/obj/structure/flora/bush/stalky/style_random, +/obj/structure/flora/bush/sparsegrass/style_random, +/turf/open/water/beach, +/area/virtual_domain/powered) +"gl" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"gJ" = ( +/obj/structure/railing{ + dir = 8 + }, +/turf/open/misc/beach/coast{ + dir = 4 + }, +/area/virtual_domain/powered) +"hk" = ( +/obj/structure/reagent_dispensers/watertank, +/turf/open/floor/pod/light, +/area/virtual_domain/powered) +"hE" = ( +/obj/structure/sign/departments/restroom/directional/east, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"hG" = ( +/obj/machinery/door/airlock/sandstone{ + name = "Surfer Shack 1" + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"iz" = ( +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"iR" = ( +/obj/structure/table, +/obj/item/book/manual/wiki/barman_recipes, +/obj/item/reagent_containers/cup/glass/shaker, +/obj/item/reagent_containers/cup/rag, +/obj/machinery/light/small/directional/west, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"jc" = ( +/turf/open/floor/iron/stairs/right, +/area/virtual_domain/powered) +"jg" = ( +/obj/machinery/vending/hydronutrients, +/turf/open/floor/iron/grimy, +/area/virtual_domain/powered) +"jl" = ( +/obj/structure/flora/rock/pile/jungle/style_random, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"jy" = ( +/obj/effect/turf_decal/sand{ + density = 1 + }, +/obj/effect/decal/fakelattice, +/turf/open/floor/pod/light{ + density = 1 + }, +/area/virtual_domain/powered) +"ke" = ( +/obj/structure/marker_beacon/bronze, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"kn" = ( +/obj/effect/turf_decal/sand, +/obj/effect/turf_decal/stripes/asteroid/line{ + dir = 8 + }, +/obj/structure/chair/stool/bar/directional/west, +/turf/open/floor/sepia, +/area/virtual_domain/powered) +"kv" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"kG" = ( +/obj/structure/table, +/obj/machinery/reagentgrinder, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"kK" = ( +/obj/structure/mirror/directional/west, +/obj/structure/sink/kitchen/directional/south, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"kT" = ( +/obj/structure/chair/stool/bar/directional/south, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"lq" = ( +/obj/item/melee/skateboard/hoverboard, +/turf/open/floor/pod/light, +/area/virtual_domain/powered) +"lB" = ( +/obj/item/toy/seashell, +/obj/effect/turf_decal/sand, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"lS" = ( +/turf/open/floor/light/colour_cycle/dancefloor_a, +/area/virtual_domain/powered) +"ml" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"mq" = ( +/obj/structure/closet/secure_closet/freezer/kitchen/all_access, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"mG" = ( +/obj/structure/easel, +/obj/item/canvas/twentythree_twentythree, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"mP" = ( +/turf/open/misc/beach/coast/corner{ + dir = 1 + }, +/area/virtual_domain/powered) +"mX" = ( +/obj/structure/closet/secure_closet/freezer/meat/all_access, +/obj/item/food/meat/rawbacon, +/obj/item/food/meat/rawbacon, +/obj/item/food/meat/rawcutlet, +/obj/item/food/meat/rawcutlet, +/obj/item/food/meat/slab/rawcrab, +/obj/item/food/meat/slab/rawcrab, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"nP" = ( +/obj/item/stack/sheet/iron/fifty, +/obj/effect/mapping_helpers/burnt_floor, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"oE" = ( +/obj/structure/railing/corner{ + dir = 1 + }, +/obj/machinery/light/directional/south, +/turf/open/misc/beach/coast/corner{ + dir = 8 + }, +/area/virtual_domain/powered) +"oP" = ( +/obj/structure/table/wood, +/obj/machinery/reagentgrinder, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"pr" = ( +/turf/template_noop, +/area/template_noop) +"pC" = ( +/obj/machinery/computer/arcade/battle, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"pT" = ( +/obj/effect/mapping_helpers/broken_floor, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"pZ" = ( +/obj/machinery/light/directional/south, +/turf/open/misc/beach/coast{ + dir = 1 + }, +/area/virtual_domain/powered) +"qc" = ( +/obj/structure/extinguisher_cabinet/directional/south, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"qg" = ( +/obj/structure/sign/poster/contraband/space_up/directional/west, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"qR" = ( +/obj/effect/spawner/structure/window, +/obj/structure/curtain, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"qW" = ( +/obj/item/melee/skateboard/hoverboard, +/mob/living/basic/chicken{ + name = "Chicken Joe" + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"rc" = ( +/obj/machinery/light/directional/east, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"ri" = ( +/obj/structure/sign/poster/official/fruit_bowl, +/turf/closed/wall/mineral/wood/nonmetal, +/area/virtual_domain/powered) +"rm" = ( +/obj/item/storage/crayons, +/obj/structure/closet/crate/wooden, +/obj/item/canvas/twentythree_twentythree, +/obj/item/canvas/twentythree_twentythree, +/obj/item/canvas/twentythree_twentythree, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"rT" = ( +/obj/item/toy/seashell, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"sT" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"tE" = ( +/obj/machinery/door/airlock/public/glass{ + name = "Resort Casino" + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"tF" = ( +/obj/structure/extinguisher_cabinet/directional/east, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"tZ" = ( +/obj/structure/toilet, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"uc" = ( +/obj/machinery/light/small/directional/east, +/obj/machinery/light/small/directional/east, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/virtual_domain/powered) +"ug" = ( +/obj/structure/closet/secure_closet{ + icon_state = "cabinet"; + name = "bartender's closet"; + req_access = list("bar") + }, +/obj/item/clothing/shoes/sandal{ + desc = "A very fashionable pair of flip-flops."; + name = "flip-flops" + }, +/obj/item/clothing/neck/beads, +/obj/item/clothing/glasses/sunglasses/reagent, +/obj/item/clothing/suit/costume/hawaiian, +/obj/machinery/light/small/directional/east, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"uk" = ( +/obj/structure/closet/crate/hydroponics, +/obj/item/shovel/spade, +/obj/item/reagent_containers/cup/bucket, +/obj/item/cultivator, +/turf/open/floor/iron/grimy, +/area/virtual_domain/powered) +"uq" = ( +/obj/structure/table/wood, +/obj/item/reagent_containers/cup/glass/drinkingglass/virtual_domain, +/obj/item/reagent_containers/cup/glass/drinkingglass/virtual_domain{ + pixel_x = -4; + pixel_y = 8 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"uU" = ( +/obj/effect/turf_decal/sand, +/turf/open/floor/sepia, +/area/virtual_domain/powered) +"uV" = ( +/obj/structure/flora/coconuts, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"ve" = ( +/obj/item/toy/dodgeball, +/obj/item/toy/dodgeball, +/obj/item/toy/dodgeball, +/obj/item/toy/dodgeball, +/obj/effect/mapping_helpers/broken_floor, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"vp" = ( +/obj/machinery/light/directional/east, +/obj/structure/chair/stool/bar/directional/south, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"vq" = ( +/obj/machinery/oven/range, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"vv" = ( +/obj/structure/chair/stool/directional/south, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"vN" = ( +/obj/structure/table/wood, +/obj/item/reagent_containers/pill/morphine, +/obj/item/reagent_containers/pill/morphine, +/obj/item/reagent_containers/pill/morphine, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"vT" = ( +/obj/structure/railing{ + dir = 8 + }, +/turf/open/misc/beach/coast/corner, +/area/virtual_domain/powered) +"wb" = ( +/obj/structure/closet/crate/freezer{ + name = "Cooler" + }, +/obj/item/reagent_containers/cup/glass/ice, +/obj/item/reagent_containers/cup/glass/colocup, +/obj/item/reagent_containers/cup/glass/colocup, +/obj/item/reagent_containers/cup/glass/bottle/beer{ + desc = "Beer advertised to be the best in space."; + name = "Masterbrand Beer" + }, +/obj/item/reagent_containers/cup/glass/bottle/beer{ + desc = "Beer advertised to be the best in space."; + name = "Masterbrand Beer" + }, +/obj/item/reagent_containers/cup/glass/bottle/beer{ + desc = "Beer advertised to be the best in space."; + name = "Masterbrand Beer" + }, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/obj/item/reagent_containers/cup/glass/bottle/beer/light, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"wD" = ( +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"xb" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"xk" = ( +/obj/structure/table/wood/poker, +/obj/item/storage/dice, +/obj/item/stack/spacecash/c1000, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"xq" = ( +/obj/structure/window/reinforced/spawner/directional/west, +/obj/structure/window/reinforced/spawner/directional/south, +/obj/item/megaphone, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"xw" = ( +/turf/open/floor/pod/dark, +/area/virtual_domain/powered) +"xJ" = ( +/obj/structure/closet/cabinet, +/obj/item/storage/backpack/duffelbag, +/obj/item/clothing/under/shorts/blue, +/obj/item/clothing/shoes/sandal{ + desc = "A very fashionable pair of flip-flops."; + name = "flip-flops" + }, +/obj/item/clothing/glasses/sunglasses, +/obj/item/clothing/neck/beads, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"xR" = ( +/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/storage/backpack/duffelbag, +/obj/item/clothing/under/shorts/red, +/obj/item/clothing/glasses/sunglasses, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"xW" = ( +/turf/open/space/basic, +/area/space) +"ya" = ( +/obj/structure/table/wood, +/obj/item/reagent_containers/pill/zoom, +/obj/item/reagent_containers/pill/zoom, +/obj/item/reagent_containers/pill/zoom, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"yi" = ( +/obj/structure/sink/kitchen/directional/west{ + desc = "A sink used for washing one's hands and face. It looks rusty and home-made"; + name = "old sink" + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"yl" = ( +/obj/item/reagent_containers/cup/glass/colocup{ + pixel_x = -7; + pixel_y = -2 + }, +/obj/item/reagent_containers/cup/glass/colocup{ + pixel_x = 5; + pixel_y = 6 + }, +/obj/item/reagent_containers/cup/glass/bottle/rum{ + pixel_x = 4; + pixel_y = -3 + }, +/turf/open/floor/carpet/red, +/area/virtual_domain/powered) +"ys" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/light/directional/east, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"yv" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/food_cart, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"yB" = ( +/obj/item/instrument/guitar, +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"yU" = ( +/obj/structure/sign/warning/gas_mask/directional/north, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"yX" = ( +/obj/structure/chair/stool/bar/directional/south, +/turf/open/floor/carpet/red, +/area/virtual_domain/powered) +"zn" = ( +/obj/machinery/light/directional/east, +/turf/open/misc/beach/coast{ + dir = 8 + }, +/area/virtual_domain/powered) +"zw" = ( +/obj/structure/punching_bag, +/turf/open/floor/pod/dark, +/area/virtual_domain/powered) +"zI" = ( +/obj/structure/marker_beacon/indigo, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"zU" = ( +/obj/structure/flora/rock/pile/style_random, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Aa" = ( +/obj/effect/turf_decal/sand, +/obj/effect/turf_decal/stripes/asteroid/line{ + dir = 8 + }, +/obj/machinery/light/directional/west, +/turf/open/floor/sepia, +/area/virtual_domain/powered) +"Ae" = ( +/obj/structure/chair, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Al" = ( +/turf/closed/mineral/random/volcanic, +/area/lavaland/surface/outdoors/virtual_domain) +"An" = ( +/obj/structure/marker_beacon/yellow, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Au" = ( +/obj/structure/fluff/beach_umbrella/science, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"AI" = ( +/obj/structure/table/reinforced, +/obj/machinery/reagentgrinder, +/turf/open/floor/pod/light, +/area/virtual_domain/powered) +"AP" = ( +/obj/machinery/chem_dispenser/drinks/beer/fullupgrade{ + dir = 1 + }, +/obj/structure/table/wood, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Br" = ( +/obj/structure/table/wood/poker, +/obj/item/toy/cards/deck/cas{ + pixel_x = -6 + }, +/obj/item/toy/cards/deck/cas/black{ + pixel_x = -6; + pixel_y = 2 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Bu" = ( +/turf/open/misc/beach/coast{ + dir = 8 + }, +/area/virtual_domain/powered) +"Bw" = ( +/obj/structure/flora/bush/sparsegrass/style_random, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"BD" = ( +/obj/structure/table/wood, +/obj/item/reagent_containers/condiment/saltshaker, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"BJ" = ( +/obj/structure/table/wood/poker, +/obj/item/toy/cards/deck, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"BM" = ( +/turf/closed/wall/mineral/wood/nonmetal, +/area/virtual_domain/powered) +"BQ" = ( +/obj/machinery/seed_extractor, +/turf/open/floor/pod/light, +/area/virtual_domain/powered) +"Cb" = ( +/obj/machinery/light/directional/north, +/mob/living/basic/crab{ + name = "Eddie" + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Cv" = ( +/obj/machinery/hydroponics/constructable, +/turf/open/floor/iron/grimy, +/area/virtual_domain/powered) +"CA" = ( +/obj/structure/window/reinforced/spawner/directional/east, +/obj/effect/mob_spawn/ghost_role/human/beach/lifeguard, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"CO" = ( +/obj/machinery/vending/dinnerware, +/obj/machinery/light/directional/east, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Db" = ( +/obj/machinery/barsign/all_access, +/turf/closed/wall/mineral/wood/nonmetal, +/area/virtual_domain/powered) +"Ds" = ( +/obj/machinery/door/airlock/public/glass{ + name = "Resort Lobby" + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Dt" = ( +/obj/machinery/light/directional/east, +/obj/effect/turf_decal/sand, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"DL" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/wall/mineral/sandstone, +/area/virtual_domain/powered) +"Em" = ( +/obj/item/reagent_containers/condiment/enzyme{ + layer = 5 + }, +/obj/item/reagent_containers/cup/beaker{ + pixel_x = 5 + }, +/obj/structure/table, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Et" = ( +/obj/machinery/light/small/directional/east, +/obj/effect/mapping_helpers/burnt_floor, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Ev" = ( +/obj/structure/reagent_dispensers/beerkeg, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"EC" = ( +/obj/structure/sign/warning/gas_mask/directional/west, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"EP" = ( +/obj/machinery/light/directional/north, +/obj/machinery/washing_machine, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Fn" = ( +/turf/closed/wall/mineral/sandstone, +/area/virtual_domain/powered) +"FM" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"FQ" = ( +/obj/structure/table/reinforced, +/obj/item/secateurs, +/obj/item/reagent_containers/cup/bottle/nutrient/ez, +/turf/open/floor/pod/light, +/area/virtual_domain/powered) +"FS" = ( +/obj/effect/turf_decal/sand, +/obj/structure/sign/warning/no_smoking/circle/directional/east, +/obj/machinery/light/directional/east, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"FY" = ( +/obj/structure/mineral_door/wood, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Gh" = ( +/obj/effect/turf_decal/sand, +/obj/structure/sign/poster/contraband/starkist/directional/north, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Gz" = ( +/obj/structure/flora/tree/palm, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"GA" = ( +/obj/structure/window/reinforced/spawner/directional/north, +/obj/structure/window/reinforced/spawner/directional/west, +/obj/item/bikehorn/airhorn, +/obj/structure/table/wood, +/obj/item/storage/medkit/regular, +/obj/item/storage/medkit/brute, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Hs" = ( +/obj/machinery/shower/directional/west, +/turf/open/floor/iron/white, +/area/virtual_domain/powered) +"HF" = ( +/obj/machinery/deepfryer, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Ia" = ( +/obj/structure/urinal/directional/north, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Ii" = ( +/obj/machinery/light/directional/west, +/turf/open/floor/iron/stairs/left, +/area/virtual_domain/powered) +"Ir" = ( +/obj/machinery/vending/cola, +/obj/effect/turf_decal/sand, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Iv" = ( +/obj/structure/table/wood, +/obj/item/reagent_containers/condiment/peppermill, +/obj/item/reagent_containers/condiment/soysauce, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"IH" = ( +/obj/item/toy/beach_ball, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"IM" = ( +/obj/machinery/hydroponics/constructable, +/obj/machinery/light/directional/east, +/turf/open/floor/iron/grimy, +/area/virtual_domain/powered) +"IP" = ( +/obj/machinery/vending/snack, +/obj/effect/turf_decal/sand, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Jt" = ( +/obj/item/reagent_containers/cup/glass/bottle/beer, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"JC" = ( +/obj/structure/fluff/beach_umbrella/engine, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"JE" = ( +/obj/structure/closet/secure_closet/freezer/kitchen/all_access, +/obj/item/reagent_containers/condiment/milk, +/obj/item/reagent_containers/condiment/mayonnaise, +/obj/item/reagent_containers/condiment/flour, +/obj/item/reagent_containers/condiment/flour, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"JY" = ( +/obj/structure/flora/rock/style_random, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Kd" = ( +/obj/structure/sign/warning/secure_area, +/turf/closed/wall/mineral/sandstone, +/area/virtual_domain/powered) +"KH" = ( +/obj/structure/mineral_door/wood{ + name = "Croupier's Booth" + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"KZ" = ( +/obj/structure/flora/bush/stalky/style_random, +/turf/open/water/beach, +/area/virtual_domain/powered) +"LD" = ( +/turf/open/floor/plating, +/area/virtual_domain/powered) +"LW" = ( +/obj/item/storage/cans/sixbeer, +/turf/open/floor/carpet/orange, +/area/virtual_domain/powered) +"Mp" = ( +/obj/structure/table/wood, +/obj/item/reagent_containers/cup/glass/drinkingglass/virtual_domain{ + pixel_y = 7; + pixel_x = 4 + }, +/obj/item/reagent_containers/cup/glass/drinkingglass/virtual_domain, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Mw" = ( +/obj/structure/chair/sofa/right/brown, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Mz" = ( +/obj/structure/chair/sofa/left/brown, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Nr" = ( +/obj/machinery/light/directional/north, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Nw" = ( +/obj/item/bedsheet/dorms{ + dir = 4 + }, +/obj/structure/bed{ + dir = 4 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"NM" = ( +/obj/structure/closet/crate/hydroponics, +/obj/item/food/grown/ambrosia/vulgaris, +/obj/item/food/grown/ambrosia/vulgaris, +/obj/item/food/grown/ambrosia/vulgaris, +/obj/item/food/grown/ambrosia/vulgaris, +/obj/item/food/grown/ambrosia/vulgaris, +/obj/item/food/grown/ambrosia/vulgaris, +/obj/item/food/grown/ambrosia/vulgaris, +/obj/item/food/grown/ambrosia/vulgaris, +/obj/item/food/grown/ambrosia/vulgaris, +/obj/item/food/grown/ambrosia/vulgaris, +/turf/open/floor/iron/grimy, +/area/virtual_domain/powered) +"NX" = ( +/obj/effect/landmark/bitrunning/loot_signal, +/turf/open/floor/light/colour_cycle/dancefloor_a, +/area/virtual_domain/powered) +"OE" = ( +/obj/effect/mob_spawn/ghost_role/human/beach{ + dir = 4 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"OK" = ( +/obj/structure/sign/warning/gas_mask/directional/north, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"OR" = ( +/obj/machinery/light/directional/south, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"OW" = ( +/obj/structure/sink/kitchen/directional/east{ + desc = "A sink used for washing one's hands and face. It looks rusty and home-made"; + name = "old sink" + }, +/turf/open/floor/pod/light, +/area/virtual_domain/powered) +"OZ" = ( +/obj/structure/marker_beacon/teal, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Pc" = ( +/obj/structure/chair/wood, +/obj/machinery/light/directional/west, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Pg" = ( +/obj/structure/sign/poster/official/high_class_martini/directional/west, +/obj/effect/mob_spawn/ghost_role/human/bartender{ + dir = 4 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"PM" = ( +/obj/machinery/door/airlock/external/ruin, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Qb" = ( +/obj/machinery/griddle, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Qu" = ( +/obj/structure/curtain, +/turf/open/floor/iron/white, +/area/virtual_domain/powered) +"QP" = ( +/obj/structure/extinguisher_cabinet/directional/north, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"QX" = ( +/obj/machinery/chem_dispenser/drinks/fullupgrade{ + dir = 1 + }, +/obj/structure/table/wood, +/obj/machinery/light/small/directional/east, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Rx" = ( +/turf/open/floor/iron/stairs/medium, +/area/virtual_domain/powered) +"RL" = ( +/obj/structure/closet/cabinet, +/obj/item/storage/backpack/duffelbag, +/obj/item/clothing/under/shorts/purple, +/obj/item/clothing/shoes/cookflops{ + desc = "A very fashionable pair of flip flops."; + name = "flip-flops" + }, +/obj/item/clothing/glasses/sunglasses/big, +/obj/item/clothing/neck/beads, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Sg" = ( +/obj/structure/flora/coconuts, +/obj/machinery/light/directional/north, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"SB" = ( +/obj/machinery/door/airlock/sandstone{ + name = "Resort Bathroom" + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"SD" = ( +/obj/machinery/door/airlock/sandstone{ + name = "Bar Access" + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"SY" = ( +/obj/machinery/door/airlock/sandstone{ + name = "Surfer Shack 2" + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"TG" = ( +/turf/open/floor/wood, +/area/virtual_domain/powered) +"TJ" = ( +/obj/structure/fluff/beach_umbrella/security, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"TX" = ( +/obj/structure/sign/poster/contraband/ambrosia_vulgaris/directional/north, +/turf/open/floor/iron/grimy, +/area/virtual_domain/powered) +"Ud" = ( +/obj/effect/turf_decal/sand, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Uh" = ( +/turf/open/floor/iron/stairs/old, +/area/virtual_domain/powered) +"Uq" = ( +/obj/structure/weightmachine/weightlifter, +/turf/open/floor/pod/dark, +/area/virtual_domain/powered) +"UU" = ( +/obj/structure/flora/bush/large/style_random, +/obj/structure/flora/bush/jungle/a/style_random, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Ve" = ( +/obj/machinery/processor, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"VA" = ( +/obj/machinery/computer/slot_machine, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"VH" = ( +/obj/machinery/light/directional/west, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"VX" = ( +/obj/structure/flora/bush/large/style_random, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"We" = ( +/obj/structure/table/wood, +/obj/item/book/manual/wiki/cooking_to_serve_man, +/obj/item/clothing/suit/apron/chef, +/obj/item/clothing/head/utility/chefhat, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Wg" = ( +/obj/structure/dresser, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Ww" = ( +/turf/open/water/beach, +/area/virtual_domain/powered) +"WL" = ( +/obj/machinery/light/directional/north, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"WO" = ( +/obj/structure/flora/bush/jungle/a/style_random, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"WW" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/icecream_vat, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"WX" = ( +/obj/item/toy/plush/lizard_plushie/green{ + name = "Soaks-The-Rays" + }, +/turf/open/floor/carpet/orange, +/area/virtual_domain/powered) +"Xt" = ( +/turf/open/misc/beach/coast/corner{ + dir = 4 + }, +/area/virtual_domain/powered) +"Xv" = ( +/obj/structure/table/wood, +/obj/structure/bedsheetbin, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"XL" = ( +/obj/machinery/light/directional/east, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"XM" = ( +/turf/open/misc/beach/coast, +/area/virtual_domain/powered) +"XP" = ( +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"XT" = ( +/obj/effect/turf_decal/sand, +/obj/structure/sign/departments/botany/directional/south, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Yi" = ( +/obj/structure/flora/bush/sparsegrass/style_random, +/obj/item/toy/seashell, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Yq" = ( +/obj/machinery/portable_atmospherics/canister/air, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"YI" = ( +/obj/machinery/door/airlock/maintenance{ + name = "Supply Room" + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"YJ" = ( +/turf/open/floor/carpet/purple, +/area/virtual_domain/powered) +"YN" = ( +/obj/effect/turf_decal/sand, +/obj/machinery/light/directional/west, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Zb" = ( +/obj/structure/sign/poster/official/cohiba_robusto_ad/directional/west, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Zd" = ( +/obj/structure/sign/poster/contraband/space_cola/directional/north, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Zg" = ( +/obj/structure/table, +/obj/machinery/microwave, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Zt" = ( +/obj/structure/table/wood, +/obj/item/reagent_containers/pill/morphine, +/obj/item/storage/fancy/donut_box, +/turf/open/floor/wood, +/area/virtual_domain/powered) + +(1,1,1) = {" +pr +pr +pr +pr +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +kv +"} +(2,1,1) = {" +pr +pr +pr +pr +iz +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +iz +"} +(3,1,1) = {" +pr +pr +pr +pr +iz +Al +gl +gl +gl +gl +gl +Al +Al +gl +gl +gl +gl +gl +Al +Al +gl +gl +gl +Al +gl +gl +gl +gl +Al +Al +gl +gl +gl +Al +Al +gl +gl +gl +gl +Al +iz +"} +(4,1,1) = {" +pr +pr +pr +pr +iz +Al +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +Al +iz +"} +(5,1,1) = {" +pr +pr +pr +pr +iz +Al +gl +gl +gl +gl +gl +gl +gl +gl +gl +zI +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +Al +iz +"} +(6,1,1) = {" +pr +pr +pr +pr +iz +Al +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +OZ +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +Al +iz +"} +(7,1,1) = {" +pr +pr +pr +pr +iz +Al +gl +gl +gl +An +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +ke +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +ml +ml +ml +ml +ml +FM +gl +Al +iz +"} +(8,1,1) = {" +pr +pr +pr +pr +iz +Al +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +ml +ml +ml +ml +ml +ml +gl +Al +iz +"} +(9,1,1) = {" +pr +pr +pr +pr +iz +Al +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +gl +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +gl +gl +ml +ml +ml +ml +ml +ml +gl +Al +iz +"} +(10,1,1) = {" +pr +pr +pr +pr +iz +Al +Al +Al +Al +xb +xb +uc +Al +Al +gl +gl +gl +Al +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Al +gl +gl +ml +ml +ml +ml +ml +ml +gl +Al +iz +"} +(11,1,1) = {" +iz +iz +iz +iz +iz +Al +Al +Al +Fn +PM +PM +Fn +Fn +Al +Al +Al +Fn +Fn +Fn +Pc +bf +Bw +Fn +Ev +Pg +iR +kG +di +Fn +DL +Al +gl +ml +ml +ml +ml +ml +ml +gl +Al +iz +"} +(12,1,1) = {" +iz +Al +Al +Al +Al +Al +Al +Al +Fn +pT +LD +LD +Fn +Fn +Kd +Fn +Fn +bQ +cv +wD +Bw +JY +Fn +db +TG +TG +TG +TG +AP +Fn +Al +gl +ml +ml +ml +ml +ml +ml +gl +Al +iz +"} +(13,1,1) = {" +iz +Al +Fn +Fn +Fn +Fn +Fn +Fn +Fn +LD +pT +LD +EC +LD +pT +PM +wD +wD +cv +wD +wD +OR +Fn +ug +TG +TG +TG +TG +QX +Fn +Al +gl +ml +ml +ml +ml +ml +sT +gl +Al +iz +"} +(14,1,1) = {" +iz +Al +Fn +VA +kT +Zb +TG +Fn +Fn +Fn +yU +LD +Et +LD +LD +PM +wD +wD +wD +wD +wD +qc +Fn +Fn +SD +Mp +uq +fc +Fn +Fn +Al +gl +gl +gl +gl +gl +gl +gl +gl +Al +iz +"} +(15,1,1) = {" +iz +Al +Fn +VA +yX +ag +kT +Br +TG +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Gz +wD +Bw +rm +wD +wD +wD +Ii +dx +kn +kn +kn +Aa +Fn +Al +gl +gl +gl +gl +gl +gl +gl +gl +Al +iz +"} +(16,1,1) = {" +iz +Al +Fn +pC +yX +ag +kT +BJ +kT +Fn +as +ab +Ir +IP +YN +uV +wD +wD +wD +mG +vv +Bw +wD +Rx +uU +lS +lS +lS +uU +Fn +Al +gl +gl +gl +gl +gl +gl +gl +gl +Al +iz +"} +(17,1,1) = {" +iz +Al +Fn +Mw +ag +ag +vp +xk +TG +Fn +Ud +Ud +bC +Ud +Ud +wD +IH +wD +wD +Bw +wD +wD +wD +Rx +uU +lS +NX +lS +cz +Fn +Al +gl +gl +gl +gl +gl +gl +gl +gl +Al +iz +"} +(18,1,1) = {" +iz +Al +Fn +Mz +TG +TG +Fn +Fn +KH +Fn +Zd +wD +wD +Bw +wD +VX +wD +UU +wD +wD +wD +wD +wD +Rx +uU +lS +lS +lS +uU +Fn +Al +gl +gl +gl +gl +gl +gl +gl +gl +Al +iz +"} +(19,1,1) = {" +iz +Al +Fn +Fn +tE +tE +Fn +uV +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +jc +uU +uU +uU +uU +uU +Fn +Fn +gl +gl +gl +gl +gl +gl +gl +gl +Al +iz +"} +(20,1,1) = {" +iz +Al +Fn +zU +wD +wD +wD +wD +Dt +Ud +Ud +Ud +Ud +Ud +Ud +Ud +ys +wD +wD +TJ +wb +wD +wD +vT +gJ +gJ +gJ +gJ +gJ +oE +Fn +gl +gl +Al +Al +gl +gl +Al +gl +Al +iz +"} +(21,1,1) = {" +iz +Al +Fn +wD +wD +Bw +wD +wD +BM +BM +BM +We +Zt +BD +Iv +BM +Db +Nr +wD +yl +ag +wD +wD +XM +KZ +Ww +Ww +Ww +cG +dj +Fn +Al +Al +Al +Al +Al +Al +Al +Al +Al +iz +"} +(22,1,1) = {" +iz +Al +Fn +Fn +wD +wD +wD +wD +BM +Zg +VH +TG +TG +TG +TG +mX +BM +wD +wD +Au +wD +rT +wD +XM +Ww +Ww +Ww +Ww +Ww +dj +Fn +Al +iz +iz +iz +iz +iz +iz +iz +iz +iz +"} +(23,1,1) = {" +iz +Al +xb +Fn +Cb +wD +JC +wD +BM +HF +TG +JE +BM +aw +TG +TG +ya +wD +wD +YJ +YJ +wD +wD +XM +Ww +Ww +Ww +Ww +Ww +dj +Fn +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(24,1,1) = {" +iz +Al +xb +Fn +wD +Gz +WX +wD +BM +Em +TG +mq +ri +Qb +TG +TG +cb +wD +wD +bQ +wD +wD +wD +XM +Ww +Ww +KZ +KZ +Ww +pZ +Fn +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(25,1,1) = {" +iz +Al +xb +Fn +OK +Gz +LW +wD +BM +bS +TG +oP +BM +vq +TG +TG +vN +wD +wD +XP +yB +wD +wD +XM +KZ +Ww +KZ +gh +Ww +dj +Fn +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(26,1,1) = {" +iz +Al +xb +Fn +Sg +wD +wD +wD +BM +Ve +rc +yi +TG +TG +TG +CO +BM +wD +Yi +XL +wD +wD +wD +XM +Ww +Ww +Ww +Ww +Ww +dj +Fn +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(27,1,1) = {" +iz +Al +Fn +Fn +Bw +wD +wD +wD +BM +BM +BM +BM +FY +BM +BM +BM +er +wD +GA +xq +jy +wD +wD +XM +Ww +cG +Ww +Ww +KZ +dj +Fn +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(28,1,1) = {" +iz +Al +Fn +Nr +wD +wD +Bw +wD +YN +Ud +WW +yv +Ud +Ud +Ud +Ud +YN +wD +xR +CA +Uh +wD +qW +XM +Ww +Ww +Ww +Ww +Ww +pZ +Fn +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(29,1,1) = {" +iz +Al +Fn +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +wD +Jt +wD +mP +zn +Bu +Bu +Bu +Bu +Xt +Fn +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(30,1,1) = {" +iz +Al +Fn +Ds +Ds +Fn +VX +wD +wD +wD +wD +wD +XL +wD +wD +wD +wD +wD +wD +wD +wD +XT +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(31,1,1) = {" +iz +Al +Fn +TG +TG +Fn +Fn +jl +Fn +Fn +qR +Fn +Fn +WO +wD +Bw +wD +wD +wD +wD +bM +Ud +aE +aE +aE +lq +fr +hk +Fn +Al +Al +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(32,1,1) = {" +iz +Al +Fn +TG +TG +TG +Fn +Fn +Fn +dZ +OE +Nw +Fn +Fn +qR +Fn +Fn +wD +wD +wD +Ae +Ud +zw +xw +Uq +aE +aE +aE +Fn +Fn +Al +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(33,1,1) = {" +iz +Al +Fn +EP +TG +TG +TG +TG +hG +TG +TG +TG +Fn +dZ +OE +Nw +Fn +Gz +uV +wD +wD +Ud +xw +xw +xw +aE +aE +aE +OW +Fn +Fn +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(34,1,1) = {" +iz +Al +Fn +Xv +TG +hE +TG +TG +Fn +Wg +rc +xJ +Fn +TG +TG +dI +Fn +wD +wD +Bw +wD +lB +zw +xw +Uq +aE +FQ +aE +aE +jg +Fn +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(35,1,1) = {" +iz +Al +Fn +Fn +SB +Fn +WL +TG +Fn +Fn +Fn +Fn +Fn +Wg +TG +RL +Fn +Gh +Ud +Ud +Ud +FS +aE +aE +aE +aE +AI +BQ +aE +NM +Fn +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(36,1,1) = {" +iz +Al +Fn +kK +TG +Fn +TG +TG +TG +TG +qg +TG +Fn +Fn +SY +Fn +Fn +Ds +Ds +Fn +YI +Fn +Fn +Fn +TX +aE +aE +aE +aE +uk +Fn +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(37,1,1) = {" +iz +Al +Fn +Ia +dI +Fn +Fn +Fn +QP +TG +TG +TG +TG +TG +TG +TG +TG +TG +TG +Fn +ve +nP +ed +Fn +Cv +Cv +IM +Cv +Cv +Fn +Fn +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(38,1,1) = {" +iz +Al +Fn +tZ +TG +Qu +Hs +Fn +Fn +Fn +TG +rc +TG +TG +TG +tF +rc +TG +TG +Fn +Yq +aZ +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Al +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(39,1,1) = {" +iz +Al +Fn +Fn +Fn +Fn +Fn +Fn +Al +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Fn +Al +Al +Al +Al +Al +Al +Al +Al +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(40,1,1) = {" +iz +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +Al +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} +(41,1,1) = {" +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +iz +pr +pr +pr +pr +pr +pr +pr +xW +"} diff --git a/_maps/virtual_domains/blood_drunk_miner.dmm b/_maps/virtual_domains/blood_drunk_miner.dmm new file mode 100644 index 00000000000..c3369a1c822 --- /dev/null +++ b/_maps/virtual_domains/blood_drunk_miner.dmm @@ -0,0 +1,1887 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"b" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"c" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"d" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"f" = ( +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile/block/cracked, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"h" = ( +/obj/machinery/light/small/blacklight/directional/south, +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/virtual_domain/powered) +"i" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile/cracked, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"j" = ( +/obj/structure/marker_beacon/jade, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"k" = ( +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"l" = ( +/obj/structure/stone_tile/block, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"m" = ( +/obj/structure/marker_beacon/olive, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"n" = ( +/obj/structure/marker_beacon/cerulean, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"o" = ( +/obj/structure/marker_beacon/yellow, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"q" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"r" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"s" = ( +/turf/closed/mineral/volcanic/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"t" = ( +/obj/structure/marker_beacon/indigo, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"u" = ( +/obj/structure/stone_tile/cracked, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"v" = ( +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"w" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"x" = ( +/obj/structure/stone_tile, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"y" = ( +/obj/structure/marker_beacon/violet, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"z" = ( +/obj/structure/stone_tile/block, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"A" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"C" = ( +/obj/effect/mob_spawn/corpse/human/miner, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"G" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"H" = ( +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"I" = ( +/obj/structure/stone_tile/block{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"J" = ( +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"K" = ( +/obj/structure/marker_beacon/teal, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"L" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"O" = ( +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile/block{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"P" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"S" = ( +/obj/structure/stone_tile/surrounding/cracked{ + dir = 6 + }, +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/virtual_domain, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"T" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"W" = ( +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"X" = ( +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Y" = ( +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Z" = ( +/obj/structure/stone_tile/block{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) + +(1,1,1) = {" +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +d +"} +(2,1,1) = {" +v +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +v +"} +(3,1,1) = {" +v +s +s +s +s +J +J +s +s +J +J +s +s +J +J +J +J +s +s +s +J +J +J +s +s +s +s +s +s +s +s +s +s +J +J +s +s +s +J +J +s +s +J +J +s +v +"} +(4,1,1) = {" +v +s +s +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +a +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +s +v +"} +(5,1,1) = {" +v +s +s +J +a +J +J +J +J +a +J +J +J +J +a +a +J +J +J +J +J +a +a +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +s +s +v +"} +(6,1,1) = {" +v +s +s +J +a +J +J +a +a +a +a +a +a +a +a +a +a +a +a +J +a +a +a +a +a +J +J +J +a +a +J +J +J +a +a +J +a +J +a +a +J +J +J +s +s +v +"} +(7,1,1) = {" +v +s +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +J +s +v +"} +(8,1,1) = {" +v +s +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +s +v +"} +(9,1,1) = {" +v +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +j +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(10,1,1) = {" +v +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +t +a +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(11,1,1) = {" +v +s +s +J +J +J +a +a +a +y +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(12,1,1) = {" +v +s +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +C +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +J +s +v +"} +(13,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(14,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +a +a +T +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(15,1,1) = {" +v +s +J +J +J +a +a +a +a +a +a +a +a +a +a +X +z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +h +c +c +c +c +c +L +a +a +J +J +s +v +"} +(16,1,1) = {" +v +s +J +J +J +J +a +a +a +a +a +a +a +T +W +a +r +a +i +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +c +c +c +c +c +c +a +a +J +J +s +v +"} +(17,1,1) = {" +v +s +s +J +J +J +a +a +a +a +a +a +a +a +I +b +k +l +x +a +T +k +a +a +a +m +a +a +a +a +a +o +a +a +c +c +c +c +c +c +a +a +J +J +s +v +"} +(18,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +a +a +a +P +S +A +O +u +r +k +a +a +a +a +a +a +a +a +a +a +a +c +c +c +c +c +c +a +J +J +s +s +v +"} +(19,1,1) = {" +v +s +J +J +J +a +a +a +a +a +a +a +a +k +G +H +x +f +k +a +Y +T +u +a +a +a +a +a +a +a +a +a +a +a +c +c +c +c +c +c +a +a +J +J +s +v +"} +(20,1,1) = {" +v +s +J +J +J +J +a +a +a +a +a +a +a +Y +x +a +Z +a +z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +c +c +c +c +c +c +a +a +J +J +s +v +"} +(21,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +a +a +a +z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +h +c +c +c +c +c +q +a +a +J +s +s +v +"} +(22,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +a +a +w +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +s +s +v +"} +(23,1,1) = {" +v +s +J +J +a +a +a +a +a +a +n +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +s +s +v +"} +(24,1,1) = {" +v +s +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +C +a +a +a +a +a +J +J +s +v +"} +(25,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +J +s +v +"} +(26,1,1) = {" +v +s +s +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +K +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(27,1,1) = {" +v +s +s +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +j +a +a +a +a +a +a +a +a +a +J +J +s +v +"} +(28,1,1) = {" +v +s +J +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +J +s +v +"} +(29,1,1) = {" +v +s +J +J +J +J +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +J +J +s +s +v +"} +(30,1,1) = {" +v +s +s +J +J +J +J +a +a +J +J +J +a +a +a +a +J +J +J +a +a +a +J +J +J +a +a +a +J +J +a +a +a +a +a +a +J +J +a +a +a +J +J +s +s +v +"} +(31,1,1) = {" +v +s +s +a +J +J +J +J +J +J +J +J +J +a +a +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +a +a +a +J +J +J +J +J +J +J +J +J +s +v +"} +(32,1,1) = {" +v +s +s +a +J +J +J +J +J +J +J +J +J +J +J +J +J +a +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +J +s +v +"} +(33,1,1) = {" +v +s +s +s +s +s +J +J +s +s +s +s +J +J +s +s +s +s +s +s +J +J +s +s +s +s +J +J +s +s +s +s +J +J +J +s +s +s +s +s +s +J +J +J +s +v +"} +(34,1,1) = {" +v +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +s +v +"} +(35,1,1) = {" +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +"} diff --git a/_maps/virtual_domains/bubblegum.dmm b/_maps/virtual_domains/bubblegum.dmm new file mode 100644 index 00000000000..3381b173539 --- /dev/null +++ b/_maps/virtual_domains/bubblegum.dmm @@ -0,0 +1,2250 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"c" = ( +/obj/effect/mob_spawn/corpse/human/miner, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"d" = ( +/obj/structure/marker_beacon/jade, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"f" = ( +/obj/structure/marker_beacon/burgundy, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"g" = ( +/obj/structure/marker_beacon/teal, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"p" = ( +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"r" = ( +/obj/structure/marker_beacon/fuchsia, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"w" = ( +/obj/machinery/light/small/blacklight/directional/south, +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/virtual_domain/powered) +"x" = ( +/obj/structure/marker_beacon/olive, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"z" = ( +/obj/structure/marker_beacon/purple, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"A" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"C" = ( +/mob/living/simple_animal/hostile/megafauna/bubblegum/virtual_domain, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"F" = ( +/turf/open/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"G" = ( +/obj/structure/marker_beacon/violet, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"I" = ( +/obj/structure/marker_beacon/bronze, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"M" = ( +/obj/structure/marker_beacon/indigo, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"R" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"S" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"T" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"W" = ( +/obj/structure/marker_beacon/cerulean, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"X" = ( +/obj/structure/marker_beacon/lime, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Y" = ( +/obj/structure/marker_beacon/yellow, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Z" = ( +/turf/closed/mineral/volcanic/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) + +(1,1,1) = {" +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +R +"} +(2,1,1) = {" +F +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +F +"} +(3,1,1) = {" +F +Z +a +a +Z +Z +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +Z +Z +a +a +a +a +a +a +a +a +Z +Z +a +a +a +a +a +a +Z +Z +Z +F +"} +(4,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(5,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +p +Z +F +"} +(6,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +G +a +a +a +a +a +a +a +a +a +a +a +a +p +p +Z +F +"} +(7,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +x +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +a +a +a +a +p +Z +F +"} +(8,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +Z +a +a +a +Z +Z +F +"} +(9,1,1) = {" +F +Z +a +a +a +a +a +a +p +p +p +p +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +Z +F +"} +(10,1,1) = {" +F +Z +Z +a +a +a +a +a +Z +Z +Z +p +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +Z +F +"} +(11,1,1) = {" +F +Z +Z +a +a +a +a +a +Z +Z +Z +p +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +Z +a +a +a +a +a +a +a +a +p +p +a +a +a +a +a +Z +F +"} +(12,1,1) = {" +F +Z +Z +a +a +a +a +a +p +Z +p +p +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +Z +a +a +a +a +a +a +a +p +a +a +a +a +a +Z +F +"} +(13,1,1) = {" +F +Z +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +Z +Z +a +a +a +a +a +M +a +a +a +a +a +a +a +Z +F +"} +(14,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +Z +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(15,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +c +a +a +a +a +a +a +Z +F +"} +(16,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +I +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(17,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +W +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(18,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(19,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(20,1,1) = {" +F +Z +a +a +a +a +a +z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(21,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +w +S +S +S +S +S +T +a +Z +F +"} +(22,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +S +S +S +S +S +S +a +Z +F +"} +(23,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +C +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +S +S +S +S +S +S +a +Z +F +"} +(24,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +X +a +a +S +S +S +S +S +S +a +Z +F +"} +(25,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +S +S +S +S +S +S +a +Z +F +"} +(26,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +S +S +S +S +S +S +a +Z +F +"} +(27,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +w +S +S +S +S +S +A +a +Z +F +"} +(28,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +f +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(29,1,1) = {" +F +Z +a +a +a +a +a +a +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +r +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(30,1,1) = {" +F +Z +a +a +a +a +a +a +Z +Z +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(31,1,1) = {" +F +Z +a +a +a +a +a +a +Z +Z +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(32,1,1) = {" +F +Z +a +a +a +a +a +a +a +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(33,1,1) = {" +F +Z +a +a +a +a +a +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +d +a +a +a +a +a +a +a +Z +F +"} +(34,1,1) = {" +F +Z +Z +a +a +a +a +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(35,1,1) = {" +F +Z +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +X +a +a +a +a +a +a +a +a +Z +Z +Z +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(36,1,1) = {" +F +Z +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +Z +Z +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(37,1,1) = {" +F +Z +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +Z +F +"} +(38,1,1) = {" +F +Z +a +a +a +p +p +a +a +a +a +a +g +a +a +a +a +Z +a +a +a +a +a +a +p +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +Z +F +"} +(39,1,1) = {" +F +Z +a +a +a +p +p +a +a +a +a +a +a +a +a +a +Z +Z +Z +a +a +a +a +a +p +p +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(40,1,1) = {" +F +Z +a +c +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(41,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Y +a +a +a +a +a +a +a +Z +F +"} +(42,1,1) = {" +F +Z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +Z +F +"} +(43,1,1) = {" +F +Z +a +Z +Z +Z +Z +a +a +a +a +a +Z +Z +Z +Z +a +a +a +Z +Z +Z +Z +Z +a +a +a +a +a +a +Z +Z +Z +Z +a +a +a +a +a +a +Z +Z +a +a +Z +F +"} +(44,1,1) = {" +F +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +Z +F +"} +(45,1,1) = {" +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +F +"} diff --git a/_maps/virtual_domains/clown_planet.dmm b/_maps/virtual_domains/clown_planet.dmm new file mode 100644 index 00000000000..01d7b88a5ef --- /dev/null +++ b/_maps/virtual_domains/clown_planet.dmm @@ -0,0 +1,2323 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"ai" = ( +/obj/structure/disposalpipe/segment{ + dir = 10 + }, +/obj/effect/decal/cleanable/dirt, +/obj/machinery/light/small/directional/west, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"aI" = ( +/obj/item/bikehorn/airhorn, +/turf/open/floor/carpet, +/area/virtual_domain/powered) +"aM" = ( +/obj/item/bikehorn, +/turf/open/indestructible/honk, +/area/virtual_domain/powered) +"aP" = ( +/obj/structure/disposalpipe/segment{ + dir = 10 + }, +/obj/effect/decal/cleanable/food/pie_smudge, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"ba" = ( +/obj/structure/mecha_wreckage/honker, +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"bi" = ( +/obj/item/bikehorn, +/obj/structure/disposalpipe/segment{ + invisibility = 101 + }, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"bp" = ( +/turf/open/indestructible/light, +/area/virtual_domain/powered) +"bq" = ( +/obj/structure/disposalpipe/segment{ + dir = 10 + }, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"by" = ( +/turf/closed/wall/r_wall, +/area/lavaland/surface/outdoors/virtual_domain) +"bQ" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/obj/effect/turf_decal/tile/red/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"bR" = ( +/obj/item/paper/crumpled/bloody/ruins/lavaland/clown_planet/hope, +/obj/effect/decal/cleanable/blood/old, +/obj/effect/mapping_helpers/no_lava, +/turf/open/floor/noslip, +/area/virtual_domain/powered) +"bU" = ( +/obj/structure/disposalpipe/segment{ + dir = 9 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"cw" = ( +/obj/structure/disposalpipe/segment{ + dir = 6 + }, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"cM" = ( +/obj/structure/disposalpipe/trunk{ + dir = 8 + }, +/obj/structure/disposaloutlet{ + dir = 1 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"cW" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/disposalpipe/segment{ + invisibility = 101 + }, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"ed" = ( +/obj/structure/disposalpipe/segment{ + invisibility = 101 + }, +/obj/machinery/light/small/directional/west, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"eE" = ( +/obj/structure/window/reinforced/spawner/directional/south, +/obj/structure/disposalpipe/segment{ + invisibility = 101 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"fh" = ( +/obj/effect/mob_spawn/corpse/human/damaged, +/obj/effect/decal/cleanable/blood/old, +/obj/structure/disposalpipe/segment{ + invisibility = 101 + }, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"gr" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"gy" = ( +/obj/structure/disposalpipe/segment{ + dir = 5 + }, +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"gH" = ( +/obj/item/bikehorn, +/obj/effect/decal/cleanable/dirt, +/turf/open/indestructible/honk, +/area/virtual_domain/powered) +"gK" = ( +/obj/structure/disposalpipe/segment{ + dir = 9 + }, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"hK" = ( +/obj/item/clothing/head/cone, +/obj/effect/mapping_helpers/no_lava, +/turf/open/floor/noslip, +/area/virtual_domain/powered) +"hY" = ( +/turf/template_noop, +/area/template_noop) +"ij" = ( +/obj/structure/disposalpipe/trunk{ + dir = 8 + }, +/obj/machinery/disposal/delivery_chute{ + dir = 4 + }, +/turf/open/floor/noslip, +/area/virtual_domain/powered) +"ik" = ( +/turf/open/lava/smooth, +/area/virtual_domain/powered) +"iR" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"ki" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"kn" = ( +/obj/structure/disposalpipe/segment{ + dir = 5 + }, +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"lj" = ( +/obj/structure/disposalpipe/trunk{ + dir = 4 + }, +/obj/machinery/disposal/delivery_chute{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"lm" = ( +/obj/structure/disposalpipe/segment{ + dir = 9 + }, +/obj/machinery/light/small/directional/east, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"lr" = ( +/obj/item/bikehorn, +/obj/effect/decal/cleanable/dirt, +/obj/structure/disposalpipe/segment{ + invisibility = 101 + }, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"lx" = ( +/obj/structure/disposalpipe/segment{ + dir = 9 + }, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"ly" = ( +/obj/structure/disposalpipe/segment{ + dir = 9 + }, +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"lP" = ( +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"mD" = ( +/turf/open/floor/plating, +/area/virtual_domain/powered) +"mE" = ( +/obj/structure/disposalpipe/segment, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"mF" = ( +/turf/open/floor/carpet, +/area/virtual_domain/powered) +"nE" = ( +/obj/effect/mapping_helpers/no_lava, +/turf/closed/wall/r_wall, +/area/virtual_domain/powered) +"oA" = ( +/obj/effect/turf_decal/tile/red/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"oI" = ( +/obj/structure/table/glass, +/obj/item/grown/bananapeel/bluespace, +/turf/open/floor/carpet, +/area/virtual_domain/powered) +"pl" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"ps" = ( +/obj/structure/disposalpipe/segment{ + dir = 6 + }, +/obj/effect/turf_decal/tile/red/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"qM" = ( +/obj/structure/disposalpipe/segment{ + invisibility = 101 + }, +/obj/machinery/light/small/directional/north, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"rg" = ( +/obj/item/coin/bananium, +/obj/item/coin/bananium, +/obj/item/coin/bananium, +/obj/item/coin/bananium, +/obj/machinery/light/small/directional/west, +/turf/open/floor/carpet, +/area/virtual_domain/powered) +"rh" = ( +/obj/structure/disposalpipe/segment{ + dir = 6 + }, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"rr" = ( +/obj/structure/disposalpipe/segment{ + dir = 5 + }, +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"rH" = ( +/obj/structure/disposalpipe/junction/yjunction{ + dir = 1; + invisibility = 101 + }, +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"rT" = ( +/obj/structure/disposalpipe/segment{ + dir = 6 + }, +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"sq" = ( +/obj/machinery/light/directional/north, +/obj/effect/turf_decal/tile/red/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"sT" = ( +/obj/structure/disposalpipe/sorting/mail/flip{ + dir = 1 + }, +/obj/effect/mapping_helpers/mail_sorting/supply/qm_office, +/turf/closed/wall/r_wall, +/area/virtual_domain/powered) +"tq" = ( +/obj/effect/spawner/structure/window/reinforced, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"tt" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/turf/open/indestructible/light, +/area/virtual_domain/powered) +"tv" = ( +/obj/effect/mob_spawn/corpse/human/damaged, +/obj/effect/decal/cleanable/blood/old, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"tF" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"tI" = ( +/obj/item/coin/bananium, +/obj/item/coin/bananium, +/obj/item/coin/bananium, +/obj/item/coin/bananium, +/obj/machinery/light/small/directional/east, +/turf/open/floor/carpet, +/area/virtual_domain/powered) +"uX" = ( +/obj/effect/mapping_helpers/no_lava, +/mob/living/basic/clown, +/turf/open/floor/noslip, +/area/virtual_domain/powered) +"uY" = ( +/turf/closed/mineral/bananium, +/area/virtual_domain/powered) +"uZ" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/obj/structure/table, +/obj/item/paper/crumpled/bloody/ruins/lavaland/clown_planet/escape, +/obj/item/pen/fourcolor, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"wz" = ( +/obj/machinery/light/small/directional/south, +/obj/effect/mapping_helpers/no_lava, +/mob/living/basic/clown, +/turf/open/floor/noslip, +/area/virtual_domain/powered) +"xt" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"yd" = ( +/obj/effect/decal/cleanable/cobweb/cobweb2, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"yz" = ( +/obj/structure/disposalpipe/segment{ + dir = 10 + }, +/turf/closed/wall/r_wall, +/area/virtual_domain/powered) +"yS" = ( +/obj/structure/marker_beacon/yellow, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"yZ" = ( +/obj/structure/disposalpipe/segment{ + dir = 9 + }, +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"zm" = ( +/obj/effect/decal/cleanable/cobweb, +/obj/effect/turf_decal/tile/red/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"zA" = ( +/obj/structure/statue/bananium/clown, +/turf/open/floor/carpet, +/area/virtual_domain/powered) +"zF" = ( +/obj/structure/disposalpipe/trunk{ + dir = 4 + }, +/obj/structure/disposaloutlet{ + dir = 8 + }, +/turf/open/floor/noslip, +/area/virtual_domain/powered) +"Aa" = ( +/obj/structure/disposalpipe/segment{ + dir = 9 + }, +/obj/effect/turf_decal/tile/red/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"Bi" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/obj/effect/decal/cleanable/cobweb, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"Cp" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/obj/machinery/light/small/directional/west, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"Cs" = ( +/obj/item/bikehorn, +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"Dh" = ( +/turf/closed/wall/r_wall, +/area/virtual_domain/powered) +"Do" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"DL" = ( +/obj/structure/disposalpipe/segment{ + dir = 5 + }, +/obj/item/bikehorn, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"Ex" = ( +/obj/structure/disposalpipe/segment{ + dir = 10 + }, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"FI" = ( +/obj/item/reagent_containers/cup/glass/trophy/gold_cup, +/obj/structure/table/glass, +/turf/open/floor/carpet, +/area/virtual_domain/powered) +"Gg" = ( +/obj/structure/table/glass, +/obj/item/gun/magic/staff/honk, +/turf/open/floor/carpet, +/area/virtual_domain/powered) +"Hq" = ( +/obj/structure/disposalpipe/segment{ + dir = 6 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Hr" = ( +/obj/structure/table/glass, +/obj/item/clothing/shoes/clown_shoes/banana_shoes, +/turf/open/floor/carpet, +/area/virtual_domain/powered) +"HQ" = ( +/obj/structure/disposalpipe/segment{ + dir = 10 + }, +/obj/machinery/light/small/directional/east, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"Ie" = ( +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"Iz" = ( +/obj/structure/disposalpipe/segment{ + dir = 10 + }, +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"IN" = ( +/obj/structure/disposalpipe/segment{ + dir = 6 + }, +/turf/closed/wall/r_wall, +/area/virtual_domain/powered) +"IY" = ( +/turf/open/indestructible/honk, +/area/virtual_domain/powered) +"Jv" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/indestructible/honk, +/area/virtual_domain/powered) +"JB" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/obj/machinery/light/small/directional/north, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"Ka" = ( +/obj/effect/decal/cleanable/food/pie_smudge, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"Kh" = ( +/obj/effect/mob_spawn/corpse/human/damaged, +/obj/effect/decal/cleanable/blood/old, +/turf/open/indestructible/honk, +/area/virtual_domain/powered) +"KG" = ( +/obj/item/pickaxe, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"KI" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/turf/closed/wall/r_wall, +/area/lavaland/surface/outdoors/virtual_domain) +"Lv" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/obj/machinery/light/small/directional/east, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"Nv" = ( +/obj/effect/decal/cleanable/cobweb, +/turf/open/indestructible/honk, +/area/virtual_domain/powered) +"NB" = ( +/obj/machinery/disposal/delivery_chute, +/obj/structure/disposalpipe/trunk{ + dir = 1 + }, +/turf/open/floor/carpet, +/area/virtual_domain/powered) +"NL" = ( +/obj/machinery/disposal/delivery_chute{ + desc = "The following is engraved upon the chute: A FATE WORSE THAN DEATH LIES WITHIN"; + dir = 1; + name = "THE TRIAL OF HONKITUDE" + }, +/obj/structure/disposalpipe/trunk, +/obj/effect/mapping_helpers/no_lava, +/turf/open/floor/noslip, +/area/virtual_domain/powered) +"NW" = ( +/obj/structure/table/glass, +/obj/item/reagent_containers/spray/waterflower/superlube, +/turf/open/floor/carpet, +/area/virtual_domain/powered) +"Ok" = ( +/obj/item/bikehorn, +/obj/structure/disposalpipe/segment{ + dir = 10 + }, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"Ov" = ( +/obj/structure/disposalpipe/segment{ + dir = 6 + }, +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"PJ" = ( +/obj/structure/disposalpipe/trunk, +/obj/structure/disposaloutlet{ + dir = 1 + }, +/obj/effect/mapping_helpers/no_lava, +/turf/open/floor/noslip, +/area/virtual_domain/powered) +"PM" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"PQ" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/obj/item/pickaxe, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"QP" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"QX" = ( +/obj/structure/closet/crate/secure/bitrunning/encrypted, +/turf/open/floor/carpet, +/area/virtual_domain/powered) +"Rh" = ( +/obj/structure/disposalpipe/segment{ + dir = 5 + }, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"Rx" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/obj/structure/table, +/obj/item/flashlight/lamp/bananalamp, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"RU" = ( +/obj/structure/disposalpipe/segment{ + invisibility = 101 + }, +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"Sg" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/obj/effect/decal/cleanable/food/pie_smudge, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"Sm" = ( +/obj/structure/disposalpipe/segment{ + dir = 5 + }, +/turf/closed/wall/r_wall, +/area/virtual_domain/powered) +"Tm" = ( +/obj/effect/decal/cleanable/food/pie_smudge, +/obj/structure/disposalpipe/segment{ + invisibility = 101 + }, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"Tx" = ( +/obj/structure/disposalpipe/segment{ + invisibility = 101 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"TH" = ( +/obj/structure/disposalpipe/trunk{ + dir = 4 + }, +/obj/structure/disposaloutlet{ + dir = 8 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"TK" = ( +/obj/structure/disposalpipe/segment{ + invisibility = 101 + }, +/turf/closed/wall/r_wall, +/area/virtual_domain/powered) +"Ug" = ( +/obj/machinery/light/small/directional/north, +/turf/open/floor/carpet, +/area/virtual_domain/powered) +"UL" = ( +/obj/effect/decal/cleanable/oil, +/obj/structure/disposalpipe/segment{ + dir = 10 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"UN" = ( +/obj/structure/disposalpipe/segment{ + dir = 10 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"UQ" = ( +/obj/structure/disposalpipe/segment{ + invisibility = 101 + }, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"UY" = ( +/obj/structure/disposalpipe/segment{ + dir = 5 + }, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"Vv" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/turf/closed/wall/r_wall, +/area/virtual_domain/powered) +"Vx" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"VI" = ( +/obj/structure/disposalpipe/segment{ + dir = 10 + }, +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"VQ" = ( +/turf/open/floor/noslip, +/area/virtual_domain/powered) +"Ww" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/tile/yellow/fourcorners, +/turf/open/indestructible/permalube, +/area/virtual_domain/powered) +"WB" = ( +/obj/machinery/disposal/delivery_chute{ + dir = 1 + }, +/obj/structure/disposalpipe/trunk, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"WT" = ( +/obj/machinery/door/airlock/bananium, +/turf/open/indestructible/honk, +/area/virtual_domain/powered) +"WX" = ( +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"Xp" = ( +/obj/machinery/light/directional/south, +/turf/open/indestructible/honk, +/area/virtual_domain/powered) +"XB" = ( +/obj/machinery/light/directional/north, +/turf/open/indestructible/honk, +/area/virtual_domain/powered) +"Yb" = ( +/obj/effect/decal/cleanable/cobweb/cobweb2, +/turf/open/indestructible/honk, +/area/virtual_domain/powered) +"YP" = ( +/obj/structure/disposalpipe/segment{ + dir = 4; + invisibility = 101 + }, +/turf/open/indestructible/white, +/area/virtual_domain/powered) +"ZR" = ( +/obj/structure/disposalpipe/trunk{ + dir = 8 + }, +/obj/structure/disposaloutlet{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) + +(1,1,1) = {" +Ie +Ie +rT +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +rr +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(2,1,1) = {" +Ie +Ie +Vx +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Vx +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(3,1,1) = {" +Ie +Ie +Vx +Ie +Ie +Ie +Ie +Ie +Dh +Dh +Dh +Dh +Dh +Dh +Dh +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Dh +Dh +Dh +Dh +Dh +Ie +Ie +Ie +Ie +Ie +Ie +Vx +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(4,1,1) = {" +Ie +Ie +Vx +Ie +Ie +Ie +Dh +Dh +Dh +ik +ik +ik +ik +ik +Dh +Dh +Ie +Ie +Ie +Ie +Ie +Dh +Dh +ik +ik +ik +Dh +Dh +Dh +Ie +Ie +Ie +Ie +Vx +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(5,1,1) = {" +Ie +Ie +Vx +Ie +Ie +Dh +Dh +ik +ik +ik +Hq +Sm +Hq +Sm +ik +Dh +Dh +Ie +Ie +Ie +Dh +Dh +ik +ik +tq +mD +ik +ik +Dh +Dh +Ie +Ie +Ie +Vx +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(6,1,1) = {" +Ie +Ie +Vx +Ie +Dh +Dh +ik +ik +IN +Tx +bU +ai +yZ +aP +Sm +ik +Dh +Dh +Dh +Dh +Dh +Nv +IY +tq +ik +ik +ik +ik +ik +Dh +Dh +Ie +Ie +Vx +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(7,1,1) = {" +Ie +Ie +Vx +Ie +Dh +ik +ik +Dh +Bi +cw +UQ +lr +UQ +UY +Vv +ik +Dh +IY +Jv +IY +Dh +IY +Jv +Kh +IY +tq +ik +tq +ik +ik +Dh +Ie +Ie +Vx +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(8,1,1) = {" +Ie +Ie +Vx +Dh +Dh +ik +IN +Tm +lx +Ww +cw +UQ +Sm +Vv +Vv +Dh +zm +oA +IY +Jv +Jv +IY +Jv +IY +IY +IY +Dh +ik +mD +ik +Dh +Dh +Ie +Vx +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(9,1,1) = {" +Ie +Ie +Vx +Dh +ik +ik +UN +UQ +UY +Ww +Vv +TH +Vv +YP +Cp +uY +Dh +sq +oA +IY +Dh +Dh +Jv +Dh +IY +IY +IY +tq +ik +ik +ik +Dh +Ie +Vx +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +tF +"} +(10,1,1) = {" +Ie +Ie +Vx +Dh +ik +IN +UQ +UQ +yZ +Do +Do +Vv +YP +YP +YP +KG +uY +Dh +Dh +oA +IY +IY +Jv +IY +IY +gH +Jv +Xp +Dh +ik +ik +Dh +by +KI +by +by +by +by +by +by +by +by +by +Ie +"} +(11,1,1) = {" +Ie +Ie +Vx +Dh +ik +yz +fh +UQ +UY +Vv +Ww +Vv +YP +YP +tt +bp +WX +oA +oA +oA +IY +Dh +IY +IY +Jv +Jv +IY +IY +ik +tq +ik +Dh +by +iR +PM +PM +PM +PM +PM +PM +PM +PM +by +Ie +"} +(12,1,1) = {" +Ie +Ie +Vx +Dh +ik +Hq +TK +qM +yZ +Ww +Ww +Vv +YP +PQ +tt +bp +uY +Dh +oA +oA +IY +IY +Dh +IY +IY +IY +IY +Jv +ik +tq +ik +ik +by +iR +yS +PM +PM +PM +PM +PM +yS +PM +by +Ie +"} +(13,1,1) = {" +Ie +Ie +Vx +Dh +ik +UN +UQ +UQ +DL +Ww +yz +lx +Vv +YP +Lv +WX +Dh +Dh +oA +IY +IY +Dh +Dh +IY +IY +Dh +IY +Jv +ik +mD +tq +ik +by +iR +PM +PM +PM +PM +PM +PM +PM +PM +by +Ie +"} +(14,1,1) = {" +Ie +Dh +Vv +nE +nE +mD +cw +UQ +lx +Ex +Tm +UQ +lx +Vv +Vv +ps +TK +Sm +Dh +Dh +Dh +zA +rg +Dh +XB +IY +Jv +gH +IY +ik +tq +ik +by +iR +PM +QP +QP +QP +QP +QP +xt +PM +by +Ie +"} +(15,1,1) = {" +Ie +Dh +ij +hK +nE +Dh +yz +UQ +UQ +UQ +UQ +bi +UQ +yZ +Do +Iz +kn +Ww +Dh +Dh +FI +mF +mF +mF +Dh +IY +Jv +Jv +IY +ik +tq +ik +by +ZR +PM +QP +QP +QP +QP +QP +QP +PM +by +Ie +"} +(16,1,1) = {" +Ie +Dh +VQ +uX +NL +TK +Tx +UQ +TK +UQ +cW +TK +Tm +UQ +yZ +pl +Do +Ex +UY +Dh +Ug +oI +NW +mF +Dh +Dh +Jv +IY +IY +ik +tq +ik +by +PM +PM +QP +QP +QP +QP +QP +QP +PM +by +Ie +"} +(17,1,1) = {" +Ie +Dh +VQ +bR +wz +Dh +Hq +UQ +Sm +cw +UY +cw +UQ +UQ +Tx +gy +Ex +UY +Iz +TK +NB +mF +aI +mF +WT +IY +Jv +IY +Dh +ik +tq +ik +by +PM +PM +QP +QP +QP +QP +QP +QP +PM +by +Ie +"} +(18,1,1) = {" +Ie +Dh +VQ +uX +PJ +TK +sT +kn +Do +Do +Vv +Do +Ov +UQ +UY +Ok +mE +rH +pl +Dh +mF +Hr +Gg +mF +Dh +IY +IY +IY +IY +ik +tq +ik +by +PM +PM +QP +QP +QP +QP +QP +QP +PM +by +Ie +"} +(19,1,1) = {" +Ie +Dh +zF +uX +nE +Dh +Dh +Ww +Ww +Ww +Do +Do +Do +lP +Ex +UY +Ka +Vv +tv +Dh +FI +mF +mF +QX +Dh +IY +IY +IY +IY +ik +tq +ik +by +lj +PM +QP +QP +QP +QP +QP +QP +PM +by +Ie +"} +(20,1,1) = {" +Ie +Dh +Vv +nE +nE +ik +Dh +Ww +Ww +Cs +Do +Do +Vv +Dh +Dh +bQ +Dh +ba +Dh +IY +Dh +zA +tI +Dh +XB +IY +Jv +Jv +IY +ik +tq +ik +by +iR +PM +QP +QP +QP +QP +QP +gr +PM +by +Ie +"} +(21,1,1) = {" +Ie +Ie +Vx +Dh +ik +Dh +Dh +Do +Do +Do +Ww +Do +Vv +rh +ed +gK +Dh +UL +Sm +IY +IY +Dh +Dh +Kh +IY +IY +Jv +IY +ik +tq +mD +ik +by +iR +PM +PM +PM +PM +PM +PM +PM +PM +by +Ie +"} +(22,1,1) = {" +Ie +Ie +Vx +Dh +ik +Dh +Dh +JB +Sg +Vv +Ww +Vv +uZ +YP +bp +bp +uY +Dh +bQ +oA +IY +IY +Dh +IY +Jv +IY +IY +IY +ik +tq +ik +ik +by +iR +PM +PM +yS +PM +PM +PM +PM +PM +by +Ie +"} +(23,1,1) = {" +Ie +Ie +Vx +Dh +ik +cM +eE +lx +Vv +ki +Ww +Vv +Rx +YP +bp +bp +WB +TK +Aa +Dh +IY +IY +Jv +Jv +Jv +IY +aM +Xp +Dh +tq +ik +Dh +by +iR +PM +PM +PM +PM +PM +PM +PM +PM +by +Ie +"} +(24,1,1) = {" +Ie +Ie +Vx +Dh +ik +Dh +Dh +lP +Do +Do +Cs +bQ +YP +bq +Rh +WX +uY +Dh +oA +oA +IY +IY +Jv +Jv +IY +IY +Dh +Dh +ik +mD +ik +Dh +by +KI +by +by +by +by +by +by +by +by +by +Ie +"} +(25,1,1) = {" +Ie +Ie +Vx +Dh +ik +Dh +Dh +pl +Do +Vv +Do +Vv +Vv +rh +lm +uY +Dh +sq +oA +IY +IY +IY +IY +IY +Dh +IY +IY +ik +mD +ik +ik +Dh +Ie +Vx +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +"} +(26,1,1) = {" +Ie +Ie +Vx +Dh +ik +ik +Dh +yd +Do +Do +Do +Ex +lx +Vv +Dh +Dh +oA +oA +IY +IY +IY +Jv +aM +IY +IY +IY +Dh +ik +tq +ik +Dh +Dh +Ie +Vx +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(27,1,1) = {" +Ie +Ie +Vx +Dh +Dh +ik +Dh +Dh +Ex +lx +HQ +UQ +UQ +bU +Dh +ik +Dh +Yb +IY +IY +Dh +IY +IY +Dh +IY +IY +ik +mD +ik +ik +Dh +Ie +Ie +Vx +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(28,1,1) = {" +Ie +Ie +Vx +Ie +Dh +Dh +ik +ik +Dh +mD +Dh +Ka +lP +mD +Dh +ik +Dh +Dh +Dh +Dh +Dh +IY +IY +IY +ik +ik +ik +ik +ik +Dh +Dh +Ie +Ie +Vx +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(29,1,1) = {" +Ie +Ie +Vx +Ie +Ie +Dh +Dh +ik +ik +ik +tq +tq +tq +Dh +ik +Dh +Dh +Ie +Ie +Ie +Dh +Dh +ik +ik +mD +tq +ik +ik +Dh +Dh +Ie +Ie +Ie +Vx +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(30,1,1) = {" +Ie +Ie +Vx +Ie +Ie +Ie +Dh +Dh +Dh +ik +ik +ik +ik +ik +Dh +Dh +Ie +Ie +Ie +Ie +Ie +Dh +Dh +ik +ik +ik +Dh +Dh +Dh +Ie +Ie +Ie +Ie +Vx +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(31,1,1) = {" +Ie +Ie +Vx +Ie +Ie +Ie +Ie +Ie +Dh +Dh +Dh +Dh +Dh +Dh +Dh +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Dh +Dh +Dh +Dh +Dh +Ie +Ie +Ie +Ie +Ie +Ie +Vx +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(32,1,1) = {" +Ie +Ie +VI +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +RU +ly +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} +(33,1,1) = {" +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +Ie +hY +hY +hY +hY +hY +hY +hY +hY +hY +hY +"} diff --git a/_maps/virtual_domains/colossus.dmm b/_maps/virtual_domains/colossus.dmm new file mode 100644 index 00000000000..a9c3c6e6d79 --- /dev/null +++ b/_maps/virtual_domains/colossus.dmm @@ -0,0 +1,2250 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"c" = ( +/obj/structure/marker_beacon/olive, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"e" = ( +/obj/structure/marker_beacon/bronze, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"j" = ( +/obj/structure/marker_beacon/cerulean, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"k" = ( +/turf/closed/mineral/volcanic/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"l" = ( +/obj/structure/marker_beacon/lime, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"m" = ( +/obj/structure/marker_beacon/violet, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"o" = ( +/obj/effect/mob_spawn/corpse/human/miner, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"p" = ( +/mob/living/simple_animal/hostile/megafauna/colossus/virtual_domain, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"q" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"r" = ( +/obj/machinery/light/small/blacklight/directional/south, +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/virtual_domain/powered) +"s" = ( +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"u" = ( +/obj/structure/marker_beacon/indigo, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"v" = ( +/turf/open/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"x" = ( +/obj/structure/marker_beacon/purple, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"z" = ( +/obj/structure/marker_beacon/jade, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"B" = ( +/obj/structure/marker_beacon/teal, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"D" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"L" = ( +/obj/structure/marker_beacon/yellow, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"N" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"T" = ( +/obj/structure/marker_beacon/burgundy, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"U" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"W" = ( +/obj/structure/marker_beacon/fuchsia, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) + +(1,1,1) = {" +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +q +"} +(2,1,1) = {" +v +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +v +"} +(3,1,1) = {" +v +k +a +a +k +k +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +k +k +a +a +a +a +a +a +a +a +k +k +a +a +a +a +a +a +k +k +k +v +"} +(4,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(5,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +s +k +v +"} +(6,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +m +a +a +a +a +a +a +a +a +a +a +a +a +s +s +k +v +"} +(7,1,1) = {" +v +k +a +a +a +a +o +a +a +a +a +a +a +a +a +a +c +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +a +a +a +a +s +k +v +"} +(8,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +k +a +a +a +k +k +v +"} +(9,1,1) = {" +v +k +a +a +a +a +a +a +s +s +s +s +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +k +v +"} +(10,1,1) = {" +v +k +k +a +a +a +a +a +k +k +k +s +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +k +v +"} +(11,1,1) = {" +v +k +k +a +a +a +a +a +k +k +k +s +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +k +a +a +a +a +a +a +a +a +s +s +a +a +a +a +a +k +v +"} +(12,1,1) = {" +v +k +k +a +a +a +a +a +s +k +s +s +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +k +a +a +a +a +a +a +a +s +a +a +a +a +a +k +v +"} +(13,1,1) = {" +v +k +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +k +k +a +a +a +a +a +u +a +a +a +a +a +a +a +k +v +"} +(14,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +k +k +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(15,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(16,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +e +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(17,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +j +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(18,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(19,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(20,1,1) = {" +v +k +a +a +a +a +a +x +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(21,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +r +U +U +U +U +U +N +a +k +v +"} +(22,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +U +U +U +U +U +U +a +k +v +"} +(23,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +p +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +U +U +U +U +U +U +a +k +v +"} +(24,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +l +a +a +U +U +U +U +U +U +a +k +v +"} +(25,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +U +U +U +U +U +U +a +k +v +"} +(26,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +U +U +U +U +U +U +a +k +v +"} +(27,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +o +a +r +U +U +U +U +U +D +a +k +v +"} +(28,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +T +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(29,1,1) = {" +v +k +a +a +a +a +a +a +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +W +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(30,1,1) = {" +v +k +a +a +a +a +a +a +k +k +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(31,1,1) = {" +v +k +a +a +a +a +a +a +k +k +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(32,1,1) = {" +v +k +a +a +a +a +a +a +a +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(33,1,1) = {" +v +k +a +a +a +a +a +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +z +a +a +a +a +a +a +a +k +v +"} +(34,1,1) = {" +v +k +k +a +a +a +a +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(35,1,1) = {" +v +k +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +l +a +a +a +a +a +a +a +a +k +k +k +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(36,1,1) = {" +v +k +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +k +k +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(37,1,1) = {" +v +k +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +k +v +"} +(38,1,1) = {" +v +k +a +a +a +s +s +a +a +a +a +a +B +a +a +a +a +k +a +a +a +a +a +a +s +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +k +v +"} +(39,1,1) = {" +v +k +a +a +a +s +s +a +a +a +a +a +a +a +a +a +k +k +k +a +a +a +a +a +s +s +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(40,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(41,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +L +a +a +a +a +a +a +a +k +v +"} +(42,1,1) = {" +v +k +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +k +v +"} +(43,1,1) = {" +v +k +a +k +k +k +k +a +a +a +a +a +k +k +k +k +a +a +a +k +k +k +k +k +a +a +a +a +a +a +k +k +k +k +a +a +a +a +a +a +k +k +a +a +k +v +"} +(44,1,1) = {" +v +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +k +v +"} +(45,1,1) = {" +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +v +"} diff --git a/_maps/virtual_domains/gondola_asteroid.dmm b/_maps/virtual_domains/gondola_asteroid.dmm new file mode 100644 index 00000000000..d6377a4a4c1 --- /dev/null +++ b/_maps/virtual_domains/gondola_asteroid.dmm @@ -0,0 +1,1784 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/closed/indestructible/binary, +/area/ruin/space/has_grav/powered/virtual_domain) +"c" = ( +/turf/open/space/basic, +/area/space) +"e" = ( +/turf/open/misc/asteroid/airless, +/area/ruin/space/has_grav/powered/virtual_domain) +"g" = ( +/obj/structure/marker_beacon{ + light_color = "#FFE8AA"; + light_range = 20 + }, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"h" = ( +/turf/closed/mineral/random, +/area/ruin/space/has_grav/powered/virtual_domain) +"m" = ( +/obj/structure/closet/crate/secure/bitrunning/encrypted/gondola, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"n" = ( +/obj/structure/flora/bush/fullgrass/style_random, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"o" = ( +/turf/template_noop, +/area/template_noop) +"q" = ( +/obj/structure/flora/tree/palm, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"r" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"s" = ( +/obj/structure/flora/bush/sparsegrass/style_random, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"t" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"w" = ( +/obj/structure/water_source/puddle, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"y" = ( +/obj/structure/flora/bush/stalky/style_random, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"z" = ( +/mob/living/simple_animal/pet/gondola/virtual_domain, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"A" = ( +/obj/structure/chair/wood{ + dir = 8 + }, +/turf/template_noop, +/area/virtual_domain/safehouse) +"C" = ( +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"D" = ( +/obj/structure/flora/bush/flowers_br/style_random, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"F" = ( +/obj/structure/flora/bush/grassy/style_random, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"I" = ( +/obj/structure/flora/bush/reed/style_random, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"J" = ( +/obj/structure/flora/bush/flowers_yw/style_random, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"K" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/ruin/space/has_grav/powered/virtual_domain) +"M" = ( +/obj/structure/table/wood, +/obj/item/storage/bag/tray, +/obj/item/kitchen/fork, +/obj/item/knife/kitchen, +/turf/template_noop, +/area/virtual_domain/safehouse) +"N" = ( +/obj/structure/flora/bush/large/style_random, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"O" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"Q" = ( +/obj/structure/flora/bush/lavendergrass/style_random, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"T" = ( +/obj/structure/flora/bush/sunny/style_random, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"V" = ( +/obj/structure/flora/coconuts, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) +"W" = ( +/obj/structure/flora/bush/ferny/style_random, +/turf/open/floor/grass, +/area/ruin/space/has_grav/powered/virtual_domain) + +(1,1,1) = {" +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +a +a +a +a +a +a +a +a +o +o +o +o +o +o +o +o +o +o +"} +(2,1,1) = {" +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +a +a +a +h +h +h +h +h +h +a +a +o +o +o +o +o +o +o +o +o +"} +(3,1,1) = {" +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +a +a +a +a +a +h +h +h +h +h +h +h +h +h +a +a +K +o +o +o +o +o +o +o +"} +(4,1,1) = {" +o +o +o +o +o +o +o +o +o +o +o +o +o +o +a +a +a +a +a +a +a +h +h +h +h +h +h +h +h +h +h +h +h +h +e +a +o +o +o +o +o +o +o +"} +(5,1,1) = {" +o +o +o +o +o +o +o +o +o +o +o +o +o +a +a +h +h +h +h +h +h +h +h +h +h +h +h +h +h +C +h +h +h +h +h +a +o +o +o +o +o +o +o +"} +(6,1,1) = {" +o +o +o +o +o +o +o +o +a +a +a +a +a +a +h +h +h +h +h +h +h +h +h +h +h +C +C +C +C +C +C +C +h +h +h +a +o +o +o +o +o +o +o +"} +(7,1,1) = {" +o +o +o +o +o +o +o +a +a +h +h +h +h +h +h +h +h +h +h +h +h +h +h +C +C +C +C +J +C +C +C +z +C +h +h +a +o +o +o +o +o +o +o +"} +(8,1,1) = {" +o +o +o +o +o +a +a +a +h +h +h +h +h +h +h +h +h +h +h +h +h +C +C +C +C +Q +C +q +C +h +h +h +h +h +e +a +a +a +a +a +a +a +a +"} +(9,1,1) = {" +o +o +o +o +a +a +h +h +h +h +h +h +h +h +h +h +C +h +C +C +C +C +C +C +C +C +V +C +C +C +C +h +h +h +e +c +c +c +c +c +c +c +a +"} +(10,1,1) = {" +o +o +a +a +a +h +h +h +h +h +h +h +h +C +q +C +C +W +C +C +V +C +C +q +C +C +C +C +F +C +C +h +h +h +e +c +c +c +c +c +c +c +a +"} +(11,1,1) = {" +o +a +a +h +h +h +h +h +h +h +h +h +h +h +h +C +C +C +C +C +N +C +C +C +C +C +C +s +C +C +C +h +h +h +e +c +c +c +c +c +c +c +a +"} +(12,1,1) = {" +o +a +h +h +h +h +h +h +h +h +h +h +h +h +C +s +I +J +C +C +g +C +C +V +C +z +C +y +C +g +C +h +h +h +e +c +c +c +c +c +c +c +a +"} +(13,1,1) = {" +a +a +h +h +h +h +h +h +h +h +h +h +C +C +C +C +Q +Q +C +z +C +C +C +C +C +C +C +s +Q +C +C +h +h +h +e +c +c +c +c +c +c +c +a +"} +(14,1,1) = {" +a +h +h +h +h +h +h +h +h +h +h +h +C +C +w +C +s +C +W +C +C +C +C +C +C +N +C +C +C +C +h +h +h +h +e +c +c +c +c +c +c +c +a +"} +(15,1,1) = {" +a +h +h +h +h +h +h +h +h +h +h +z +C +C +C +C +y +C +C +C +F +s +C +C +C +C +C +w +C +h +h +h +h +h +e +c +c +c +c +c +c +c +a +"} +(16,1,1) = {" +a +h +h +h +h +h +h +h +h +h +h +h +h +h +h +C +C +C +C +C +s +Q +C +C +C +C +C +C +C +C +h +h +h +h +e +c +c +c +c +c +c +c +a +"} +(17,1,1) = {" +a +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +C +C +C +Q +D +C +C +C +C +q +C +C +C +C +h +h +h +h +t +t +t +t +t +O +c +a +"} +(18,1,1) = {" +a +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +C +n +I +C +C +C +C +C +C +C +C +C +C +C +h +h +h +t +t +M +M +t +t +c +a +"} +(19,1,1) = {" +a +h +h +h +h +h +h +h +h +h +C +n +C +h +h +h +h +h +C +C +C +C +C +C +s +T +C +C +C +s +C +C +h +C +C +t +t +A +A +t +t +c +a +"} +(20,1,1) = {" +a +h +h +h +h +h +h +h +h +C +C +C +C +C +C +h +h +h +C +C +q +V +C +C +C +J +C +C +C +C +C +C +C +C +C +t +t +t +t +t +t +c +a +"} +(21,1,1) = {" +a +e +h +h +h +h +h +h +h +z +C +C +g +C +C +C +C +C +C +C +C +C +C +C +C +C +C +C +C +C +C +C +C +C +C +t +t +t +t +t +t +c +a +"} +(22,1,1) = {" +a +e +e +h +h +h +h +h +h +C +C +C +q +C +s +s +C +C +W +C +m +C +C +C +g +C +z +C +C +C +C +C +h +h +h +t +t +t +t +t +t +c +a +"} +(23,1,1) = {" +a +e +e +h +h +h +h +h +h +C +C +C +C +C +C +y +C +C +C +C +C +C +C +C +C +C +C +C +C +q +C +C +h +h +h +t +t +t +t +t +r +c +a +"} +(24,1,1) = {" +a +e +e +h +h +h +h +h +h +C +C +V +C +C +C +C +C +C +C +w +C +z +N +C +C +C +N +C +C +C +C +C +h +h +h +c +c +c +c +c +c +c +a +"} +(25,1,1) = {" +a +a +e +e +h +h +h +h +n +C +C +C +C +C +z +C +C +C +C +C +C +C +C +C +F +C +C +C +C +C +C +C +h +h +h +c +c +c +c +c +c +c +a +"} +(26,1,1) = {" +o +a +e +e +h +h +h +C +C +C +C +C +C +C +C +C +s +y +C +C +C +C +C +C +I +F +C +C +C +C +C +h +h +h +c +c +c +c +c +c +c +c +a +"} +(27,1,1) = {" +o +a +e +e +h +h +h +C +C +C +w +C +C +C +C +F +D +s +C +J +C +C +C +C +C +C +q +C +C +V +C +h +h +h +c +c +c +c +c +c +c +c +a +"} +(28,1,1) = {" +o +a +e +e +h +h +h +h +C +C +C +C +C +C +C +C +C +C +C +g +F +s +C +C +C +C +C +C +C +C +h +h +h +c +c +c +c +c +c +c +c +c +a +"} +(29,1,1) = {" +o +a +a +e +e +h +h +h +C +C +C +C +C +n +C +C +C +C +C +C +s +y +D +C +C +C +C +w +C +h +h +h +h +c +c +c +c +c +c +c +c +c +a +"} +(30,1,1) = {" +o +o +a +e +e +h +h +C +C +C +n +C +C +C +C +C +C +C +C +C +C +C +C +C +C +s +C +C +C +h +h +h +e +e +c +c +c +c +c +c +c +c +a +"} +(31,1,1) = {" +o +o +a +e +h +h +C +g +J +C +s +C +C +C +h +C +C +C +C +C +V +C +C +C +C +C +C +C +h +h +h +e +e +e +c +c +c +c +c +c +c +c +a +"} +(32,1,1) = {" +o +o +a +h +h +h +h +C +C +C +C +C +C +h +h +h +C +C +C +q +C +C +C +C +C +C +h +h +h +h +e +e +e +h +h +a +a +a +a +a +a +a +a +"} +(33,1,1) = {" +o +o +a +h +h +h +C +C +C +C +C +C +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +e +e +h +h +h +a +o +o +o +o +o +o +o +"} +(34,1,1) = {" +o +o +a +h +h +C +C +C +C +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +h +a +a +h +h +h +a +a +o +o +o +o +o +o +o +"} +(35,1,1) = {" +o +o +a +h +h +h +h +h +h +h +h +h +h +h +e +e +e +e +e +h +h +h +h +h +a +a +a +a +a +a +a +h +h +h +a +a +o +o +o +o +o +o +o +"} +(36,1,1) = {" +o +o +a +a +h +h +h +h +h +h +h +h +h +a +a +a +a +a +a +a +a +a +a +a +a +o +o +o +o +o +a +a +a +a +a +o +o +o +o +o +o +o +o +"} +(37,1,1) = {" +o +o +o +a +a +a +a +a +a +a +a +a +a +a +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +"} diff --git a/_maps/virtual_domains/hierophant.dmm b/_maps/virtual_domains/hierophant.dmm new file mode 100644 index 00000000000..02b11ad4e1e --- /dev/null +++ b/_maps/virtual_domains/hierophant.dmm @@ -0,0 +1,1066 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/open/indestructible/hierophant, +/area/lavaland/surface/outdoors/virtual_domain) +"c" = ( +/obj/effect/light_emitter{ + set_cap = 3; + set_luminosity = 5 + }, +/turf/open/indestructible/hierophant/two, +/area/lavaland/surface/outdoors/virtual_domain) +"h" = ( +/obj/effect/light_emitter{ + set_cap = 3; + set_luminosity = 5 + }, +/turf/open/indestructible/hierophant, +/area/lavaland/surface/outdoors/virtual_domain) +"n" = ( +/obj/structure/marker_beacon/indigo, +/turf/open/indestructible/hierophant, +/area/lavaland/surface/outdoors/virtual_domain) +"o" = ( +/turf/template_noop, +/area/template_noop) +"r" = ( +/turf/closed/indestructible/riveted/hierophant, +/area/lavaland/surface/outdoors/virtual_domain) +"u" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"w" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"y" = ( +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"E" = ( +/mob/living/simple_animal/hostile/megafauna/hierophant/virtual_domain, +/turf/open/indestructible/hierophant/two, +/area/lavaland/surface/outdoors/virtual_domain) +"H" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"K" = ( +/turf/open/indestructible/hierophant/two, +/area/lavaland/surface/outdoors/virtual_domain) +"N" = ( +/obj/machinery/light/small/blacklight/directional/south, +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/indestructible/hierophant, +/area/virtual_domain/powered) +"S" = ( +/obj/effect/mob_spawn/corpse/human/miner, +/turf/open/indestructible/hierophant, +/area/lavaland/surface/outdoors/virtual_domain) +"W" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"Y" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) + +(1,1,1) = {" +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +o +o +y +y +y +y +y +y +y +y +y +y +W +"} +(2,1,1) = {" +y +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +y +o +o +y +Y +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(3,1,1) = {" +y +r +a +a +a +a +a +a +a +a +a +a +h +a +a +a +a +a +a +a +a +a +a +r +y +o +o +y +Y +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(4,1,1) = {" +y +r +a +a +a +h +h +a +a +a +r +a +a +a +r +a +a +a +h +h +a +a +a +r +y +y +y +y +Y +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(5,1,1) = {" +y +r +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +r +r +y +y +Y +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(6,1,1) = {" +y +r +a +h +a +r +r +a +h +n +a +a +h +a +a +a +h +a +r +r +a +h +a +a +a +a +r +y +Y +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(7,1,1) = {" +y +r +a +h +a +r +r +a +h +a +a +a +h +a +a +a +h +a +r +r +a +h +a +a +a +a +a +r +Y +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(8,1,1) = {" +y +r +a +a +a +a +a +K +K +K +K +K +K +K +K +K +K +K +a +a +a +n +a +a +r +a +a +a +r +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(9,1,1) = {" +y +r +a +a +a +h +h +K +K +K +r +K +K +K +r +K +K +K +h +h +a +a +a +r +y +r +S +a +a +r +Y +Y +Y +Y +Y +Y +Y +y +"} +(10,1,1) = {" +y +r +a +a +a +a +a +K +K +K +K +K +c +K +K +K +K +K +a +a +a +a +a +r +y +y +r +a +a +N +w +w +w +w +w +H +Y +y +"} +(11,1,1) = {" +y +r +a +r +a +a +a +K +r +K +K +K +K +K +K +K +r +K +a +a +a +r +a +r +y +y +y +r +a +a +w +w +w +w +w +w +Y +y +"} +(12,1,1) = {" +y +r +a +a +a +a +a +K +K +K +K +K +K +K +K +K +K +K +a +a +a +a +a +r +y +y +y +r +a +a +w +w +w +w +w +w +Y +y +"} +(13,1,1) = {" +y +r +h +a +a +h +h +K +K +c +K +K +E +K +K +c +K +K +h +h +a +a +h +r +y +y +y +r +a +a +w +w +w +w +w +w +Y +y +"} +(14,1,1) = {" +y +r +a +a +a +a +a +K +K +K +K +K +K +K +K +K +K +K +a +a +a +a +a +r +y +y +y +r +a +a +w +w +w +w +w +w +Y +y +"} +(15,1,1) = {" +y +r +a +r +a +a +a +K +r +K +K +K +K +K +K +K +r +K +a +a +a +r +a +r +y +y +y +r +a +a +w +w +w +w +w +w +Y +y +"} +(16,1,1) = {" +y +r +a +a +a +a +a +K +K +K +K +K +c +K +K +K +K +K +a +a +a +a +a +r +y +y +r +a +a +N +w +w +w +w +w +u +Y +y +"} +(17,1,1) = {" +y +r +a +a +a +h +h +K +K +K +r +K +K +K +r +K +K +K +h +h +a +a +a +r +y +r +a +a +a +r +Y +Y +Y +Y +Y +Y +Y +y +"} +(18,1,1) = {" +y +r +a +a +a +a +a +K +K +K +K +K +K +K +K +K +K +K +a +a +a +a +a +a +r +a +a +a +r +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(19,1,1) = {" +y +r +a +h +a +r +r +a +h +a +a +a +h +a +a +a +h +a +r +r +a +h +a +a +a +a +a +r +Y +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(20,1,1) = {" +y +r +a +h +a +r +r +S +h +a +a +a +h +a +n +a +h +a +r +r +a +h +a +a +a +a +r +y +Y +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(21,1,1) = {" +y +r +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +r +r +y +y +Y +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(22,1,1) = {" +y +r +a +a +a +h +h +a +a +a +r +a +a +a +r +a +a +a +h +h +a +a +a +r +y +y +y +y +Y +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(23,1,1) = {" +y +r +a +a +a +a +a +a +a +a +a +a +h +a +a +a +a +a +a +a +a +a +a +r +y +o +o +y +Y +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(24,1,1) = {" +y +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +r +y +o +o +y +Y +Y +Y +Y +Y +Y +Y +Y +Y +y +"} +(25,1,1) = {" +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +y +o +o +y +y +y +y +y +y +y +y +y +y +y +"} diff --git a/_maps/virtual_domains/legion.dmm b/_maps/virtual_domains/legion.dmm new file mode 100644 index 00000000000..55843177ad0 --- /dev/null +++ b/_maps/virtual_domains/legion.dmm @@ -0,0 +1,6370 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"ah" = ( +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"ak" = ( +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"aI" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"aR" = ( +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"bd" = ( +/obj/structure/stone_tile/block, +/obj/structure/stone_tile/block{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"be" = ( +/obj/structure/stone_tile/block{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"bt" = ( +/obj/effect/decal/cleanable/blood, +/obj/effect/decal/cleanable/blood/drip, +/obj/effect/decal/cleanable/blood/footprints{ + dir = 1 + }, +/turf/open/floor/pod/dark, +/area/lavaland/surface/outdoors/virtual_domain) +"bu" = ( +/obj/structure/marker_beacon/bronze, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"ca" = ( +/obj/effect/mob_spawn/corpse/human/legioninfested, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"cf" = ( +/obj/structure/stone_tile/block{ + dir = 1 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"cp" = ( +/turf/template_noop, +/area/template_noop) +"dm" = ( +/obj/structure/stone_tile/block{ + dir = 1 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"dn" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/effect/mob_spawn/corpse/human/legioninfested, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"dr" = ( +/obj/structure/stone_tile/block{ + dir = 1 + }, +/obj/structure/stone_tile/block/cracked, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"dx" = ( +/obj/effect/decal/cleanable/blood/footprints{ + dir = 8 + }, +/obj/effect/decal/cleanable/blood/drip, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"dL" = ( +/obj/structure/stone_tile/block{ + dir = 4 + }, +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"dQ" = ( +/turf/closed/wall/mineral/titanium/survival/pod, +/area/lavaland/surface/outdoors/virtual_domain) +"et" = ( +/obj/structure/stone_tile/block/cracked, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"ew" = ( +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"eJ" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"fA" = ( +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"fG" = ( +/obj/structure/marker_beacon/violet, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"gh" = ( +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"gk" = ( +/obj/structure/necropolis_gate/locked, +/obj/structure/stone_tile/slab, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"gK" = ( +/obj/effect/decal/cleanable/blood/footprints, +/obj/effect/decal/cleanable/blood/drip, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"gQ" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"hc" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"hw" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile/block/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"hx" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"hU" = ( +/obj/structure/stone_tile/block{ + dir = 4 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"ib" = ( +/turf/closed/mineral/volcanic/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"ie" = ( +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile/block{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"iP" = ( +/obj/structure/fluff/drake_statue/falling, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"iR" = ( +/obj/structure/stone_tile/block{ + dir = 1 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"iV" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"jk" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"jt" = ( +/obj/structure/stone_tile/slab/cracked, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"jw" = ( +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"jN" = ( +/obj/machinery/sleeper/survival_pod, +/turf/open/floor/pod/dark, +/area/lavaland/surface/outdoors/virtual_domain) +"ka" = ( +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"kg" = ( +/turf/closed/indestructible/riveted/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"kT" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"kZ" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"ll" = ( +/obj/structure/stone_tile/cracked, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"lz" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"lC" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"lO" = ( +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"lT" = ( +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"mz" = ( +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"mG" = ( +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"nm" = ( +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"nu" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"nv" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"ny" = ( +/obj/structure/stone_tile, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"nI" = ( +/obj/structure/stone_tile/block, +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"nO" = ( +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/surrounding_tile{ + dir = 1 + }, +/obj/structure/stone_tile/center, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 8 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"ob" = ( +/obj/structure/necropolis_gate/legion_gate, +/obj/structure/necropolis_arch, +/obj/structure/stone_tile/slab, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"og" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"oo" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile/block/cracked{ + dir = 1 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"ox" = ( +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"oS" = ( +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"pP" = ( +/obj/structure/stone_tile/block{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"qo" = ( +/obj/structure/stone_tile/slab/cracked, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"qs" = ( +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"qW" = ( +/obj/effect/decal/cleanable/blood/footprints{ + dir = 1 + }, +/obj/machinery/door/airlock/survival_pod/glass, +/turf/open/floor/pod/dark, +/area/lavaland/surface/outdoors/virtual_domain) +"rt" = ( +/obj/effect/mob_spawn/corpse/human/miner, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"rU" = ( +/obj/structure/stone_tile/block{ + dir = 1 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"sd" = ( +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/block{ + dir = 4 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"sk" = ( +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"sz" = ( +/obj/structure/stone_tile/center, +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"sA" = ( +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"tk" = ( +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"tF" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/block{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"uK" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/obj/structure/stone_tile/block{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"vf" = ( +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"wq" = ( +/obj/structure/marker_beacon/teal, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"wy" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"xd" = ( +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"xm" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"xw" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"xD" = ( +/obj/structure/stone_tile/cracked, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"yu" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 1 + }, +/obj/structure/stone_tile/block, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"yZ" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"zg" = ( +/obj/machinery/light/small/directional/south, +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/virtual_domain/powered) +"zo" = ( +/obj/effect/turf_decal/mining/survival, +/turf/closed/wall/mineral/titanium/survival/pod, +/area/lavaland/surface/outdoors/virtual_domain) +"zW" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/center/cracked, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Ah" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/lavaland/surface/outdoors/virtual_domain) +"Aj" = ( +/obj/structure/marker_beacon/burgundy, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Ak" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/center/cracked, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 4 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"As" = ( +/obj/structure/marker_beacon/cerulean, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"AY" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Bo" = ( +/obj/structure/marker_beacon/indigo, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"BO" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"CX" = ( +/obj/effect/decal/cleanable/blood/drip, +/obj/effect/decal/cleanable/blood/footprints{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Dm" = ( +/turf/closed/mineral/random/volcanic, +/area/lavaland/surface/outdoors/virtual_domain) +"DP" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile/center, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Ek" = ( +/obj/structure/stone_tile/block, +/obj/structure/stone_tile/block/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Ep" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"Ez" = ( +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile/cracked, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"EC" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Fg" = ( +/obj/structure/stone_tile/surrounding/cracked{ + dir = 6 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Fp" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"Fq" = ( +/obj/structure/marker_beacon/fuchsia, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"FV" = ( +/obj/structure/stone_tile/block, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Gj" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Gn" = ( +/turf/closed/indestructible/riveted/boss/see_through, +/area/lavaland/surface/outdoors/virtual_domain) +"Go" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 1 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"GH" = ( +/obj/structure/fans, +/turf/open/floor/pod/dark, +/area/lavaland/surface/outdoors/virtual_domain) +"GM" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Hi" = ( +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Hu" = ( +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"Hw" = ( +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/surrounding_tile{ + dir = 1 + }, +/obj/structure/stone_tile/center/cracked, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 8 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"HK" = ( +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"HQ" = ( +/obj/structure/stone_tile/block/cracked, +/obj/structure/stone_tile/block{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"HZ" = ( +/obj/structure/stone_tile{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Ii" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Io" = ( +/obj/structure/marker_beacon/jade, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Ip" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"IB" = ( +/obj/structure/stone_tile, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"IG" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"IL" = ( +/obj/structure/stone_tile/surrounding, +/obj/structure/stone_tile/center/cracked, +/mob/living/simple_animal/hostile/megafauna/legion/virtual_domain, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"IQ" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Jc" = ( +/obj/structure/stone_tile/slab, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Jp" = ( +/obj/structure/stone_tile/block/cracked, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Jt" = ( +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Jw" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"JD" = ( +/obj/structure/fluff/drake_statue, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"KG" = ( +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Le" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Lx" = ( +/obj/effect/decal/cleanable/blood/footprints{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"LH" = ( +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile/block{ + dir = 8 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Ml" = ( +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Mm" = ( +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/center/cracked, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 1 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Mo" = ( +/obj/structure/stone_tile/block/cracked, +/obj/structure/stone_tile/block/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"MH" = ( +/obj/structure/stone_tile/cracked, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"MP" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"MW" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Nl" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 1 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/obj/structure/stone_tile/center, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"Ot" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 1 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/obj/structure/stone_tile/center, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Pv" = ( +/obj/effect/turf_decal/mining/survival{ + dir = 4 + }, +/turf/closed/wall/mineral/titanium/survival/pod, +/area/lavaland/surface/outdoors/virtual_domain) +"Px" = ( +/obj/structure/stone_tile{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"PO" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/obj/structure/stone_tile/block{ + dir = 4 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"Qi" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/cracked, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Qx" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/marker_beacon/burgundy, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"QD" = ( +/obj/item/pickaxe, +/obj/effect/decal/cleanable/blood, +/turf/open/floor/pod/dark, +/area/lavaland/surface/outdoors/virtual_domain) +"RC" = ( +/obj/effect/turf_decal/mining/survival{ + dir = 1 + }, +/turf/closed/wall/mineral/titanium/survival/pod, +/area/lavaland/surface/outdoors/virtual_domain) +"RV" = ( +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"So" = ( +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Sw" = ( +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"SI" = ( +/obj/effect/turf_decal/mining, +/turf/closed/wall/mineral/titanium/survival/pod, +/area/lavaland/surface/outdoors/virtual_domain) +"SJ" = ( +/obj/structure/stone_tile/slab/cracked, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"SX" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Ti" = ( +/turf/closed/mineral/random/high_chance/volcanic, +/area/lavaland/surface/outdoors/virtual_domain) +"Tm" = ( +/obj/structure/bed/pod, +/obj/item/bedsheet/black, +/obj/structure/tubes, +/obj/machinery/light/small/broken/directional/east, +/turf/open/floor/pod/dark, +/area/lavaland/surface/outdoors/virtual_domain) +"TC" = ( +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"TJ" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Ud" = ( +/obj/machinery/light/small/directional/north, +/obj/effect/baseturf_helper/virtual_domain, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/virtual_domain/powered) +"UD" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 1 + }, +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/center/cracked, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"UM" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Vc" = ( +/obj/structure/tubes, +/obj/item/crowbar, +/obj/effect/decal/cleanable/blood/drip, +/turf/open/floor/pod/dark, +/area/lavaland/surface/outdoors/virtual_domain) +"VI" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 1 + }, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/center, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"Wa" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/obj/structure/stone_tile/center/cracked, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Wm" = ( +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"WM" = ( +/obj/structure/stone_tile/block, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"WR" = ( +/obj/structure/stone_tile/block, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"WS" = ( +/obj/item/gps/computer, +/obj/structure/tubes, +/turf/open/floor/pod/dark, +/area/lavaland/surface/outdoors/virtual_domain) +"Xb" = ( +/obj/structure/marker_beacon/yellow, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Xn" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Xo" = ( +/obj/structure/stone_tile/block, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Xv" = ( +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile/cracked, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"XO" = ( +/obj/effect/turf_decal/mining/survival{ + dir = 8 + }, +/turf/closed/wall/mineral/titanium/survival/pod, +/area/lavaland/surface/outdoors/virtual_domain) +"Yu" = ( +/obj/structure/stone_tile/block{ + dir = 4 + }, +/obj/structure/stone_tile/block{ + dir = 8 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"YN" = ( +/obj/structure/stone_tile/block/cracked, +/obj/structure/stone_tile/block/cracked{ + dir = 1 + }, +/turf/open/lava/smooth/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"YV" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Zc" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/block{ + dir = 8 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"Zh" = ( +/obj/structure/marker_beacon/purple, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/lavaland/surface/outdoors/virtual_domain) +"Zj" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/block{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"Zq" = ( +/obj/structure/stone_tile/block{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/indestructible/boss, +/area/lavaland/surface/outdoors/virtual_domain) +"Zu" = ( +/obj/machinery/smartfridge/survival_pod{ + desc = "A heated storage unit. This one's seen better days."; + name = "dusty survival pod storage" + }, +/turf/open/floor/pod/dark, +/area/lavaland/surface/outdoors/virtual_domain) +"ZM" = ( +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors/virtual_domain) +"ZN" = ( +/obj/structure/table/survival_pod, +/obj/item/knife/combat/survival, +/turf/open/floor/pod/dark, +/area/lavaland/surface/outdoors/virtual_domain) + +(1,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +Ah +"} +(2,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ox +"} +(3,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +ib +nu +nu +ib +ib +ib +ib +nu +nu +nu +nu +nu +nu +nu +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ib +nu +nu +nu +nu +nu +nu +ib +ib +nu +nu +ib +ox +"} +(4,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(5,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(6,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +wq +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(7,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +nu +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ox +"} +(8,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +ib +nu +nu +nu +nu +nu +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +rt +nu +nu +ib +ib +ox +"} +(9,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +ib +nu +nu +nu +nu +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ox +"} +(10,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ib +ib +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ox +"} +(11,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ib +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(12,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +fA +fA +fA +nu +nu +nu +nu +ib +ox +"} +(13,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +ib +nu +fG +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +fA +fA +fA +nu +nu +nu +nu +ib +ox +"} +(14,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +fA +fA +nu +nu +nu +nu +ib +ox +"} +(15,1,1) = {" +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(16,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +nu +nu +nu +nu +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(17,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +Ti +Ti +Ti +Ti +GM +nu +nu +nu +nu +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +Io +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +Xb +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(18,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +et +Ti +GM +GM +GM +nu +nu +nu +nu +GM +GM +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(19,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +GM +nu +nu +nu +nu +nu +nu +nu +nu +GM +GM +GM +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +wq +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(20,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +Ti +nu +nu +nu +nu +nu +nu +nu +nu +GM +GM +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(21,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +Ti +nu +nu +nu +nu +GM +Ti +GM +GM +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +Zh +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(22,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +FV +nu +nu +nu +nu +Ti +Dm +Dm +GM +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(23,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +Ti +GM +GM +GM +Ti +Ti +Dm +Dm +Ti +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(24,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +FV +Ml +Ti +Dm +Dm +Ti +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ox +"} +(25,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +GM +GM +Ti +Dm +Dm +IB +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +GM +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ox +"} +(26,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +GM +GM +GM +GM +GM +GM +fA +fA +fA +fA +RV +fA +fA +fA +fA +fA +fA +Xn +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +As +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ox +"} +(27,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +Ek +Le +be +be +kT +GM +GM +GM +GM +fA +fA +xm +fA +fA +fA +GM +ZM +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(28,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +dr +KG +mz +KG +KG +jt +GM +GM +GM +GM +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +HZ +nu +bu +nu +nu +nu +MH +nu +nu +lz +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +nu +nu +ib +ox +"} +(29,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +bd +mG +Hw +hU +Mm +lO +et +GM +tk +fA +fA +fA +fA +ak +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +YV +nu +nu +So +nu +nu +nu +nu +nu +nu +bu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +nu +nu +ib +ox +"} +(30,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +Ez +WR +JD +rU +KG +dm +GM +GM +fA +Hi +fA +fA +fA +ll +fA +fA +Wm +fA +fA +YV +qs +MH +nu +nu +nu +ny +ca +oS +nu +nu +Qx +nu +nu +hx +nu +nu +nu +nu +nu +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +nu +nu +ib +ox +"} +(31,1,1) = {" +ox +kg +kg +kg +Hu +Zq +wy +Zq +lT +kg +kg +Gn +Gn +KG +Ak +nv +Ot +mG +hw +kg +kg +Wm +fA +fA +fA +fA +fA +fA +fA +fA +fA +ak +nu +nu +qs +nu +nu +TC +nu +YV +nu +ny +nu +oS +nu +nu +nu +SX +nu +nu +nu +zg +BO +BO +BO +BO +BO +og +Ud +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +nu +nu +ib +ox +"} +(32,1,1) = {" +ox +kg +kg +Gn +VI +xw +gQ +ka +iR +kg +kg +Gn +Gn +sz +KG +KG +KG +KG +mz +kg +kZ +kZ +sd +kZ +lC +kZ +TJ +UM +kZ +IQ +UM +UM +AY +nu +nI +nu +nu +nu +nu +oS +nu +nu +nu +nu +nu +qs +nu +nu +nu +nu +nu +nu +BO +BO +BO +BO +BO +BO +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +nu +nu +ib +ox +"} +(33,1,1) = {" +ox +PO +PO +gk +PO +Zc +IL +Yu +SJ +Yu +Yu +Yu +ob +dL +uK +MP +uK +uK +dL +Jc +Mo +eJ +Mo +hc +yu +eJ +Fg +eJ +YN +tF +Mo +Zj +HQ +qo +Jp +nu +aR +nu +TC +nu +YV +nu +nu +oS +nu +nu +ny +Sw +nu +nu +nu +nu +BO +BO +BO +BO +BO +BO +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +Io +ib +ib +nu +nu +ib +ox +"} +(34,1,1) = {" +ox +kg +kg +Gn +Nl +gh +jw +lT +oo +kg +kg +Gn +Gn +Wa +KG +xd +Ez +mz +HK +kg +ie +Jw +Jw +jk +Jw +jk +dn +Jw +Jw +LH +Ii +Qi +aI +nu +Xo +nu +nu +YV +Sw +nu +nu +nu +sA +nu +Gj +nu +nu +HZ +nu +YV +nu +nu +BO +BO +BO +BO +BO +BO +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +nu +nu +ib +ox +"} +(35,1,1) = {" +ox +kg +kg +kg +Hu +Ep +iV +Go +Ip +kg +kg +Gn +Gn +lO +nO +hU +UD +KG +dm +kg +kg +ll +fA +fA +fA +ak +fA +fA +fA +fA +ll +fA +nu +nu +ny +nu +nu +Aj +HZ +nu +ew +nu +nu +bu +nu +nu +nu +nu +nu +Aj +nu +nu +BO +BO +BO +BO +BO +BO +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +nu +nu +ib +ox +"} +(36,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +KG +WR +iP +rU +Ez +cf +GM +GM +fA +fA +yZ +vf +ll +fA +fA +fA +ak +fA +fA +oS +ny +qs +YV +qs +nu +nu +nu +nu +nu +nu +Sw +nu +qs +oS +nu +nu +Sw +nu +nu +BO +BO +BO +BO +BO +BO +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +nu +nu +ib +ox +"} +(37,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +bd +xd +zW +nv +DP +KG +FV +GM +GM +fA +fA +fA +GM +Px +fA +IG +GM +Hi +fA +fA +nu +nu +nu +nu +TC +nu +ah +nu +nu +nm +nu +nu +nu +nu +nu +nu +sk +nu +nu +zg +BO +BO +BO +BO +BO +Fp +Ud +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +nu +nu +ib +ox +"} +(38,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +bd +KG +KG +KG +lO +Jc +GM +GM +GM +fA +fA +fA +fA +fA +fA +GM +Jt +fA +fA +fA +nu +TC +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(39,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +Ek +KG +lO +MW +pP +GM +GM +GM +GM +fA +fA +fA +fA +fA +fA +fA +xD +fA +fA +fA +oS +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(40,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +IB +nu +nu +nu +nu +GM +RV +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(41,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +GM +nu +nu +Sw +Xv +GM +fA +fA +fA +fA +fA +fA +fA +fA +fA +GM +GM +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(42,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +GM +nu +nu +nu +ny +GM +fA +fA +fA +fA +fA +fA +fA +fA +fA +EC +GM +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +Bo +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ox +"} +(43,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +WM +GM +Px +ny +nu +nu +nu +nu +nu +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ox +"} +(44,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +GM +GM +GM +nu +nu +nu +nu +nu +nu +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(45,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +GM +GM +GM +nu +nu +nu +nu +nu +nu +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(46,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +Ti +GM +GM +nu +nu +nu +nu +nu +nu +fA +fA +ak +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +wq +nu +nu +nu +nu +nu +nu +nu +nu +nu +fA +ib +ox +"} +(47,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +Ti +GM +nu +nu +nu +nu +nu +nu +fA +fA +fA +xD +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +fA +fA +ib +ox +"} +(48,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +Ti +GM +nu +nu +nu +nu +nu +nu +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +fA +fA +ib +ox +"} +(49,1,1) = {" +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +Ti +GM +nu +nu +nu +nu +nu +nu +fA +fA +fA +GM +fA +fA +fA +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +Fq +nu +ib +ib +ib +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +fA +ib +ox +"} +(50,1,1) = {" +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +kg +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +rt +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(51,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +dQ +dQ +XO +dQ +dQ +GM +nu +nu +nu +ib +ox +"} +(52,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +Fq +nu +nu +nu +nu +nu +nu +nu +nu +nu +dQ +GH +jN +ZN +zo +GM +nu +nu +nu +ib +ox +"} +(53,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +RC +Zu +QD +bt +qW +CX +nu +nu +nu +ib +ox +"} +(54,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +dQ +WS +Tm +Vc +SI +Lx +nu +nu +nu +ib +ox +"} +(55,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +dQ +dQ +Pv +dQ +dQ +Lx +nu +nu +nu +ib +ox +"} +(56,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +ib +nu +nu +nu +fA +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +GM +GM +GM +GM +dx +gK +nu +nu +nu +ib +ox +"} +(57,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +Io +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(58,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ox +"} +(59,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +fA +fA +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +ox +"} +(60,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +fA +fA +fA +fA +nu +nu +nu +nu +nu +nu +Xb +nu +nu +nu +ib +ox +"} +(61,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +nu +nu +ib +ox +"} +(62,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +nu +ib +ox +"} +(63,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +ib +nu +nu +nu +ib +ib +ib +nu +nu +nu +nu +nu +ib +ib +nu +nu +nu +nu +ib +ib +ib +nu +nu +nu +nu +nu +nu +nu +nu +ib +ib +nu +nu +nu +nu +ib +ib +ox +"} +(64,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ib +ox +"} +(65,1,1) = {" +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +cp +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +ox +"} diff --git a/_maps/virtual_domains/pipedream.dmm b/_maps/virtual_domains/pipedream.dmm new file mode 100644 index 00000000000..44bd845477a --- /dev/null +++ b/_maps/virtual_domains/pipedream.dmm @@ -0,0 +1,3713 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"af" = ( +/obj/structure/chair/plastic{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"aw" = ( +/obj/structure/disposalpipe/sorting/mail/flip{ + dir = 1 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"ax" = ( +/obj/effect/turf_decal/tile/yellow/fourcorners, +/obj/structure/frame/computer{ + anchored = 1; + dir = 4 + }, +/obj/item/shard{ + icon_state = "medium" + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"aK" = ( +/turf/open/space/basic, +/area/space) +"aL" = ( +/obj/effect/turf_decal/tile/yellow/half/contrasted, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"bq" = ( +/obj/machinery/light/small/red/dim{ + dir = 1 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"bs" = ( +/turf/open/floor/carpet/orange, +/area/virtual_domain/powered) +"bw" = ( +/obj/structure/disposalpipe/broken{ + dir = 1 + }, +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/closet/crate/preopen, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"bx" = ( +/obj/structure/frame/computer, +/obj/item/shard, +/turf/open/floor/carpet/royalblue{ + icon_state = "carpet_royalblue-12" + }, +/area/virtual_domain/powered) +"bA" = ( +/obj/structure/chair/plastic, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"bG" = ( +/obj/structure/lattice/catwalk{ + name = "industrial lift" + }, +/obj/structure/closet/crate/preopen, +/obj/structure/railing, +/turf/open/chasm, +/area/virtual_domain/powered) +"bS" = ( +/obj/effect/turf_decal/tile/yellow/anticorner/contrasted{ + dir = 1 + }, +/obj/structure/table/reinforced, +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/item/folder/yellow, +/obj/item/folder/blue{ + pixel_x = 2; + pixel_y = -2 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"cw" = ( +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 9 + }, +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"cB" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 10 + }, +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"cF" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 9 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"dx" = ( +/obj/effect/turf_decal/trimline/yellow/line, +/obj/item/shard, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"dz" = ( +/obj/machinery/light/broken, +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/trimline/yellow/corner, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 8 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"dA" = ( +/obj/machinery/light/dim{ + dir = 4 + }, +/obj/structure/disposalpipe/segment, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"dP" = ( +/obj/effect/turf_decal/tile/yellow/half/contrasted, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"eg" = ( +/turf/closed/wall, +/area/virtual_domain/powered) +"ei" = ( +/obj/machinery/conveyor/auto{ + dir = 6; + icon_state = "conveyor_map_inverted"; + inverted = 1 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 5 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"ev" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"eJ" = ( +/obj/structure/disposalpipe/sorting{ + dir = 2 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"eN" = ( +/obj/effect/turf_decal/trimline/yellow/arrow_cw{ + dir = 1 + }, +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/light/broken{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"eY" = ( +/turf/closed/wall/r_wall, +/area/virtual_domain/powered) +"fe" = ( +/obj/effect/turf_decal/tile/yellow/anticorner/contrasted{ + dir = 8 + }, +/obj/structure/table/reinforced, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"fg" = ( +/turf/open/floor/iron/stairs/left{ + dir = 8 + }, +/area/virtual_domain/powered) +"fj" = ( +/obj/structure/closet/crate/preopen, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"fl" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 1 + }, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"fw" = ( +/obj/structure/door_assembly/door_assembly_eng, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"fK" = ( +/obj/structure/chair/stool/bar/directional/west, +/turf/open/floor/iron/cafeteria, +/area/virtual_domain/powered) +"fR" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 8 + }, +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 5 + }, +/obj/structure/sign/poster/official/random/directional/west, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"fZ" = ( +/obj/effect/turf_decal/tile/yellow/fourcorners, +/obj/structure/chair/office{ + dir = 8 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"gc" = ( +/obj/structure/disposalpipe/broken, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"gj" = ( +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"gs" = ( +/obj/machinery/door/airlock/external/glass/ruin, +/obj/effect/mapping_helpers/airlock/cyclelink_helper{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"gN" = ( +/obj/structure/disposalpipe/sorting{ + dir = 8 + }, +/turf/open/floor/catwalk_floor/iron, +/area/virtual_domain/powered) +"gV" = ( +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 8 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"hg" = ( +/obj/effect/turf_decal/caution{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"hi" = ( +/turf/open/floor/iron, +/area/virtual_domain/powered) +"hk" = ( +/obj/effect/turf_decal/tile/yellow/anticorner/contrasted, +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/light/broken{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"ho" = ( +/obj/effect/turf_decal/siding/white{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"iw" = ( +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/effect/mapping_helpers/broken_floor, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"iz" = ( +/obj/structure/broken_flooring/corner, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"iI" = ( +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 8 + }, +/obj/effect/turf_decal/trimline/yellow/corner, +/obj/effect/decal/cleanable/blood/drip, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"jv" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 8 + }, +/obj/machinery/light/small/red/dim{ + dir = 8 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"jw" = ( +/obj/effect/turf_decal/delivery, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"jH" = ( +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/turf/open/floor/catwalk_floor/iron, +/area/virtual_domain/powered) +"jQ" = ( +/obj/structure/disposalpipe/segment, +/obj/machinery/light/small/red/dim{ + dir = 1 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"jS" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/stripes/corner{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"jW" = ( +/obj/effect/decal/cleanable/generic, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"kh" = ( +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"ki" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"kn" = ( +/obj/machinery/light/small/red/dim{ + dir = 1 + }, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"kJ" = ( +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/machinery/door/poddoor/shutters/indestructible{ + dir = 4; + id = "factorylockdown" + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"kU" = ( +/turf/open/floor/plating, +/area/virtual_domain/powered) +"lp" = ( +/obj/machinery/door/airlock/maintenance, +/obj/effect/mapping_helpers/airlock/locked, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"lt" = ( +/obj/structure/disposalpipe/sorting{ + dir = 8 + }, +/mob/living/basic/hivebot/range, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"lx" = ( +/obj/machinery/door/poddoor/shutters/indestructible{ + dir = 4; + id = "factorylockdown" + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"lB" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 5 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"lC" = ( +/obj/machinery/door/airlock/glass, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"lI" = ( +/obj/effect/mapping_helpers/burnt_floor, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"lN" = ( +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"lW" = ( +/obj/structure/disposalpipe/sorting{ + dir = 8 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"mh" = ( +/obj/structure/broken_flooring/pile{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"mu" = ( +/obj/structure/disposalpipe/segment, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"mE" = ( +/obj/machinery/door/airlock/maintenance, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"mY" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 1 + }, +/obj/machinery/light/small/red/dim{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"nc" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 1 + }, +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/structure/sign/poster/official/safety_internals/directional/south, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"nz" = ( +/obj/structure/broken_flooring/side/directional/north, +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"nD" = ( +/obj/structure/disposalpipe/trunk/multiz, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"nL" = ( +/obj/effect/turf_decal/tile/dark/half, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"nS" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/disposalpipe/segment{ + dir = 5 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"op" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/light/dim{ + dir = 1 + }, +/obj/structure/sign/warning/doors/directional/north, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"oN" = ( +/obj/machinery/conveyor/auto, +/obj/structure/window/reinforced/spawner/directional/west, +/obj/structure/window/reinforced/spawner/directional/east, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"oX" = ( +/obj/structure/broken_flooring/corner/directional/north, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"pa" = ( +/obj/machinery/light/small/red/dim{ + dir = 1 + }, +/turf/open/floor/carpet/orange, +/area/virtual_domain/powered) +"pb" = ( +/obj/structure/broken_flooring/corner{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"pf" = ( +/obj/effect/spawner/structure/window/reinforced, +/obj/effect/mapping_helpers/damaged_window, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"pi" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"po" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/closet/crate/maint, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"pv" = ( +/obj/structure/broken_flooring/side{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"pI" = ( +/obj/effect/turf_decal/tile/yellow/fourcorners, +/obj/machinery/light/small/red/dim{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"pJ" = ( +/obj/structure/broken_flooring/pile{ + dir = 1 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"qc" = ( +/obj/effect/turf_decal/tile/yellow/anticorner/contrasted{ + dir = 8 + }, +/obj/structure/table/reinforced, +/obj/effect/spawner/random/bureaucracy/briefcase, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"qk" = ( +/obj/structure/disposalpipe/segment{ + dir = 10 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"qK" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/sign/warning/secure_area/directional/north, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"qN" = ( +/obj/effect/turf_decal/siding/white{ + dir = 4 + }, +/obj/effect/mob_spawn/corpse/human/factory, +/obj/effect/decal/cleanable/blood/old, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"qT" = ( +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/machinery/light/small/red/dim{ + dir = 1 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"qV" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"qW" = ( +/obj/machinery/light/dim{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"rc" = ( +/obj/structure/disposalpipe/segment, +/obj/structure/sign/poster/contraband/random/directional/north, +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"rz" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 1 + }, +/obj/machinery/light/small/red/dim, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"rG" = ( +/obj/machinery/light/dim, +/obj/effect/turf_decal/trimline/yellow/line, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"rJ" = ( +/obj/structure/railing, +/obj/effect/decal/cleanable/oil, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"rM" = ( +/obj/structure/disposalpipe/broken{ + dir = 1 + }, +/mob/living/basic/hivebot/strong, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"rO" = ( +/turf/closed/mineral, +/area/space) +"sn" = ( +/turf/open/floor/carpet/royalblue{ + icon_state = "carpet_royalblue-38" + }, +/area/virtual_domain/powered) +"sB" = ( +/obj/machinery/light/broken{ + dir = 8 + }, +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/stripes{ + dir = 8 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"sW" = ( +/obj/effect/decal/cleanable/oil/streak, +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 4 + }, +/obj/effect/decal/cleanable/blood/drip, +/obj/effect/decal/cleanable/blood/drip, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"tl" = ( +/obj/machinery/door/poddoor/shutters/indestructible{ + id = "factorylockdown" + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"tr" = ( +/obj/effect/spawner/structure/window/reinforced, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"tE" = ( +/obj/structure/disposalpipe/segment, +/mob/living/basic/hivebot/range, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"ud" = ( +/obj/effect/decal/cleanable/blood/drip, +/turf/open/floor/carpet/royalblue{ + icon_state = "carpet_royalblue-110" + }, +/area/virtual_domain/powered) +"uk" = ( +/obj/effect/spawner/structure/window/reinforced, +/obj/effect/decal/cleanable/blood/splatter/over_window, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"um" = ( +/obj/machinery/light/dim{ + dir = 1 + }, +/turf/open/floor/iron/cafeteria, +/area/virtual_domain/powered) +"uv" = ( +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/structure/broken_flooring/pile{ + dir = 1 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"uz" = ( +/obj/effect/spawner/random/trash/mess, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"uC" = ( +/obj/structure/falsewall, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"uF" = ( +/obj/structure/disposalpipe/segment, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"uP" = ( +/turf/open/floor/carpet/royalblue{ + icon_state = "carpet_royalblue-55" + }, +/area/virtual_domain/powered) +"uU" = ( +/obj/structure/broken_flooring/side, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"vb" = ( +/obj/effect/turf_decal/trimline/yellow/line, +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 5 + }, +/obj/machinery/light/broken, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"ve" = ( +/obj/machinery/mass_driver/trash{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"vA" = ( +/obj/structure/closet/crate/maint, +/obj/effect/turf_decal/stripes{ + dir = 9 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"vL" = ( +/obj/effect/decal/cleanable/glass, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"vQ" = ( +/obj/structure/disposalpipe/segment, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"vU" = ( +/obj/effect/mapping_helpers/broken_floor, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"wg" = ( +/obj/machinery/light/small/red/dim{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt, +/obj/structure/closet/crate/preopen, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"wh" = ( +/obj/structure/table/wood, +/turf/open/floor/carpet/royalblue{ + icon_state = "carpet_royalblue-207" + }, +/area/virtual_domain/powered) +"wl" = ( +/obj/item/shard, +/turf/open/space/basic, +/area/space) +"wm" = ( +/obj/effect/turf_decal/tile/yellow/half/contrasted{ + dir = 1 + }, +/obj/structure/sign/clock/directional/north, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"wq" = ( +/obj/structure/table/wood, +/obj/machinery/button/door{ + name = "Cargo Bay Lockdown"; + id = "factorylockdown" + }, +/turf/open/floor/carpet/royalblue{ + icon_state = "carpet_royalblue-63" + }, +/area/virtual_domain/powered) +"ws" = ( +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"ww" = ( +/obj/effect/turf_decal/stripes{ + dir = 8 + }, +/obj/effect/decal/cleanable/dirt, +/mob/living/basic/hivebot, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"wU" = ( +/obj/structure/disposalpipe/segment{ + dir = 9 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"wW" = ( +/obj/effect/turf_decal/tile/yellow/fourcorners, +/obj/structure/disposalpipe/segment, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"xa" = ( +/obj/machinery/door/poddoor/shutters/indestructible{ + dir = 8; + id = "factorylockdown" + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"xj" = ( +/obj/structure/railing/corner/end{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"xk" = ( +/obj/machinery/light/dim{ + dir = 4 + }, +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"xl" = ( +/obj/structure/disposalpipe/segment{ + dir = 5 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"xA" = ( +/obj/effect/decal/cleanable/generic, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"xE" = ( +/obj/effect/turf_decal/trimline/yellow/line, +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 9 + }, +/obj/machinery/light/broken, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"xF" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/virtual_domain/powered) +"xM" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt, +/obj/structure/bed/dogbed{ + name = "cat bed" + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"xT" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"yB" = ( +/obj/machinery/door/airlock/maintenance, +/obj/effect/mapping_helpers/airlock/welded, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"yM" = ( +/turf/closed/indestructible/fakedoor{ + name = "Stairwell Access" + }, +/area/virtual_domain/powered) +"yQ" = ( +/turf/template_noop, +/area/template_noop) +"yX" = ( +/obj/structure/fans/tiny, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"zp" = ( +/obj/structure/chair/sofa/corp/right{ + dir = 1 + }, +/obj/effect/decal/cleanable/dirt, +/obj/machinery/light/broken, +/turf/open/floor/carpet/orange, +/area/virtual_domain/powered) +"zB" = ( +/obj/structure/closet/crate/bin, +/obj/item/trash/tray, +/obj/effect/spawner/random/trash/garbage, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"zE" = ( +/obj/structure/disposalpipe/broken{ + dir = 1 + }, +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"zO" = ( +/obj/effect/turf_decal/siding/white{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/light/broken, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Av" = ( +/obj/item/stack/rods/two, +/turf/open/space/basic, +/area/space) +"Aw" = ( +/obj/structure/lattice/catwalk{ + name = "industrial lift" + }, +/mob/living/basic/hivebot/rapid, +/turf/open/chasm, +/area/virtual_domain/powered) +"AJ" = ( +/obj/effect/decal/cleanable/generic, +/obj/structure/disposalpipe/segment, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"AP" = ( +/obj/structure/railing, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"AU" = ( +/turf/open/misc/asteroid/airless, +/area/space) +"Bd" = ( +/obj/structure/closet/secure_closet/tac{ + req_access = null + }, +/obj/item/ammo_casing/shotgun/buckshot, +/obj/item/ammo_casing/shotgun/buckshot, +/obj/item/ammo_casing/shotgun/buckshot, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Bh" = ( +/obj/structure/broken_flooring/corner/directional/east, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Bx" = ( +/obj/structure/table/reinforced, +/obj/machinery/microwave{ + broken = 1; + desc = "No longer cooks and boils stuff." + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"BA" = ( +/obj/structure/broken_flooring/corner/directional/south, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"BI" = ( +/obj/machinery/door/airlock/command/glass{ + name = "Quartermaster's Office" + }, +/obj/effect/mapping_helpers/airlock/access/any/away/command, +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 8 + }, +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"BN" = ( +/obj/structure/flora/rock/pile/style_random, +/turf/open/misc/asteroid/airless, +/area/space) +"BW" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"BX" = ( +/obj/effect/decal/cleanable/robot_debris/old, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Ci" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 8 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Ct" = ( +/obj/machinery/conveyor/auto{ + dir = 6 + }, +/obj/machinery/light/broken{ + dir = 1 + }, +/obj/structure/sign/warning/vacuum/directional/north, +/obj/structure/window/reinforced/spawner/directional/east, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Cv" = ( +/obj/structure/chair/office{ + dir = 8 + }, +/turf/open/floor/carpet/royalblue{ + icon_state = "carpet_royalblue-157" + }, +/area/virtual_domain/powered) +"CA" = ( +/obj/structure/disposalpipe/segment, +/obj/structure/broken_flooring/side{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"CQ" = ( +/obj/effect/spawner/random/trash/botanical_waste, +/obj/item/trash/chips, +/obj/structure/closet/secure_closet/freezer/empty/open, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"CR" = ( +/obj/structure/flora/rock/pile/style_random, +/turf/open/misc/asteroid/airless, +/area/virtual_domain/powered) +"CX" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Dr" = ( +/obj/structure/disposalpipe/segment{ + dir = 6 + }, +/obj/machinery/light/dim{ + dir = 8 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"DA" = ( +/obj/structure/disposalpipe/segment, +/turf/open/floor/catwalk_floor/iron, +/area/virtual_domain/powered) +"DE" = ( +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"DP" = ( +/obj/structure/table/reinforced, +/obj/effect/spawner/random/food_or_drink/snack, +/turf/open/floor/iron/cafeteria, +/area/virtual_domain/powered) +"Ex" = ( +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/carpet/royalblue{ + icon_state = "carpet_royalblue-137" + }, +/area/virtual_domain/powered) +"Ez" = ( +/obj/structure/flora/rock/style_random, +/turf/open/misc/asteroid/airless, +/area/space) +"EI" = ( +/obj/effect/turf_decal/stripes{ + dir = 8 + }, +/obj/effect/decal/cleanable/oil, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"EJ" = ( +/obj/machinery/recycler/deathtrap{ + dir = 8 + }, +/obj/machinery/conveyor/auto{ + dir = 4 + }, +/obj/effect/turf_decal/stripes/line, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Fa" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/closet/crate/maint, +/obj/effect/turf_decal/delivery, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Ff" = ( +/obj/structure/disposalpipe/trunk/multiz{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Fo" = ( +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron/cafeteria, +/area/virtual_domain/powered) +"Fr" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/closet/crate/preopen, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Fw" = ( +/obj/structure/flora/bush/fullgrass/style_random, +/obj/structure/flora/rock/pile/style_random, +/obj/structure/flora/bush/flowers_yw/style_random, +/obj/structure/window/reinforced/spawner/directional/north, +/obj/structure/window/reinforced/spawner/directional/west, +/turf/open/floor/grass, +/area/virtual_domain/powered) +"FK" = ( +/obj/effect/mapping_helpers/burnt_floor, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"FO" = ( +/turf/open/misc/asteroid/airless, +/area/virtual_domain/powered) +"FP" = ( +/turf/open/floor/carpet/royalblue{ + icon_state = "carpet_royalblue-110" + }, +/area/virtual_domain/powered) +"Gb" = ( +/obj/effect/turf_decal/stripes{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Ge" = ( +/turf/open/floor/carpet/royalblue{ + icon_state = "carpet_royalblue-21" + }, +/area/virtual_domain/powered) +"Gh" = ( +/obj/machinery/door/airlock/maintenance, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Gi" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 9 + }, +/obj/effect/turf_decal/trimline/yellow/corner, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Gs" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 1 + }, +/obj/machinery/light/broken{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Gy" = ( +/obj/machinery/conveyor/auto{ + dir = 9; + inverted = 1; + icon_state = "conveyor_map_inverted" + }, +/obj/effect/turf_decal/stripes/line, +/obj/structure/window/reinforced/spawner/directional/west, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"GI" = ( +/obj/effect/turf_decal/trimline/yellow/arrow_ccw, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"GV" = ( +/obj/machinery/light/small/red/dim{ + dir = 8 + }, +/obj/effect/turf_decal/stripes{ + dir = 9 + }, +/obj/effect/mapping_helpers/broken_floor, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Hn" = ( +/turf/open/floor/iron/cafeteria, +/area/virtual_domain/powered) +"HI" = ( +/obj/structure/broken_flooring/pile/directional/north, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Ib" = ( +/obj/structure/chair/sofa/corp/left{ + dir = 1 + }, +/turf/open/floor/carpet/orange, +/area/virtual_domain/powered) +"Ip" = ( +/obj/machinery/door/airlock/engineering/glass, +/obj/effect/mapping_helpers/airlock/access/any/away/supply, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Is" = ( +/obj/machinery/door/airlock/engineering/glass, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"IF" = ( +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"IK" = ( +/obj/effect/turf_decal/trimline/yellow/line, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"IZ" = ( +/obj/structure/lattice/catwalk{ + name = "industrial lift" + }, +/obj/structure/closet/crate, +/turf/open/chasm, +/area/virtual_domain/powered) +"Jl" = ( +/obj/effect/decal/cleanable/blood/old, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Jm" = ( +/obj/structure/broken_flooring/pile/directional/north, +/obj/machinery/light/dim, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Jn" = ( +/obj/effect/turf_decal/stripes{ + dir = 4 + }, +/obj/structure/disposalpipe/segment, +/obj/structure/railing/corner/end/flip{ + dir = 8 + }, +/obj/structure/sign/warning/doors/directional/east, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Jq" = ( +/obj/structure/broken_flooring/pile{ + dir = 1 + }, +/obj/structure/sign/poster/contraband/random/directional/west, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"JE" = ( +/obj/machinery/light/small/red/dim{ + dir = 1 + }, +/obj/structure/sign/warning/chem_diamond/directional/west, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"JR" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"JT" = ( +/obj/structure/lattice/catwalk{ + name = "industrial lift" + }, +/obj/effect/spawner/random/trash/grime, +/turf/open/chasm, +/area/virtual_domain/powered) +"Kb" = ( +/obj/effect/mob_spawn/corpse/human/factory/guard, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Kt" = ( +/obj/structure/disposalpipe/segment{ + dir = 6 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"KO" = ( +/obj/structure/broken_flooring/side/directional/north, +/obj/machinery/light/small/red/dim, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"KX" = ( +/obj/structure/lattice/catwalk{ + name = "industrial lift" + }, +/turf/open/chasm, +/area/virtual_domain/powered) +"Ln" = ( +/obj/structure/disposalpipe/broken{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Lp" = ( +/obj/effect/turf_decal/trimline/yellow/line, +/obj/machinery/light/broken, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"LN" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 4 + }, +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 10 + }, +/obj/machinery/light/dim{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"LU" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 6 + }, +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Mc" = ( +/obj/effect/turf_decal/trimline/yellow/warning, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Mh" = ( +/obj/machinery/conveyor/auto{ + dir = 8 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Mi" = ( +/obj/effect/mob_spawn/corpse/human/factory, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Mj" = ( +/turf/closed/mineral, +/area/virtual_domain/powered) +"Mu" = ( +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 4 + }, +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Mx" = ( +/obj/effect/turf_decal/tile/yellow/anticorner/contrasted{ + dir = 1 + }, +/obj/structure/filingcabinet, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"My" = ( +/obj/machinery/conveyor/auto{ + dir = 1 + }, +/obj/machinery/light/small/red/dim{ + dir = 8 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"MI" = ( +/turf/open/floor/carpet/royalblue{ + icon_state = "carpet_royalblue-74" + }, +/area/virtual_domain/powered) +"MN" = ( +/obj/effect/turf_decal/tile/dark, +/obj/effect/decal/cleanable/dirt, +/obj/structure/disposalpipe/segment{ + dir = 5 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Nc" = ( +/obj/structure/chair/plastic{ + dir = 8 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Nu" = ( +/obj/effect/turf_decal/trimline/yellow/line, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"NW" = ( +/obj/effect/decal/cleanable/dirt, +/mob/living/basic/hivebot/strong, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Ok" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 1 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"On" = ( +/obj/structure/broken_flooring/side{ + dir = 4 + }, +/obj/machinery/light/broken{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"OJ" = ( +/obj/structure/disposalpipe/segment, +/obj/effect/turf_decal/stripes{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"OL" = ( +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"OQ" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/sign/poster/ripped/directional/west, +/turf/open/floor/carpet/orange, +/area/virtual_domain/powered) +"OR" = ( +/obj/machinery/light/broken, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Po" = ( +/obj/machinery/light/small/red/dim{ + dir = 4 + }, +/obj/structure/disposalpipe/segment, +/obj/structure/broken_flooring/corner, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Pr" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 5 + }, +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 8 + }, +/obj/structure/sign/poster/official/random/directional/east, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"PH" = ( +/obj/structure/railing/corner/end/flip{ + dir = 8 + }, +/obj/structure/disposalpipe/segment, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Qd" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 8 + }, +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 6 + }, +/obj/machinery/light/dim{ + dir = 8 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Qh" = ( +/obj/structure/disposalpipe/segment, +/obj/machinery/light/broken{ + dir = 8 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Qj" = ( +/obj/machinery/light/dim{ + dir = 8 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Qo" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"Qr" = ( +/turf/closed/indestructible/fakedoor/maintenance{ + name = "maintenance access" + }, +/area/virtual_domain/powered) +"Qv" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 4 + }, +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 9 + }, +/obj/machinery/light/small/red/dim{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Qy" = ( +/obj/structure/disposalpipe/trunk{ + dir = 8 + }, +/obj/structure/disposaloutlet{ + dir = 1 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"QI" = ( +/obj/structure/sign/calendar/directional/north, +/obj/effect/spawner/random/trash/garbage, +/turf/open/floor/iron/cafeteria, +/area/virtual_domain/powered) +"QK" = ( +/obj/structure/table, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"QN" = ( +/obj/structure/disposalpipe/segment{ + dir = 9 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"QP" = ( +/obj/structure/sign/poster/contraband/random/directional/east, +/obj/effect/decal/cleanable/blood/old, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"QW" = ( +/obj/machinery/conveyor/auto{ + dir = 5 + }, +/obj/effect/decal/cleanable/cobweb, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Ru" = ( +/obj/machinery/door/airlock/external/glass/ruin, +/obj/effect/mapping_helpers/airlock/cyclelink_helper, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Ry" = ( +/obj/effect/turf_decal/tile/yellow/anticorner/contrasted{ + dir = 4 + }, +/obj/structure/disposalpipe/trunk{ + dir = 8 + }, +/obj/machinery/disposal/bin, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"RJ" = ( +/turf/closed/indestructible/binary, +/area/virtual_domain/powered) +"RK" = ( +/obj/effect/decal/cleanable/blood/drip, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"RZ" = ( +/obj/effect/turf_decal/tile/yellow/half/contrasted{ + dir = 1 + }, +/obj/structure/disposalpipe/segment{ + dir = 6 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Sg" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 1 + }, +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Sl" = ( +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 4 + }, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 8 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"SC" = ( +/mob/living/basic/hivebot/strong, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"SR" = ( +/obj/effect/turf_decal/trimline/yellow/line, +/obj/effect/decal/cleanable/glass, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"SS" = ( +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/turf/closed/mineral, +/area/virtual_domain/powered) +"SU" = ( +/obj/effect/spawner/structure/window, +/obj/item/stack/rods/two, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"SZ" = ( +/obj/structure/table/reinforced, +/obj/effect/spawner/random/food_or_drink/booze, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Te" = ( +/obj/effect/decal/cleanable/blood/tracks{ + dir = 5 + }, +/obj/effect/mob_spawn/corpse/human/factory/qm, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Tn" = ( +/obj/structure/broken_flooring/corner{ + dir = 4 + }, +/mob/living/basic/hivebot, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Tp" = ( +/obj/machinery/door/poddoor/shutters/indestructible{ + dir = 4; + id = "factorylockdown" + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Tt" = ( +/obj/machinery/conveyor/auto{ + dir = 9 + }, +/obj/effect/turf_decal/stripes/corner{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"TH" = ( +/obj/structure/broken_flooring/corner/directional/south, +/obj/item/ammo_casing/shotgun/buckshot/spent, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Ue" = ( +/obj/structure/table/reinforced, +/obj/machinery/light/small/red/dim{ + dir = 8 + }, +/obj/structure/sign/poster/official/cleanliness/directional/west, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Uw" = ( +/obj/machinery/light/dim{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"UO" = ( +/obj/structure/broken_flooring/side/directional/north, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"UV" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 6 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"UX" = ( +/obj/effect/turf_decal/trimline/yellow/line, +/obj/machinery/light/small/red/dim, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"UY" = ( +/obj/effect/decal/cleanable/blood/tracks{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Vb" = ( +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 4 + }, +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/machinery/light/broken, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Vg" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 1 + }, +/obj/structure/disposalpipe/segment{ + dir = 9 + }, +/obj/machinery/light/small/red/dim, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Vh" = ( +/obj/structure/table/reinforced, +/turf/open/floor/iron/cafeteria, +/area/virtual_domain/powered) +"Vy" = ( +/obj/structure/broken_flooring/singular{ + dir = 4 + }, +/obj/effect/mob_spawn/corpse/human/factory/guard, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"VA" = ( +/obj/machinery/light/small/red/dim{ + dir = 1 + }, +/obj/structure/table, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"VJ" = ( +/obj/structure/broken_flooring/corner{ + dir = 1 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"VL" = ( +/obj/structure/sign/warning/secure_area/directional/south, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"VO" = ( +/obj/machinery/light/broken{ + dir = 1 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Wd" = ( +/obj/structure/lattice/catwalk{ + name = "industrial lift" + }, +/obj/structure/railing, +/turf/open/chasm, +/area/virtual_domain/powered) +"Wp" = ( +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"WT" = ( +/obj/effect/turf_decal/stripes{ + dir = 8 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"WV" = ( +/obj/machinery/conveyor/auto{ + dir = 10; + inverted = 1; + icon_state = "conveyor_map_inverted" + }, +/obj/effect/turf_decal/stripes/line{ + dir = 6 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Xb" = ( +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/turf/closed/wall, +/area/virtual_domain/powered) +"Xc" = ( +/obj/effect/turf_decal/trimline/yellow/line, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Xm" = ( +/obj/item/gun/ballistic/shotgun/lethal, +/obj/machinery/light/broken{ + dir = 1 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Xo" = ( +/obj/machinery/conveyor/auto{ + dir = 4 + }, +/obj/effect/turf_decal/stripes/line, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Xw" = ( +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/yellow/line{ + dir = 10 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"XL" = ( +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 5 + }, +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 4 + }, +/obj/structure/sign/warning/vacuum/external/directional/south, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"XN" = ( +/obj/effect/spawner/structure/window/reinforced, +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"XO" = ( +/obj/effect/turf_decal/delivery, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"XP" = ( +/obj/structure/disposalpipe/segment, +/obj/structure/railing/corner/end{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"XQ" = ( +/obj/effect/turf_decal/tile/yellow/anticorner/contrasted{ + dir = 4 + }, +/obj/machinery/light/dim{ + dir = 4 + }, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"XR" = ( +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 1 + }, +/obj/structure/disposalpipe/segment, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Yc" = ( +/obj/item/gun/ballistic/revolver, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Yl" = ( +/obj/structure/broken_flooring/corner/directional/west, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Ym" = ( +/mob/living/simple_animal/pet/cat/space, +/obj/structure/bed/dogbed{ + name = "cat bed" + }, +/obj/item/toy/plush/moth{ + pixel_x = 3; + pixel_y = 4 + }, +/obj/machinery/light/small/dim/directional/south, +/obj/structure/sign/poster/official/moth_hardhat/directional/west, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Yt" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Yz" = ( +/obj/structure/disposalpipe/segment, +/obj/machinery/door/poddoor/shutters/indestructible{ + id = "factorylockdown" + }, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"YF" = ( +/obj/machinery/light/small/red/dim, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"YL" = ( +/obj/effect/turf_decal/tile/yellow/anticorner/contrasted, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"YP" = ( +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 1 + }, +/obj/machinery/light/dim, +/turf/open/floor/iron, +/area/virtual_domain/powered) +"Zb" = ( +/obj/effect/decal/cleanable/oil, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"Zg" = ( +/turf/open/floor/carpet/royalblue{ + icon_state = "carpet_royalblue-157" + }, +/area/virtual_domain/powered) +"Zy" = ( +/obj/structure/table, +/obj/item/flashlight/lantern, +/turf/open/floor/plating, +/area/virtual_domain/powered) +"ZI" = ( +/turf/open/floor/carpet/royalblue{ + icon_state = "carpet_royalblue-203" + }, +/area/virtual_domain/powered) +"ZP" = ( +/obj/structure/railing, +/turf/open/floor/iron/stairs/right{ + dir = 8 + }, +/area/virtual_domain/powered) + +(1,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +"} +(2,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +Mj +Mj +eg +eg +SS +eg +eg +eg +Xb +Mj +Mj +RJ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +"} +(3,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +Mj +Bx +SZ +Ue +CQ +zB +eg +OQ +bs +Ib +Mj +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +yQ +yQ +"} +(4,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +eg +ho +ho +qN +ho +zO +eg +pa +bs +zp +eY +RJ +RJ +Mj +RJ +RJ +rO +rO +RJ +RJ +xF +"} +(5,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +eg +eg +eg +eg +eg +eg +eg +RJ +eg +um +DP +Vh +Vh +Hn +tr +fg +ZP +Fw +eY +Mj +Mj +Mj +AU +AU +AU +rO +rO +rO +RJ +"} +(6,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +eg +vA +Dr +bw +Jq +gc +eg +RJ +eg +Hn +fK +fK +fK +Hn +lC +hi +FK +eY +eY +Mj +Ez +AU +aK +aK +AU +AU +BN +rO +RJ +"} +(7,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +eg +jQ +lt +kU +vU +lN +eg +RJ +eg +QI +Fo +Hn +Hn +Hn +tr +hi +Ln +eY +Mj +Mj +AU +AU +BN +aK +aK +aK +AU +rO +RJ +"} +(8,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +eg +uU +gj +kU +Tn +OR +eg +RJ +eg +eg +uk +pf +pf +eg +eg +qV +Vb +eY +aK +aK +aK +aK +aK +aK +aK +aK +aK +aK +RJ +"} +(9,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +eg +ki +gj +kU +kU +kU +eg +RJ +eg +kU +kU +nD +nS +kU +Qj +FK +nc +eY +aK +aK +aK +aK +aK +aK +AU +aK +aK +aK +RJ +"} +(10,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +RJ +RJ +RJ +RJ +RJ +eg +eg +kJ +lx +lx +lp +eg +RJ +eg +mu +AJ +mu +eJ +MN +BW +hi +Sg +eY +aK +aK +aK +aK +aK +aK +aK +aK +aK +aK +RJ +"} +(11,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +eg +eg +eg +Qr +eg +eg +JE +gj +pi +kU +VL +eg +eg +eg +VO +kU +Fr +jw +qk +XR +vQ +Vg +eY +aK +aK +aK +JR +JR +JR +JR +JR +Qo +aK +RJ +"} +(12,1,1) = {" +yQ +yQ +yQ +yQ +RJ +RJ +RJ +eg +eg +eg +eg +eg +eg +yM +eg +GV +WT +xj +qk +Qh +XP +lW +ww +EI +Kt +Qh +mu +Yz +xl +HI +kU +Bh +Kb +kU +hi +CX +eY +eY +aK +aK +JR +JR +JR +JR +JR +JR +aK +RJ +"} +(13,1,1) = {" +yQ +yQ +yQ +yQ +RJ +RJ +RJ +eg +VA +bA +eg +kU +sB +WT +mh +kU +pv +AP +IZ +KX +Wd +uv +DE +Mi +gj +xT +hi +tl +IF +kU +hi +Yc +kU +kU +vU +lB +XL +eg +tr +eY +JR +JR +JR +JR +JR +JR +aK +RJ +"} +(14,1,1) = {" +yQ +yQ +yQ +yQ +RJ +RJ +kU +kU +Zy +kU +Gh +Kt +mu +mu +mu +uF +xl +AP +KX +JT +bG +qk +DA +DA +zE +gc +mu +Yz +lW +pi +kU +kU +nL +BW +hi +hg +Wp +Ru +hi +gs +JR +JR +JR +JR +JR +JR +aK +RJ +"} +(15,1,1) = {" +yQ +RJ +RJ +RJ +RJ +RJ +RJ +kU +QP +kU +eg +qT +Gb +Uw +iz +jS +gj +rJ +KX +Aw +Wd +Ff +lN +XO +gj +DE +SC +tl +qk +mu +mu +xl +kU +oX +hi +cF +cw +eY +tr +eY +JR +JR +JR +JR +JR +JR +aK +RJ +"} +(16,1,1) = {" +yQ +RJ +Mj +Mj +Mj +Mj +RJ +eg +eg +eg +eg +Xb +yB +eg +eg +kU +qk +PH +dA +Po +Jn +aw +OJ +CA +QN +kU +Uw +tl +kU +Mc +kU +kh +fj +kU +FK +CX +eY +eY +aK +aK +JR +JR +JR +JR +JR +JR +aK +RJ +"} +(17,1,1) = {" +yQ +RJ +Mj +BN +AU +Mj +Mj +Mj +eg +QW +My +Qy +kU +po +eg +tr +Is +tr +eg +eg +eg +kU +pi +kU +lI +YF +eg +eg +op +kU +BA +iw +kU +kU +FK +rz +eY +aK +aK +aK +JR +JR +JR +JR +JR +ev +aK +RJ +"} +(18,1,1) = {" +yQ +RJ +aK +AU +AU +AU +aK +aK +eg +Ct +oN +Gy +jW +xT +eg +Tp +Tp +Tp +eg +Ym +eg +eg +xa +xa +xa +eg +eg +eg +kU +Zb +kU +Yt +kU +UO +hi +CX +eY +aK +aK +aK +aK +aK +aK +aK +aK +aK +aK +RJ +"} +(19,1,1) = {" +RJ +RJ +aK +aK +aK +aK +wl +aK +yX +ve +Tt +Xo +DE +oX +eg +Sl +gV +dz +eg +uC +eg +qK +mh +kU +kU +Qj +VJ +eg +kU +kU +kU +gj +kU +kU +hi +Ok +eY +aK +aK +aK +aK +aK +aK +aK +aK +aK +aK +RJ +"} +(20,1,1) = {" +RJ +aK +aK +aK +aK +aK +aK +aK +tr +uz +Mh +EJ +kU +kU +mE +Mu +Yl +pJ +eg +kn +pb +kU +kU +kU +Fa +kU +YF +eg +eg +pf +tr +XN +eg +eg +Ci +YP +eY +eY +aK +aK +aK +aK +aK +aK +aK +aK +aK +RJ +"} +(21,1,1) = {" +RJ +aK +aK +aK +aK +aK +Av +CR +tr +kU +ei +WV +pi +Jm +eg +eN +gN +GI +eg +rc +mu +rM +gc +vQ +tE +mu +mu +eg +Mx +qc +ax +bS +fe +pf +hi +hi +af +eY +aK +aK +aK +aK +aK +aK +aK +aK +aK +RJ +"} +(22,1,1) = {" +RJ +aK +aK +aK +aK +aK +aK +FO +eg +eg +wg +uU +lN +uz +eg +mY +jH +rG +eg +xT +Uw +kU +kU +On +pi +kU +Mj +eg +wm +xA +fZ +OL +aL +Ip +hi +lN +QK +eY +aK +aK +aK +aK +BN +aK +aK +aK +aK +RJ +"} +(23,1,1) = {" +RJ +aK +aK +aK +aK +aK +AU +FO +Mj +eg +eg +Mj +Mj +eY +eY +UO +jH +Nu +eg +eg +eg +eg +eg +Mj +Mj +Mj +Mj +eg +RZ +vQ +wW +wU +dP +tr +hi +hi +Nc +eY +aK +BN +AU +aK +aK +aK +aK +aK +aK +RJ +"} +(24,1,1) = {" +RJ +RJ +aK +aK +aK +AU +Ez +Mj +Mj +RJ +Mj +Mj +Vy +hi +eY +CX +jH +Nu +eg +Gi +Qd +jv +fR +cB +Mj +RJ +RJ +eg +Ry +hk +pI +XQ +YL +eg +bq +hi +Mj +eY +Mj +Mj +AU +AU +aK +aK +aK +aK +rO +RJ +"} +(25,1,1) = {" +yQ +RJ +aK +AU +BN +AU +Mj +Mj +RJ +RJ +eY +Xm +TH +fw +eY +qW +hi +dx +pf +UV +sn +uP +Ge +vb +eg +RJ +RJ +eg +Mj +Mj +eg +eg +eg +eg +eg +Mj +Mj +RJ +RJ +Mj +Mj +Mj +RJ +RJ +Mj +Mj +Mj +RJ +"} +(26,1,1) = {" +yQ +RJ +RJ +RJ +RJ +rO +Mj +RJ +RJ +RJ +eY +Bd +vL +hi +vU +sW +hi +SR +SU +CX +FP +wh +Zg +Nu +eg +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +RJ +"} +(27,1,1) = {" +yQ +yQ +yQ +yQ +RJ +RJ +RJ +RJ +yQ +RJ +eY +Jl +RK +BX +eY +CX +RK +iI +BI +ws +ud +bx +Cv +Nu +eg +RJ +RJ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +"} +(28,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +eY +NW +UY +KO +eY +fl +jH +Nu +uk +CX +FP +wq +Zg +Nu +eg +RJ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +"} +(29,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +eY +xk +Te +xT +eY +CX +jH +Nu +pf +Xw +MI +ZI +Ex +xE +eg +RJ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +"} +(30,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +eY +eY +eY +eY +eY +CX +jH +Xc +eg +Pr +LN +xM +Qv +LU +Mj +RJ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +"} +(31,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +RJ +RJ +RJ +RJ +eg +Gs +jH +UX +eg +eg +eg +eg +Mj +Mj +Mj +RJ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +"} +(32,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +eg +Ok +nz +IK +eg +RJ +RJ +RJ +RJ +RJ +RJ +RJ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +"} +(33,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +Mj +CX +hi +Nu +RJ +RJ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +"} +(34,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +Mj +CX +RJ +Lp +eg +RJ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +"} +(35,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +RJ +fl +RJ +RJ +RJ +RJ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +"} +(36,1,1) = {" +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +RJ +RJ +RJ +RJ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +yQ +"} diff --git a/_maps/virtual_domains/pirates.dmm b/_maps/virtual_domains/pirates.dmm new file mode 100644 index 00000000000..9c970f78c37 --- /dev/null +++ b/_maps/virtual_domains/pirates.dmm @@ -0,0 +1,2601 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"ag" = ( +/obj/effect/mapping_helpers/burnt_floor, +/obj/effect/decal/cleanable/garbage, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"by" = ( +/obj/effect/turf_decal/weather/sand{ + dir = 5 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"bz" = ( +/obj/structure/flora/bush/grassy{ + pixel_y = 8 + }, +/obj/structure/flora/bush/lavendergrass{ + pixel_y = -10 + }, +/turf/open/misc/grass, +/area/virtual_domain/powered) +"bP" = ( +/obj/structure/flora/bush/flowers_br/style_random, +/turf/open/misc/grass, +/area/virtual_domain/powered) +"cl" = ( +/obj/structure/flora/rock/style_3, +/turf/open/water/beach, +/area/virtual_domain/powered) +"ct" = ( +/obj/structure/closet/cabinet, +/obj/item/clothing/head/costume/pirate/armored, +/obj/item/clothing/suit/costume/pirate/captain/armored, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/wood/parquet, +/area/virtual_domain/powered) +"cx" = ( +/turf/closed/indestructible/binary, +/area/virtual_domain/powered) +"cJ" = ( +/obj/item/stack/cannonball/shellball{ + pixel_x = 13; + pixel_y = 11 + }, +/obj/item/stack/cannonball{ + pixel_x = 9; + pixel_y = 9 + }, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"cQ" = ( +/obj/structure/flora/grass/jungle/b{ + pixel_x = -15; + pixel_y = 9 + }, +/obj/structure/flora/rock/pile/jungle/large/style_2{ + pixel_x = -3; + pixel_y = -1 + }, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"dp" = ( +/turf/closed/wall/mineral/wood/nonmetal, +/area/virtual_domain/powered) +"dA" = ( +/obj/structure/bonfire/prelit, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"dQ" = ( +/obj/structure/flora/rock/style_4, +/turf/open/water/beach, +/area/virtual_domain/powered) +"eb" = ( +/obj/structure/flora/bush/sparsegrass, +/turf/open/misc/grass, +/area/virtual_domain/powered) +"eD" = ( +/obj/structure/flora/coconuts{ + pixel_x = 9; + pixel_y = -14 + }, +/obj/structure/flora/tree/palm/style_2, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"eQ" = ( +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"eW" = ( +/obj/effect/turf_decal/weather/sand{ + dir = 6 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"fx" = ( +/obj/structure/fluff/beach_umbrella{ + pixel_x = -7; + pixel_y = -10 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"gk" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"gw" = ( +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"ht" = ( +/obj/structure/bookcase/random/fiction, +/turf/open/floor/wood/parquet, +/area/virtual_domain/powered) +"hH" = ( +/obj/item/clothing/suit/armor/militia{ + pixel_x = -5; + pixel_y = 12 + }, +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/obj/item/clothing/suit/armor/militia{ + pixel_x = -5; + pixel_y = 6 + }, +/obj/item/clothing/suit/armor/militia{ + pixel_x = -5; + pixel_y = -3 + }, +/obj/item/clothing/head/costume/fancy{ + pixel_x = 6; + pixel_y = 12 + }, +/obj/item/clothing/head/costume/fancy{ + pixel_x = 6; + pixel_y = 6 + }, +/obj/item/clothing/head/hats/coordinator{ + pixel_x = 8; + pixel_y = -5 + }, +/obj/structure/closet/cabinet, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"hM" = ( +/obj/structure/closet/crate/goldcrate, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"hW" = ( +/obj/structure/chair/comfy/carp{ + dir = 1 + }, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"iM" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/effect/decal/cleanable/dirt/dust, +/mob/living/simple_animal/hostile/pirate/ranged/space, +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"iO" = ( +/obj/effect/turf_decal/weather/sand{ + dir = 4 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"jl" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/obj/structure/table/wood, +/obj/item/flashlight/flare/torch{ + pixel_y = 10; + pixel_x = 7 + }, +/obj/item/reagent_containers/cup/bucket/wooden{ + pixel_y = -16; + pixel_x = 12 + }, +/obj/machinery/recharger{ + pixel_y = 6; + pixel_x = -5 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"jz" = ( +/obj/effect/mapping_helpers/burnt_floor, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"jB" = ( +/obj/effect/turf_decal/weather/sand{ + dir = 4 + }, +/obj/effect/decal/cleanable/cobweb/cobweb2, +/obj/machinery/jukebox, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"jC" = ( +/obj/structure/table/wood, +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/item/reagent_containers/cup/glass/bottle/rum{ + desc = "Rum with ghostly properties that can help the drinker enter the spirit realm. It has fermented under the sea of space for ages."; + name = "Ghost Pirate Rum"; + pixel_x = -4; + pixel_y = 12 + }, +/obj/item/reagent_containers/cup/glass/drinkingglass/shotglass{ + pixel_x = -7; + pixel_y = 5 + }, +/obj/item/reagent_containers/cup/glass/drinkingglass/shotglass{ + pixel_x = 3; + pixel_y = 7 + }, +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"jQ" = ( +/obj/item/gun/energy/laser/hellgun{ + pixel_y = 10 + }, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"kg" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"kl" = ( +/obj/structure/cannon, +/obj/effect/turf_decal/siding/wood, +/obj/effect/decal/cleanable/ash/large{ + pixel_y = -5; + pixel_x = 8 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"lC" = ( +/obj/item/stack/cannonball{ + pixel_x = 7; + pixel_y = 8 + }, +/obj/item/stack/cannonball{ + pixel_x = 11; + pixel_y = -4 + }, +/obj/effect/turf_decal/weather/sand{ + dir = 1 + }, +/obj/effect/decal/cleanable/oil/streak, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"me" = ( +/obj/effect/turf_decal/siding/wood, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"mw" = ( +/obj/structure/flora/grass/jungle/b/style_random{ + pixel_x = -13; + pixel_y = 18 + }, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"mP" = ( +/obj/structure/flora/bush/fullgrass, +/turf/open/misc/grass, +/area/virtual_domain/powered) +"nz" = ( +/obj/effect/mob_spawn/corpse/human/pirate, +/turf/open/misc/beach/coast{ + dir = 8 + }, +/area/virtual_domain/powered) +"nQ" = ( +/obj/machinery/loot_locator, +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"nS" = ( +/obj/structure/flora/rock/pile/jungle/large, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"nX" = ( +/obj/effect/decal/cleanable/dirt/dust, +/mob/living/simple_animal/hostile/pirate/melee/space, +/turf/open/floor/wood/parquet, +/area/virtual_domain/powered) +"oo" = ( +/obj/machinery/smartfridge/drying_rack, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"oU" = ( +/turf/open/misc/beach/coast{ + dir = 10 + }, +/area/virtual_domain/powered) +"pq" = ( +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"px" = ( +/obj/structure/headpike/bone{ + pixel_y = 24 + }, +/turf/open/misc/beach/coast, +/area/virtual_domain/powered) +"pP" = ( +/turf/open/misc/beach/coast, +/area/virtual_domain/powered) +"pU" = ( +/obj/effect/mob_spawn/corpse/human/pirate, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"qj" = ( +/obj/structure/barricade/wooden, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"qm" = ( +/obj/effect/turf_decal/siding/wood, +/obj/item/melee/sabre{ + pixel_y = 12; + pixel_x = -10 + }, +/obj/item/gun/energy/laser/retro, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"qx" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/effect/mapping_helpers/burnt_floor, +/obj/effect/decal/cleanable/dirt/dust, +/mob/living/simple_animal/hostile/pirate/ranged, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"qN" = ( +/obj/structure/flora/bush/sunny/style_3{ + pixel_y = 22 + }, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"qX" = ( +/obj/effect/turf_decal/weather/sand{ + dir = 9 + }, +/obj/structure/fermenting_barrel{ + pixel_x = 6; + pixel_y = 11 + }, +/obj/effect/mob_spawn/ghost_role/human/pirate/skeleton, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"sn" = ( +/obj/structure/table/wood, +/obj/item/book/manual/wiki/ordnance, +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"so" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"th" = ( +/obj/effect/turf_decal/weather/sand, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"tk" = ( +/obj/structure/flora/bush/flowers_pp, +/turf/open/misc/grass, +/area/virtual_domain/powered) +"to" = ( +/mob/living/simple_animal/hostile/pirate/melee, +/turf/open/misc/grass, +/area/virtual_domain/powered) +"ub" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"uw" = ( +/obj/structure/barricade/sandbags, +/obj/effect/turf_decal/weather/sand{ + dir = 4 + }, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"uM" = ( +/obj/structure/flora/bush/stalky{ + pixel_y = 13; + pixel_x = -8 + }, +/turf/open/water/beach, +/area/virtual_domain/powered) +"uT" = ( +/obj/structure/closet/crate/grave, +/obj/structure/flora/grass/jungle/b, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"wb" = ( +/obj/structure/flora/rock, +/turf/open/water/beach, +/area/virtual_domain/powered) +"we" = ( +/obj/effect/mine/explosive/light, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"xc" = ( +/turf/open/misc/beach/coast{ + dir = 4 + }, +/area/virtual_domain/powered) +"xg" = ( +/turf/template_noop, +/area/template_noop) +"xm" = ( +/obj/structure/barricade/wooden/crude, +/turf/closed/wall/mineral/wood/nonmetal, +/area/virtual_domain/powered) +"xB" = ( +/obj/structure/fermenting_barrel/gunpowder{ + pixel_x = -4; + pixel_y = 17 + }, +/obj/structure/fermenting_barrel/gunpowder{ + pixel_x = 4 + }, +/obj/item/stack/cannonball/four{ + pixel_x = -9; + pixel_y = -10 + }, +/obj/item/stack/cannonball{ + pixel_x = 3; + pixel_y = 8 + }, +/obj/item/reagent_containers/cup/bucket/wooden{ + pixel_y = -10 + }, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"xC" = ( +/obj/effect/turf_decal/weather/dirt{ + dir = 1 + }, +/obj/effect/turf_decal/weather/dirt, +/turf/open/water/beach, +/area/virtual_domain/powered) +"xI" = ( +/obj/structure/flora/rock/pile/style_2, +/turf/open/water/beach, +/area/virtual_domain/powered) +"yc" = ( +/obj/effect/turf_decal/weather/dirt, +/turf/open/water/beach, +/area/virtual_domain/powered) +"ye" = ( +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"yi" = ( +/mob/living/simple_animal/hostile/pirate/melee, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"yq" = ( +/obj/structure/barricade/sandbags, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"yw" = ( +/obj/effect/mapping_helpers/burnt_floor, +/mob/living/simple_animal/hostile/pirate/ranged, +/obj/structure/chair/wood, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"yA" = ( +/obj/item/bedsheet/rainbow/double, +/obj/structure/bed/double, +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"zf" = ( +/obj/structure/flora/bush/flowers_br/style_random, +/obj/structure/flora/bush/ferny, +/turf/open/misc/grass, +/area/virtual_domain/powered) +"zg" = ( +/obj/structure/flora/rock/pile/style_3, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"zR" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"Ax" = ( +/obj/effect/turf_decal/weather/dirt, +/obj/effect/turf_decal/weather/dirt, +/turf/open/water/beach, +/area/virtual_domain/powered) +"AU" = ( +/obj/effect/turf_decal/weather/dirt{ + dir = 1 + }, +/turf/open/water/beach, +/area/virtual_domain/powered) +"BC" = ( +/obj/effect/turf_decal/siding/wood, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"BO" = ( +/obj/structure/bookcase/random/adult, +/obj/effect/decal/cleanable/cobweb, +/turf/open/floor/wood/parquet, +/area/virtual_domain/powered) +"Cc" = ( +/obj/structure/flora/tree/palm, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Ct" = ( +/turf/open/misc/beach/coast/corner, +/area/virtual_domain/powered) +"Dm" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/table/wood, +/obj/item/flashlight/flare/torch{ + pixel_y = 10 + }, +/obj/item/flashlight/flare/torch{ + pixel_x = 8; + pixel_y = 6 + }, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"DJ" = ( +/obj/effect/mapping_helpers/burnt_floor, +/obj/structure/bed/maint{ + pixel_x = -10; + pixel_y = 9 + }, +/obj/effect/decal/cleanable/wrapping, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"DL" = ( +/obj/structure/flora/bush/sunny, +/turf/open/misc/grass, +/area/virtual_domain/powered) +"ED" = ( +/obj/effect/turf_decal/siding/wood, +/obj/effect/turf_decal/weather/sand{ + dir = 10 + }, +/obj/effect/turf_decal/weather/sand{ + dir = 9 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"EZ" = ( +/obj/effect/turf_decal/weather/sand{ + dir = 9 + }, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"FG" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/item/claymore/cutlass, +/obj/item/clothing/head/costume/pirate/bandana/armored{ + pixel_x = -9; + pixel_y = 7 + }, +/obj/structure/table/wood, +/obj/item/gun/energy/laser{ + pixel_y = -3 + }, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"FT" = ( +/turf/closed/mineral/random/jungle, +/area/virtual_domain/powered) +"GF" = ( +/obj/effect/turf_decal/weather/dirt, +/obj/structure/flora/rock/pile, +/turf/open/water/beach, +/area/virtual_domain/powered) +"GG" = ( +/obj/structure/barricade/sandbags, +/obj/effect/turf_decal/weather/sand{ + dir = 6 + }, +/obj/item/binoculars{ + pixel_x = -1; + pixel_y = 1 + }, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"Hp" = ( +/obj/effect/turf_decal/siding/wood, +/mob/living/simple_animal/hostile/pirate/ranged, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"HY" = ( +/turf/open/misc/beach/coast{ + dir = 6 + }, +/area/virtual_domain/powered) +"It" = ( +/obj/structure/flora/bush/sparsegrass, +/obj/structure/flora/bush/lavendergrass, +/turf/open/misc/grass, +/area/virtual_domain/powered) +"Iz" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/bed/maint, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"IF" = ( +/obj/effect/turf_decal/weather/dirt{ + dir = 4 + }, +/turf/open/water/beach, +/area/virtual_domain/powered) +"IG" = ( +/obj/effect/mob_spawn/corpse/human/pirate, +/obj/effect/decal/cleanable/blood/gibs/old, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"IM" = ( +/obj/effect/turf_decal/weather/sand{ + dir = 10 + }, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"Jo" = ( +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"Jr" = ( +/obj/structure/headpike/bone, +/turf/open/misc/beach/coast, +/area/virtual_domain/powered) +"Jv" = ( +/obj/effect/turf_decal/siding/wood, +/obj/effect/mapping_helpers/broken_floor, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"JT" = ( +/obj/effect/decal/cleanable/ants, +/turf/open/misc/grass, +/area/virtual_domain/powered) +"Kb" = ( +/obj/structure/railing{ + color = "#4C3117"; + name = "wooden railing" + }, +/obj/effect/decal/cleanable/vomit/old, +/obj/effect/turf_decal/weather/sand{ + dir = 1 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Kl" = ( +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Kw" = ( +/obj/machinery/door/airlock/vault{ + color = "#825427"; + name = "Ye Olde Strong Door" + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain/powered) +"KC" = ( +/obj/effect/turf_decal/weather/dirt{ + dir = 5 + }, +/turf/open/water/beach, +/area/virtual_domain/powered) +"KG" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/obj/structure/table/wood, +/obj/item/gun/energy/laser/musket{ + pixel_y = 7 + }, +/obj/item/gun/energy/laser/musket{ + pixel_y = 2 + }, +/obj/item/gun/energy/laser/musket{ + pixel_y = -3 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"KQ" = ( +/obj/structure/flora/rock/style_2, +/turf/open/water/beach, +/area/virtual_domain/powered) +"Ld" = ( +/obj/structure/flora/rock/pile, +/turf/open/water/beach, +/area/virtual_domain/powered) +"Ma" = ( +/obj/structure/flora/bush/sparsegrass/style_random, +/turf/open/misc/grass, +/area/virtual_domain/powered) +"Mi" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/decal/cleanable/oil, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"MW" = ( +/obj/effect/turf_decal/weather/sand{ + dir = 5 + }, +/obj/effect/decal/cleanable/glass, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Nh" = ( +/obj/structure/flora/rock/pile/jungle/style_3{ + pixel_x = -15; + pixel_y = -4 + }, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"Nk" = ( +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Nz" = ( +/obj/structure/flora/bush/jungle, +/turf/open/misc/grass, +/area/virtual_domain/powered) +"NI" = ( +/obj/structure/railing{ + color = "#4C3117"; + name = "wooden railing" + }, +/obj/effect/turf_decal/weather/sand{ + dir = 9 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"ON" = ( +/obj/item/kirbyplants/organic/plant21{ + pixel_x = -8 + }, +/obj/structure/filingcabinet{ + pixel_x = 11 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain/powered) +"OP" = ( +/obj/structure/flora/bush/stalky, +/turf/open/misc/beach/coast, +/area/virtual_domain/powered) +"Pq" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/effect/mapping_helpers/burnt_floor, +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/bed/maint{ + pixel_x = 2; + pixel_y = 13 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Pz" = ( +/obj/structure/table/wood, +/mob/living/simple_animal/parrot{ + name = "pepper" + }, +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"PQ" = ( +/obj/structure/flora/grass/jungle/b, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"Qb" = ( +/obj/structure/flora/rock{ + pixel_x = 7 + }, +/turf/open/water/beach, +/area/virtual_domain/powered) +"Rr" = ( +/obj/structure/bed/maint{ + pixel_x = -5; + pixel_y = 9 + }, +/obj/effect/turf_decal/weather/sand{ + dir = 6 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"RR" = ( +/obj/effect/mapping_helpers/broken_floor, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"Sm" = ( +/obj/structure/flora/tree/jungle, +/obj/structure/flora/bush/fullgrass/style_random, +/turf/open/misc/grass, +/area/virtual_domain/powered) +"St" = ( +/obj/structure/table/wood, +/obj/item/melee/energy/sword/pirate{ + pixel_y = 10 + }, +/obj/item/clothing/mask/cigarette/cigar{ + pixel_x = 4 + }, +/obj/item/lighter{ + pixel_x = 10; + pixel_y = -8 + }, +/obj/machinery/light/small/directional/north, +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"Tp" = ( +/turf/open/misc/beach/coast/corner{ + dir = 1 + }, +/area/virtual_domain/powered) +"Tt" = ( +/obj/structure/cannon{ + dir = 1 + }, +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"TO" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/machinery/light/small/directional/south, +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/floor/carpet/blue, +/area/virtual_domain/powered) +"TP" = ( +/obj/effect/turf_decal/weather/dirt{ + dir = 4 + }, +/turf/open/misc/beach/coast{ + dir = 6 + }, +/area/virtual_domain/powered) +"TQ" = ( +/obj/effect/mapping_helpers/broken_floor, +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/bed/maint{ + pixel_x = 2; + pixel_y = 1 + }, +/obj/effect/decal/cleanable/cobweb, +/obj/item/toy/plush/beeplushie, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Uy" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/virtual_domain/powered) +"UE" = ( +/obj/structure/barricade/sandbags, +/obj/effect/turf_decal/weather/sand{ + dir = 10 + }, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"Vg" = ( +/obj/effect/mob_spawn/corpse/human/pirate/melee, +/turf/open/water/beach, +/area/virtual_domain/powered) +"Vk" = ( +/obj/structure/barricade/sandbags, +/obj/effect/turf_decal/weather/sand, +/turf/open/floor/wood{ + icon_state = "wood_large" + }, +/area/virtual_domain/powered) +"VC" = ( +/obj/effect/mob_spawn/corpse/human/damaged, +/turf/open/water/beach, +/area/virtual_domain/powered) +"VF" = ( +/turf/open/water/beach, +/area/virtual_domain/powered) +"VX" = ( +/obj/effect/mapping_helpers/burnt_floor, +/obj/structure/rack{ + icon = 'icons/obj/fluff/general.dmi'; + icon_state = "minibar"; + name = "skeletal minibar" + }, +/obj/item/storage/bag/money/dutchmen{ + pixel_y = 13 + }, +/turf/open/floor/wood/parquet, +/area/virtual_domain/powered) +"WM" = ( +/obj/structure/flora/rock/pile/jungle/style_2, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) +"WP" = ( +/turf/open/floor/wood/parquet, +/area/virtual_domain/powered) +"Xn" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"XG" = ( +/obj/structure/fermenting_barrel/gunpowder{ + pixel_x = -4; + pixel_y = 17 + }, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Yj" = ( +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"Yk" = ( +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/wood/parquet, +/area/virtual_domain/powered) +"Yq" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 10 + }, +/turf/open/floor/wood, +/area/virtual_domain/powered) +"Zk" = ( +/obj/structure/flora/coconuts{ + pixel_x = 12 + }, +/obj/structure/flora/tree/palm, +/turf/open/misc/beach/sand, +/area/virtual_domain/powered) +"ZZ" = ( +/obj/structure/flora/grass/jungle, +/turf/open/misc/dirt/jungle, +/area/virtual_domain/powered) + +(1,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +cx +cx +cx +cx +cx +cx +cx +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(2,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +cx +cx +FT +FT +FT +FT +FT +FT +cx +cx +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(3,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +cx +cx +cx +FT +FT +FT +FT +FT +FT +FT +FT +FT +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(4,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +cx +cx +FT +FT +FT +FT +FT +FT +FT +FT +FT +FT +FT +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(5,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +cx +FT +FT +FT +FT +FT +FT +zf +eb +we +FT +FT +FT +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(6,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +cx +cx +cx +FT +FT +FT +Sm +Ma +bz +JT +Kl +Kl +Kl +FT +FT +cx +cx +cx +cx +cx +cx +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(7,1,1) = {" +xg +xg +xg +xg +xg +cx +cx +cx +cx +FT +FT +FT +FT +It +tk +DL +Kl +Kl +Kl +Cc +Kl +IG +FT +cx +cx +FT +FT +FT +FT +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(8,1,1) = {" +xg +xg +xg +xg +cx +cx +FT +FT +FT +FT +FT +Nz +bP +mP +to +Kl +Kl +we +Kl +Kl +Kl +Kl +we +cx +cx +FT +FT +FT +FT +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(9,1,1) = {" +xg +xg +xg +cx +cx +FT +dp +dp +dp +dp +xB +yq +yq +Kl +Kl +Ct +xc +xc +xc +xc +xc +xc +xc +HY +VF +VF +VF +VF +VF +cx +cx +cx +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(10,1,1) = {" +xg +xg +xg +cx +FT +dp +dp +Pq +qx +Mi +th +Kl +yq +Kl +IG +OP +uM +VF +VF +VF +VF +VF +VF +VF +VF +VC +VF +VF +VF +VF +VF +dp +cx +cx +cx +xg +xg +xg +xg +xg +xg +xg +"} +(11,1,1) = {" +xg +xg +cx +cx +FT +dp +TQ +Iz +DJ +ag +eW +Kl +Kl +ED +Kl +Jr +VF +VF +VF +VF +VF +VF +VF +VF +VF +KQ +VF +VF +VF +VF +VF +dp +KG +pq +cx +cx +cx +cx +cx +cx +cx +Uy +"} +(12,1,1) = {" +xg +xg +cx +FT +FT +dp +Yj +MW +iO +Rr +qj +Kl +NI +xm +Ct +HY +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +wb +dp +jl +pq +BC +ub +ub +ub +ub +ub +Xn +cx +"} +(13,1,1) = {" +cx +cx +cx +FT +FT +FT +qj +Kl +Kl +Kl +Kl +Kl +lC +kl +pP +VF +VF +VF +VF +VF +VF +Vg +VF +VF +VF +VF +VF +VF +VF +VF +cl +dp +hH +Nk +qm +ub +ub +ub +ub +ub +ub +cx +"} +(14,1,1) = {" +cx +dp +dp +dp +dp +dp +dp +oo +Kl +Kl +Kl +Kl +Kb +dp +px +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +dp +dp +Nk +Jv +ub +ub +ub +ub +ub +ub +cx +"} +(15,1,1) = {" +cx +dp +BO +ht +VX +ct +dp +yi +Kl +dA +Kl +Kl +by +Hp +pP +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +me +ub +ub +ub +ub +ub +ub +cx +"} +(16,1,1) = {" +cx +dp +ON +WP +nX +Yk +Kw +Kl +Kl +Kl +Kl +Kl +Kl +dp +px +VF +VF +VF +VF +Qb +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +ub +ub +ub +ub +ub +ub +cx +"} +(17,1,1) = {" +cx +dp +jC +iM +so +TO +dp +Kl +Kl +EZ +IM +Kl +Kl +Kl +pP +VF +VF +VF +VF +cl +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +ub +ub +ub +ub +ub +ub +cx +"} +(18,1,1) = {" +cx +dp +sn +hW +eQ +gk +dp +dp +qX +gw +jz +UE +Kl +Zk +pP +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +me +VF +ub +ub +ub +ub +ub +zR +cx +"} +(19,1,1) = {" +cx +dp +St +Pz +nQ +yA +dp +dp +Dm +jz +jz +Vk +Kl +Kl +pP +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +dp +BC +VF +VF +VF +VF +VF +VF +cx +cx +"} +(20,1,1) = {" +cx +dp +dp +dp +dp +dp +dp +xm +FG +RR +yw +Vk +Kl +Kl +pP +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +Tt +cJ +Yq +VF +VF +VF +VF +VF +cx +xg +"} +(21,1,1) = {" +cx +cx +cx +FT +FT +Kl +Kl +dp +dp +jB +uw +GG +Kl +Kl +pP +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +xI +dp +XG +BC +VF +VF +VF +VF +VF +cx +xg +"} +(22,1,1) = {" +xg +xg +cx +FT +FT +fx +Kl +Kl +dp +dp +Kl +Kl +Kl +Kl +pP +VF +VF +VF +VF +VF +VF +dQ +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +kg +pq +Ld +VF +VF +VF +VF +VF +cx +xg +"} +(23,1,1) = {" +xg +xg +cx +FT +FT +FT +Kl +Kl +Kl +Kl +Kl +Kl +Kl +Kl +Tp +oU +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +FT +FT +cx +xg +"} +(24,1,1) = {" +xg +xg +cx +FT +FT +FT +FT +dp +Kl +eD +Kl +Kl +Kl +Kl +Kl +Tp +nz +oU +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +FT +FT +cx +xg +"} +(25,1,1) = {" +xg +xg +cx +cx +FT +FT +FT +FT +Kl +Kl +Kl +Kl +Kl +Kl +yi +Kl +Kl +pP +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +FT +FT +FT +cx +xg +"} +(26,1,1) = {" +xg +xg +xg +cx +cx +cx +FT +FT +FT +FT +FT +FT +FT +FT +FT +FT +FT +TP +VF +FT +FT +cx +cx +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +cx +FT +FT +FT +FT +cx +xg +"} +(27,1,1) = {" +xg +xg +xg +xg +xg +cx +FT +FT +FT +FT +FT +FT +FT +FT +FT +FT +FT +PQ +AU +FT +FT +FT +cx +cx +VF +VF +VF +VF +VF +VF +VF +VF +VF +VF +cx +cx +FT +FT +FT +cx +cx +xg +"} +(28,1,1) = {" +xg +xg +xg +xg +xg +cx +cx +FT +FT +FT +FT +FT +FT +FT +FT +FT +FT +Nh +AU +FT +FT +FT +FT +cx +cx +cx +cx +VF +VF +VF +VF +VF +VF +cx +cx +cx +cx +cx +cx +cx +xg +xg +"} +(29,1,1) = {" +xg +xg +xg +xg +xg +xg +cx +cx +cx +cx +cx +cx +FT +FT +FT +FT +FT +ye +KC +VF +FT +FT +FT +FT +FT +FT +cx +cx +cx +cx +cx +cx +cx +cx +xg +xg +xg +xg +xg +xg +xg +xg +"} +(30,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +cx +cx +FT +FT +FT +FT +FT +zg +ye +KC +IF +VF +FT +FT +FT +FT +FT +cx +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(31,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +cx +cx +FT +FT +FT +FT +FT +ye +ye +pU +AU +VF +GF +WM +FT +FT +FT +cx +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(32,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +cx +cx +FT +FT +FT +FT +FT +ye +nS +KC +VF +Ax +ye +hM +FT +FT +FT +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(33,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +cx +cx +FT +FT +FT +FT +ZZ +ye +cQ +KC +yc +qN +ye +hM +FT +FT +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(34,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +cx +cx +FT +FT +FT +FT +ye +ye +mw +xC +uT +jQ +Jo +FT +FT +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(35,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +cx +cx +cx +FT +FT +FT +FT +FT +FT +FT +FT +FT +FT +FT +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(36,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +cx +FT +FT +FT +FT +FT +FT +FT +FT +FT +FT +FT +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} +(37,1,1) = {" +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +cx +cx +cx +cx +cx +cx +cx +cx +cx +cx +cx +cx +cx +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +xg +"} diff --git a/_maps/virtual_domains/stairs_and_cliffs.dmm b/_maps/virtual_domains/stairs_and_cliffs.dmm new file mode 100644 index 00000000000..82e15fcc090 --- /dev/null +++ b/_maps/virtual_domains/stairs_and_cliffs.dmm @@ -0,0 +1,6056 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"be" = ( +/obj/structure/railing/corner{ + dir = 1 + }, +/turf/open/cliff/snowrock/virtual_domain, +/area/icemoon/underground/explored/virtual_domain) +"cu" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 4 + }, +/obj/structure/railing, +/obj/structure/railing{ + dir = 1 + }, +/turf/open/floor/wood, +/area/icemoon/underground/explored/virtual_domain) +"cJ" = ( +/obj/structure/chair/sofa/bench, +/turf/open/floor/plating/snowed/smoothed, +/area/icemoon/underground/explored/virtual_domain) +"dR" = ( +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"en" = ( +/obj/item/clothing/under/color/grey, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"eB" = ( +/obj/structure/flora/rock/icy/style_random, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"gB" = ( +/obj/structure/railing/corner, +/turf/open/cliff/snowrock/virtual_domain, +/area/icemoon/underground/explored/virtual_domain) +"hc" = ( +/obj/structure/railing/corner/end{ + dir = 8 + }, +/obj/structure/railing/corner/end/flip{ + dir = 8 + }, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"hE" = ( +/turf/open/floor/wood, +/area/icemoon/underground/explored/virtual_domain) +"jK" = ( +/obj/structure/railing/corner{ + dir = 8 + }, +/turf/open/cliff/snowrock/virtual_domain, +/area/icemoon/underground/explored/virtual_domain) +"kc" = ( +/obj/effect/decal/cleanable/blood/old, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"km" = ( +/obj/effect/decal/cleanable/blood/old, +/obj/effect/decal/cleanable/ash/large, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"kK" = ( +/obj/structure/flora/tree/pine/style_random, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"mx" = ( +/obj/structure/railing, +/obj/structure/railing{ + dir = 1 + }, +/turf/open/floor/wood, +/area/icemoon/underground/explored/virtual_domain) +"nj" = ( +/obj/structure/chair/sofa/bench/left, +/turf/open/floor/plating/snowed/smoothed, +/area/icemoon/underground/explored/virtual_domain) +"no" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 8 + }, +/obj/structure/railing, +/obj/structure/railing{ + dir = 1 + }, +/turf/open/floor/wood, +/area/icemoon/underground/explored/virtual_domain) +"pl" = ( +/obj/structure/bonfire/prelit, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"pL" = ( +/turf/open/lava/plasma/virtual_domain, +/area/icemoon/underground/explored/virtual_domain) +"qc" = ( +/turf/open/misc/ice, +/area/icemoon/underground/explored/virtual_domain) +"sa" = ( +/obj/structure/flora/grass/green/style_random, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"sw" = ( +/obj/structure/flora/rock/pile/icy/style_random, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"sM" = ( +/turf/open/cliff/snowrock/virtual_domain, +/area/icemoon/underground/explored/virtual_domain) +"uJ" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"vz" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"xB" = ( +/obj/structure/railing/corner{ + dir = 4 + }, +/turf/open/floor/wood, +/area/icemoon/underground/explored/virtual_domain) +"yo" = ( +/turf/open/floor/plating/snowed/smoothed, +/area/icemoon/underground/explored/virtual_domain) +"yJ" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 8 + }, +/obj/structure/railing, +/obj/structure/railing/corner{ + dir = 1 + }, +/turf/open/floor/wood, +/area/icemoon/underground/explored/virtual_domain) +"yL" = ( +/obj/structure/chair/sofa/bench/right, +/turf/open/floor/plating/snowed/smoothed, +/area/icemoon/underground/explored/virtual_domain) +"zn" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 8 + }, +/turf/open/floor/wood, +/area/icemoon/underground/explored/virtual_domain) +"Am" = ( +/turf/closed/indestructible/binary, +/area/icemoon/underground/explored/virtual_domain) +"AI" = ( +/obj/structure/flora/grass/green/style_random, +/turf/open/floor/plating/snowed/smoothed, +/area/icemoon/underground/explored/virtual_domain) +"BV" = ( +/obj/effect/decal/remains/plasma, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"Dz" = ( +/obj/structure/railing/corner/end{ + dir = 4 + }, +/obj/structure/railing/corner/end/flip{ + dir = 4 + }, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"DB" = ( +/obj/structure/flora/rock/icy/style_random, +/obj/structure/flora/rock/pile/icy/style_random, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"DY" = ( +/obj/structure/flora/rock/icy/style_random, +/obj/structure/flora/grass/green/style_random, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"Eh" = ( +/obj/structure/railing/corner{ + dir = 4 + }, +/turf/open/cliff/snowrock/virtual_domain, +/area/icemoon/underground/explored/virtual_domain) +"Gn" = ( +/obj/structure/flora/rock/pile/icy/style_random, +/obj/effect/decal/cleanable/blood/old, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"GX" = ( +/obj/effect/decal/cleanable/ash/large, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"HU" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 4 + }, +/turf/open/floor/wood, +/area/icemoon/underground/explored/virtual_domain) +"Kl" = ( +/obj/effect/decal/remains/plasma, +/obj/effect/decal/cleanable/ash/large, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"KA" = ( +/obj/structure/statue/snow/snowman{ + name = "Norm"; + desc = "Norm has seen many a man roll down these cliffs, some more stubborn than others. Its usually the stubborn ones who stop getting back up." + }, +/obj/item/pickaxe/mini, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"Lw" = ( +/obj/structure/flora/rock/pile/icy/style_random, +/obj/structure/flora/grass/green/style_random, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"MP" = ( +/obj/structure/railing/corner/end/flip{ + dir = 4 + }, +/obj/structure/railing/corner/end{ + dir = 4 + }, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"MT" = ( +/obj/structure/railing/corner{ + dir = 8 + }, +/turf/open/floor/wood, +/area/icemoon/underground/explored/virtual_domain) +"Nv" = ( +/turf/open/floor/iron/stairs, +/area/icemoon/underground/explored/virtual_domain) +"NM" = ( +/obj/structure/railing/corner/end/flip{ + dir = 8 + }, +/obj/structure/railing/corner/end{ + dir = 8 + }, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"Pl" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 4 + }, +/obj/structure/railing{ + dir = 1 + }, +/obj/structure/railing/corner, +/turf/open/floor/wood, +/area/icemoon/underground/explored/virtual_domain) +"Qv" = ( +/turf/closed/indestructible/rock/snow/ice, +/area/icemoon/underground/explored/virtual_domain) +"RD" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/icemoon/underground/explored/virtual_domain) +"Tz" = ( +/obj/item/pickaxe/mini, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"Ug" = ( +/obj/structure/flora/rock/icy/style_random, +/obj/effect/decal/cleanable/blood/old, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"VW" = ( +/obj/structure/closet/crate/secure/bitrunning/encrypted, +/turf/open/floor/plating/snowed/smoothed, +/area/icemoon/underground/explored/virtual_domain) +"YR" = ( +/obj/structure/flora/tree/pine/style_random, +/obj/structure/flora/grass/green/style_random, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"YT" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +RD +"} +(2,1,1) = {" +Am +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(3,1,1) = {" +Am +Qv +Qv +Qv +Qv +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(4,1,1) = {" +Am +Qv +Qv +Qv +dR +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(5,1,1) = {" +Am +Qv +Qv +dR +dR +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(6,1,1) = {" +Am +Qv +Qv +kK +sw +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(7,1,1) = {" +Am +Qv +Qv +dR +dR +sa +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(8,1,1) = {" +Am +Qv +Qv +eB +sw +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +Qv +Qv +Qv +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(9,1,1) = {" +Am +Qv +Qv +dR +eB +dR +sw +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(10,1,1) = {" +Am +Qv +dR +sw +eB +eB +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(11,1,1) = {" +Am +Qv +dR +eB +sw +sa +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pl +dR +sw +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(12,1,1) = {" +Am +Qv +dR +dR +sw +sa +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sw +dR +Nv +Nv +Nv +sM +sM +Nv +Nv +Nv +Nv +Nv +Nv +sM +sM +sM +sM +Nv +Nv +Nv +Nv +dR +qc +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(13,1,1) = {" +Am +Qv +dR +sa +sw +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sw +dR +dR +sM +Nv +Nv +Nv +Nv +sM +sM +sM +sM +Nv +Nv +Nv +Nv +Nv +Nv +Nv +Nv +Nv +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(14,1,1) = {" +Am +Qv +dR +dR +dR +dR +kK +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sw +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sa +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(15,1,1) = {" +Am +Qv +sw +sa +dR +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(16,1,1) = {" +Am +Qv +dR +sa +sa +sa +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +pl +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +GX +pL +pL +pL +pL +Qv +Qv +Qv +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(17,1,1) = {" +Am +Qv +dR +sa +sa +dR +yo +Nv +Nv +Nv +Nv +sM +sM +Nv +Nv +Nv +dR +dR +sM +sM +sM +sM +sM +dR +qc +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +sw +dR +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +dR +dR +pL +pL +pL +Qv +Qv +Qv +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(18,1,1) = {" +Am +Qv +Qv +dR +dR +yo +yo +sM +sM +sM +Nv +Nv +Nv +Nv +sM +sM +sa +qc +sM +sM +sM +sM +sM +dR +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +pL +dR +sa +pL +pL +sM +sM +sM +sM +sM +sM +sw +dR +sM +sM +sM +sM +sM +sM +sM +sM +pL +dR +en +dR +dR +pL +pL +Qv +Qv +Qv +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(19,1,1) = {" +Am +Qv +Qv +kK +sa +yo +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sw +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sw +BV +pL +sM +sM +sM +sM +sM +sM +eB +dR +sM +sM +sM +sM +sM +sM +sM +sM +pL +GX +sw +dR +dR +pL +pL +pL +pL +Qv +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(20,1,1) = {" +Am +Qv +Qv +dR +dR +yo +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +qc +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +kK +GX +pL +pL +sM +sM +sM +sM +sM +sM +sM +dR +qc +dR +sM +sM +sM +sM +sM +sM +pL +dR +dR +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(21,1,1) = {" +Am +Qv +Qv +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +pL +pL +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +sM +dR +dR +dR +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(22,1,1) = {" +Am +Qv +Qv +dR +sa +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sw +dR +dR +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Am +"} +(23,1,1) = {" +Am +Qv +Qv +Qv +sa +qc +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +qc +sM +sM +sM +sM +sM +sM +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +qc +sM +sM +sM +sM +sM +sM +DB +kc +dR +dR +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Am +"} +(24,1,1) = {" +Am +Qv +Qv +sw +eB +qc +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +qc +sM +sM +sM +sM +sM +sM +qc +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sa +dR +sM +sM +sM +sM +sM +sM +Ug +eB +dR +dR +dR +pL +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Am +"} +(25,1,1) = {" +Am +Qv +Qv +dR +dR +sa +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +qc +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +dR +Nv +Nv +Nv +Nv +Nv +Nv +Nv +Nv +Nv +Nv +Nv +Nv +sM +Nv +Nv +dR +dR +sM +sM +sM +sM +sM +sM +Ug +sw +dR +dR +dR +dR +dR +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Am +"} +(26,1,1) = {" +Am +Qv +Qv +dR +yo +dR +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +qc +dR +sM +sM +sM +sM +sM +dR +Nv +Nv +Nv +Nv +Nv +sM +sM +sM +sM +Nv +Nv +Nv +Nv +Nv +Nv +qc +qc +sM +sM +sM +sM +sM +sM +Ug +sw +dR +dR +dR +sa +dR +dR +dR +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Am +"} +(27,1,1) = {" +Am +Qv +Qv +dR +yo +sa +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sa +dR +sM +sM +sM +sM +dR +sM +sM +sM +sM +eB +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +dR +dR +kK +dR +dR +dR +sw +dR +dR +pL +pL +pL +pL +pL +Qv +Qv +Qv +Am +"} +(28,1,1) = {" +Am +Qv +Qv +sa +yo +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sa +dR +sM +sM +sM +sM +dR +sM +sM +sM +sM +dR +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +kK +sa +dR +qc +qc +sa +sa +dR +kK +dR +pL +pL +pL +pL +pL +pL +Qv +Qv +Am +"} +(29,1,1) = {" +Am +Qv +Qv +sa +yo +yo +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +dR +sM +sM +sM +sM +MP +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +dR +qc +qc +qc +sa +sa +dR +dR +dR +pL +pL +pL +pL +pL +Qv +Qv +Qv +Am +"} +(30,1,1) = {" +Am +Qv +Qv +qc +dR +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +dR +sM +sM +sM +sM +no +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +yL +dR +dR +dR +kK +dR +dR +dR +dR +dR +dR +dR +dR +pL +pL +pL +Qv +Qv +Qv +Am +"} +(31,1,1) = {" +Am +Qv +pl +qc +dR +yo +sM +sM +sM +sM +sM +sM +sM +dR +dR +Nv +Nv +Nv +Nv +qc +sM +sM +sM +sM +dR +dR +sM +sM +sM +mx +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +cJ +yo +dR +dR +dR +dR +dR +kK +sa +sa +dR +dR +dR +dR +dR +dR +dR +dR +Qv +Am +"} +(32,1,1) = {" +Am +Qv +sM +sM +zn +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sw +dR +sM +sM +sM +cu +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +nj +yo +dR +kK +dR +dR +dR +kK +sa +sa +dR +dR +dR +dR +kK +dR +dR +Qv +Qv +Am +"} +(33,1,1) = {" +Am +Qv +sM +sM +hE +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +NM +sM +sM +sM +sM +sM +sM +sM +qc +dR +Nv +Nv +Nv +Nv +Nv +dR +dR +sw +sM +sM +sM +sM +sM +sM +sM +sM +yo +yo +dR +dR +sa +dR +sw +dR +dR +dR +dR +sa +sa +dR +dR +dR +dR +Qv +Qv +Am +"} +(34,1,1) = {" +Am +Qv +sM +sM +hE +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sa +dR +sM +sM +sM +sM +sM +sM +sM +sM +qc +sM +sM +sM +dR +qc +sM +sM +sM +sM +sM +sM +qc +yo +Nv +Nv +Nv +Nv +Nv +yo +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +yo +dR +dR +sa +dR +dR +dR +dR +kK +qc +dR +dR +dR +dR +dR +dR +dR +Qv +Am +"} +(35,1,1) = {" +Am +Qv +sM +sM +hE +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +qc +sM +sM +sM +qc +sa +sM +sM +sM +sM +sM +sM +qc +dR +sM +sM +sM +sM +sM +eB +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +dR +yo +yo +dR +dR +dR +qc +qc +qc +qc +qc +dR +YT +YT +YT +YT +YT +vz +Qv +Am +"} +(36,1,1) = {" +Am +Qv +dR +sM +HU +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +qc +sM +sM +sM +sM +sM +sM +sM +sM +dR +qc +sM +sM +sw +dR +sM +sM +sM +sM +sM +sM +qc +dR +sM +sM +sM +sM +sM +kK +dR +qc +sM +sM +sM +sM +sM +sM +sM +sM +pl +dR +yo +dR +dR +dR +dR +dR +qc +qc +qc +dR +YT +YT +YT +YT +YT +YT +Qv +Am +"} +(37,1,1) = {" +Am +Qv +Qv +dR +dR +dR +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +dR +dR +sM +sM +sM +sM +eB +dR +sa +sM +sM +sM +sM +sM +sM +sa +yo +dR +sM +sM +sM +sM +sM +sM +sM +yo +dR +yo +dR +dR +dR +dR +sw +qc +qc +dR +dR +YT +YT +YT +YT +YT +YT +Qv +Am +"} +(38,1,1) = {" +Am +Qv +Qv +dR +yo +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +qc +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +dR +dR +sM +sM +sM +sM +dR +dR +qc +sM +sM +sM +sM +sM +sM +sM +yo +AI +Nv +Nv +Nv +Nv +Nv +Nv +Nv +yo +yo +yo +yo +yo +yo +dR +dR +yo +dR +yo +yo +YT +YT +YT +YT +YT +YT +Qv +Am +"} +(39,1,1) = {" +Am +Qv +Qv +dR +dR +dR +kK +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +qc +sM +sM +sM +sM +sM +sM +sM +dR +qc +sM +sM +sM +pl +dR +dR +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +yo +AI +Nv +Nv +Nv +Nv +Nv +Nv +Nv +yo +yo +yo +yo +dR +yo +yo +yo +yo +yo +yo +yo +YT +YT +YT +YT +YT +YT +Qv +Am +"} +(40,1,1) = {" +Am +Qv +Qv +dR +yo +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +qc +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +dR +dR +Nv +Nv +Nv +dR +kK +sM +sM +sM +sM +sM +sM +sM +sM +sw +dR +Nv +Nv +Nv +Nv +Nv +Nv +Nv +yo +yo +dR +dR +dR +yo +dR +yo +yo +yo +dR +yo +YT +YT +YT +YT +YT +YT +Qv +Am +"} +(41,1,1) = {" +Am +Qv +dR +sa +yo +dR +sa +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +dR +qc +sM +sM +sM +qc +Tz +Nv +Nv +Nv +qc +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +dR +sw +dR +dR +dR +dR +dR +dR +dR +dR +YT +YT +YT +YT +YT +uJ +Qv +Am +"} +(42,1,1) = {" +Am +Qv +dR +AI +yo +yo +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pl +KA +dR +dR +dR +sa +sa +dR +qc +qc +qc +dR +dR +dR +dR +dR +dR +dR +Qv +Am +"} +(43,1,1) = {" +Am +Qv +yo +yo +VW +yo +yo +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sa +dR +dR +kK +sa +sa +dR +dR +qc +qc +qc +dR +sa +sa +dR +dR +dR +Qv +Am +"} +(44,1,1) = {" +Am +Qv +dR +yo +yo +yo +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +qc +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sa +dR +sa +dR +dR +dR +dR +kK +qc +qc +qc +dR +sa +sa +dR +dR +dR +Qv +Am +"} +(45,1,1) = {" +Am +Qv +dR +dR +yo +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +dR +dR +dR +dR +kK +dR +dR +dR +dR +Qv +dR +Qv +dR +kK +dR +Qv +Qv +Am +"} +(46,1,1) = {" +Am +Qv +Qv +sa +dR +dR +sa +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +qc +dR +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +kK +dR +dR +dR +dR +dR +dR +dR +dR +dR +dR +Qv +Qv +Qv +dR +dR +dR +Qv +Qv +Am +"} +(47,1,1) = {" +Am +Qv +Qv +dR +dR +YR +sa +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +dR +dR +qc +Nv +Nv +Nv +sM +sM +sM +sM +Nv +Nv +Nv +Nv +sM +sM +sM +Nv +Nv +Nv +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +dR +pL +dR +dR +dR +dR +dR +dR +Qv +Qv +Qv +Qv +Qv +dR +dR +dR +Qv +Qv +Am +"} +(48,1,1) = {" +Am +Qv +Qv +dR +kK +sa +YR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +dR +dR +dR +sw +sM +Nv +Nv +Nv +Nv +Nv +Nv +sM +sM +Nv +Nv +Nv +Nv +Nv +sM +Nv +Nv +Nv +qc +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +dR +dR +dR +dR +Qv +Qv +Qv +Qv +dR +sw +dR +dR +dR +Qv +Am +"} +(49,1,1) = {" +Am +Qv +Qv +dR +dR +YR +sa +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +qc +sM +sM +sM +sM +sM +eB +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +dR +dR +dR +dR +dR +pL +pL +Qv +Qv +Qv +Qv +dR +sw +dR +dR +Qv +Am +"} +(50,1,1) = {" +Am +Qv +Qv +sa +dR +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sa +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +pl +sM +sM +sM +sM +sM +sM +dR +pL +pL +dR +dR +sa +dR +pL +pL +pL +pL +Qv +Qv +Qv +sw +eB +dR +dR +Qv +Am +"} +(51,1,1) = {" +Am +Qv +Qv +Qv +dR +sa +kK +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sa +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sa +sM +sM +sM +sM +sM +sM +dR +pL +dR +dR +kK +dR +dR +pL +pL +pL +pL +Qv +Qv +Qv +Qv +dR +dR +sa +Qv +Am +"} +(52,1,1) = {" +Am +Qv +Qv +Qv +dR +sa +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +pL +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sa +sM +sM +sM +sM +sM +sM +dR +pL +dR +dR +dR +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +dR +dR +dR +Qv +Am +"} +(53,1,1) = {" +Am +Qv +Qv +Qv +dR +YR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sw +Dz +sM +sM +sM +sM +sM +pL +pL +pL +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +dR +dR +dR +sa +dR +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +dR +dR +Qv +Am +"} +(54,1,1) = {" +Am +Qv +Qv +Qv +dR +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +jK +yJ +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +sM +sM +GX +dR +dR +pL +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sa +dR +sw +sa +dR +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +dR +Qv +Qv +Am +"} +(55,1,1) = {" +Am +Qv +Qv +dR +dR +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +MT +be +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +pL +dR +kK +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +qc +sM +sM +sM +sM +sM +dR +sw +eB +dR +dR +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +dR +dR +dR +Qv +Am +"} +(56,1,1) = {" +Am +Qv +Qv +dR +eB +sw +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +gB +xB +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +qc +sM +sM +sM +sM +sM +km +sw +eB +eB +sw +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +dR +dR +dR +Qv +Am +"} +(57,1,1) = {" +Am +Qv +Qv +dR +Lw +sa +sa +kK +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +Pl +Eh +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +dR +sw +dR +sM +sM +sM +sM +sM +sM +sM +sM +sa +eB +sM +sM +sM +sM +sM +pL +dR +eB +eB +dR +dR +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +dR +dR +dR +Qv +Am +"} +(58,1,1) = {" +Am +Qv +Qv +dR +sa +sa +YR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +hc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +Gn +eB +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +dR +sw +sM +sM +sM +sM +sM +pL +GX +dR +pL +dR +dR +pL +pL +pL +pL +Qv +Qv +Qv +Qv +dR +sa +dR +dR +Qv +Am +"} +(59,1,1) = {" +Am +Qv +Qv +sa +YR +sa +sa +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +qc +sM +sM +sM +dR +sM +sM +Nv +Nv +Nv +dR +sM +sM +sM +sM +sM +kK +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +pL +dR +dR +pL +dR +pL +pL +pL +pL +pL +pL +Qv +Qv +dR +dR +dR +Qv +Qv +Qv +Am +"} +(60,1,1) = {" +Am +Qv +Qv +dR +dR +sw +kK +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +dR +dR +Nv +Nv +Nv +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +kK +dR +qc +sM +sM +sM +sM +sM +pL +GX +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +dR +dR +dR +Qv +Qv +Qv +Am +"} +(61,1,1) = {" +Am +Qv +dR +kK +sw +eB +eB +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +dR +sM +sM +sM +sM +sM +dR +sa +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +dR +dR +dR +Qv +Qv +Qv +Qv +Am +"} +(62,1,1) = {" +Am +Qv +dR +sa +DY +eB +eB +dR +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sa +dR +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sa +dR +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +dR +dR +Qv +Qv +Qv +Qv +Qv +Am +"} +(63,1,1) = {" +Am +Qv +dR +Lw +Lw +dR +sa +dR +sM +sM +sM +sM +sM +sM +sM +sM +eB +dR +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sa +dR +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +dR +Qv +Qv +dR +dR +dR +dR +Qv +Qv +Qv +Qv +Am +"} +(64,1,1) = {" +Am +Qv +dR +sa +sa +sw +kK +dR +sM +sM +sM +sM +sM +sM +sM +sM +sw +dR +sM +sM +sM +sM +sM +qc +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +eB +dR +dR +dR +sa +dR +sw +Qv +Qv +Qv +Qv +Qv +Am +"} +(65,1,1) = {" +Am +Qv +dR +dR +kK +dR +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +qc +sM +sM +sM +sM +qc +sM +sM +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +Nv +Nv +Nv +Nv +Nv +Nv +dR +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +sw +sa +sa +dR +sw +sw +eB +Qv +Qv +Qv +Qv +Qv +Am +"} +(66,1,1) = {" +Am +Qv +dR +kK +sa +sa +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +sa +dR +sM +sM +sM +sM +qc +sM +sM +sM +sM +sa +dR +sM +sM +sM +sM +sM +sw +dR +Nv +Nv +Nv +Nv +Nv +sM +sM +sM +Nv +Nv +dR +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +dR +sa +sa +dR +dR +dR +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(67,1,1) = {" +Am +Qv +dR +dR +YR +DY +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sa +dR +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +dR +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +sa +dR +dR +dR +Qv +Qv +Qv +Qv +Qv +Am +"} +(68,1,1) = {" +Am +Qv +dR +dR +Lw +sa +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +sM +sM +sM +dR +dR +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +sa +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +dR +dR +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(69,1,1) = {" +Am +Qv +Qv +dR +eB +sw +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +dR +Nv +Nv +Nv +dR +dR +sM +sM +sM +sM +sM +dR +sM +sM +sM +sM +sM +dR +qc +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(70,1,1) = {" +Am +Qv +Qv +Qv +dR +eB +sw +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +kK +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +Nv +dR +dR +dR +dR +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(71,1,1) = {" +Am +Qv +Qv +dR +sa +sa +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +dR +qc +dR +pl +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +Kl +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(72,1,1) = {" +Am +Qv +Qv +sa +sa +Lw +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(73,1,1) = {" +Am +Qv +Qv +Qv +dR +sw +dR +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +sM +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +pL +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(74,1,1) = {" +Am +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Qv +Am +"} +(75,1,1) = {" +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +Am +"} diff --git a/_maps/virtual_domains/syndicate_assault.dmm b/_maps/virtual_domains/syndicate_assault.dmm new file mode 100644 index 00000000000..770f0967404 --- /dev/null +++ b/_maps/virtual_domains/syndicate_assault.dmm @@ -0,0 +1,4265 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"aq" = ( +/obj/item/storage/backpack/duffelbag/syndie/surgery, +/obj/structure/table/reinforced, +/turf/open/floor/plastic, +/area/ruin/space/has_grav/powered/virtual_domain) +"aw" = ( +/obj/structure/table/reinforced, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"aN" = ( +/obj/structure/closet/crate/secure/gear{ + req_access = list("syndicate") + }, +/obj/item/stack/sheet/iron/fifty, +/obj/item/stack/sheet/iron/fifty, +/obj/item/stack/sheet/iron/fifty, +/obj/item/stack/sheet/plasteel/twenty, +/obj/item/stack/sheet/mineral/plastitanium{ + amount = 50 + }, +/obj/item/stack/sheet/glass/fifty, +/obj/item/stack/rods/fifty, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"aO" = ( +/obj/machinery/recharge_station, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"aZ" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 8 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/mob/living/basic/syndicate/ranged/shotgun/space/stormtrooper, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"bh" = ( +/turf/open/floor/carpet/royalblack, +/area/ruin/space/has_grav/powered/virtual_domain) +"bo" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 1 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"bD" = ( +/obj/structure/table/reinforced, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"bG" = ( +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"cc" = ( +/obj/structure/closet/crate/secure/gear{ + req_access = list("syndicate") + }, +/obj/effect/spawner/random/clothing/costume, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"cj" = ( +/obj/structure/transit_tube/crossing, +/turf/closed/wall/r_wall/syndicate, +/area/ruin/space/has_grav/powered/virtual_domain) +"ct" = ( +/obj/structure/closet/syndicate{ + anchored = 1; + desc = "A basic closet for all your villainous needs."; + locked = 1; + name = "Closet"; + req_access = list("syndicate"); + secure = 1 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"cw" = ( +/obj/structure/closet/syndicate{ + anchored = 1; + desc = "A basic closet for all your villainous needs."; + locked = 1; + name = "Closet"; + req_access = list("syndicate"); + secure = 1 + }, +/obj/item/clothing/under/syndicate/combat, +/obj/item/clothing/gloves/combat, +/obj/item/clothing/shoes/combat, +/obj/item/clothing/mask/gas/syndicate, +/obj/item/clothing/under/syndicate/skirt, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"cy" = ( +/obj/machinery/door/airlock/grunge{ + name = "Syndicate Ship Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2{ + dir = 8 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"cB" = ( +/obj/machinery/camera/xray{ + c_tag = "Medbay"; + dir = 6; + network = list("fsci"); + screen_loc = "" + }, +/turf/open/floor/plastic, +/area/ruin/space/has_grav/powered/virtual_domain) +"cR" = ( +/obj/machinery/light/small/directional/south, +/turf/open/floor/carpet/royalblack, +/area/ruin/space/has_grav/powered/virtual_domain) +"cZ" = ( +/obj/structure/table/reinforced, +/obj/item/gun/ballistic/automatic/l6_saw/unrestricted, +/obj/item/ammo_box/magazine/m7mm, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"da" = ( +/obj/machinery/stasis, +/turf/open/floor/plastic, +/area/ruin/space/has_grav/powered/virtual_domain) +"dd" = ( +/obj/structure/sign/warning/vacuum/external, +/turf/closed/wall/r_wall/syndicate, +/area/ruin/space/has_grav/powered/virtual_domain) +"di" = ( +/obj/machinery/power/terminal{ + dir = 1 + }, +/obj/structure/cable, +/obj/item/paper/fluff/ruins/forgottenship/powerissues, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"dw" = ( +/obj/machinery/light/small/directional/south, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"dz" = ( +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"dU" = ( +/obj/structure/cable, +/obj/structure/fans/tiny, +/obj/machinery/door/airlock/external/ruin{ + name = "Syndicate Ship Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"eB" = ( +/obj/machinery/camera/xray{ + c_tag = "Cargo pod"; + dir = 9; + network = list("fsci"); + screen_loc = "" + }, +/obj/structure/closet, +/obj/item/clothing/under/syndicate/tacticool, +/obj/item/clothing/under/syndicate/tacticool, +/obj/item/clothing/under/syndicate/tacticool, +/obj/item/card/id/advanced/black/syndicate_command/crew_id, +/obj/item/card/id/advanced/black/syndicate_command/crew_id, +/obj/item/card/id/advanced/black/syndicate_command/crew_id, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"fd" = ( +/obj/structure/transit_tube/crossing, +/turf/open/space/basic, +/area/space) +"fG" = ( +/obj/structure/toilet{ + dir = 1 + }, +/obj/machinery/light/directional/south, +/turf/open/floor/iron, +/area/ruin/space/has_grav/powered/virtual_domain) +"fJ" = ( +/obj/machinery/light/small/directional/north, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"fM" = ( +/obj/machinery/computer/crew/syndie{ + dir = 8 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"fV" = ( +/obj/machinery/atmospherics/components/unary/vent_pump, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"gD" = ( +/obj/effect/mob_spawn/ghost_role/human/syndicatespace, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"hg" = ( +/obj/structure/window/reinforced/plasma/plastitanium, +/obj/machinery/door/poddoor{ + id = "fslockdown"; + name = "Ship Blast Door"; + state_open = 1 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"hy" = ( +/obj/structure/table/reinforced, +/obj/item/paper/fluff/ruins/forgottenship/missionobj, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"hA" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/wall/r_wall/syndicate, +/area/ruin/space/has_grav/powered/virtual_domain) +"hD" = ( +/obj/structure/table/reinforced, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"ip" = ( +/mob/living/basic/syndicate/melee/sword/space/stormtrooper, +/turf/open/floor/plastic, +/area/ruin/space/has_grav/powered/virtual_domain) +"iB" = ( +/obj/machinery/light/directional/north, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"iL" = ( +/obj/structure/sign/departments/cargo, +/turf/closed/wall/r_wall/syndicate, +/area/ruin/space/has_grav/powered/virtual_domain) +"iU" = ( +/obj/structure/closet/crate/secure/gear{ + req_access = list("syndicate") + }, +/obj/item/melee/energy/sword/saber/red, +/obj/machinery/light/small/directional/north, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"iW" = ( +/obj/structure/table/reinforced, +/obj/machinery/button/door{ + id = "fslockdown"; + name = "Window shutters"; + req_access = list("syndicate") + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"iX" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/mob/living/basic/syndicate/ranged/smg/space/stormtrooper, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"ja" = ( +/obj/machinery/door/window{ + dir = 1; + name = "Spare Equipment"; + req_access = list("syndicate") + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"jl" = ( +/obj/structure/bodycontainer/crematorium{ + id = "fscremate" + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"jA" = ( +/obj/structure/cable, +/mob/living/basic/syndicate/melee/space/stormtrooper, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"jJ" = ( +/obj/machinery/door/airlock/grunge{ + name = "Syndicate Ship Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/iron/dark/side{ + dir = 1 + }, +/area/ruin/space/has_grav/powered/virtual_domain) +"kh" = ( +/obj/machinery/door/airlock/grunge{ + name = "Syndicate Ship Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"ki" = ( +/obj/structure/table/reinforced, +/obj/machinery/computer/security/telescreen/interrogation{ + name = "Cameras monitor"; + network = list("fsci"); + req_access = list("syndicate"); + screen_loc = "" + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"kI" = ( +/obj/machinery/computer/atmos_alert{ + dir = 8 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"kJ" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"li" = ( +/obj/structure/transit_tube/station/dispenser/reverse{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"ln" = ( +/obj/machinery/turretid{ + control_area = "/area/ruin/space/has_grav/syndicate_forgotten_ship"; + enabled = 0; + icon_state = "control_kill"; + lethal = 1; + name = "Ship turret control panel"; + pixel_y = 32; + req_access = list("syndicate") + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"lo" = ( +/obj/structure/fans/tiny, +/obj/machinery/door/airlock/external/ruin{ + name = "Syndicate Ship Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"lN" = ( +/obj/machinery/light/small/directional/east, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"mo" = ( +/obj/machinery/atmospherics/components/unary/vent_scrubber/layer2{ + dir = 8 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"mD" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 10 + }, +/obj/item/wrench, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"nk" = ( +/obj/machinery/power/apc/auto_name/directional/north, +/obj/effect/mapping_helpers/apc/syndicate_access, +/obj/structure/cable, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"nB" = ( +/turf/closed/mineral/random, +/area/space) +"nG" = ( +/obj/machinery/light/directional/south, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"nO" = ( +/obj/machinery/mineral/ore_redemption{ + name = "Syndicate ore redemption machine"; + ore_multiplier = 4; + req_access = list("syndicate") + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"nU" = ( +/obj/structure/sign/poster/contraband/syndicate_pistol, +/turf/closed/wall/r_wall/syndicate, +/area/ruin/space/has_grav/powered/virtual_domain) +"oM" = ( +/obj/structure/cable, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"oZ" = ( +/mob/living/basic/syndicate/melee/sword/space/stormtrooper, +/turf/open/floor/carpet/royalblack, +/area/ruin/space/has_grav/powered/virtual_domain) +"pl" = ( +/obj/machinery/atmospherics/components/tank/air{ + dir = 8 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"pz" = ( +/obj/machinery/computer/security{ + desc = "Used to access interrogation room camera."; + dir = 8; + name = "Ship cameras console"; + network = list("fsc","fsci"); + screen_loc = "" + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"pH" = ( +/obj/structure/table/reinforced, +/obj/item/toy/plush/nukeplushie, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"pM" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"pS" = ( +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 6 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"pU" = ( +/obj/machinery/shower/directional/north, +/obj/machinery/light/directional/south, +/turf/open/floor/iron, +/area/ruin/space/has_grav/powered/virtual_domain) +"qf" = ( +/obj/structure/table/optable, +/obj/machinery/light/small/directional/north, +/turf/open/floor/plastic, +/area/ruin/space/has_grav/powered/virtual_domain) +"qx" = ( +/turf/open/space/basic, +/area/space) +"qU" = ( +/obj/structure/sign/poster/contraband/c20r, +/turf/closed/wall/r_wall/syndicate, +/area/ruin/space/has_grav/powered/virtual_domain) +"qY" = ( +/obj/machinery/light/small/directional/south, +/turf/open/floor/iron/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"rm" = ( +/obj/machinery/button/crematorium{ + id = "fscremate"; + pixel_x = -32 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"ru" = ( +/turf/closed/wall/r_wall/syndicate, +/area/ruin/space/has_grav/powered/virtual_domain) +"rH" = ( +/obj/machinery/airalarm/directional/north, +/obj/effect/mapping_helpers/airalarm/syndicate_access, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"rM" = ( +/obj/structure/closet/syndicate{ + anchored = 1; + desc = "A basic closet for all your villainous needs."; + locked = 1; + name = "Closet"; + req_access = list("syndicate"); + secure = 1 + }, +/obj/effect/spawner/random/contraband/armory, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"rP" = ( +/obj/effect/mob_spawn/ghost_role/human/syndicatespace, +/obj/machinery/light/small/directional/south, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"sg" = ( +/obj/machinery/ore_silo, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"sq" = ( +/obj/machinery/door/window{ + name = "Control Room"; + req_access = list("syndicate") + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"sz" = ( +/obj/machinery/atmospherics/components/unary/vent_pump{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"sH" = ( +/obj/structure/displaycase{ + req_access = list("syndicate"); + start_showpiece_type = /obj/item/gun/ballistic/automatic/pistol/deagle/camo + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"sK" = ( +/obj/structure/closet/crate/secure/gear{ + req_access = list("syndicate") + }, +/obj/item/stack/sheet/mineral/titanium{ + amount = 40 + }, +/obj/item/stack/sheet/mineral/uranium{ + amount = 15 + }, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"sL" = ( +/obj/structure/chair/comfy, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"sM" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"tv" = ( +/obj/structure/table/reinforced, +/obj/machinery/button/door{ + id = "fscaproom"; + name = "Room shutters control"; + req_access = list("syndicate") + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"tI" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"uP" = ( +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"vp" = ( +/obj/structure/table/reinforced, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"vD" = ( +/obj/machinery/portable_atmospherics/canister/oxygen, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"vK" = ( +/obj/machinery/door/airlock/grunge{ + name = "Syndicate Ship Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"vU" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"wb" = ( +/obj/machinery/atmospherics/components/unary/portables_connector/visible/layer2, +/obj/machinery/portable_atmospherics/scrubber{ + anchored = 1 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"we" = ( +/turf/closed/mineral/random/high_chance, +/area/space) +"wK" = ( +/obj/machinery/door/airlock/grunge{ + name = "Syndicate Ship Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/iron/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"wL" = ( +/obj/structure/table/reinforced, +/obj/item/storage/medkit/regular, +/obj/machinery/light/small/directional/north, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"xJ" = ( +/obj/structure/closet/syndicate{ + anchored = 1; + desc = "A basic closet for all your villainous needs."; + locked = 1; + name = "Closet"; + req_access = list("syndicate"); + secure = 1 + }, +/obj/item/ammo_box/c9mm, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"xZ" = ( +/obj/machinery/computer/camera_advanced/syndie{ + dir = 8 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"yl" = ( +/obj/machinery/door/airlock/grunge{ + name = "Captain's Room" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/obj/machinery/door/poddoor{ + id = "fscaproom"; + name = "Captain's Blast Door"; + state_open = 1 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"yD" = ( +/mob/living/basic/syndicate/ranged/smg/space/stormtrooper, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"yJ" = ( +/obj/structure/table/reinforced, +/obj/machinery/atmospherics/components/unary/vent_scrubber/layer2, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"yR" = ( +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"yT" = ( +/obj/item/ai_module/core/full/cybersun, +/obj/structure/table/reinforced, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"yV" = ( +/obj/structure/table/reinforced, +/obj/item/assembly/prox_sensor, +/obj/item/assembly/prox_sensor, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"yZ" = ( +/turf/closed/mineral, +/area/space) +"zi" = ( +/obj/machinery/vending/cigarette/syndicate, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"zt" = ( +/obj/structure/cable, +/obj/machinery/atmospherics/components/unary/vent_scrubber/layer2{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"Aa" = ( +/obj/structure/chair/comfy/shuttle, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"AN" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 4 + }, +/mob/living/basic/syndicate/ranged/smg/space/stormtrooper, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Bm" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/syndicate, +/area/ruin/space/has_grav/powered/virtual_domain) +"BK" = ( +/obj/structure/lattice/catwalk, +/obj/structure/cable, +/turf/open/space/basic, +/area/space) +"BN" = ( +/obj/structure/transit_tube/crossing, +/turf/template_noop, +/area/virtual_domain/safehouse) +"Cf" = ( +/obj/machinery/light/directional/south, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Ci" = ( +/obj/structure/closet/syndicate{ + anchored = 1; + desc = "A basic closet for all your villainous needs."; + locked = 1; + name = "Closet"; + req_access = list("syndicate"); + secure = 1 + }, +/obj/item/crowbar/red, +/obj/item/ammo_box/magazine/m9mm_aps, +/obj/item/ammo_box/magazine/m9mm_aps, +/turf/open/floor/carpet/royalblack, +/area/ruin/space/has_grav/powered/virtual_domain) +"Cn" = ( +/obj/machinery/camera/xray/directional/east{ + c_tag = "Conference room"; + network = list("fsc"); + screen_loc = "" + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"CK" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 4 + }, +/mob/living/basic/syndicate/ranged/smg/pilot, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"CR" = ( +/obj/structure/closet/syndicate{ + anchored = 1; + desc = "A basic closet for all your villainous needs."; + locked = 1; + name = "Closet"; + req_access = list("syndicate"); + secure = 1 + }, +/obj/item/coin/antagtoken, +/obj/item/dnainjector/thermal, +/obj/item/storage/box/firingpins/syndicate, +/obj/item/storage/box/firingpins/syndicate, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"De" = ( +/obj/machinery/door/airlock/grunge{ + name = "Syndicate Ship Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"Dj" = ( +/obj/structure/table/reinforced, +/obj/item/ammo_box/magazine/smgm45, +/obj/item/ammo_box/magazine/smgm45, +/obj/item/ammo_box/magazine/smgm45, +/obj/item/gun/ballistic/automatic/c20r/unrestricted, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"DA" = ( +/obj/structure/closet/crate/secure/gear{ + req_access = list("syndicate") + }, +/obj/effect/spawner/random/maintenance, +/obj/effect/spawner/random/maintenance, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"EB" = ( +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"EX" = ( +/mob/living/basic/syndicate/ranged/shotgun/space/stormtrooper, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Fp" = ( +/obj/structure/tank_dispenser/oxygen, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"FN" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Gn" = ( +/obj/structure/chair/comfy{ + dir = 1 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Gs" = ( +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"GB" = ( +/obj/structure/cable, +/obj/machinery/door/airlock/external/ruin{ + name = "Syndicate Ship Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"GZ" = ( +/obj/machinery/door/airlock/external/ruin{ + name = "Syndicate Ship Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Hq" = ( +/turf/closed/indestructible/binary, +/area/space) +"HU" = ( +/obj/machinery/door/airlock/grunge{ + name = "Bridge" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"Ia" = ( +/obj/effect/mob_spawn/ghost_role/human/syndicatespace/captain, +/turf/open/floor/carpet/royalblack, +/area/ruin/space/has_grav/powered/virtual_domain) +"Id" = ( +/obj/machinery/power/shuttle_engine/huge{ + dir = 8 + }, +/turf/open/space/basic, +/area/ruin/space/has_grav/powered/virtual_domain) +"If" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 9 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"Ig" = ( +/obj/machinery/porta_turret/syndicate/energy{ + dir = 4; + name = "Syndicate Ship Turret"; + on = 0; + shot_delay = 10 + }, +/turf/closed/wall/r_wall/syndicate/nodiagonal, +/area/ruin/space/has_grav/powered/virtual_domain) +"Im" = ( +/obj/structure/table/reinforced, +/obj/item/ammo_box/c9mm, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Io" = ( +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"IC" = ( +/obj/structure/table/reinforced, +/obj/item/paper, +/obj/item/pen, +/turf/open/floor/carpet/royalblack, +/area/ruin/space/has_grav/powered/virtual_domain) +"IH" = ( +/obj/machinery/door/airlock/external/ruin{ + name = "Syndicate Ship Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/obj/structure/cable, +/obj/structure/fans/tiny, +/turf/open/floor/plating, +/area/ruin/space/has_grav/powered/virtual_domain) +"IV" = ( +/obj/machinery/door/airlock/grunge{ + name = "Syndicate Ship Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/plating, +/area/ruin/space/has_grav/powered/virtual_domain) +"Jg" = ( +/obj/machinery/light/small/directional/south, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"Jz" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 5 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"JA" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"JN" = ( +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 5 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"JP" = ( +/obj/structure/sink/directional/south, +/turf/open/floor/iron/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"Kz" = ( +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"Lk" = ( +/obj/structure/transit_tube/crossing, +/turf/closed/mineral/random, +/area/space) +"Lo" = ( +/obj/structure/filingcabinet, +/obj/machinery/door/window{ + dir = 8; + name = "Syndicate Interior Door"; + req_access = list("syndicate") + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"Mc" = ( +/obj/structure/closet/syndicate{ + anchored = 1; + desc = "A basic closet for all your villainous needs."; + locked = 1; + name = "Closet"; + req_access = list("syndicate"); + secure = 1 + }, +/obj/item/crowbar/red, +/obj/item/ammo_box/magazine/m9mm, +/obj/item/ammo_box/magazine/m9mm, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Mm" = ( +/obj/structure/closet/syndicate{ + anchored = 1; + desc = "A basic closet for all your villainous needs."; + locked = 1; + name = "Closet"; + req_access = list("syndicate"); + secure = 1 + }, +/obj/item/clothing/head/hats/hos/beret/syndicate, +/obj/item/clothing/suit/armor/vest/capcarapace/syndicate, +/obj/item/clothing/mask/gas/syndicate, +/obj/item/clothing/under/syndicate, +/obj/item/clothing/under/syndicate/skirt, +/obj/item/clothing/gloves/combat, +/obj/item/clothing/shoes/combat, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"MR" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/obj/machinery/light/small/directional/south, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"Nm" = ( +/obj/structure/closet/crate/secure/gear{ + req_access = list("syndicate") + }, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"Nr" = ( +/obj/structure/closet/crate/secure/gear{ + req_access = list("syndicate") + }, +/obj/item/stack/sheet/mineral/gold{ + amount = 30 + }, +/obj/item/stack/sheet/mineral/silver{ + amount = 30 + }, +/obj/machinery/light/small/directional/south, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"Nt" = ( +/obj/structure/table/reinforced, +/obj/machinery/atmospherics/components/unary/vent_pump, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Of" = ( +/obj/structure/closet/crate/secure/gear{ + req_access = list("syndicate") + }, +/obj/item/disk/surgery/forgottenship, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"Ox" = ( +/obj/machinery/atmospherics/components/unary/vent_pump, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"OH" = ( +/obj/structure/cable, +/obj/structure/table/reinforced, +/obj/item/storage/toolbox/syndicate, +/obj/item/storage/toolbox/syndicate, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"OI" = ( +/obj/structure/chair/comfy/shuttle{ + dir = 1 + }, +/obj/structure/cable, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"PR" = ( +/obj/machinery/door/password/voice/sfc{ + password = null + }, +/obj/structure/fans/tiny, +/obj/machinery/door/airlock/grunge{ + desc = "Vault airlock preventing air from going out."; + name = "Syndicate Vault Airlock" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"Qg" = ( +/obj/machinery/suit_storage_unit/syndicate{ + helmet_type = /obj/item/clothing/head/helmet/space/syndicate/black; + suit_type = /obj/item/clothing/suit/space/syndicate/black + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Qi" = ( +/obj/item/stack/sheet/mineral/uranium{ + amount = 15 + }, +/obj/structure/cable, +/obj/machinery/light/small/directional/north, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"QF" = ( +/obj/structure/table/reinforced, +/obj/item/dualsaber/green, +/obj/machinery/light/small/directional/east, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"QG" = ( +/obj/structure/tank_dispenser/oxygen, +/turf/closed/mineral/random, +/area/space) +"QX" = ( +/mob/living/basic/syndicate/ranged/space/stormtrooper, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Ra" = ( +/obj/machinery/atmospherics/components/unary/vent_scrubber/layer2, +/obj/machinery/light/small/directional/north, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"RQ" = ( +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"RU" = ( +/obj/machinery/suit_storage_unit/syndicate, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Sc" = ( +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Sd" = ( +/obj/structure/closet/syndicate{ + anchored = 1; + desc = "A basic closet for all your villainous needs."; + locked = 1; + name = "Closet"; + req_access = list("syndicate"); + secure = 1 + }, +/obj/item/crowbar/red, +/obj/item/ammo_box/magazine/m9mm, +/obj/item/ammo_box/magazine/m9mm, +/obj/machinery/light/small/directional/north, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Sq" = ( +/obj/machinery/power/smes, +/obj/structure/cable, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"Sv" = ( +/obj/structure/closet/crate/secure/gear{ + req_access = list("syndicate") + }, +/obj/effect/spawner/random/food_or_drink/donkpockets, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Sz" = ( +/turf/open/floor/iron/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"SX" = ( +/obj/machinery/vending/medical/syndicate_access/cybersun, +/turf/open/floor/plastic, +/area/ruin/space/has_grav/powered/virtual_domain) +"TB" = ( +/turf/closed/indestructible/syndicate, +/area/ruin/space/has_grav/powered/virtual_domain) +"UQ" = ( +/obj/structure/sign/poster/contraband/syndicate_recruitment, +/turf/closed/wall/r_wall/syndicate, +/area/ruin/space/has_grav/powered/virtual_domain) +"Vk" = ( +/obj/machinery/porta_turret/syndicate/energy{ + dir = 4; + name = "Syndicate Ship Turret"; + on = 0; + shot_delay = 10 + }, +/turf/closed/wall/r_wall/syndicate, +/area/ruin/space/has_grav/powered/virtual_domain) +"Vq" = ( +/obj/structure/transit_tube/station/dispenser/reverse{ + dir = 8 + }, +/turf/template_noop, +/area/virtual_domain/safehouse) +"Wd" = ( +/obj/structure/sign/poster/contraband/tools, +/turf/closed/wall/r_wall/syndicate, +/area/ruin/space/has_grav/powered/virtual_domain) +"Wy" = ( +/obj/structure/closet/crate/secure/gear{ + req_access = list("syndicate") + }, +/obj/item/stack/ore/plasma{ + amount = 19 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"WR" = ( +/obj/machinery/power/port_gen/pacman/super{ + anchored = 1 + }, +/obj/structure/cable, +/turf/open/floor/mineral/plastitanium/red, +/area/ruin/space/has_grav/powered/virtual_domain) +"Xp" = ( +/turf/open/space/basic, +/area/ruin/space/has_grav/powered/virtual_domain) +"XS" = ( +/obj/machinery/light/directional/north, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Yb" = ( +/obj/structure/closet/crate/secure/gear{ + req_access = list("syndicate") + }, +/obj/item/clothing/head/helmet/space/syndicate/black/engie, +/obj/item/clothing/suit/space/syndicate/black/engie, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"Yi" = ( +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/floor/plastic, +/area/ruin/space/has_grav/powered/virtual_domain) +"Yj" = ( +/obj/structure/closet/crate/secure/gear{ + req_access = list("syndicate") + }, +/obj/item/stack/ore/diamond{ + amount = 3 + }, +/turf/open/floor/mineral/plastitanium, +/area/ruin/space/has_grav/powered/virtual_domain) +"Yk" = ( +/obj/machinery/door/airlock/grunge{ + name = "Captain's Room" + }, +/obj/effect/mapping_helpers/airlock/access/all/syndicate/general, +/obj/machinery/door/poddoor{ + id = "fscaproom"; + name = "Captain's Blast Door"; + state_open = 1 + }, +/turf/open/floor/carpet/royalblack, +/area/ruin/space/has_grav/powered/virtual_domain) +"Yr" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"Yu" = ( +/obj/structure/chair/comfy/black, +/turf/open/floor/carpet/royalblack, +/area/ruin/space/has_grav/powered/virtual_domain) +"YV" = ( +/obj/structure/sink/directional/south, +/obj/structure/mirror/directional/west, +/turf/open/floor/iron/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"Za" = ( +/obj/machinery/computer/operating, +/turf/open/floor/plastic, +/area/ruin/space/has_grav/powered/virtual_domain) +"Zb" = ( +/turf/open/floor/plastic, +/area/ruin/space/has_grav/powered/virtual_domain) +"ZA" = ( +/obj/machinery/power/shuttle_engine/propulsion{ + dir = 8 + }, +/turf/open/space/basic, +/area/ruin/space/has_grav/powered/virtual_domain) + +(1,1,1) = {" +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +"} +(2,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(3,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(4,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(5,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(6,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(7,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +we +we +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(8,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +we +we +we +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(9,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +we +we +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(10,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Xp +Xp +Id +qx +qx +Xp +Xp +Id +qx +qx +Xp +Xp +Id +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(11,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Xp +Xp +Xp +qx +qx +Xp +Xp +Xp +qx +qx +Xp +Xp +Xp +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(12,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +ZA +Xp +Xp +Xp +ZA +ZA +Xp +Xp +Xp +ZA +ZA +Xp +Xp +Xp +ZA +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +we +we +we +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(13,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +ru +ru +ru +ru +ru +ru +ru +ru +ru +ru +ru +ru +ru +ru +ru +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +we +we +we +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(14,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +Vk +ru +Sv +vD +uP +uP +Yj +vD +uP +Wy +DA +uP +QX +vD +cc +ru +Vk +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(15,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +Io +uP +uP +QX +lN +uP +uP +uP +uP +uP +lN +uP +uP +uP +uP +hA +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(16,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +ru +ru +ru +ru +ru +ru +ru +IV +ru +ru +ru +IV +ru +ru +ru +ru +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(17,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +ru +vp +ru +Ia +Ci +ru +Sq +di +WR +ru +yV +Gn +uP +Mc +uP +ru +qx +qx +qx +qx +qx +qx +qx +qx +qx +nB +we +nB +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(18,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +vp +Jg +ru +bh +cR +ru +Qi +sz +Kz +ru +Mc +gD +yD +uP +rP +ru +qx +qx +qx +qx +qx +qx +qx +nB +nB +nB +nB +nB +nB +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(19,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +sH +Kz +yl +oZ +bh +Yk +pS +RQ +Jz +vK +uP +uP +uP +sL +hy +ru +qx +qx +qx +qx +qx +qx +nB +nB +TB +TB +TB +TB +nB +nB +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(20,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +CR +Kz +ru +bh +bh +Yk +pM +zt +pM +vK +uP +uP +EX +sL +Im +ru +qx +qx +qx +qx +qx +nB +nB +TB +TB +Yb +Yb +TB +Bm +nB +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(21,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +yT +Kz +ru +Yu +IC +ru +mD +JN +MR +ru +Sd +gD +uP +uP +gD +ru +qx +qx +qx +qx +nB +QG +nB +TB +aN +bG +bG +sK +TB +nB +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(22,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +Ig +ru +Lo +ru +tv +ki +nU +wb +EB +pl +ru +hD +Gn +uP +Mc +ru +Ig +qx +qx +qx +qx +nB +we +nB +TB +iU +bG +bG +Nr +TB +nB +we +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(23,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +ru +ru +ru +ru +ru +ru +ru +cy +ru +ru +ru +ru +ru +ru +ru +qx +qx +qx +qx +qx +qx +nB +nB +TB +Nm +bG +bG +Of +TB +nB +nB +qx +qx +qx +qx +qx +qx +qx +sM +sM +sM +sM +sM +kJ +qx +Hq +"} +(24,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +ru +Za +Yi +Zb +SX +ru +uP +yR +uP +ru +YV +Sz +jJ +fG +ru +qx +qx +qx +qx +qx +nB +nB +nB +TB +TB +PR +TB +TB +TB +nB +nB +nB +qx +qx +qx +qx +qx +qx +sM +sM +sM +sM +sM +sM +qx +Hq +"} +(25,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +ru +qf +Zb +ip +da +ru +Ra +Sc +uP +ru +JP +qY +ru +ru +ru +qx +qx +qx +qx +qx +nB +nB +we +ru +ru +uP +sg +ru +ru +nB +nB +nB +nB +qx +qx +qx +qx +qx +sM +sM +sM +sM +sM +sM +qx +Hq +"} +(26,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +ru +aq +cB +Zb +Zb +De +Ox +Gs +uP +wK +Sz +Sz +jJ +pU +ru +qx +qx +qx +qx +qx +qx +nB +nB +qU +Fp +uP +uP +li +cj +Lk +Lk +fd +fd +fd +fd +fd +fd +fd +BN +Vq +sM +sM +sM +sM +qx +Hq +"} +(27,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +Vk +ru +ru +ru +ru +ru +ru +ru +kh +ru +ru +ru +ru +ru +ru +ru +Vk +qx +qx +qx +qx +qx +nB +nB +ru +eB +uP +nO +uP +ru +nB +qx +qx +qx +qx +qx +qx +qx +qx +sM +sM +sM +sM +sM +sM +qx +Hq +"} +(28,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +jl +rm +ru +nk +oM +oM +oM +yR +oM +oM +oM +uP +ru +uP +Qg +ru +qx +qx +qx +qx +qx +qx +nB +ru +wL +oM +uP +dw +ru +yZ +nB +nB +nB +qx +qx +qx +qx +qx +sM +sM +sM +sM +sM +sM +qx +Hq +"} +(29,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +uP +dw +ru +rH +uP +AN +JA +iX +JA +JA +oM +uP +ru +fJ +Qg +ru +qx +qx +qx +qx +qx +qx +qx +Wd +OH +oM +uP +RU +ru +we +nB +nB +qx +qx +qx +qx +qx +qx +sM +sM +sM +sM +sM +Yr +qx +Hq +"} +(30,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +lo +uP +uP +GZ +uP +Aa +hD +yJ +bD +hD +hD +OI +oM +GB +jA +oM +dU +BK +BK +BK +BK +BK +BK +BK +IH +oM +oM +uP +RU +ru +nB +nB +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(31,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +uP +uP +dd +uP +Aa +hD +Nt +aw +hD +hD +bo +uP +dd +uP +Qg +ru +qx +qx +qx +qx +qx +qx +qx +iL +cZ +uP +uP +RU +ru +nB +nB +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(32,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +aO +uP +ru +XS +uP +uP +hD +aZ +hD +uP +uP +Cf +ru +uP +Fp +ru +qx +qx +qx +qx +qx +qx +nB +ru +ru +Dj +QF +ru +ru +nB +nB +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(33,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +Ig +ru +hD +ru +zi +uP +uP +uP +FN +Cn +uP +uP +uP +ru +hD +ru +Ig +qx +qx +qx +qx +qx +nB +nB +nB +ru +ru +ru +ru +nB +nB +we +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(34,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +ru +ru +ru +ru +ru +ru +ru +HU +ru +ru +ru +ru +ru +ru +ru +qx +qx +qx +qx +qx +qx +qx +nB +nB +nB +nB +we +nB +nB +nB +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(35,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +UQ +rM +xJ +Kz +Kz +tI +Kz +Kz +ct +xJ +ru +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +we +nB +nB +nB +nB +nB +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(36,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +ru +ru +ru +Kz +Kz +Kz +Kz +tI +Kz +Kz +Kz +Kz +ru +ru +ru +ru +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(37,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +ln +Kz +ru +iB +Kz +Kz +fV +If +Kz +Kz +Kz +nG +ru +cw +cw +ru +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(38,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +ru +Kz +dz +sq +Kz +CK +Kz +vU +mo +vU +Kz +CK +Kz +ja +Kz +Jg +ru +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(39,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +Ig +ru +Kz +ru +Kz +pz +Kz +xZ +Kz +fM +Kz +kI +Kz +ru +Mm +ru +Ig +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(40,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +ru +ru +ru +Kz +Kz +Kz +Kz +Kz +Kz +Kz +Kz +Kz +ru +ru +ru +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(41,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +ru +ru +vp +vp +pH +vp +iW +vp +vp +vp +vp +ru +ru +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +we +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(42,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Ig +hg +hg +hg +hg +hg +hg +hg +hg +hg +Ig +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +we +we +we +we +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(43,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +we +we +we +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(44,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +we +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(45,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +we +we +we +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(46,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +we +we +we +we +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(47,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +we +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(48,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(49,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(50,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(51,1,1) = {" +Hq +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +qx +Hq +"} +(52,1,1) = {" +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +Hq +"} diff --git a/_maps/virtual_domains/test_only.dmm b/_maps/virtual_domains/test_only.dmm new file mode 100644 index 00000000000..22b647188b6 --- /dev/null +++ b/_maps/virtual_domains/test_only.dmm @@ -0,0 +1,52 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/open/floor, +/area/virtual_domain/powered) +"D" = ( +/obj/effect/mob_spawn/corpse/human/miner, +/turf/open/floor, +/area/virtual_domain/powered) +"I" = ( +/mob/living/basic/pet/dog/corgi, +/turf/open/floor, +/area/virtual_domain/powered) +"U" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/open/floor, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +I +a +a +a +U +"} +(2,1,1) = {" +D +a +a +a +a +"} +(3,1,1) = {" +a +a +a +a +a +"} +(4,1,1) = {" +a +a +a +a +a +"} +(5,1,1) = {" +a +a +a +a +a +"} diff --git a/_maps/virtual_domains/vaporwave.dmm b/_maps/virtual_domains/vaporwave.dmm new file mode 100644 index 00000000000..984bbbe2914 --- /dev/null +++ b/_maps/virtual_domains/vaporwave.dmm @@ -0,0 +1,1017 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"aA" = ( +/obj/machinery/light/small/directional/north, +/obj/effect/turf_decal/sand/plating, +/turf/open/floor/plating{ + initial_gas_mix = "TEMP=2.7" + }, +/area/ruin/space/has_grav/powered/virtual_domain) +"bs" = ( +/obj/effect/turf_decal/sand, +/turf/open/floor/iron/airless, +/area/ruin/space/has_grav/powered/virtual_domain) +"bF" = ( +/obj/effect/turf_decal/sand/plating, +/turf/open/floor/plating{ + initial_gas_mix = "TEMP=2.7" + }, +/area/ruin/space/has_grav/powered/virtual_domain) +"cz" = ( +/turf/open/misc/asteroid/airless, +/area/ruin/space/has_grav/powered/virtual_domain) +"cL" = ( +/obj/structure/table/reinforced, +/obj/item/reagent_containers/cup/glass/drinkingglass{ + pixel_x = 6; + pixel_y = 4 + }, +/obj/item/reagent_containers/cup/glass/drinkingglass, +/obj/item/reagent_containers/cup/glass/drinkingglass{ + pixel_x = -6; + pixel_y = 8 + }, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"df" = ( +/obj/effect/turf_decal/sand, +/turf/open/floor/iron/airless{ + icon_state = "stairs-l" + }, +/area/ruin/space/has_grav/powered/virtual_domain) +"eF" = ( +/turf/closed/indestructible/binary, +/area/space) +"fx" = ( +/obj/item/statuebust, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"fQ" = ( +/obj/structure/flora/tree/palm, +/turf/open/floor/holofloor/beach, +/area/ruin/space/has_grav/powered/virtual_domain) +"gM" = ( +/obj/structure/table/reinforced, +/obj/item/clothing/glasses/sunglasses/big{ + name = "aesthetic sunglasses" + }, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"hN" = ( +/turf/open/floor/holofloor/beach, +/area/ruin/space/has_grav/powered/virtual_domain) +"il" = ( +/obj/effect/turf_decal/sand, +/obj/effect/turf_decal/sand, +/turf/open/floor/iron/airless, +/area/ruin/space/has_grav/powered/virtual_domain) +"iP" = ( +/obj/machinery/suit_storage_unit/standard_unit, +/turf/template_noop, +/area/virtual_domain/safehouse) +"kj" = ( +/obj/structure/window/spawner/directional/east, +/obj/structure/closet/crate/secure/bitrunning/encrypted, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"ku" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"kF" = ( +/obj/effect/turf_decal/sand, +/turf/open/floor/iron/airless{ + icon_state = "stairs-r" + }, +/area/ruin/space/has_grav/powered/virtual_domain) +"ll" = ( +/obj/structure/sign/poster/contraband/clown/directional/north, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"lu" = ( +/turf/closed/wall/rust, +/area/ruin/space/has_grav/powered/virtual_domain) +"lB" = ( +/obj/item/tank/internals/emergency_oxygen, +/obj/item/tank/internals/emergency_oxygen, +/obj/item/tank/internals/emergency_oxygen, +/turf/template_noop, +/area/virtual_domain/safehouse) +"lI" = ( +/obj/structure/table/reinforced, +/obj/machinery/chem_dispenser/drinks/beer/fullupgrade, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"qm" = ( +/obj/structure/flora/tree/palm, +/obj/machinery/light/directional/west, +/turf/open/floor/holofloor/beach, +/area/ruin/space/has_grav/powered/virtual_domain) +"qu" = ( +/turf/open/floor/holofloor/beach/water, +/area/ruin/space/has_grav/powered/virtual_domain) +"rn" = ( +/obj/structure/statue/sandstone/venus{ + anchored = 1; + dir = 4 + }, +/obj/effect/turf_decal/sand/plating, +/turf/open/floor/plating{ + initial_gas_mix = "TEMP=2.7" + }, +/area/ruin/space/has_grav/powered/virtual_domain) +"xb" = ( +/obj/structure/chair/stool/directional/west, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"xp" = ( +/obj/structure/table/reinforced, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"xK" = ( +/turf/closed/wall, +/area/ruin/space/has_grav/powered/virtual_domain) +"ym" = ( +/obj/structure/lattice, +/turf/open/space/basic, +/area/space) +"AX" = ( +/obj/effect/turf_decal/stripes/asteroid/line, +/obj/effect/turf_decal/sand/plating, +/turf/open/floor/plating{ + initial_gas_mix = "TEMP=2.7" + }, +/area/ruin/space/has_grav/powered/virtual_domain) +"Cq" = ( +/obj/item/instrument/eguitar, +/turf/open/floor/holofloor/beach, +/area/ruin/space/has_grav/powered/virtual_domain) +"CR" = ( +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"Dk" = ( +/obj/structure/window/spawner/directional/east, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"Es" = ( +/obj/structure/chair/comfy/black{ + dir = 4 + }, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"Fd" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/wall/rust, +/area/ruin/space/has_grav/powered/virtual_domain) +"Hf" = ( +/obj/effect/turf_decal/sand, +/turf/open/floor/iron/airless{ + icon_state = "recharge_floor_asteroid" + }, +/area/ruin/space/has_grav/powered/virtual_domain) +"HA" = ( +/turf/open/floor/holofloor/beach/coast, +/area/ruin/space/has_grav/powered/virtual_domain) +"HV" = ( +/obj/structure/chair/comfy/black{ + dir = 4 + }, +/obj/machinery/light/small/directional/north, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"Jr" = ( +/obj/structure/window/spawner/directional/west, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"KO" = ( +/obj/structure/chair/comfy/black{ + dir = 8 + }, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"KY" = ( +/obj/effect/turf_decal/sand, +/turf/open/floor/iron/airless{ + icon_state = "stairs-m" + }, +/area/ruin/space/has_grav/powered/virtual_domain) +"LG" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"LJ" = ( +/obj/structure/lattice, +/turf/open/misc/asteroid/airless, +/area/ruin/space/has_grav/powered/virtual_domain) +"LP" = ( +/obj/structure/flora/tree/palm, +/obj/machinery/light/directional/east, +/turf/open/floor/holofloor/beach, +/area/ruin/space/has_grav/powered/virtual_domain) +"Nz" = ( +/obj/structure/window/spawner/directional/east, +/obj/structure/table/reinforced, +/obj/item/storage/fancy/cigarettes/cigars/havana, +/obj/effect/spawner/random/entertainment/lighter, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"NT" = ( +/turf/open/space/basic, +/area/space) +"Qh" = ( +/obj/structure/closet/crate/bin, +/turf/open/misc/asteroid/airless, +/area/ruin/space/has_grav/powered/virtual_domain) +"Uy" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"UE" = ( +/obj/effect/turf_decal/sand, +/obj/effect/turf_decal/sand/plating, +/turf/open/floor/plating{ + initial_gas_mix = "TEMP=2.7" + }, +/area/ruin/space/has_grav/powered/virtual_domain) +"UV" = ( +/obj/structure/lattice, +/turf/open/floor/plating/airless, +/area/ruin/space/has_grav/powered/virtual_domain) +"Vc" = ( +/obj/structure/flora/tree/palm, +/turf/open/misc/asteroid/airless, +/area/ruin/space/has_grav/powered/virtual_domain) +"XJ" = ( +/obj/structure/fans/tiny, +/obj/machinery/door/airlock/hatch, +/turf/open/floor/pod/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"Yo" = ( +/obj/structure/statue/sandstone/venus{ + anchored = 1; + desc = "Ugh, this is merely an ugly amateurish replica of the other statue! The letters RIPGOAT are scribbled onto the base."; + dir = 8 + }, +/obj/effect/turf_decal/sand/plating, +/turf/open/floor/plating{ + initial_gas_mix = "TEMP=2.7" + }, +/area/ruin/space/has_grav/powered/virtual_domain) +"YE" = ( +/mob/living/basic/butterfly, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) +"ZI" = ( +/obj/effect/spawner/random/structure/musician/piano/random_piano, +/obj/structure/window/spawner/directional/west, +/turf/open/floor/iron/vaporwave, +/area/ruin/space/has_grav/powered/virtual_domain) + +(1,1,1) = {" +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +"} +(2,1,1) = {" +eF +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(3,1,1) = {" +eF +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(4,1,1) = {" +eF +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(5,1,1) = {" +eF +NT +NT +NT +NT +NT +NT +cz +LJ +cz +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(6,1,1) = {" +eF +NT +NT +NT +NT +NT +cz +cz +LJ +cz +cz +LJ +cz +cz +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(7,1,1) = {" +eF +NT +NT +NT +NT +cz +cz +cz +UV +LJ +cz +UV +cz +cz +cz +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(8,1,1) = {" +eF +NT +NT +NT +NT +cz +xK +xK +lu +lu +lu +xK +lu +UE +cz +cz +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(9,1,1) = {" +eF +NT +NT +NT +LJ +Qh +xK +fQ +hN +qm +HA +qu +xK +aA +cz +cz +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(10,1,1) = {" +eF +NT +NT +NT +LJ +UV +lu +ZI +Jr +Jr +Jr +Jr +xK +bF +Vc +cz +cz +NT +NT +NT +ku +ku +ku +ku +ku +Uy +NT +eF +"} +(11,1,1) = {" +eF +NT +NT +NT +cz +cz +xK +xb +CR +CR +fx +CR +xK +Yo +il +UE +cz +NT +NT +NT +ku +iP +iP +iP +ku +ku +NT +eF +"} +(12,1,1) = {" +eF +NT +NT +NT +cz +cz +xK +ll +CR +CR +CR +CR +xK +Hf +df +il +cz +NT +NT +NT +ku +ku +ku +ku +ku +ku +NT +eF +"} +(13,1,1) = {" +eF +NT +NT +NT +cz +LJ +xK +HV +Es +CR +CR +CR +XJ +bs +KY +bs +il +NT +NT +NT +ku +ku +ku +ku +ku +ku +NT +eF +"} +(14,1,1) = {" +eF +NT +NT +NT +cz +LJ +lu +xp +gM +CR +CR +cL +xK +Hf +kF +bs +il +NT +NT +NT +ku +ku +ku +ku +ku +ku +NT +eF +"} +(15,1,1) = {" +eF +NT +NT +NT +cz +LJ +lu +KO +KO +CR +YE +lI +lu +rn +il +UE +UE +NT +NT +NT +ku +ku +ku +ku +lB +ku +NT +eF +"} +(16,1,1) = {" +eF +NT +NT +NT +LJ +UV +xK +kj +Dk +Dk +Dk +Nz +xK +AX +Vc +cz +cz +NT +NT +NT +ku +ku +ku +ku +ku +LG +NT +eF +"} +(17,1,1) = {" +eF +NT +NT +NT +cz +cz +lu +fQ +Cq +LP +HA +qu +lu +aA +cz +cz +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(18,1,1) = {" +eF +NT +NT +NT +cz +cz +xK +xK +lu +xK +lu +lu +Fd +UE +cz +cz +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(19,1,1) = {" +eF +NT +NT +NT +NT +cz +cz +cz +UV +LJ +Qh +UV +cz +cz +cz +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(20,1,1) = {" +eF +NT +NT +NT +NT +NT +NT +cz +LJ +cz +cz +LJ +cz +cz +cz +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(21,1,1) = {" +eF +NT +NT +NT +NT +NT +NT +NT +ym +cz +cz +cz +cz +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(22,1,1) = {" +eF +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(23,1,1) = {" +eF +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(24,1,1) = {" +eF +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +NT +eF +"} +(25,1,1) = {" +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +eF +"} diff --git a/_maps/virtual_domains/wendigo.dmm b/_maps/virtual_domains/wendigo.dmm new file mode 100644 index 00000000000..17bcb48d688 --- /dev/null +++ b/_maps/virtual_domains/wendigo.dmm @@ -0,0 +1,1373 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/open/indestructible/necropolis{ + initial_gas_mix = "ICEMOON_ATMOS" + }, +/area/icemoon/underground/explored/virtual_domain) +"b" = ( +/turf/closed/indestructible/rock/snow/ice/ore, +/area/icemoon/underground/explored/virtual_domain) +"e" = ( +/turf/open/misc/asteroid/snow/ice/icemoon, +/area/icemoon/underground/explored/virtual_domain) +"f" = ( +/obj/structure/marker_beacon/olive, +/turf/open/indestructible/necropolis{ + initial_gas_mix = "ICEMOON_ATMOS" + }, +/area/icemoon/underground/explored/virtual_domain) +"i" = ( +/turf/closed/indestructible/binary, +/area/icemoon/underground/explored/virtual_domain) +"o" = ( +/obj/structure/marker_beacon/indigo, +/turf/open/indestructible/necropolis{ + initial_gas_mix = "ICEMOON_ATMOS" + }, +/area/icemoon/underground/explored/virtual_domain) +"p" = ( +/obj/structure/marker_beacon/bronze, +/turf/open/indestructible/necropolis{ + initial_gas_mix = "ICEMOON_ATMOS" + }, +/area/icemoon/underground/explored/virtual_domain) +"q" = ( +/obj/structure/marker_beacon/yellow, +/turf/open/indestructible/necropolis{ + initial_gas_mix = "ICEMOON_ATMOS" + }, +/area/icemoon/underground/explored/virtual_domain) +"t" = ( +/obj/structure/marker_beacon/teal, +/turf/open/indestructible/necropolis{ + initial_gas_mix = "ICEMOON_ATMOS" + }, +/area/icemoon/underground/explored/virtual_domain) +"x" = ( +/obj/structure/marker_beacon/burgundy, +/turf/open/indestructible/necropolis{ + initial_gas_mix = "ICEMOON_ATMOS" + }, +/area/icemoon/underground/explored/virtual_domain) +"A" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"E" = ( +/obj/effect/mob_spawn/corpse/human/miner, +/turf/open/misc/asteroid/snow/ice/icemoon, +/area/icemoon/underground/explored/virtual_domain) +"H" = ( +/mob/living/simple_animal/hostile/megafauna/wendigo/virtual_domain, +/turf/open/indestructible/necropolis{ + initial_gas_mix = "ICEMOON_ATMOS" + }, +/area/icemoon/underground/explored/virtual_domain) +"L" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/icemoon/underground/explored/virtual_domain) +"R" = ( +/obj/item/paper/crumpled/bloody{ + default_raw_text = "for your own sake, do not enter" + }, +/turf/open/misc/asteroid/snow/ice/icemoon, +/area/icemoon/underground/explored/virtual_domain) +"S" = ( +/turf/template_noop, +/area/template_noop) +"V" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"Z" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +S +S +S +S +S +S +i +i +i +i +i +i +i +i +i +i +i +i +i +i +i +i +i +S +S +S +S +S +S +S +S +S +S +S +S +S +S +"} +(2,1,1) = {" +S +S +S +S +S +i +i +e +e +e +e +e +e +e +e +e +e +e +e +e +e +e +i +i +S +S +S +S +S +S +S +S +S +S +S +S +S +"} +(3,1,1) = {" +S +S +S +S +i +i +e +e +e +e +e +e +e +e +e +e +e +e +e +e +e +e +e +i +i +S +S +S +S +S +S +S +S +S +S +S +S +"} +(4,1,1) = {" +S +S +S +i +i +e +e +e +b +b +b +b +b +e +e +e +b +b +b +b +b +e +e +e +i +i +S +S +S +S +S +S +S +S +S +S +S +"} +(5,1,1) = {" +S +S +i +i +e +e +e +b +b +b +b +b +b +b +e +b +b +b +b +b +b +b +e +e +e +i +i +i +i +i +i +i +i +i +i +i +L +"} +(6,1,1) = {" +S +i +i +e +e +e +b +b +b +b +b +b +b +b +e +b +b +b +b +b +b +b +b +e +e +e +i +i +e +e +e +e +e +e +e +e +i +"} +(7,1,1) = {" +i +i +e +e +e +b +b +b +b +b +b +b +b +b +e +b +b +b +b +b +b +b +b +b +e +e +e +i +e +e +e +e +e +e +e +e +i +"} +(8,1,1) = {" +i +e +e +e +b +b +b +b +b +b +a +a +a +a +a +a +a +a +a +b +b +b +b +b +b +e +e +e +e +e +e +e +e +e +e +e +i +"} +(9,1,1) = {" +i +e +e +b +b +b +b +b +b +a +a +a +a +a +a +a +a +a +a +a +b +b +b +b +b +b +e +e +e +e +e +e +e +e +e +e +i +"} +(10,1,1) = {" +i +e +e +b +b +b +b +b +a +a +a +a +a +a +a +a +a +a +a +a +a +b +b +b +b +b +e +e +e +e +e +e +e +e +e +e +i +"} +(11,1,1) = {" +i +e +E +b +b +b +b +a +a +a +a +a +a +a +a +a +a +a +a +o +a +a +b +b +b +b +e +e +e +e +e +e +e +e +e +e +i +"} +(12,1,1) = {" +i +e +e +b +b +b +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +b +b +b +e +e +e +e +e +e +e +e +e +e +i +"} +(13,1,1) = {" +i +e +e +b +b +b +a +a +a +a +q +a +a +a +a +a +a +a +a +a +a +a +a +b +b +b +e +e +e +e +e +e +e +e +e +e +i +"} +(14,1,1) = {" +i +e +e +b +b +b +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +b +b +b +e +e +e +V +V +V +V +V +Z +e +i +"} +(15,1,1) = {" +i +e +e +b +b +b +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +b +b +b +E +e +e +V +V +V +V +V +V +e +i +"} +(16,1,1) = {" +i +e +e +e +b +b +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +b +b +e +e +e +e +V +V +V +V +V +V +e +i +"} +(17,1,1) = {" +i +e +e +e +e +e +a +a +a +a +a +a +a +a +H +a +a +a +a +x +a +a +a +e +e +e +e +R +e +V +V +V +V +V +V +e +i +"} +(18,1,1) = {" +i +e +e +e +b +b +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +b +b +e +e +e +e +V +V +V +V +V +V +e +i +"} +(19,1,1) = {" +i +e +e +b +b +b +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +b +b +b +e +e +e +V +V +V +V +V +V +e +i +"} +(20,1,1) = {" +i +e +e +b +b +b +a +a +a +a +a +a +a +a +p +a +a +a +a +a +a +a +a +b +b +b +e +e +e +V +V +V +V +V +A +e +i +"} +(21,1,1) = {" +i +e +e +b +b +b +a +a +a +a +f +a +a +a +a +a +a +a +a +a +a +a +a +b +b +b +e +e +e +e +e +e +e +e +e +e +i +"} +(22,1,1) = {" +i +e +e +b +b +b +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +b +b +b +e +e +e +e +e +e +e +e +e +e +i +"} +(23,1,1) = {" +i +e +e +b +b +b +b +a +a +a +a +a +a +a +a +a +a +a +a +a +t +a +b +b +b +b +e +e +e +e +e +e +e +e +e +e +i +"} +(24,1,1) = {" +i +e +e +b +b +b +b +b +a +a +a +a +a +a +a +a +a +a +a +a +a +b +b +b +b +b +e +e +e +e +e +e +e +e +e +e +i +"} +(25,1,1) = {" +i +e +e +b +b +b +b +b +b +a +a +a +a +a +a +a +a +a +a +a +b +b +b +b +b +b +e +e +e +e +e +e +e +e +e +e +i +"} +(26,1,1) = {" +i +e +e +e +b +b +b +b +b +b +a +a +a +a +a +a +a +a +a +b +b +b +b +b +b +e +e +e +e +e +e +e +e +e +e +e +i +"} +(27,1,1) = {" +i +i +e +e +e +b +b +b +b +b +b +b +b +b +e +b +b +b +b +b +b +b +b +b +e +e +e +i +e +e +e +e +e +e +e +e +i +"} +(28,1,1) = {" +S +i +i +e +e +e +b +b +b +b +b +b +b +b +e +b +b +b +b +b +b +b +b +e +e +e +i +i +e +e +e +e +e +e +e +e +i +"} +(29,1,1) = {" +S +S +i +i +e +e +e +b +b +b +b +b +b +b +e +b +b +b +b +b +b +b +e +e +e +i +i +i +i +i +i +i +i +i +i +i +i +"} +(30,1,1) = {" +S +S +S +i +i +e +e +e +b +b +b +b +b +e +e +e +b +b +b +b +b +e +e +e +i +i +S +S +S +S +S +S +S +S +S +S +S +"} +(31,1,1) = {" +S +S +S +S +i +i +e +e +e +e +e +e +e +e +e +e +e +e +e +e +e +e +e +i +i +S +S +S +S +S +S +S +S +S +S +S +S +"} +(32,1,1) = {" +S +S +S +S +S +i +i +e +e +e +e +e +e +e +e +e +e +e +e +e +e +e +i +i +S +S +S +S +S +S +S +S +S +S +S +S +S +"} +(33,1,1) = {" +S +S +S +S +S +S +i +i +i +i +i +i +i +i +i +i +i +i +i +i +i +i +i +S +S +S +S +S +S +S +S +S +S +S +S +S +S +"} diff --git a/_maps/virtual_domains/xeno_nest.dmm b/_maps/virtual_domains/xeno_nest.dmm new file mode 100644 index 00000000000..fcbd7cc116c --- /dev/null +++ b/_maps/virtual_domains/xeno_nest.dmm @@ -0,0 +1,2071 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/template_noop, +/area/template_noop) +"c" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/resin/wall, +/obj/structure/alien/resin/wall, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"d" = ( +/obj/structure/alien/resin/wall, +/turf/closed/indestructible/binary, +/area/ruin/space/has_grav/powered/virtual_domain) +"e" = ( +/obj/structure/alien/weeds, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"f" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/egg/burst, +/obj/effect/decal/cleanable/blood, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"h" = ( +/obj/structure/alien/weeds, +/mob/living/simple_animal/hostile/alien/sentinel, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"i" = ( +/obj/structure/alien/weeds, +/obj/structure/bed/nest, +/obj/effect/decal/cleanable/blood/gibs, +/obj/effect/decal/cleanable/blood, +/obj/item/clothing/under/syndicate, +/obj/item/clothing/glasses/night, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"j" = ( +/obj/machinery/suit_storage_unit/spaceruin, +/turf/template_noop, +/area/virtual_domain/safehouse) +"k" = ( +/obj/structure/alien/weeds/node, +/obj/structure/alien/resin/wall, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"l" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/resin/wall, +/turf/closed/indestructible/binary, +/area/ruin/space/has_grav/powered/virtual_domain) +"m" = ( +/obj/structure/alien/weeds, +/obj/structure/bed/nest, +/obj/structure/alien/resin/wall, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"n" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"o" = ( +/obj/structure/alien/weeds, +/obj/effect/decal/cleanable/blood/gibs, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"p" = ( +/obj/structure/alien/weeds, +/mob/living/simple_animal/hostile/alien/drone{ + plants_off = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"q" = ( +/obj/structure/alien/resin/wall, +/turf/open/space/basic, +/area/ruin/space/has_grav/powered/virtual_domain) +"r" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"s" = ( +/obj/structure/alien/weeds/node, +/mob/living/simple_animal/hostile/alien/drone{ + plants_off = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"t" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/weeds, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"u" = ( +/obj/structure/alien/weeds/node, +/obj/effect/decal/cleanable/blood, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"v" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"x" = ( +/obj/structure/alien/weeds, +/obj/structure/bed/nest, +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"z" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/resin/wall, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"A" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/ruin/space/has_grav/powered/virtual_domain) +"B" = ( +/obj/structure/alien/weeds, +/obj/effect/decal/cleanable/blood, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"C" = ( +/obj/structure/alien/weeds, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/ruin/space/has_grav/powered/virtual_domain) +"D" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/weeds, +/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins, +/area/ruin/space/has_grav/powered/virtual_domain) +"E" = ( +/turf/closed/indestructible/binary, +/area/ruin/space/has_grav/powered/virtual_domain) +"F" = ( +/obj/structure/table/greyscale, +/obj/item/gun/energy/beam_rifle, +/obj/item/gun/energy/laser{ + pixel_x = 4; + pixel_y = -6 + }, +/obj/item/gun/energy/laser{ + pixel_x = -8; + pixel_y = 6 + }, +/turf/template_noop, +/area/virtual_domain/safehouse) +"G" = ( +/obj/structure/alien/resin/wall, +/obj/structure/alien/weeds, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"H" = ( +/obj/structure/table/greyscale, +/obj/machinery/recharger{ + pixel_x = 8; + pixel_y = 4 + }, +/obj/machinery/recharger{ + pixel_x = -8; + pixel_y = 4 + }, +/turf/template_noop, +/area/virtual_domain/safehouse) +"I" = ( +/obj/structure/alien/weeds, +/obj/structure/bed/nest, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"J" = ( +/obj/structure/alien/weeds, +/mob/living/simple_animal/hostile/alien/queen/large{ + desc = "A gigantic alien who is in charge of the hive and all of its loyal servants."; + name = "alien queen"; + pixel_x = -16; + plants_off = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"K" = ( +/obj/structure/alien/weeds, +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"L" = ( +/obj/item/storage/medkit/regular, +/obj/item/storage/medkit/regular, +/turf/template_noop, +/area/virtual_domain/safehouse) +"M" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/resin/wall{ + move_force = 1000; + move_resist = 3000; + pull_force = 1000 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"N" = ( +/obj/structure/alien/weeds, +/obj/structure/bed/nest, +/obj/effect/decal/cleanable/blood/gibs, +/obj/item/clothing/under/rank/security/officer, +/obj/item/clothing/suit/armor/vest, +/obj/item/melee/baton/security/loaded, +/obj/item/clothing/head/helmet, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"O" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/ruin/space/has_grav/powered/virtual_domain) +"P" = ( +/obj/structure/alien/weeds/node, +/mob/living/simple_animal/hostile/alien, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"Q" = ( +/obj/structure/alien/resin/wall, +/obj/structure/alien/resin/wall, +/turf/closed/indestructible/binary, +/area/ruin/space/has_grav/powered/virtual_domain) +"S" = ( +/obj/structure/alien/weeds, +/mob/living/simple_animal/hostile/alien, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"T" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/egg/burst, +/obj/effect/decal/cleanable/blood/gibs, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"U" = ( +/obj/structure/alien/weeds, +/obj/structure/bed/nest, +/obj/effect/decal/cleanable/blood/gibs, +/obj/item/gun/ballistic/automatic/pistol, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"V" = ( +/obj/structure/alien/weeds/node, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"W" = ( +/obj/structure/alien/weeds, +/obj/structure/alien/egg/burst, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"Y" = ( +/obj/structure/alien/weeds, +/obj/effect/decal/cleanable/blood, +/mob/living/simple_animal/hostile/alien/drone{ + plants_off = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) +"Z" = ( +/obj/structure/alien/weeds, +/obj/structure/bed/nest, +/obj/effect/decal/cleanable/blood/gibs, +/obj/item/tank/internals/oxygen, +/obj/item/clothing/suit/space/syndicate/orange, +/obj/item/clothing/mask/gas, +/obj/item/clothing/head/helmet/space/syndicate/orange, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/ruin/space/has_grav/powered/virtual_domain) + +(1,1,1) = {" +a +a +a +E +E +E +E +E +E +E +E +E +E +E +E +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(2,1,1) = {" +a +a +a +E +E +z +z +z +z +z +z +z +z +E +E +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(3,1,1) = {" +a +a +a +E +E +z +e +W +W +z +e +e +z +M +E +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(4,1,1) = {" +a +a +a +E +z +z +e +e +e +e +p +e +W +z +E +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(5,1,1) = {" +a +a +a +E +z +e +e +k +z +z +z +k +z +z +E +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(6,1,1) = {" +a +a +a +E +z +e +e +m +K +J +o +i +z +z +E +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(7,1,1) = {" +a +a +a +E +z +W +h +e +e +e +B +o +e +z +E +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(8,1,1) = {" +a +a +a +E +z +I +o +z +e +V +e +h +W +z +E +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(9,1,1) = {" +a +a +a +E +z +U +u +e +z +e +e +W +z +z +E +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(10,1,1) = {" +a +a +a +E +z +e +o +z +e +e +e +k +W +z +E +a +a +a +a +a +a +a +E +E +E +E +E +E +E +E +E +E +a +a +a +a +a +a +a +"} +(11,1,1) = {" +a +a +a +E +z +z +e +h +e +e +h +e +e +z +E +a +a +a +a +a +a +a +E +E +E +E +z +z +z +z +E +E +a +a +a +a +a +a +a +"} +(12,1,1) = {" +a +a +a +E +E +z +W +e +e +e +e +e +e +z +E +a +a +a +a +a +a +a +E +E +E +z +z +Z +I +z +z +E +a +a +a +a +a +a +a +"} +(13,1,1) = {" +a +a +a +E +E +z +z +e +e +V +e +e +z +z +E +a +a +a +a +a +a +a +E +E +z +z +W +o +Y +e +z +E +a +a +a +a +a +a +a +"} +(14,1,1) = {" +a +a +a +E +E +E +z +z +e +e +e +z +z +E +E +a +a +a +a +E +E +E +E +E +z +I +e +V +e +W +z +E +a +a +a +a +a +a +a +"} +(15,1,1) = {" +a +a +a +a +a +E +E +z +z +z +z +z +E +E +E +E +E +E +E +E +E +E +z +z +z +e +e +e +I +z +z +E +a +a +a +a +a +a +a +"} +(16,1,1) = {" +a +a +a +a +a +a +E +z +V +V +z +E +E +E +E +E +E +E +E +E +E +z +z +e +S +e +W +z +z +z +E +E +a +a +a +a +a +a +a +"} +(17,1,1) = {" +a +a +a +a +a +a +E +z +p +e +z +z +E +z +z +z +z +z +z +z +z +z +e +e +z +z +z +z +E +E +E +E +a +a +a +a +a +a +a +"} +(18,1,1) = {" +a +a +a +a +a +a +E +z +e +e +p +z +z +z +e +e +e +e +e +e +z +z +e +z +z +E +E +E +E +E +E +E +E +E +E +E +E +E +O +"} +(19,1,1) = {" +E +E +E +E +E +E +E +z +z +e +e +e +z +e +e +e +e +e +e +V +e +e +e +z +E +E +E +E +E +E +Q +d +q +q +q +q +q +q +E +"} +(20,1,1) = {" +E +z +z +z +z +E +E +E +z +z +e +V +e +e +e +z +z +z +z +e +e +t +z +z +E +E +a +a +a +E +q +A +A +A +A +A +A +A +E +"} +(21,1,1) = {" +E +z +W +I +z +z +z +z +z +z +e +e +e +e +z +z +E +E +z +z +e +e +z +E +E +E +a +a +a +E +q +A +A +A +A +A +A +A +E +"} +(22,1,1) = {" +E +G +t +S +e +z +z +e +e +e +e +e +e +z +z +E +E +E +z +e +e +e +z +E +E +E +a +a +a +E +q +A +C +A +A +A +A +A +E +"} +(23,1,1) = {" +E +G +K +W +V +e +e +e +z +z +e +z +z +z +E +E +E +E +z +e +e +z +z +E +E +a +a +a +a +E +q +C +C +C +A +A +A +A +E +"} +(24,1,1) = {" +E +z +z +I +I +z +z +z +z +z +e +z +E +E +E +E +E +E +z +e +e +z +E +E +E +E +E +E +E +E +d +C +C +A +A +C +A +A +E +"} +(25,1,1) = {" +E +E +z +z +z +z +E +E +E +z +p +z +z +E +E +E +E +E +z +e +s +z +z +z +E +E +E +E +E +E +z +C +C +C +A +C +C +A +E +"} +(26,1,1) = {" +a +E +E +E +E +E +E +E +E +z +e +e +z +E +E +E +E +E +z +e +e +e +e +z +z +z +E +E +E +z +z +n +n +n +n +n +v +A +E +"} +(27,1,1) = {" +a +a +a +a +a +E +E +z +z +z +e +e +z +z +E +E +E +E +z +z +e +e +e +e +e +z +z +z +z +k +e +n +j +j +j +n +n +A +E +"} +(28,1,1) = {" +a +a +a +a +a +E +z +z +T +e +e +V +W +z +E +E +E +z +z +e +e +z +z +e +e +e +z +V +e +e +e +n +n +n +n +n +n +A +E +"} +(29,1,1) = {" +a +a +a +a +a +E +z +N +f +S +e +W +I +z +E +E +E +z +e +e +z +z +z +z +e +V +z +V +t +e +e +n +n +F +H +n +n +A +E +"} +(30,1,1) = {" +a +a +a +a +a +E +z +x +o +e +I +I +z +z +E +E +E +z +e +z +z +E +E +z +z +z +z +z +k +e +e +n +n +n +n +n +n +A +E +"} +(31,1,1) = {" +a +a +a +a +a +E +z +z +z +e +z +z +z +E +E +E +E +z +e +z +z +E +E +E +E +E +E +E +z +e +e +n +L +n +n +n +n +A +E +"} +(32,1,1) = {" +a +a +a +a +a +E +E +E +z +e +z +E +E +E +E +E +E +z +e +e +z +E +a +a +a +a +E +E +z +e +e +n +n +n +n +n +r +A +E +"} +(33,1,1) = {" +a +a +a +a +a +a +a +E +z +e +z +E +E +a +a +a +E +l +z +V +z +E +a +a +a +a +E +z +z +z +q +C +A +A +C +A +A +A +E +"} +(34,1,1) = {" +a +a +a +a +a +a +a +E +z +V +z +E +E +a +a +a +E +E +z +e +z +E +a +a +a +a +E +z +E +d +q +C +C +C +A +A +A +A +E +"} +(35,1,1) = {" +a +a +a +a +a +a +a +E +z +e +z +E +E +a +a +a +E +E +z +e +z +E +a +a +a +a +E +E +E +d +q +A +A +A +A +A +A +A +E +"} +(36,1,1) = {" +a +a +a +a +a +a +a +E +z +e +z +E +E +E +E +E +E +z +z +e +z +E +a +a +a +a +a +a +E +d +q +A +C +D +A +A +A +A +E +"} +(37,1,1) = {" +a +a +a +a +E +E +E +E +z +e +z +E +E +E +E +E +z +z +e +e +z +E +a +a +a +a +a +a +E +d +q +A +A +A +A +A +A +A +E +"} +(38,1,1) = {" +a +a +a +a +E +E +E +z +z +e +z +z +z +z +z +z +z +e +e +z +z +E +a +a +a +a +a +a +E +d +q +A +A +A +A +A +A +A +E +"} +(39,1,1) = {" +a +a +a +a +E +E +z +z +e +e +W +z +z +e +e +P +e +e +z +z +E +E +a +a +a +a +a +a +E +Q +q +q +q +q +q +q +q +q +E +"} +(40,1,1) = {" +a +a +a +a +E +E +z +I +p +e +e +e +e +e +z +z +z +z +z +E +E +a +a +a +a +a +a +a +E +E +E +E +E +E +E +E +E +E +E +"} +(41,1,1) = {" +a +a +a +a +E +z +z +W +e +V +e +W +z +z +z +E +E +E +E +E +E +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(42,1,1) = {" +a +a +a +a +E +z +W +K +e +I +I +z +z +E +E +E +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(43,1,1) = {" +a +a +a +a +E +c +z +z +z +z +z +z +E +E +E +E +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(44,1,1) = {" +a +a +a +a +E +E +E +E +E +E +E +E +E +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} diff --git a/code/__DEFINES/bitrunning.dm b/code/__DEFINES/bitrunning.dm new file mode 100644 index 00000000000..343801c477e --- /dev/null +++ b/code/__DEFINES/bitrunning.dm @@ -0,0 +1,20 @@ +#define BITRUNNER_COST_NONE 0 +#define BITRUNNER_COST_LOW 1 +#define BITRUNNER_COST_MEDIUM 2 +#define BITRUNNER_COST_HIGH 3 +#define BITRUNNER_COST_EXTREME 20 + +#define BITRUNNER_REWARD_MIN 1 +#define BITRUNNER_REWARD_LOW 3 +#define BITRUNNER_REWARD_MEDIUM 4 +#define BITRUNNER_REWARD_HIGH 5 +#define BITRUNNER_REWARD_EXTREME 6 + +/// Blue in ui +#define BITRUNNER_DIFFICULTY_NONE 0 +/// Yellow +#define BITRUNNER_DIFFICULTY_LOW 1 +/// Orange +#define BITRUNNER_DIFFICULTY_MEDIUM 2 +/// Red with skull +#define BITRUNNER_DIFFICULTY_HIGH 3 diff --git a/code/__DEFINES/dcs/signals/signals_bitrunning.dm b/code/__DEFINES/dcs/signals/signals_bitrunning.dm new file mode 100644 index 00000000000..3d008449ee7 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_bitrunning.dm @@ -0,0 +1,31 @@ +/// from /obj/machinery/netpod/default_pry_open() : (mob/living/intruder) +#define COMSIG_BITRUNNER_CROWBAR_ALERT "bitrunner_crowbar" + +/// from /obj/effect/bitrunning/loot_signal: (points) +#define COMSIG_BITRUNNER_GOAL_POINT "bitrunner_goal_point" + +/// from /obj/machinery/quantum_server/on_goal_turf_entered(): (atom/entered, reward_points) +#define COMSIG_BITRUNNER_DOMAIN_COMPLETE "bitrunner_complete" + +/// from /obj/machinery/netpod/on_take_damage() +#define COMSIG_BITRUNNER_NETPOD_INTEGRITY "bitrunner_netpod_damage" + +/// from /obj/structure/hololadder and complete alert +#define COMSIG_BITRUNNER_SAFE_DISCONNECT "bitrunner_disconnect" + +/// from /obj/machinery/netpod/open_machine(), /obj/machinery/quantum_server, etc (obj/machinery/netpod) +#define COMSIG_BITRUNNER_SEVER_AVATAR "bitrunner_sever" + +/// from /obj/machinery/quantum_server/shutdown() : (mob/living) +#define COMSIG_BITRUNNER_SHUTDOWN_ALERT "bitrunner_shutdown" + +// Notifies the bitrunners +/// from /datum/antagonist/cyber_police/proc/notify() : +#define COMSIG_BITRUNNER_THREAT_CREATED "bitrunner_threat" + +// Informs the server to up the threat count +/// from event spawns: (mob/living) +#define COMSIG_BITRUNNER_SPAWN_GLITCH "bitrunner_spawn_glitch" + +/// from /obj/machinery/quantum_server/refreshParts(): (servo rating) +#define COMSIG_BITRUNNER_SERVER_UPGRADED "bitrunner_server_upgraded" diff --git a/code/__DEFINES/dcs/signals/signals_blob.dm b/code/__DEFINES/dcs/signals/signals_blob.dm new file mode 100644 index 00000000000..afd4737bdd9 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_blob.dm @@ -0,0 +1,4 @@ +/// Signal sent when a blob overmind picked a new strain (/mob/camera/blob/overmind, /datum/blobstrain/new_strain) +#define COMSIG_BLOB_SELECTED_STRAIN "blob_selected_strain" +/// Signal sent by a blob spore when it creates a zombie (/mob/living/basic/blob_minion/spore/spore, //mob/living/basic/blob_minion/zombie/zombie) +#define COMSIG_BLOB_ZOMBIFIED "blob_zombified" diff --git a/code/__DEFINES/dcs/signals/signals_camera.dm b/code/__DEFINES/dcs/signals/signals_camera.dm new file mode 100644 index 00000000000..6ec142f54fa --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_camera.dm @@ -0,0 +1,2 @@ +///Signal sent when a /datum/trackable found a target: (datum/trackable/source, mob/living/target) +#define COMSIG_TRACKABLE_TRACKING_TARGET "comsig_trackable_tracking_target" diff --git a/code/__DEFINES/dcs/signals/signals_lazy_templates.dm b/code/__DEFINES/dcs/signals/signals_lazy_templates.dm new file mode 100644 index 00000000000..1c6ce7926ea --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_lazy_templates.dm @@ -0,0 +1,2 @@ +/// Fired on the lazy template datum when the template is finished loading. (list/loaded_atom_movables, list/loaded_turfs, list/loaded_areas) +#define COMSIG_LAZY_TEMPLATE_LOADED "lazy_template_loaded" diff --git a/code/__DEFINES/dcs/signals/signals_saboteur.dm b/code/__DEFINES/dcs/signals/signals_saboteur.dm new file mode 100644 index 00000000000..5b0fef52aee --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_saboteur.dm @@ -0,0 +1,5 @@ +// Light disruptor. Not to be confused with the light eater, which permanently disables lights. + +/// from /obj/projectile/energy/fisher/on_hit() or /obj/item/gun/energy/recharge/fisher when striking a target +#define COMSIG_HIT_BY_SABOTEUR "HIT_BY_SABOTEUR" + #define COMSIG_SABOTEUR_SUCCESS (1<<0) diff --git a/code/__DEFINES/fish.dm b/code/__DEFINES/fish.dm new file mode 100644 index 00000000000..89b7963d91d --- /dev/null +++ b/code/__DEFINES/fish.dm @@ -0,0 +1,136 @@ +/// Use in fish tables to denote miss chance. +#define FISHING_DUD "dud" + +// Baseline fishing difficulty levels +#define FISHING_DEFAULT_DIFFICULTY 15 + +/// Difficulty modifier when bait is fish's favorite +#define FAV_BAIT_DIFFICULTY_MOD -5 +/// Difficulty modifier when bait is fish's disliked +#define DISLIKED_BAIT_DIFFICULTY_MOD 15 +/// Difficulty modifier when our fisherman has the trait TRAIT_SETTLER +#define SETTLER_DIFFICULTY_MOD -5 + +#define FISH_TRAIT_MINOR_DIFFICULTY_BOOST 5 + +// These define how the fish will behave in the minigame +#define FISH_AI_DUMB "dumb" +#define FISH_AI_ZIPPY "zippy" +#define FISH_AI_SLOW "slow" + +#define ADDITIVE_FISHING_MOD "additive" +#define MULTIPLICATIVE_FISHING_MOD "multiplicative" + +// These defines are intended for use to interact with fishing hooks when going +// through the fishing rod, and not the hook itself. They could probably be +// handled differently, but for now that's how they work. It's grounds for +// a future refactor, however. +/// Fishing hook trait that signifies that it's shiny. Useful for fishes +/// that care about shiner hooks more. +#define FISHING_HOOK_SHINY (1 << 0) +/// Fishing hook trait that lessens the bounce from hitting the edges of the minigame bar. +#define FISHING_HOOK_WEIGHTED (1 << 1) +///See FISHING_MINIGAME_RULE_BIDIRECTIONAL +#define FISHING_HOOK_BIDIRECTIONAL (1 << 2) +///Prevents the user from losing the game by letting the fish get away. +#define FISHING_HOOK_NO_ESCAPE (1 << 3) +///Limits the completion loss of the minigame when the fsh is not on the bait area. +#define FISHING_HOOK_ENSNARE (1 << 4) +///Automatically kills the fish after a while, at the cost of killing it. +#define FISHING_HOOK_KILL (1 << 5) + +///Reduces the difficulty of the minigame +#define FISHING_LINE_CLOAKED (1 << 0) +///Required to cast a line on lava. +#define FISHING_LINE_REINFORCED (1 << 1) +/// Much like FISHING_HOOK_ENSNARE but for the fishing line. +#define FISHING_LINE_BOUNCY (1 << 2) + +///Keeps the bait from falling from gravity, instead allowing the player to move the bait down with right click. +#define FISHING_MINIGAME_RULE_BIDIRECTIONAL (1 << 2) +///Prevents the player from losing the minigame when the completion reaches 0 +#define FISHING_MINIGAME_RULE_NO_ESCAPE (1 << 3) +///Automatically kills the fish after a while, at the cost of killing it +#define FISHING_MINIGAME_RULE_KILL (1 << 4) +///Prevents the fishing skill from having an effect on the minigame and experience from being awarded +#define FISHING_MINIGAME_RULE_NO_EXP (1 << 5) +///If enabled, the minigame will occasionally screw around and invert the velocity of the bait +#define FISHING_MINIGAME_RULE_ANTIGRAV (1 << 6) +///Will filp the minigame hud for the duration of the effect +#define FISHING_MINIGAME_RULE_FLIP (1 << 7) + +///all the effects that are active and will last for a few seconds before triggering a cooldown +#define FISHING_MINIGAME_ACTIVE_EFFECTS (FISHING_MINIGAME_RULE_ANTIGRAV|FISHING_MINIGAME_RULE_FLIP) + +/// The default additive value for fishing hook catch weight modifiers. +#define FISHING_DEFAULT_HOOK_BONUS_ADDITIVE 0 +/// The default multiplicative value for fishing hook catch weight modifiers. +#define FISHING_DEFAULT_HOOK_BONUS_MULTIPLICATIVE 1 + +//Fish icon defines, used by fishing minigame +#define FISH_ICON_DEF "fish" +#define FISH_ICON_HOSTILE "hostile" +#define FISH_ICON_STAR "star" +#define FISH_ICON_CHUNKY "chunky" +#define FISH_ICON_SLIME "slime" +#define FISH_ICON_COIN "coin" +#define FISH_ICON_GEM "gem" +#define FISH_ICON_CRAB "crab" +#define FISH_ICON_JELLYFISH "jellyfish" +#define FISH_ICON_BONE "bone" + +#define AQUARIUM_ANIMATION_FISH_SWIM "fish" +#define AQUARIUM_ANIMATION_FISH_DEAD "dead" + +#define AQUARIUM_PROPERTIES_PX_MIN "px_min" +#define AQUARIUM_PROPERTIES_PX_MAX "px_max" +#define AQUARIUM_PROPERTIES_PY_MIN "py_min" +#define AQUARIUM_PROPERTIES_PY_MAX "py_max" + +#define AQUARIUM_LAYER_MODE_BOTTOM "bottom" +#define AQUARIUM_LAYER_MODE_TOP "top" +#define AQUARIUM_LAYER_MODE_AUTO "auto" + +#define FISH_ALIVE "alive" +#define FISH_DEAD "dead" + +///Fish size thresholds for w_class. +#define FISH_SIZE_TINY_MAX 30 +#define FISH_SIZE_SMALL_MAX 50 +#define FISH_SIZE_NORMAL_MAX 90 +#define FISH_SIZE_BULKY_MAX 130 + +///The coefficient for maximum weight/size divergence relative to the averages. +#define MAX_FISH_DEVIATION_COEFF 2.5 + +///The volume of the grind results is multiplied by the fish' weight and divided by this. +#define FISH_GRIND_RESULTS_WEIGHT_DIVISOR 500 +///The number of fillets is multiplied by the fish' size and divided by this. +#define FISH_FILLET_NUMBER_SIZE_DIVISOR 30 + +///The breeding timeout for newly instantiated fish is multiplied by this. +#define NEW_FISH_BREEDING_TIMEOUT_MULT 2 +///The last feeding timestamp of newly instantiated fish is multiplied by this: ergo, they spawn 50% hungry. +#define NEW_FISH_LAST_FEEDING_MULT 0.5 + +#define MIN_AQUARIUM_TEMP T0C +#define MAX_AQUARIUM_TEMP (T0C + 100) +#define DEFAULT_AQUARIUM_TEMP (T0C + 24) + +///How likely one's to find a given fish from random fish cases. +#define FISH_RARITY_BASIC 1000 +#define FISH_RARITY_RARE 400 +#define FISH_RARITY_VERY_RARE 200 +#define FISH_RARITY_GOOD_LUCK_FINDING_THIS 5 +#define FISH_RARITY_NOPE 0 + +///Aquarium fluid variables. The fish' required fluid has to match this, or it'll slowly die. +#define AQUARIUM_FLUID_FRESHWATER "Freshwater" +#define AQUARIUM_FLUID_SALTWATER "Saltwater" +#define AQUARIUM_FLUID_SULPHWATEVER "Sulfuric Water" +#define AQUARIUM_FLUID_AIR "Air" +#define AQUARIUM_FLUID_ANADROMOUS "Adaptive to both Freshwater and Saltwater" +#define AQUARIUM_FLUID_ANY_WATER "Adaptive to all kind of water" + +///Fluff. The name of the aquarium company shown in the fish catalog +#define AQUARIUM_COMPANY "Aquatech Ltd." diff --git a/code/__DEFINES/holiday.dm b/code/__DEFINES/holiday.dm new file mode 100644 index 00000000000..1c35940e718 --- /dev/null +++ b/code/__DEFINES/holiday.dm @@ -0,0 +1 @@ +#define HOLIDAY_HAT_CHANCE 20 diff --git a/code/__DEFINES/mood.dm b/code/__DEFINES/mood.dm new file mode 100644 index 00000000000..161f253b04c --- /dev/null +++ b/code/__DEFINES/mood.dm @@ -0,0 +1 @@ +#define MOOD_CATEGORY_LEGION_CORE "regenerative core" diff --git a/code/__DEFINES/radioactive_nebula.dm b/code/__DEFINES/radioactive_nebula.dm new file mode 100644 index 00000000000..68849ca1bc2 --- /dev/null +++ b/code/__DEFINES/radioactive_nebula.dm @@ -0,0 +1,2 @@ +/// Name of the glow we use for the radioation effect outside +#define GLOW_NEBULA "glow_nebula" diff --git a/code/__DEFINES/research/slimes.dm b/code/__DEFINES/research/slimes.dm new file mode 100644 index 00000000000..64a85afc217 --- /dev/null +++ b/code/__DEFINES/research/slimes.dm @@ -0,0 +1,27 @@ +// Just slimin' here. +// Warning: These defines are used for slime cores and their icon states, so if you +// touch these names, remember to update icons/mob/simple/slimes.dmi and the respective +// slime core paths too! + +#define SLIME_TYPE_ADAMANTINE "adamantine" +#define SLIME_TYPE_BLACK "black" +#define SLIME_TYPE_BLUE "blue" +#define SLIME_TYPE_BLUESPACE "bluespace" +#define SLIME_TYPE_CERULEAN "cerulean" +#define SLIME_TYPE_DARK_BLUE "dark blue" +#define SLIME_TYPE_DARK_PURPLE "dark purple" +#define SLIME_TYPE_GOLD "gold" +#define SLIME_TYPE_GREEN "green" +#define SLIME_TYPE_GREY "grey" +#define SLIME_TYPE_LIGHT_PINK "light pink" +#define SLIME_TYPE_METAL "metal" +#define SLIME_TYPE_OIL "oil" +#define SLIME_TYPE_ORANGE "orange" +#define SLIME_TYPE_PINK "pink" +#define SLIME_TYPE_PURPLE "purple" +#define SLIME_TYPE_PYRITE "pyrite" +#define SLIME_TYPE_RAINBOW "rainbow" +#define SLIME_TYPE_RED "red" +#define SLIME_TYPE_SEPIA "sepia" +#define SLIME_TYPE_SILVER "silver" +#define SLIME_TYPE_YELLOW "yellow" diff --git a/code/__DEFINES/~ff_defines/signals.dm b/code/__DEFINES/~ff_defines/signals.dm new file mode 100644 index 00000000000..f62d312701d --- /dev/null +++ b/code/__DEFINES/~ff_defines/signals.dm @@ -0,0 +1,4 @@ +/// Из /obj/item/clothing/head/mob_holde/human/proc/Deposit() : (mob/living/carbon/human, obj/item/storage/backpack) +#define COMSIG_HUMAN_ENTER_STORAGE "human_enter_storage" +/// Из /obj/item/clothing/head/mob_holde/human/proc/Release() : (mob/living/carbon/human, obj/item/storage/backpack) +#define COMSIG_HUMAN_EXIT_STORAGE "human_exit_storage" diff --git a/code/__DEFINES/~ff_defines/traits.dm b/code/__DEFINES/~ff_defines/traits.dm new file mode 100644 index 00000000000..f99cec031ef --- /dev/null +++ b/code/__DEFINES/~ff_defines/traits.dm @@ -0,0 +1,10 @@ +// Могут ли моба взять в руки с помощью двойного граба. +#define TRAIT_CAN_BUCKLED_TO_HAND "can_buckled_to_hand" +// Обладает ли бом слабым телом. Квирк присутствует у таких специй как тешари. Блокирует возможность брать мобов на плечо, или спину. А так же бросать тела. +#define TRAIT_WEAK_BODY "weak_body" +//Защита от опрокидываний. +#define TRAIT_KNOCKDOWN_IMMUNE "knock_immune" +//Позволяет забираться в сумки и будет положенным в них. +#define TRAIT_CAN_ENTER_BAG "can_enter_bag" +// Идеальный слух. Позволяет слышать шепот в приделах экрана и речь на любом расстоянии. +#define TRAIT_PERFECT_HEARING "perfect_hearing" diff --git a/code/__DEFINES/~skyrat_defines/wounds.dm b/code/__DEFINES/~skyrat_defines/wounds.dm new file mode 100644 index 00000000000..f6a9275db8e --- /dev/null +++ b/code/__DEFINES/~skyrat_defines/wounds.dm @@ -0,0 +1,2 @@ +/// If this wound, when bandaged, will cause a splint overlay to generate rather than a bandage overlay. +#define SPLINT_OVERLAY (1<<200) // we use a big number since tg realistically wouldnt go to it diff --git a/code/__HELPERS/cameras.dm b/code/__HELPERS/cameras.dm new file mode 100644 index 00000000000..9d74f3fe71b --- /dev/null +++ b/code/__HELPERS/cameras.dm @@ -0,0 +1,35 @@ +/** + * get_camera_list + * + * Builds a list of all available cameras that can be seen to networks_available + * Args: + * networks_available - List of networks that we use to see which cameras are visible to it. + */ +/proc/get_camera_list(list/networks_available) + var/list/all_camera_list = list() + for(var/obj/machinery/camera/camera as anything in GLOB.cameranet.cameras) + all_camera_list.Add(camera) + + camera_sort(all_camera_list) + + var/list/usable_camera_list = list() + + for(var/obj/machinery/camera/camera as anything in all_camera_list) + var/list/tempnetwork = camera.network & networks_available + if(length(tempnetwork)) + usable_camera_list["[camera.c_tag][camera.can_use() ? null : " (Deactivated)"]"] = camera + + return usable_camera_list + +///Sorts the list of cameras by their c_tag to display to players. +/proc/camera_sort(list/camera_list) + var/obj/machinery/camera/camera_comparing_a + var/obj/machinery/camera/camera_comparing_b + + for(var/i = length(camera_list), i > 0, i--) + for(var/j = 1 to i - 1) + camera_comparing_a = camera_list[j] + camera_comparing_b = camera_list[j + 1] + if(sorttext(camera_comparing_a.c_tag, camera_comparing_b.c_tag) < 0) + camera_list.Swap(j, j + 1) + return camera_list diff --git a/code/_globalvars/lists/icons.dm b/code/_globalvars/lists/icons.dm new file mode 100644 index 00000000000..ff60e6bc8d9 --- /dev/null +++ b/code/_globalvars/lists/icons.dm @@ -0,0 +1,2 @@ +/// Cache of the width and height of icon files, to avoid repeating the same expensive operation +GLOBAL_LIST_EMPTY(icon_dimensions) diff --git a/code/controllers/subsystem/processing/fishing.dm b/code/controllers/subsystem/processing/fishing.dm new file mode 100644 index 00000000000..da10d3d631a --- /dev/null +++ b/code/controllers/subsystem/processing/fishing.dm @@ -0,0 +1,7 @@ +/** + * So far, only used by the fishing minigame. Feel free to rename it to something like veryfastprocess + * if you need one that fires 10 times a second + */ +PROCESSING_SUBSYSTEM_DEF(fishing) + name = "Fishing" + wait = 0.1 SECONDS diff --git a/code/controllers/subsystem/radioactive_nebula.dm b/code/controllers/subsystem/radioactive_nebula.dm new file mode 100644 index 00000000000..3b11a7870af --- /dev/null +++ b/code/controllers/subsystem/radioactive_nebula.dm @@ -0,0 +1,64 @@ +/// Trait for tracking if something already has the fake irradiation effect, so we don't waste time on effect operations if otherwise unnecessary +#define TRAIT_RADIOACTIVE_NEBULA_FAKE_IRRADIATED "radioactive_nebula_fake_irradiated" + +/// Controls making objects irradiated when Radioactive Nebula is in effect. +SUBSYSTEM_DEF(radioactive_nebula) + name = "Radioactive Nebula" + flags = SS_BACKGROUND + wait = 30 SECONDS + + VAR_PRIVATE + datum/station_trait/nebula/hostile/radiation/radioactive_nebula + +/datum/controller/subsystem/radioactive_nebula/Initialize() + radioactive_nebula = locate() in SSstation.station_traits + if (!radioactive_nebula) + can_fire = FALSE + return SS_INIT_NO_NEED + + // We don't *really* care that this happens by the time the server is ready to play. + ASYNC + irradiate_everything() + + // Don't leak that the station trait has been picked + return SS_INIT_NO_MESSAGE + +/// Makes something appear irradiated for the purposes of the Radioactive Nebula +/datum/controller/subsystem/radioactive_nebula/proc/fake_irradiate(atom/movable/target) + if (HAS_TRAIT(target, TRAIT_RADIOACTIVE_NEBULA_FAKE_IRRADIATED)) + return + + ADD_TRAIT(target, TRAIT_RADIOACTIVE_NEBULA_FAKE_IRRADIATED, REF(src)) + + if(iscarbon(target))//Don't actually make EVERY. SINGLE. THING. RADIOACTIVE. Just irradiate people + target.AddComponent( \ + /datum/component/radioactive_exposure, \ + minimum_exposure_time = NEBULA_RADIATION_MINIMUM_EXPOSURE_TIME, \ + irradiation_chance_base = RADIATION_EXPOSURE_NEBULA_BASE_CHANCE, \ + irradiation_chance_increment = RADIATION_EXPOSURE_NEBULA_CHANCE_INCREMENT, \ + irradiation_interval = RADIATION_EXPOSURE_NEBULA_CHECK_INTERVAL, \ + source = src, \ + radioactive_areas = radioactive_nebula.radioactive_areas, \ + ) + else if(isobj(target)) //and fake the rest + //outline clashes too much with other outlines and creates pretty ugly lines + target.add_filter(GLOW_NEBULA, 2, list("type" = "drop_shadow", "color" = radioactive_nebula.nebula_radglow, "size" = 2)) + +/datum/controller/subsystem/radioactive_nebula/fire() + irradiate_everything() + +/// Loop through radioactive space (with lag checks) and make it all radioactive! +/datum/controller/subsystem/radioactive_nebula/proc/irradiate_everything() + for (var/area/area as anything in get_areas(radioactive_nebula.radioactive_areas)) + for (var/turf/turf as anything in area.get_contained_turfs()) + for (var/atom/movable/target as anything in turf) + fake_irradiate(target) + + CHECK_TICK + +/// Remove the fake radiation. The compontent we add to mobs handles its own removal +/datum/controller/subsystem/radioactive_nebula/proc/fake_unirradiate(atom/movable/leaver) + REMOVE_TRAIT(leaver, TRAIT_RADIOACTIVE_NEBULA_FAKE_IRRADIATED, REF(src)) + leaver.remove_filter(GLOW_NEBULA) + +#undef TRAIT_RADIOACTIVE_NEBULA_FAKE_IRRADIATED diff --git a/code/controllers/subsystem/stock_market.dm b/code/controllers/subsystem/stock_market.dm new file mode 100644 index 00000000000..7c2cb71dc49 --- /dev/null +++ b/code/controllers/subsystem/stock_market.dm @@ -0,0 +1,154 @@ + +SUBSYSTEM_DEF(stock_market) + name = "Stock Market" + wait = 20 SECONDS + init_order = INIT_ORDER_DEFAULT + runlevels = RUNLEVEL_GAME + + /// Associated list of materials and their prices at the given time. + var/list/materials_prices = list() + /// Associated list of materials alongside their market trends. 1 is up, 0 is stable, -1 is down. + var/list/materials_trends = list() + /// Associated list of materials alongside the life of it's current trend. After it's life is up, it will change to a new trend. + var/list/materials_trend_life = list() + /// Associated list of materials alongside their available quantity. This is used to determine how much of a material is available to buy, and how much buying and selling affects the price. + var/list/materials_quantity = list() + /// HTML string that is used to display the market events to the player. + var/news_string = "" + +/datum/controller/subsystem/stock_market/Initialize() + for(var/datum/material/possible_market as anything in subtypesof(/datum/material)) // I need to make this work like this, but lets hardcode it for now + if(initial(possible_market.tradable)) + materials_prices += possible_market + materials_prices[possible_market] = initial(possible_market.value_per_unit) * SHEET_MATERIAL_AMOUNT + + materials_trends += possible_market + materials_trends[possible_market] = rand(MARKET_TREND_DOWNWARD,MARKET_TREND_UPWARD) //aka -1 to 1 + + materials_trend_life += possible_market + materials_trend_life[possible_market] = rand(1,10) + + materials_quantity += possible_market + materials_quantity[possible_market] = initial(possible_market.tradable_base_quantity) + (rand(-initial(possible_market.tradable_base_quantity) * 0.5, initial(possible_market.tradable_base_quantity) * 0.5)) + return SS_INIT_SUCCESS +/datum/controller/subsystem/stock_market/fire(resumed) + for(var/datum/material/market as anything in materials_prices) + handle_trends_and_price(market) + +/** + * Handles shifts in the cost of materials, and in what direction the material is most likely to move. + */ +/datum/controller/subsystem/stock_market/proc/handle_trends_and_price(datum/material/mat) + if(prob(MARKET_EVENT_PROBABILITY)) + handle_market_event(mat) + return + var/trend = materials_trends[mat] + var/trend_life = materials_trend_life[mat] + + var/price_units = materials_prices[mat] + var/price_minimum = round(initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT * 0.5) + if(!isnull(initial(mat.minimum_value_override))) + price_minimum = round(initial(mat.minimum_value_override) * SHEET_MATERIAL_AMOUNT) + var/price_maximum = round(initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT * 3) + var/price_baseline = initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT + + var/stock_quantity = materials_quantity[mat] + + if(HAS_TRAIT(SSeconomy, TRAIT_MARKET_CRASHING)) //We hardset to the worst possible price and lowest possible impact if sold + materials_prices[mat] = price_minimum + materials_quantity[mat] = stock_quantity * 2 + materials_trends[mat] = MARKET_TREND_DOWNWARD + trend_life = materials_trend_life[mat] = 1 + return + + if(trend_life == 0) + ///We want to scale our trend so that if we're closer to our minimum or maximum price, we're more likely to trend the other way. + if((price_units < price_baseline)) + var/chance_swap = 100 - ((clamp((price_units - price_minimum), 1, 1000) / (price_baseline - price_minimum))*100) + if(prob(chance_swap)) + materials_trends[mat] = MARKET_TREND_UPWARD + else + materials_trends[mat] = MARKET_TREND_STABLE + else if((price_units > price_baseline)) + var/chance_swap = 100 - ((clamp((price_units - price_maximum), 1, 1000) / (price_maximum - price_baseline))*100) + if(prob(chance_swap)) + materials_trends[mat] = MARKET_TREND_DOWNWARD + else + materials_trends[mat] = MARKET_TREND_STABLE + materials_trend_life[mat] = rand(3,10) // Change our trend life for x number of cycles + else + materials_trend_life[mat] -= 1 + + var/price_change = 0 + var/quantity_change = 0 + switch(trend) + if(MARKET_TREND_UPWARD) + price_change = ROUND_UP(gaussian(price_units * 0.1, price_baseline * 0.05)) //If we don't ceil, small numbers will get trapped at low values + quantity_change = -round(gaussian(stock_quantity * 0.1, stock_quantity * 0.05)) + if(MARKET_TREND_STABLE) + price_change = round(gaussian(0, price_baseline * 0.01)) + quantity_change = round(gaussian(0, stock_quantity * 0.01)) + if(MARKET_TREND_DOWNWARD) + price_change = -ROUND_UP(gaussian(price_units * 0.1, price_baseline * 0.05)) + quantity_change = round(gaussian(stock_quantity * 0.1, stock_quantity * 0.05)) + materials_prices[mat] = round(clamp(price_units + price_change, price_minimum, price_maximum)) + materials_quantity[mat] = round(clamp(stock_quantity + quantity_change, 0, initial(mat.tradable_base_quantity) * 2)) + +/** + * Market events are a way to spice up the market and make it more interesting. + * Randomly one will occur to a random material, and it will change the price of that material more drastically, or reset it to a stable price. + * Events are also broadcast to the newscaster as a fun little fluff piece. Good way to tell some lore as well, or just make a joke. + */ +/datum/controller/subsystem/stock_market/proc/handle_market_event(datum/material/mat) + + var/company_name = list( // Pick a random company name from the list, I let copilot make a few up for me which is why some suck + "Nakamura Engineering", + "Robust Industries, LLC", + "MODular Solutions", + "SolGov", + "Australicus Industrial Mining", + "Vey-Medical", + "Aussec Armory", + "Dreamland Robotics" + ) + var/circumstance + var/event = rand(1,3) + + var/price_units = materials_prices[mat] + var/price_minimum = round(initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT * 0.5) + if(!isnull(initial(mat.minimum_value_override))) + price_minimum = round(initial(mat.minimum_value_override) * SHEET_MATERIAL_AMOUNT) + var/price_maximum = round(initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT * 3) + var/price_baseline = initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT + + switch(event) + if(1) //Reset to stable + materials_prices[mat] = price_baseline + materials_trends[mat] = MARKET_TREND_STABLE + materials_trend_life[mat] = 1 + circumstance = pick(list( + "[pick(company_name)] has been bought out by a private investment firm. As a result, [initial(mat.name)] is now stable at [materials_prices[mat]] cr.", + "Due to a corporate restructuring, the largest supplier of [initial(mat.name)] has had the price changed to [materials_prices[mat]] cr.", + "[initial(mat.name)] is now under a monopoly by [pick(company_name)]. The price has been changed to [materials_prices[mat]] cr accordingly." + )) + if(2) //Big boost + materials_prices[mat] += round(gaussian(price_units * 0.5, price_units * 0.1)) + materials_prices[mat] = clamp(materials_prices[mat], price_minimum, price_maximum) + materials_trends[mat] = MARKET_TREND_UPWARD + materials_trend_life[mat] = rand(1,5) + circumstance = pick(list( + "[pick(company_name)] has just released a new product that uses [initial(mat.name)]! As a result, the price has been raised to [materials_prices[mat]] cr.", + "Due to [pick(company_name)] finding a new property of [initial(mat.name)], its price has been raised to [materials_prices[mat]] cr.", + "A study has found that [initial(mat.name)] may run out within the next 100 years. The price has raised to [materials_prices[mat]] cr due to panic." + )) + if(3) //Big drop + materials_prices[mat] -= round(gaussian(price_units * 1.5, price_units * 0.1)) + materials_prices[mat] = clamp(materials_prices[mat], price_minimum, price_maximum) + materials_trends[mat] = MARKET_TREND_DOWNWARD + materials_trend_life[mat] = rand(1,5) + circumstance = pick(list( + "[pick(company_name)]'s latest product has seen major controversy, and as a result, the price of [initial(mat.name)] has dropped to [materials_prices[mat]] cr.", + "Due to a new competitor, the price of [initial(mat.name)] has dropped to [materials_prices[mat]] cr.", + "[initial(mat.name)] has been found to be a carcinogen. The price has dropped to [materials_prices[mat]] cr due to panic." + )) + news_string += circumstance + "
" // Add the event to the news_string, formatted for newscasters. diff --git a/code/datums/actions/mobs/assume_form.dm b/code/datums/actions/mobs/assume_form.dm new file mode 100644 index 00000000000..b10a91c5d65 --- /dev/null +++ b/code/datums/actions/mobs/assume_form.dm @@ -0,0 +1,87 @@ +/// Allows a mob to assume the form of another item or mob. +/// Warning, this will likely shit the bricks if you add this action to anything more sophisticated than a basic mob- this isn't built for anything carbon-wise. +/datum/action/cooldown/mob_cooldown/assume_form + name = "Assume Form" + desc = "Choose something that you wish to blend into the environment as. Click on yourself to reset your appearance." + button_icon_state = "sniper_zoom" + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + ranged_mousepointer = 'icons/effects/mouse_pointers/supplypod_target.dmi' + check_flags = AB_CHECK_CONSCIOUS + cooldown_time = 1.5 SECONDS + + /// Stuff that we can not disguise as. + var/static/list/blacklist_typecache = typecacheof(list( + /atom/movable/screen, + /obj/effect, + /obj/energy_ball, + /obj/narsie, + /obj/singularity, + )) + +/datum/action/cooldown/mob_cooldown/assume_form/Grant(mob/grant_to) + . = ..() + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(reset_appearances)) + +/datum/action/cooldown/mob_cooldown/assume_form/Remove(mob/remove_from) + reset_appearances() + UnregisterSignal(owner, COMSIG_LIVING_DEATH) + return ..() + +/datum/action/cooldown/mob_cooldown/assume_form/Activate(atom/target_atom) + StartCooldown(360 SECONDS, 360 SECONDS) + determine_intent(target_atom) + StartCooldown() + return TRUE + +/// Rapid proc to test if we can assume the form of a given atom. Returns TRUE if we can, FALSE if we can't. Done like this so we can be nice and explicit. +/datum/action/cooldown/mob_cooldown/assume_form/proc/can_assume_form(atom/target_atom) + if(is_type_in_typecache(target_atom, blacklist_typecache) || (!isobj(target_atom) && !ismob(target_atom))) + return FALSE + + return TRUE + +/// Determines what our user meant by their action. If they clicked on themselves, we reset our appearance. Otherwise, we assume the appearance of the clicked-on item. +/datum/action/cooldown/mob_cooldown/assume_form/proc/determine_intent(atom/target_atom) + if(!can_assume_form(target_atom)) + return + + if(target_atom == owner) + reset_appearances() + return + + assume_appearances(target_atom) + +/// Assumes the appearance of a desired movable and applies it to our mob. Target is the movable in question. +/datum/action/cooldown/mob_cooldown/assume_form/proc/assume_appearances(atom/movable/target_atom) + owner.appearance = target_atom.appearance + owner.copy_overlays(target_atom) + owner.alpha = max(target_atom.alpha, 150) //fucking chameleons + owner.transform = initial(target_atom.transform) + owner.pixel_x = target_atom.base_pixel_x + owner.pixel_y = target_atom.base_pixel_y + + // important: do this at the very end because we might have SIGNAL_ADDTRAIT for this on the mob that's dependent on the above logic + SEND_SIGNAL(owner, COMSIG_ACTION_DISGUISED_APPEARANCE, target_atom) + ADD_TRAIT(owner, TRAIT_DISGUISED, REF(src)) + +/// Resets the appearances of the mob to the default. +/datum/action/cooldown/mob_cooldown/assume_form/proc/reset_appearances() + SIGNAL_HANDLER + + if(!HAS_TRAIT(owner, TRAIT_DISGUISED)) + return // in case we're being invoked on death and we aren't disguised (or we just click on ourselves randomly), no need to do this additional work. + + owner.animate_movement = SLIDE_STEPS + owner.maptext = null + owner.alpha = initial(owner.alpha) + owner.color = initial(owner.color) + owner.desc = initial(owner.desc) + + owner.name = initial(owner.name) + owner.icon = initial(owner.icon) + owner.icon_state = initial(owner.icon_state) + owner.cut_overlays() + + // important: do this very end because we might have SIGNAL_REMOVETRAIT for this on the mob that's dependent on the above logic + REMOVE_TRAIT(owner, TRAIT_DISGUISED, REF(src)) diff --git a/code/datums/actions/mobs/conjure_foamwall.dm b/code/datums/actions/mobs/conjure_foamwall.dm new file mode 100644 index 00000000000..8814225f8a2 --- /dev/null +++ b/code/datums/actions/mobs/conjure_foamwall.dm @@ -0,0 +1,21 @@ +/datum/action/cooldown/spell/conjure/foam_wall + name = "Foam wall" + desc = "Create a wall of foam." + + button_icon = 'icons/effects/effects.dmi' + button_icon_state = "metalfoam" + + cooldown_time = 2 MINUTES + spell_requirements = NONE + + summon_radius = 0 + summon_type = list(/obj/structure/foamedmetal) + +/datum/action/cooldown/spell/conjure/foam_wall/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + var/turf/owner_turf = get_turf(owner) + if(owner_turf.is_blocked_turf(exclude_mobs = TRUE)) + return FALSE + return TRUE diff --git a/code/datums/actions/mobs/defensive_mode.dm b/code/datums/actions/mobs/defensive_mode.dm new file mode 100644 index 00000000000..30cb9a5980a --- /dev/null +++ b/code/datums/actions/mobs/defensive_mode.dm @@ -0,0 +1,49 @@ +/// An ability that allows the viper spider to get in an defensive mode at the cost of speed. +/datum/action/cooldown/mob_cooldown/defensive_mode + name = "Change Mode" + button_icon = 'icons/mob/actions/actions_animal.dmi' + button_icon_state = "defensive_mode" + desc = "Activates a defensive mode to reduce damage but will make you slower." + cooldown_time = 5 SECONDS + click_to_activate = FALSE + /// If the defensive mode is activated or not. + var/defense_active = FALSE + /// Movement speed modifier used. + var/datum/movespeed_modifier/modifier_type = /datum/movespeed_modifier/viper_defensive + +/datum/action/cooldown/mob_cooldown/defensive_mode/Remove(mob/living/remove_from) + var/mob/living/basic/owner_mob = owner + if(defense_active && istype(owner_mob)) + offence(owner_mob) + + return ..() + +/datum/action/cooldown/mob_cooldown/defensive_mode/Activate(atom/target_atom) + StartCooldown(360 SECONDS, 360 SECONDS) + activate_defence(owner) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/defensive_mode/proc/activate_defence(mob/living/basic/owner_mob) + if(!istype(owner_mob)) + return + if(defense_active) + offence(owner_mob) + return + defence(owner_mob) + +/datum/action/cooldown/mob_cooldown/defensive_mode/proc/offence(mob/living/basic/owner_mob) + owner_mob.damage_coeff = list(BRUTE = 1, BURN = 1.25, TOX = 1, CLONE = 1, STAMINA = 1, OXY = 1) + owner_mob.icon_state = initial(owner_mob.icon_state) + owner_mob.icon_living = initial(owner_mob.icon_living) + owner_mob.icon_dead = initial(owner_mob.icon_dead) + owner_mob.remove_movespeed_modifier(modifier_type) + defense_active = FALSE + +/datum/action/cooldown/mob_cooldown/defensive_mode/proc/defence(mob/living/basic/owner_mob) + owner_mob.damage_coeff = list(BRUTE = 0.4, BURN = 0.5, TOX = 1, CLONE = 1, STAMINA = 1, OXY = 1) + owner_mob.icon_dead = "[owner_mob.icon_state]_d_dead" + owner_mob.icon_state = "[owner_mob.icon_state]_d" + owner_mob.icon_living = "[owner_mob.icon_living]_d" + owner_mob.add_movespeed_modifier(modifier_type) + defense_active = TRUE diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/find_mineable_wall.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/find_mineable_wall.dm new file mode 100644 index 00000000000..ad5749c9161 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/find_mineable_wall.dm @@ -0,0 +1,33 @@ +//behavior to find mineable mineral walls +/datum/ai_behavior/find_mineral_wall + +/datum/ai_behavior/find_mineral_wall/perform(seconds_per_tick, datum/ai_controller/controller, found_wall_key) + . = ..() + var/mob/living_pawn = controller.pawn + + for(var/turf/closed/mineral/potential_wall in oview(9, living_pawn)) + if(!check_if_mineable(controller, potential_wall)) //check if its surrounded by walls + continue + controller.set_blackboard_key(found_wall_key, potential_wall) //closest wall first! + finish_action(controller, TRUE) + return + + finish_action(controller, FALSE) + +/datum/ai_behavior/find_mineral_wall/proc/check_if_mineable(datum/ai_controller/controller, turf/target_wall) + var/mob/living/source = controller.pawn + var/direction_to_turf = get_dir(target_wall, source) + if(!ISDIAGONALDIR(direction_to_turf)) + return TRUE + var/list/directions_to_check = list() + for(var/direction_check in GLOB.cardinals) + if(direction_check & direction_to_turf) + directions_to_check += direction_check + + for(var/direction in directions_to_check) + var/turf/test_turf = get_step(target_wall, direction) + if(isnull(test_turf)) + continue + if(!test_turf.is_blocked_turf(ignore_atoms = list(source))) + return TRUE + return FALSE diff --git a/code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm new file mode 100644 index 00000000000..c3d334165d9 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm @@ -0,0 +1,33 @@ +/// Attack something which is already adjacent to us, without ending planning +/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic + melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/opportunistic + end_planning = FALSE + +/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if(QDELETED(target) || !controller.pawn.Adjacent(target)) + return + if (isliving(controller.pawn)) + var/mob/living/pawn = controller.pawn + if (LAZYLEN(pawn.do_afters)) + return + controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + +/// Attack something which is already adjacent to us without moving +/datum/ai_behavior/basic_melee_attack/opportunistic + action_cooldown = 0.2 SECONDS // We gotta check unfortunately often because we're in a race condition with nextmove + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/basic_melee_attack/opportunistic/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) + if (!controller.blackboard_key_exists(targetting_datum_key)) + CRASH("No target datum was supplied in the blackboard for [controller.pawn]") + return controller.blackboard_key_exists(target_key) + +/datum/ai_behavior/basic_melee_attack/opportunistic/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) + var/atom/movable/atom_pawn = controller.pawn + if(!atom_pawn.CanReach(controller.blackboard[target_key])) + finish_action(controller, TRUE, target_key) // Don't clear target + return FALSE + . = ..() + finish_action(controller, TRUE, target_key) // Try doing something else diff --git a/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm b/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm new file mode 100644 index 00000000000..c09e7cdbf75 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm @@ -0,0 +1,82 @@ +/// Step away if too close, or towards if too far +/datum/ai_planning_subtree/maintain_distance + /// Blackboard key holding atom we want to stay away from + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + /// How close will we allow our target to get? + var/minimum_distance = 4 + /// How far away will we allow our target to get? + var/maximum_distance = 6 + /// How far do we look for our target? + var/view_distance = 10 + +/datum/ai_planning_subtree/maintain_distance/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + var/atom/target = controller.blackboard[target_key] + if (!isliving(target) || !can_see(controller.pawn, target, view_distance)) + return // Don't run away from cucumbers, they're not snakes + var/range = get_dist(controller.pawn, target) + if (range < minimum_distance) + controller.queue_behavior(/datum/ai_behavior/step_away, target_key) + return + if (range > maximum_distance) + controller.queue_behavior(/datum/ai_behavior/pursue_to_range, target_key, maximum_distance) + return + +/// Take one step away +/datum/ai_behavior/step_away + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + required_distance = 0 + action_cooldown = 0.2 SECONDS + +/datum/ai_behavior/step_away/setup(datum/ai_controller/controller, target_key) + . = ..() + var/atom/current_target = controller.blackboard[target_key] + if (QDELETED(current_target)) + return FALSE + + var/mob/living/our_pawn = controller.pawn + our_pawn.face_atom(current_target) + + var/turf/next_step = get_step_away(controller.pawn, current_target) + if (!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) + set_movement_target(controller, target = next_step, new_movement = /datum/ai_movement/basic_avoidance/backstep) + return TRUE + + var/list/all_dirs = GLOB.alldirs.Copy() + all_dirs -= get_dir(controller.pawn, next_step) + all_dirs -= get_dir(controller.pawn, current_target) + shuffle_inplace(all_dirs) + + for (var/dir in all_dirs) + next_step = get_step(controller.pawn, dir) + if (!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) + set_movement_target(controller, target = next_step, new_movement = /datum/ai_movement/basic_avoidance/backstep) + return TRUE + return FALSE + +/datum/ai_behavior/step_away/perform(seconds_per_tick, datum/ai_controller/controller) + . = ..() + finish_action(controller, succeeded = TRUE) + +/datum/ai_behavior/step_away/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.change_ai_movement_type(initial(controller.ai_movement)) + +/// Pursue a target until we are within a provided range +/datum/ai_behavior/pursue_to_range + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_MOVE_AND_PERFORM + +/datum/ai_behavior/pursue_to_range/setup(datum/ai_controller/controller, target_key, range) + . = ..() + var/atom/current_target = controller.blackboard[target_key] + if (QDELETED(current_target)) + return FALSE + if (get_dist(controller.pawn, current_target) <= range) + return FALSE + set_movement_target(controller, current_target) + +/datum/ai_behavior/pursue_to_range/perform(seconds_per_tick, datum/ai_controller/controller, target_key, range) + var/atom/current_target = controller.blackboard[target_key] + if (!QDELETED(current_target) && get_dist(controller.pawn, current_target) > range) + return + finish_action(controller, succeeded = TRUE) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm b/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm new file mode 100644 index 00000000000..c98878e0fd7 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm @@ -0,0 +1,71 @@ +/// Try to line up with a cardinal direction of your target +/datum/ai_planning_subtree/move_to_cardinal + /// Behaviour to execute to line ourselves up + var/move_behaviour = /datum/ai_behavior/move_to_cardinal + /// Blackboard key in which to store selected target + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + +/datum/ai_planning_subtree/move_to_cardinal/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + if(!controller.blackboard_key_exists(target_key)) + return + controller.queue_behavior(move_behaviour, target_key) + +/// Try to line up with a cardinal direction of your target +/datum/ai_behavior/move_to_cardinal + required_distance = 0 + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + /// How close to our target is too close? + var/minimum_distance = 1 + /// How far away is too far? + var/maximum_distance = 9 + +/datum/ai_behavior/move_to_cardinal/setup(datum/ai_controller/controller, target_key) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + target_nearest_cardinal(controller, target) + return TRUE + +/// Set our movement target to the closest cardinal space to our target +/datum/ai_behavior/move_to_cardinal/proc/target_nearest_cardinal(datum/ai_controller/controller, atom/target) + var/atom/move_target + var/closest = INFINITY + + for (var/dir in GLOB.cardinals) + var/turf/cardinal_turf = get_ranged_target_turf(target, dir, minimum_distance) + if (cardinal_turf.is_blocked_turf()) + continue + var/distance_to = get_dist(controller.pawn, cardinal_turf) + if (distance_to >= closest) + continue + closest = distance_to + move_target = cardinal_turf + + if (isnull(move_target)) + move_target = target + if (controller.current_movement_target == move_target) + return + set_movement_target(controller, move_target) + +/datum/ai_behavior/move_to_cardinal/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + var/atom/target = controller.blackboard[target_key] + if (QDELETED(target)) + finish_action(controller = controller, succeeded = FALSE, target_key = target_key) + return + if (!(get_dir(controller.pawn, target) in GLOB.cardinals)) + target_nearest_cardinal(controller, target) + return + var/distance_to_target = get_dist(controller.pawn, target) + if (distance_to_target < minimum_distance) + target_nearest_cardinal(controller, target) + return + if (distance_to_target > maximum_distance) + return + finish_action(controller = controller, succeeded = TRUE, target_key = target_key) + return + +/datum/ai_behavior/move_to_cardinal/finish_action(datum/ai_controller/controller, succeeded, target_key) + if (!succeeded) + controller.clear_blackboard_key(target_key) + return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm new file mode 100644 index 00000000000..1ff752d925f --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm @@ -0,0 +1,52 @@ +/// Fire a ranged attack without interrupting movement. +/datum/ai_planning_subtree/ranged_skirmish + operational_datums = list(/datum/component/ranged_attacks) + /// Blackboard key holding target atom + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + /// What AI behaviour do we actually run? + var/datum/ai_behavior/ranged_skirmish/attack_behavior = /datum/ai_behavior/ranged_skirmish + +/datum/ai_planning_subtree/ranged_skirmish/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + if(!controller.blackboard_key_exists(target_key)) + return + controller.queue_behavior(attack_behavior, target_key, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + +/// How often will we try to perform our ranged attack? +/datum/ai_behavior/ranged_skirmish + action_cooldown = 1 SECONDS + /// If target is further away than this we don't fire + var/max_range = 9 + /// If target is closer than this we don't fire + var/min_range = 2 + +/datum/ai_behavior/ranged_skirmish/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) + . = ..() + var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] + return !QDELETED(target) + +/datum/ai_behavior/ranged_skirmish/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if (QDELETED(target)) + finish_action(controller, succeeded = FALSE) + return + + var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] + if(!targetting_datum.can_attack(controller.pawn, target)) + finish_action(controller, succeeded = FALSE) + return + + var/hiding_target = targetting_datum.find_hidden_mobs(controller.pawn, target) + controller.set_blackboard_key(hiding_location_key, hiding_target) + + target = hiding_target || target + + var/distance = get_dist(controller.pawn, target) + if (distance > max_range || distance < min_range) + finish_action(controller, succeeded = FALSE) + return + + var/mob/living/basic/gunman = controller.pawn + gunman.RangedAttack(target) + finish_action(controller, succeeded = TRUE) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm b/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm new file mode 100644 index 00000000000..9ce7cc95c07 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm @@ -0,0 +1,18 @@ +/// Simply walk to a location +/datum/ai_planning_subtree/travel_to_point + /// Blackboard key where we travel a place we walk to + var/location_key = BB_TRAVEL_DESTINATION + /// What do we do in order to travel + var/travel_behaviour = /datum/ai_behavior/travel_towards + +/datum/ai_planning_subtree/travel_to_point/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + var/atom/target = controller.blackboard[location_key] + if (QDELETED(target)) + return + controller.queue_behavior(travel_behaviour, location_key) + return SUBTREE_RETURN_FINISH_PLANNING + + +/datum/ai_planning_subtree/travel_to_point/and_clear_target + travel_behaviour = /datum/ai_behavior/travel_towards/stop_on_arrival diff --git a/code/datums/ai/basic_mobs/generic_controllers.dm b/code/datums/ai/basic_mobs/generic_controllers.dm new file mode 100644 index 00000000000..208c1833add --- /dev/null +++ b/code/datums/ai/basic_mobs/generic_controllers.dm @@ -0,0 +1,26 @@ +/// The most basic AI tree which just finds a guy and then runs at them to click them +/datum/ai_controller/basic_controller/simple_hostile + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Find a target, walk at target, attack intervening obstacles +/datum/ai_controller/basic_controller/simple_hostile_obstacles + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) diff --git a/code/datums/ai/hunting_behavior/hunting_corpses.dm b/code/datums/ai/hunting_behavior/hunting_corpses.dm new file mode 100644 index 00000000000..e720e4da947 --- /dev/null +++ b/code/datums/ai/hunting_behavior/hunting_corpses.dm @@ -0,0 +1,17 @@ +/// Find and attack corpses +/datum/ai_planning_subtree/find_and_hunt_target/corpses + finding_behavior = /datum/ai_behavior/find_hunt_target/corpses + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target + hunt_targets = list(/mob/living) + +/// Find nearby dead mobs +/datum/ai_behavior/find_hunt_target/corpses + +/datum/ai_behavior/find_hunt_target/corpses/valid_dinner(mob/living/source, mob/living/dinner, radius) + if (!isliving(dinner) || dinner.stat != DEAD) + return FALSE + return can_see(source, dinner, radius) + +/// Find and attack specifically human corpses +/datum/ai_planning_subtree/find_and_hunt_target/corpses/human + hunt_targets = list(/mob/living/carbon/human) diff --git a/code/datums/components/ai_has_target_timer.dm b/code/datums/components/ai_has_target_timer.dm new file mode 100644 index 00000000000..bcd748ce638 --- /dev/null +++ b/code/datums/components/ai_has_target_timer.dm @@ -0,0 +1,79 @@ +/// Increments a blackboard key while the attached mob is engaged with a particular target, does nothing else on its own +/datum/component/ai_target_timer + /// Blackboard key to store data inside + var/increment_key + /// Blackboard key to watch to indicate whether we are 'in combat' + var/target_key + /// Amount of time we have spent focused on one target + var/time_on_target = 0 + /// The last target we had + var/atom/last_target + /// Timer used to see if you + var/reset_clock_timer + +/datum/component/ai_target_timer/Initialize(increment_key = BB_BASIC_MOB_HAS_TARGET_TIME, target_key = BB_BASIC_MOB_CURRENT_TARGET) + . = ..() + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE + var/mob/living/mob_parent = parent + if (isnull(mob_parent.ai_controller)) + return COMPONENT_INCOMPATIBLE + src.increment_key = increment_key + src.target_key = target_key + +/datum/component/ai_target_timer/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_AI_BLACKBOARD_KEY_SET(target_key), PROC_REF(changed_target)) + RegisterSignal(parent, COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_key), PROC_REF(lost_target)) + ADD_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type) + +/datum/component/ai_target_timer/UnregisterFromParent() + finalise_losing_target() + UnregisterSignal(parent, list(COMSIG_AI_BLACKBOARD_KEY_SET(target_key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_key))) + REMOVE_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type) + return ..() + +/datum/component/ai_target_timer/Destroy(force, silent) + finalise_losing_target() + return ..() + +/// When we get a new target, reset the timer and start processing +/datum/component/ai_target_timer/proc/changed_target(mob/living/source) + SIGNAL_HANDLER + var/mob/living/living_parent = parent + var/atom/new_target = living_parent.ai_controller.blackboard[target_key] + deltimer(reset_clock_timer) + if (new_target == last_target) + return + time_on_target = 0 + store_current_time() + START_PROCESSING(SSdcs, src) + if (!isnull(last_target)) + UnregisterSignal(last_target, COMSIG_QDELETING) + RegisterSignal(new_target, COMSIG_QDELETING, PROC_REF(finalise_losing_target)) + last_target = new_target + +/// When we lose our target, start a short timer in case we reacquire it very quickly +/datum/component/ai_target_timer/proc/lost_target() + SIGNAL_HANDLER + reset_clock_timer = addtimer(CALLBACK(src, PROC_REF(finalise_losing_target)), 3 SECONDS, TIMER_STOPPABLE | TIMER_DELETE_ME) + +/// Called if we have had no target for long enough +/datum/component/ai_target_timer/proc/finalise_losing_target() + deltimer(reset_clock_timer) + STOP_PROCESSING(SSdcs, src) + if (!isnull(last_target)) + UnregisterSignal(last_target, COMSIG_QDELETING) + last_target = null + time_on_target = 0 + if (!QDELETED(parent)) + store_current_time() + +/// Store the current time on our timer in our blackboard key +/datum/component/ai_target_timer/proc/store_current_time() + var/mob/living/living_parent = parent + living_parent.ai_controller.set_blackboard_key(increment_key, time_on_target) + +/datum/component/ai_target_timer/process(seconds_per_tick) + time_on_target += seconds_per_tick SECONDS + store_current_time() diff --git a/code/datums/components/ai_listen_to_weather.dm b/code/datums/components/ai_listen_to_weather.dm new file mode 100644 index 00000000000..a7bb95ee8c1 --- /dev/null +++ b/code/datums/components/ai_listen_to_weather.dm @@ -0,0 +1,36 @@ +/** + * given to a mob to set a key on or off when a storm is coming or ending + */ +/datum/component/ai_listen_to_weather + ///what weather type are we listening to + var/weather_type + ///what blackboard key are we setting + var/weather_key + +/datum/component/ai_listen_to_weather/Initialize(weather_type = /datum/weather/ash_storm, weather_key = BB_STORM_APPROACHING) + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + src.weather_type = weather_type + src.weather_key = weather_key + +/datum/component/ai_listen_to_weather/RegisterWithParent() + RegisterSignal(SSdcs, COMSIG_WEATHER_START(weather_type), PROC_REF(storm_start)) + RegisterSignal(SSdcs, COMSIG_WEATHER_END(weather_type), PROC_REF(storm_end)) + +/datum/component/ai_listen_to_weather/UnregisterFromParent() + UnregisterSignal(SSdcs, list(COMSIG_WEATHER_START(weather_type), COMSIG_WEATHER_END(weather_type))) + +/datum/component/ai_listen_to_weather/proc/storm_start() + SIGNAL_HANDLER + + var/mob/living/basic/source = parent + if(!source.ai_controller) + return + source.ai_controller.CancelActions() + source.ai_controller.set_blackboard_key(weather_key, TRUE) + +/datum/component/ai_listen_to_weather/proc/storm_end() + SIGNAL_HANDLER + + var/mob/living/basic/source = parent + source.ai_controller?.set_blackboard_key(weather_key, FALSE) diff --git a/code/datums/components/appearance_on_aggro.dm b/code/datums/components/appearance_on_aggro.dm new file mode 100644 index 00000000000..33a3d7c2e90 --- /dev/null +++ b/code/datums/components/appearance_on_aggro.dm @@ -0,0 +1,82 @@ + +/** + * Changes visuals of the attached mob while it has a target + */ +/datum/component/appearance_on_aggro + /// Blackboardey to search for a target + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + /// Icon state to use when we have a target + var/aggro_state + /// path of the overlay to apply + var/mutable_appearance/aggro_overlay + /// visibility of our icon when aggroed + var/alpha_on_aggro + /// visibility of our icon when deaggroed + var/alpha_on_deaggro + /// do we currently have a target + var/atom/current_target + +/datum/component/appearance_on_aggro/Initialize(aggro_state, overlay_icon, overlay_state, alpha_on_aggro, alpha_on_deaggro) + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE + src.aggro_state = aggro_state + src.alpha_on_aggro = alpha_on_aggro + src.alpha_on_deaggro = alpha_on_deaggro + if (!isnull(overlay_icon) && !isnull(overlay_state)) + aggro_overlay = mutable_appearance(overlay_icon, overlay_state) + +/datum/component/appearance_on_aggro/RegisterWithParent() + RegisterSignal(parent, COMSIG_AI_BLACKBOARD_KEY_SET(target_key), PROC_REF(on_set_target)) + RegisterSignal(parent, COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_key), PROC_REF(on_clear_target)) + if (!isnull(aggro_state)) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON_STATE, PROC_REF(on_icon_state_updated)) + if (!isnull(aggro_overlay)) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_overlays_updated)) + +/datum/component/appearance_on_aggro/UnregisterFromParent() + . = ..() + UnregisterSignal(parent, list(COMSIG_AI_BLACKBOARD_KEY_SET(target_key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_key))) + +/datum/component/appearance_on_aggro/proc/on_set_target(mob/living/source) + SIGNAL_HANDLER + + var/atom/target = source.ai_controller.blackboard[target_key] + if (QDELETED(target)) + return + + current_target = target + RegisterSignal(target, COMSIG_QDELETING, PROC_REF(on_clear_target)) + if (!isnull(aggro_overlay) || !isnull(aggro_state)) + source.update_appearance(UPDATE_ICON) + if (!isnull(alpha_on_aggro)) + animate(source, alpha = alpha_on_aggro, time = 2 SECONDS) + +/datum/component/appearance_on_aggro/Destroy() + if (!isnull(current_target)) + revert_appearance(parent) + return ..() + +/datum/component/appearance_on_aggro/proc/on_clear_target(atom/source) + SIGNAL_HANDLER + revert_appearance(parent) + +/datum/component/appearance_on_aggro/proc/revert_appearance(mob/living/source) + UnregisterSignal(current_target, COMSIG_QDELETING) + current_target = null + if (!isnull(aggro_overlay) || !isnull(aggro_state)) + source.update_appearance(UPDATE_ICON) + if (!isnull(alpha_on_deaggro)) + animate(source, alpha = alpha_on_deaggro, time = 2 SECONDS) + +/datum/component/appearance_on_aggro/proc/on_icon_state_updated(mob/living/source) + SIGNAL_HANDLER + if (source.stat == DEAD) + return + source.icon_state = isnull(current_target) ? initial(source.icon_state) : aggro_state + +/datum/component/appearance_on_aggro/proc/on_overlays_updated(atom/source, list/overlays) + SIGNAL_HANDLER + + if (isnull(current_target)) + return + overlays += aggro_overlay diff --git a/code/datums/components/basic_ranged_ready_overlay.dm b/code/datums/components/basic_ranged_ready_overlay.dm new file mode 100644 index 00000000000..434a64dd6ff --- /dev/null +++ b/code/datums/components/basic_ranged_ready_overlay.dm @@ -0,0 +1,56 @@ +/** + * Fade in an overlay x seconds after a basic mob makes a ranged attack + * Indicates that it will be ready to fire again + */ +/datum/component/basic_ranged_ready_overlay + /// Icon state for the overlay to display + var/overlay_state + /// Time after which to redisplay the overlay + var/display_after + /// Timer tracking when we can next fire + var/waiting_timer + +/datum/component/basic_ranged_ready_overlay/Initialize(overlay_state = "", display_after = 2.5 SECONDS) + . = ..() + if (!isbasicmob(parent)) + return COMPONENT_INCOMPATIBLE + if (!overlay_state) + CRASH("Attempted to assign basic ranged ready overlay with a null or empty overlay state") + src.overlay_state = overlay_state + src.display_after = display_after + restore_overlay(parent) + +/datum/component/basic_ranged_ready_overlay/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_BASICMOB_POST_ATTACK_RANGED, PROC_REF(on_ranged_attack)) + RegisterSignal(parent, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_changed)) + +/datum/component/basic_ranged_ready_overlay/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_BASICMOB_POST_ATTACK_RANGED, COMSIG_LIVING_REVIVE)) + return ..() + +/datum/component/basic_ranged_ready_overlay/Destroy(force, silent) + deltimer(waiting_timer) + return ..() + +/// When we shoot, get rid of our overlay and queue its return +/datum/component/basic_ranged_ready_overlay/proc/on_ranged_attack(mob/living/basic/firer, atom/target, modifiers) + SIGNAL_HANDLER + firer.cut_overlay(overlay_state) + waiting_timer = addtimer(CALLBACK(src, PROC_REF(restore_overlay), firer), display_after, TIMER_UNIQUE | TIMER_STOPPABLE | TIMER_DELETE_ME) + +/// Don't show overlay on a dead man +/datum/component/basic_ranged_ready_overlay/proc/on_stat_changed(mob/living/basic/gunman) + SIGNAL_HANDLER + if (gunman.stat == DEAD) + gunman.cut_overlay(overlay_state) + return + if (timeleft(waiting_timer) <= 0) + restore_overlay(parent) + +/// Try putting our overlay back +/datum/component/basic_ranged_ready_overlay/proc/restore_overlay(mob/living/basic/gunman) + if (QDELETED(gunman) || gunman.stat == DEAD) + return + gunman.cut_overlay(overlay_state) + gunman.add_overlay(overlay_state) diff --git a/code/datums/components/blob_minion.dm b/code/datums/components/blob_minion.dm new file mode 100644 index 00000000000..41f58231e2d --- /dev/null +++ b/code/datums/components/blob_minion.dm @@ -0,0 +1,154 @@ +/** + * Common behaviour shared by things which are minions to a blob + */ +/datum/component/blob_minion + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + /// Overmind who is our boss + var/mob/camera/blob/overmind + /// Callback to run if overmind strain changes + var/datum/callback/on_strain_changed + +/datum/component/blob_minion/Initialize(mob/camera/blob/overmind, datum/callback/on_strain_changed) + . = ..() + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE + src.on_strain_changed = on_strain_changed + register_overlord(overmind) + +/datum/component/blob_minion/InheritComponent(datum/component/new_comp, i_am_original, mob/camera/blob/overmind, datum/callback/on_strain_changed) + if (!isnull(on_strain_changed)) + src.on_strain_changed = on_strain_changed + register_overlord(overmind) + +/datum/component/blob_minion/proc/register_overlord(mob/camera/blob/overmind) + if (isnull(overmind)) + return + src.overmind = overmind + overmind.register_new_minion(parent) + RegisterSignal(overmind, COMSIG_QDELETING, PROC_REF(overmind_deleted)) + RegisterSignal(overmind, COMSIG_BLOB_SELECTED_STRAIN, PROC_REF(overmind_properties_changed)) + overmind_properties_changed(overmind, overmind.blobstrain) + +/// Our overmind is gone, uh oh! +/datum/component/blob_minion/proc/overmind_deleted() + SIGNAL_HANDLER + overmind = null + overmind_properties_changed() + +/// Our overmind has changed colour and properties +/datum/component/blob_minion/proc/overmind_properties_changed(mob/camera/blob/overmind, datum/blobstrain/new_strain) + SIGNAL_HANDLER + var/mob/living/living_parent = parent + living_parent.update_appearance(UPDATE_ICON) + on_strain_changed?.Invoke(overmind, new_strain) + +/datum/component/blob_minion/RegisterWithParent() + var/mob/living/living_parent = parent + living_parent.pass_flags |= PASSBLOB + living_parent.faction |= ROLE_BLOB + ADD_TRAIT(parent, TRAIT_BLOB_ALLY, REF(src)) + remove_verb(parent, /mob/living/verb/pulled) // No dragging people into the blob + RegisterSignal(parent, COMSIG_MOB_MIND_INITIALIZED, PROC_REF(on_mind_init)) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON, PROC_REF(on_update_appearance)) + RegisterSignal(parent, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(on_update_status_tab)) + RegisterSignal(parent, COMSIG_ATOM_BLOB_ACT, PROC_REF(on_blob_touched)) + RegisterSignal(parent, COMSIG_ATOM_FIRE_ACT, PROC_REF(on_burned)) + RegisterSignal(parent, COMSIG_ATOM_TRIED_PASS, PROC_REF(on_attempted_pass)) + RegisterSignal(parent, COMSIG_MOVABLE_SPACEMOVE, PROC_REF(on_space_move)) + RegisterSignal(parent, COMSIG_LIVING_TRY_SPEECH, PROC_REF(on_try_speech)) + RegisterSignal(parent, COMSIG_MOB_CHANGED_TYPE, PROC_REF(on_transformed)) + living_parent.update_appearance(UPDATE_ICON) + GLOB.blob_telepathy_mobs |= parent + +/datum/component/blob_minion/UnregisterFromParent() + if (!isnull(overmind)) + overmind.blob_mobs -= parent + var/mob/living/living_parent = parent + living_parent.pass_flags &= ~PASSBLOB + living_parent.faction -= ROLE_BLOB + REMOVE_TRAIT(parent, TRAIT_BLOB_ALLY, REF(src)) + add_verb(parent, /mob/living/verb/pulled) + UnregisterSignal(parent, list( + COMSIG_ATOM_BLOB_ACT, + COMSIG_ATOM_FIRE_ACT, + COMSIG_ATOM_TRIED_PASS, + COMSIG_ATOM_UPDATE_ICON, + COMSIG_LIVING_TRY_SPEECH, + COMSIG_MOB_CHANGED_TYPE, + COMSIG_MOB_GET_STATUS_TAB_ITEMS, + COMSIG_MOB_MIND_INITIALIZED, + COMSIG_MOVABLE_SPACEMOVE, + )) + GLOB.blob_telepathy_mobs -= parent + +/// Become blobpilled when we gain a mind +/datum/component/blob_minion/proc/on_mind_init(mob/living/minion, datum/mind/new_mind) + SIGNAL_HANDLER + if (isnull(overmind)) + return + var/datum/antagonist/blob_minion/minion_motive = new(overmind) + new_mind.add_antag_datum(minion_motive) + +/// When our icon is updated, update our colour too +/datum/component/blob_minion/proc/on_update_appearance(mob/living/minion) + SIGNAL_HANDLER + if(isnull(overmind)) + minion.remove_atom_colour(FIXED_COLOUR_PRIORITY) + return + minion.add_atom_colour(overmind.blobstrain.color, FIXED_COLOUR_PRIORITY) + +/// When our icon is updated, update our colour too +/datum/component/blob_minion/proc/on_update_status_tab(mob/living/minion, list/status_items) + SIGNAL_HANDLER + if (isnull(overmind)) + return + status_items += "Blobs to Win: [length(overmind.blobs_legit)]/[overmind.blobwincount]" + +/// If we feel the gentle caress of a blob, we feel better +/datum/component/blob_minion/proc/on_blob_touched(mob/living/minion) + SIGNAL_HANDLER + if(minion.stat == DEAD || minion.health >= minion.maxHealth) + return COMPONENT_CANCEL_BLOB_ACT // Don't hurt us in order to heal us + for(var/i in 1 to 2) + var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(parent)) // hello yes you are being healed + heal_effect.color = isnull(overmind) ? COLOR_BLACK : overmind.blobstrain.complementary_color + minion.heal_overall_damage(minion.maxHealth * BLOBMOB_HEALING_MULTIPLIER) + return COMPONENT_CANCEL_BLOB_ACT + +/// If we feel the fearsome bite of open flame, we feel worse +/datum/component/blob_minion/proc/on_burned(mob/living/minion, exposed_temperature, exposed_volume) + SIGNAL_HANDLER + if(isnull(exposed_temperature)) + minion.adjustFireLoss(5) + return + minion.adjustFireLoss(clamp(0.01 * exposed_temperature, 1, 5)) + +/// Someone is attempting to move through us, allow it if it is a blob tile +/datum/component/blob_minion/proc/on_attempted_pass(mob/living/minion, atom/movable/incoming) + SIGNAL_HANDLER + if(istype(incoming, /obj/structure/blob)) + return COMSIG_COMPONENT_PERMIT_PASSAGE + +/// If we're near a blob, stop drifting +/datum/component/blob_minion/proc/on_space_move(mob/living/minion) + SIGNAL_HANDLER + var/obj/structure/blob/blob_handhold = locate() in range(1, parent) + if (!isnull(blob_handhold)) + return COMSIG_MOVABLE_STOP_SPACEMOVE + +/// We only speak telepathically to blobs +/datum/component/blob_minion/proc/on_try_speech(mob/living/minion, message, ignore_spam, forced) + SIGNAL_HANDLER + var/spanned_message = minion.say_quote(message) + var/rendered = span_blob("\[Blob Telepathy\] [minion.real_name] [spanned_message]") + blob_telepathy(rendered, minion) + return COMPONENT_CANNOT_SPEAK + +/// Called when a blob minion is transformed into something else, hopefully a spore into a zombie +/datum/component/blob_minion/proc/on_transformed(mob/living/minion, mob/living/replacement) + SIGNAL_HANDLER + overmind?.assume_direct_control(replacement) + +/datum/component/blob_minion/PostTransfer() + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/crafting/slapcrafting.dm b/code/datums/components/crafting/slapcrafting.dm new file mode 100644 index 00000000000..32a901dc73e --- /dev/null +++ b/code/datums/components/crafting/slapcrafting.dm @@ -0,0 +1,202 @@ +/// Slapcrafting component! +/datum/component/slapcrafting + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + var/list/slapcraft_recipes = list() + +/** + * Slapcraft component + * + * Slap it onto a item to be able to slapcraft with it + * + * args: + * * slapcraft_recipes (required) = The recipe to attempt crafting. + * Hit it with an ingredient of the recipe to attempt crafting. + * It will check the area near the user for the rest of the ingredients and tools. + * * +**/ +/datum/component/slapcrafting/Initialize( + slapcraft_recipes = null, + ) + + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + + var/obj/item/parent_item = parent + + if((parent_item.item_flags & ABSTRACT) || (parent_item.item_flags & DROPDEL)) + return COMPONENT_NOTRANSFER + + RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(attempt_slapcraft)) + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(get_examine_info)) + RegisterSignal(parent, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(get_examine_more_info)) + RegisterSignal(parent, COMSIG_TOPIC, PROC_REF(topic_handler)) + + src.slapcraft_recipes += slapcraft_recipes + +/datum/component/slapcrafting/InheritComponent(datum/component/slapcrafting/new_comp, original, slapcraft_recipes) + if(!original) + return + src.slapcraft_recipes += slapcraft_recipes + +/datum/component/slapcrafting/Destroy(force, silent) + UnregisterSignal(parent, list(COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_EXAMINE, COMSIG_ATOM_EXAMINE_MORE)) + return ..() + +/datum/component/slapcrafting/proc/attempt_slapcraft(obj/item/parent_item, obj/item/slapper, mob/user) + + if(isnull(slapcraft_recipes)) + CRASH("NULL SLAPCRAFT RECIPES?") + + var/datum/component/personal_crafting/craft_sheet = user.GetComponent(/datum/component/personal_crafting) + if(!craft_sheet) + CRASH("No craft sheet on user ??") + + var/list/valid_recipes + for(var/datum/crafting_recipe/recipe as anything in slapcraft_recipes) + // Gotta instance it to copy the list over. + recipe = new recipe() + var/list/type_ingredient_list = recipe.reqs + qdel(recipe) + if(length(type_ingredient_list) == 1) // No ingredients besides itself? We use one of the tools then + type_ingredient_list = recipe.tool_paths + // Check the tool behaviours differently as they aren't types + for(var/behaviour in initial(recipe.tool_behaviors)) + if(slapper.tool_behaviour == behaviour) + LAZYADD(valid_recipes, recipe) + break + if(is_type_in_list(slapper, type_ingredient_list)) + LAZYADD(valid_recipes, recipe) + + if(!valid_recipes) + return + + // We might use radials so we need to split the proc chain + INVOKE_ASYNC(src, PROC_REF(slapcraft_async), valid_recipes, user, craft_sheet) + +/datum/component/slapcrafting/proc/slapcraft_async(list/valid_recipes, mob/user, datum/component/personal_crafting/craft_sheet) + + var/list/recipe_choices = list() + + var/list/result_to_recipe = list() + + var/final_recipe = valid_recipes[1] + var/string_chosen_recipe + if(length(valid_recipes) > 1) + for(var/datum/crafting_recipe/recipe as anything in valid_recipes) + var/atom/recipe_result = initial(recipe.result) + result_to_recipe[initial(recipe_result.name)] = recipe + recipe_choices += list("[initial(recipe_result.name)]" = image(icon = initial(recipe_result.icon), icon_state = initial(recipe_result.icon_state))) + + if(!recipe_choices) + CRASH("No recipe choices despite validating in earlier proc") + + string_chosen_recipe = show_radial_menu(user, parent, recipe_choices, require_near = TRUE) + if(isnull(string_chosen_recipe)) + return // they closed the thing + + if(string_chosen_recipe) + final_recipe = result_to_recipe[string_chosen_recipe] + + + var/datum/crafting_recipe/actual_recipe = final_recipe + + if(istype(actual_recipe, /datum/crafting_recipe/food)) + actual_recipe = locate(final_recipe) in GLOB.cooking_recipes + else + actual_recipe = locate(final_recipe) in GLOB.crafting_recipes + + if(!actual_recipe) + CRASH("Recipe not located in cooking or crafting recipes: [final_recipe]") + + var/atom/final_result = initial(actual_recipe.result) + + to_chat(user, span_notice("You start crafting \a [initial(final_result.name)]...")) + + var/error_string = craft_sheet.construct_item(user, actual_recipe) + + if(!isatom(error_string)) + to_chat(user, span_warning("crafting failed" + error_string)) + +/// Alerts any examiners to the recipe, if they wish to know more. +/datum/component/slapcrafting/proc/get_examine_info(atom/source, mob/user, list/examine_list) + SIGNAL_HANDLER + + var/list/string_results = list() + // This list saves the recipe result names we've already used to cross-check other recipes so we don't have ', a spear, or a spear!' in the desc. + var/list/already_used_names + for(var/datum/crafting_recipe/recipe as anything in slapcraft_recipes) + // Identical name to a previous recipe's result? Skip in description. + var/atom/result = initial(recipe.result) + if(locate(initial(result.name)) in already_used_names) + continue + already_used_names += initial(result.name) + string_results += list("\a [initial(result.name)]") + + examine_list += span_notice("You think [parent] could be used to make [english_list(string_results)]! Examine again to look at the details...") + +/// Alerts any examiners to the details of the recipe. +/datum/component/slapcrafting/proc/get_examine_more_info(atom/source, mob/user, list/examine_list) + SIGNAL_HANDLER + + for(var/datum/crafting_recipe/recipe as anything in slapcraft_recipes) + var/atom/result = initial(recipe.result) + examine_list += "See Recipe For [initial(result.name)]" + +/datum/component/slapcrafting/proc/topic_handler(atom/source, user, href_list) + SIGNAL_HANDLER + + if(!href_list["check_recipe"]) + return + + var/datum/crafting_recipe/cur_recipe = locate(href_list["check_recipe"]) in slapcraft_recipes + + if(isnull(cur_recipe)) + CRASH("null recipe!") + + var/atom/result = initial(cur_recipe.result) + + to_chat(user, span_notice("You could craft \a [initial(result.name)] by applying one of these items to it!")) + + // Gotta instance it to copy the lists over. + cur_recipe = new cur_recipe() + var/list/type_ingredient_list = cur_recipe.reqs + + // Final return string. + var/string_ingredient_list = "" + + // Check the ingredients of the crafting recipe. + for(var/valid_type in type_ingredient_list) + // Check if they're datums, specifically reagents. + var/datum/reagent/reagent_ingredient = valid_type + if(istype(reagent_ingredient)) + var/amount = initial(cur_recipe.reqs[reagent_ingredient]) + string_ingredient_list += "[amount] unit[amount > 1 ? "s" : ""] of [initial(reagent_ingredient.name)]\n" + + // Redundant! + if(parent.type == valid_type) + continue + var/atom/ingredient = valid_type + var/amount = initial(cur_recipe.reqs[ingredient]) + string_ingredient_list += "[amount > 1 ? ("[amount]" + " of") : "a"] [initial(ingredient.name)]\n" + + // If we did find ingredients then add them onto the list. + if(length(string_ingredient_list)) + to_chat(user, span_boldnotice("Ingredients:")) + to_chat(user, examine_block(span_notice(string_ingredient_list))) + + var/list/tool_list = "" + + // Paste the required tools. + for(var/valid_type in cur_recipe.tool_paths) + var/atom/tool = valid_type + tool_list += "\a [initial(tool.name)]\n" + + for(var/string in cur_recipe.tool_behaviors) + tool_list += "\a [string]\n" + + if(length(tool_list)) + to_chat(user, span_boldnotice("Required Tools:")) + to_chat(user, examine_block(span_notice(tool_list))) + + qdel(cur_recipe) + diff --git a/code/datums/components/death_linked.dm b/code/datums/components/death_linked.dm new file mode 100644 index 00000000000..59d2ce5e855 --- /dev/null +++ b/code/datums/components/death_linked.dm @@ -0,0 +1,30 @@ +/** + * ## Death link component + * + * When the owner of this component dies it also gibs a linked mob + */ +/datum/component/death_linked + ///The mob that also dies when the user dies + var/datum/weakref/linked_mob + +/datum/component/death_linked/Initialize(mob/living/target_mob) + . = ..() + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + if(isnull(target_mob)) + stack_trace("[type] added to [parent] with no linked mob.") + src.linked_mob = WEAKREF(target_mob) + +/datum/component/death_linked/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + +/datum/component/death_linked/UnregisterFromParent() + . = ..() + UnregisterSignal(parent, COMSIG_LIVING_DEATH) + +///signal called by the stat of the target changing +/datum/component/death_linked/proc/on_death(mob/living/target, gibbed) + SIGNAL_HANDLER + var/mob/living/linked_mob_resolved = linked_mob?.resolve() + linked_mob_resolved?.gib() diff --git a/code/datums/components/food/ghost_edible.dm b/code/datums/components/food/ghost_edible.dm new file mode 100644 index 00000000000..25207800a74 --- /dev/null +++ b/code/datums/components/food/ghost_edible.dm @@ -0,0 +1,59 @@ +/** + * Allows ghosts to eat this by orbiting it + * They do this by consuming the reagents in the object, so if it doesn't have any then it won't work + */ +/datum/component/ghost_edible + /// Amount of reagents which will be consumed by each bite + var/bite_consumption + /// Chance per ghost that a bite will be taken + var/bite_chance + /// Minimum size the food will display as before being deleted + var/minimum_scale + /// How many reagents this had on initialisation, used to figure out how eaten we are + var/initial_reagent_volume = 0 + +/datum/component/ghost_edible/Initialize(bite_consumption = 3, bite_chance = 20, minimum_scale = 0.6) + . = ..() + if (!isatom(parent)) + return COMPONENT_INCOMPATIBLE + var/atom/atom_parent = parent + if (isnull(atom_parent.reagents) || atom_parent.reagents.total_volume == 0) + return COMPONENT_INCOMPATIBLE + src.bite_consumption = bite_consumption + src.bite_chance = bite_chance + src.minimum_scale = minimum_scale + initial_reagent_volume = atom_parent.reagents.total_volume + notify_ghosts("[parent] is edible by ghosts!", source = parent, action = NOTIFY_ORBIT, header="Something Tasty!") + +/datum/component/ghost_edible/RegisterWithParent() + START_PROCESSING(SSdcs, src) + +/datum/component/ghost_edible/UnregisterFromParent() + STOP_PROCESSING(SSdcs, src) + +/datum/component/ghost_edible/Destroy(force, silent) + STOP_PROCESSING(SSdcs, src) + return ..() + +/datum/component/ghost_edible/process(seconds_per_tick) + var/atom/atom_parent = parent + // Ghosts can eat this burger + var/munch_chance = 0 + for(var/mob/dead/observer/ghost in atom_parent.orbiters?.orbiter_list) + munch_chance += bite_chance + if (munch_chance >= 100) + break + if (!prob(munch_chance)) + return + playsound(atom_parent.loc,'sound/items/eatfood.ogg', vol = rand(10,50), vary = TRUE) + atom_parent.reagents.remove_any(bite_consumption) + if (atom_parent.reagents.total_volume <= 0) + atom_parent.visible_message(span_notice("[atom_parent] disappears completely!")) + new /obj/item/ectoplasm(atom_parent.loc) + qdel(parent) + return + + var/final_transform = matrix().Scale(LERP(minimum_scale, 1, atom_parent.reagents.total_volume / initial_reagent_volume)) + var/animate_transform = matrix(final_transform).Scale(0.8) + animate(parent, transform = animate_transform, time = 0.1 SECONDS) + animate(transform = final_transform, time = 0.1 SECONDS) diff --git a/code/datums/components/ling_decoy_brain.dm b/code/datums/components/ling_decoy_brain.dm new file mode 100644 index 00000000000..7bcb4e38c8f --- /dev/null +++ b/code/datums/components/ling_decoy_brain.dm @@ -0,0 +1,68 @@ +/// Component applied to ling brains to make them into decoy brains, as ling brains are vestigial and don't do anything +/datum/component/ling_decoy_brain + /// The ling this brain is linked to + VAR_FINAL/datum/antagonist/changeling/parent_ling + /// A talk action that is granted to the ling when this decoy enters an MMI + VAR_FINAL/datum/action/changeling/mmi_talk/talk_action + +/datum/component/ling_decoy_brain/Initialize(datum/antagonist/changeling/ling) + if(!istype(parent, /obj/item/organ/internal/brain)) + return COMPONENT_INCOMPATIBLE + if(isnull(ling)) + stack_trace("[type] instantiated without a changeling to link to.") + return COMPONENT_INCOMPATIBLE + + parent_ling = ling + RegisterSignal(parent_ling, COMSIG_QDELETING, PROC_REF(clear_decoy)) + +/datum/component/ling_decoy_brain/Destroy() + UnregisterSignal(parent_ling, COMSIG_QDELETING) + parent_ling = null + QDEL_NULL(talk_action) + return ..() + +/datum/component/ling_decoy_brain/RegisterWithParent() + var/obj/item/organ/internal/brain/ling_brain = parent + ling_brain.organ_flags &= ~ORGAN_VITAL + ling_brain.decoy_override = TRUE + RegisterSignal(ling_brain, COMSIG_ATOM_ENTERING, PROC_REF(entered_mmi)) + +/datum/component/ling_decoy_brain/UnregisterFromParent() + var/obj/item/organ/internal/brain/ling_brain = parent + ling_brain.organ_flags |= ORGAN_VITAL + ling_brain.decoy_override = FALSE + UnregisterSignal(ling_brain, COMSIG_ATOM_ENTERING, PROC_REF(entered_mmi)) + +/** + * Signal proc for [COMSIG_ATOM_ENTERING], when the brain enters an MMI grant the MMI talk action to the ling + * + * Unfortunately this is hooked on Entering rather than its own dedicated MMI signal becuase MMI code is a fuck + */ +/datum/component/ling_decoy_brain/proc/entered_mmi(obj/item/organ/internal/brain/source, atom/entering, atom/old_loc, ...) + SIGNAL_HANDLER + + var/mob/living/the_real_ling = parent_ling.owner.current + if(!istype(the_real_ling)) + return + + if(istype(source.loc, /obj/item/mmi) && talk_action?.owner != the_real_ling) + if(isnull(talk_action)) + talk_action = new() // Not linked to anything, we manage the reference (and don't want it disappearing on us) + talk_action.brain_ref = source + + if(the_real_ling.key) + to_chat(the_real_ling, span_ghostalert("We detect our decoy brain has been placed within a Man-Machine Interface. \ + We can use the \"MMI Talk\" action to command it to speak.")) + else + the_real_ling.notify_ghost_cloning("Your decoy brain has been placed in an MMI, re-enter your body to talk via it!", source = the_real_ling, flashwindow = TRUE) + talk_action.Grant(the_real_ling) + + else if(talk_action?.owner == the_real_ling) + to_chat(the_real_ling, span_ghostalert("We can no longer detect our decoy brain.")) + talk_action.Remove(the_real_ling) + +/// Clear up the decoy if the ling is de-linged +/datum/component/ling_decoy_brain/proc/clear_decoy(datum/source) + SIGNAL_HANDLER + + qdel(src) diff --git a/code/datums/components/magnet.dm b/code/datums/components/magnet.dm new file mode 100644 index 00000000000..5c78b8665ce --- /dev/null +++ b/code/datums/components/magnet.dm @@ -0,0 +1,70 @@ +/// Attracts items of a certain typepath +/datum/component/magnet + /// Range at which to pull items + var/pull_range + /// List of things we attract + var/list/attracted_typecache + /// What to do when we pull something + var/datum/callback/on_pulled + /// What to do when something reaches us + var/datum/callback/on_contact + /// Are we currently working? + var/active = TRUE + +/datum/component/magnet/Initialize( + pull_range = 5, + attracted_typecache = list(/obj/item/kitchen/spoon, /obj/item/kitchen/fork, /obj/item/knife), + on_pulled, + on_contact, +) + . = ..() + if (!length(attracted_typecache)) + CRASH("Attempted to instantiate a [src] on [parent] which does not do anything.") + if (!isatom(parent)) + return COMPONENT_INCOMPATIBLE + + src.pull_range = pull_range + src.attracted_typecache = typecacheof(attracted_typecache) + src.on_pulled = on_pulled + src.on_contact = on_contact + +/datum/component/magnet/RegisterWithParent() + . = ..() + START_PROCESSING(SSdcs, src) + if (!isliving(parent)) + return + RegisterSignal(parent, COMSIG_MOB_STATCHANGE, PROC_REF(toggle_on_stat_change)) + +/datum/component/magnet/UnregisterFromParent() + . = ..() + STOP_PROCESSING(SSdcs, src) + UnregisterSignal(parent, COMSIG_MOB_STATCHANGE) + +/datum/component/magnet/Destroy(force, silent) + STOP_PROCESSING(SSdcs, src) + on_pulled = null + on_contact = null + return ..() + +/// If a mob dies we stop attracting stuff +/datum/component/magnet/proc/toggle_on_stat_change(mob/living/source) + SIGNAL_HANDLER + if (source.stat == DEAD) + STOP_PROCESSING(SSdcs, src) + else + START_PROCESSING(SSdcs, src) + +/datum/component/magnet/process(seconds_per_tick) + for (var/atom/movable/thing in orange(pull_range, parent)) + if (!is_type_in_typecache(thing, attracted_typecache)) + continue + var/range = get_dist(thing, parent) + if (range == 0) + continue + if (range == 1 && !isnull(on_contact)) + on_contact.Invoke(thing) + continue + var/moved = thing.Move(get_step_towards(thing, parent)) + if (moved && !isnull(on_pulled)) + on_pulled.Invoke(thing) + CHECK_TICK diff --git a/code/datums/components/ranged_attacks.dm b/code/datums/components/ranged_attacks.dm new file mode 100644 index 00000000000..f75d29a10f4 --- /dev/null +++ b/code/datums/components/ranged_attacks.dm @@ -0,0 +1,88 @@ +/** + * Configurable ranged attack for basic mobs. + */ +/datum/component/ranged_attacks + /// What kind of casing do we use to fire? + var/casing_type + /// What kind of projectile to we fire? Use only one of this or casing_type + var/projectile_type + /// Sound to play when we fire our projectile + var/projectile_sound + /// how many shots we will fire + var/burst_shots + /// intervals between shots + var/burst_intervals + /// Time to wait between shots + var/cooldown_time + /// Tracks time between shots + COOLDOWN_DECLARE(fire_cooldown) + +/datum/component/ranged_attacks/Initialize( + casing_type, + projectile_type, + projectile_sound = 'sound/weapons/gun/pistol/shot.ogg', + burst_shots, + burst_intervals = 0.2 SECONDS, + cooldown_time = 3 SECONDS, +) + . = ..() + if(!isbasicmob(parent)) + return COMPONENT_INCOMPATIBLE + + src.casing_type = casing_type + src.projectile_sound = projectile_sound + src.projectile_type = projectile_type + src.cooldown_time = cooldown_time + + if (casing_type && projectile_type) + CRASH("Set both casing type and projectile type in [parent]'s ranged attacks component! uhoh! stinky!") + if (!casing_type && !projectile_type) + CRASH("Set neither casing type nor projectile type in [parent]'s ranged attacks component! What are they supposed to be attacking with, air?") + if(burst_shots <= 1) + return + src.burst_shots = burst_shots + src.burst_intervals = burst_intervals + +/datum/component/ranged_attacks/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_MOB_ATTACK_RANGED, PROC_REF(fire_ranged_attack)) + ADD_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type) + +/datum/component/ranged_attacks/UnregisterFromParent() + . = ..() + UnregisterSignal(parent, COMSIG_MOB_ATTACK_RANGED) + REMOVE_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type) + +/datum/component/ranged_attacks/proc/fire_ranged_attack(mob/living/basic/firer, atom/target, modifiers) + SIGNAL_HANDLER + if (!COOLDOWN_FINISHED(src, fire_cooldown)) + return + COOLDOWN_START(src, fire_cooldown, cooldown_time) + INVOKE_ASYNC(src, PROC_REF(async_fire_ranged_attack), firer, target, modifiers) + if(isnull(burst_shots)) + return + for(var/i in 1 to (burst_shots - 1)) + addtimer(CALLBACK(src, PROC_REF(async_fire_ranged_attack), firer, target, modifiers), i * burst_intervals) + +/// Actually fire the damn thing +/datum/component/ranged_attacks/proc/async_fire_ranged_attack(mob/living/basic/firer, atom/target, modifiers) + firer.face_atom(target) + if(projectile_type) + firer.fire_projectile(projectile_type, target, projectile_sound) + SEND_SIGNAL(parent, COMSIG_BASICMOB_POST_ATTACK_RANGED, target, modifiers) + return + playsound(firer, projectile_sound, 100, TRUE) + var/turf/startloc = get_turf(firer) + var/obj/item/ammo_casing/casing = new casing_type(startloc) + var/target_zone + if(ismob(target)) + var/mob/target_mob = target + target_zone = target_mob.get_random_valid_zone() + else + 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) + SEND_SIGNAL(parent, COMSIG_BASICMOB_POST_ATTACK_RANGED, target, modifiers) + return + diff --git a/code/datums/components/seethrough_mob.dm b/code/datums/components/seethrough_mob.dm new file mode 100644 index 00000000000..b52cfb334ab --- /dev/null +++ b/code/datums/components/seethrough_mob.dm @@ -0,0 +1,135 @@ +///A component that lets you turn your character transparent in order to see and click through yourself. +/datum/component/seethrough_mob + ///The atom that enables our dark magic + var/atom/movable/render_source_atom + ///The fake version of ourselves + var/image/trickery_image + ///Which alpha do we animate towards? + var/target_alpha + ///How long our faze in/out takes + var/animation_time + ///Does this object let clicks from players its transparent to pass through it + var/clickthrough + ///Is the seethrough effect currently active + var/is_active + ///The mob's original render_target value + var/initial_render_target_value + ///This component's personal uid + var/personal_uid + +/datum/component/seethrough_mob/Initialize(target_alpha = 100, animation_time = 0.5 SECONDS, clickthrough = TRUE) + . = ..() + + if(!ismob(parent)) + return COMPONENT_INCOMPATIBLE + + src.target_alpha = target_alpha + src.animation_time = animation_time + src.clickthrough = clickthrough + src.is_active = FALSE + src.render_source_atom = new() + + var/static/uid = 0 + uid++ + src.personal_uid = uid + + render_source_atom.appearance_flags |= ( RESET_COLOR | RESET_TRANSFORM) + + render_source_atom.vis_flags |= (VIS_INHERIT_ID | VIS_INHERIT_PLANE | VIS_INHERIT_LAYER) + + render_source_atom.render_source = "*transparent_bigmob[personal_uid]" + + var/datum/action/toggle_seethrough/action = new(src) + action.Grant(parent) + +/datum/component/seethrough_mob/Destroy(force, silent) + QDEL_NULL(render_source_atom) + return ..() + +///Set up everything we need to trick the client and keep it looking normal for everyone else +/datum/component/seethrough_mob/proc/trick_mob() + SIGNAL_HANDLER + + var/mob/fool = parent + var/datum/hud/our_hud = fool.hud_used + for(var/atom/movable/screen/plane_master/seethrough as anything in our_hud.get_true_plane_masters(SEETHROUGH_PLANE)) + seethrough.unhide_plane(fool) + + var/icon/current_mob_icon = icon(fool.icon, fool.icon_state) + render_source_atom.pixel_x = -fool.pixel_x + render_source_atom.pixel_y = ((current_mob_icon.Height() - 32) * 0.5) + + initial_render_target_value = fool.render_target + fool.render_target = "*transparent_bigmob[personal_uid]" + fool.vis_contents.Add(render_source_atom) + + trickery_image = new(render_source_atom) + trickery_image.loc = render_source_atom + trickery_image.override = TRUE + + trickery_image.pixel_x = 0 + trickery_image.pixel_y = 0 + + if(clickthrough) + //Special plane so we can click through the overlay + SET_PLANE_EXPLICIT(trickery_image, SEETHROUGH_PLANE, fool) + + fool.client.images += trickery_image + + animate(trickery_image, alpha = target_alpha, time = animation_time) + + RegisterSignal(fool, COMSIG_MOB_LOGOUT, PROC_REF(on_client_disconnect)) + +///Remove the screen object and make us appear solid to ourselves again +/datum/component/seethrough_mob/proc/untrick_mob() + var/mob/fool = parent + animate(trickery_image, alpha = 255, time = animation_time) + UnregisterSignal(fool, COMSIG_MOB_LOGOUT) + + //after playing the fade-in animation, remove the image and the trick atom + addtimer(CALLBACK(src, PROC_REF(clear_image), trickery_image, fool.client), animation_time) + +///Remove the image and the trick atom +/datum/component/seethrough_mob/proc/clear_image(image/removee, client/remove_from) + var/atom/movable/atom_parent = parent + atom_parent.vis_contents -= render_source_atom + atom_parent.render_target = initial_render_target_value + remove_from?.images -= removee + +///Effect is disabled when they log out because client gets deleted +/datum/component/seethrough_mob/proc/on_client_disconnect() + SIGNAL_HANDLER + + var/mob/fool = parent + UnregisterSignal(fool, COMSIG_MOB_LOGOUT) + var/datum/hud/our_hud = fool.hud_used + for(var/atom/movable/screen/plane_master/seethrough as anything in our_hud.get_true_plane_masters(SEETHROUGH_PLANE)) + seethrough.hide_plane(fool) + clear_image(trickery_image, fool.client) + +/datum/component/seethrough_mob/proc/toggle_active() + is_active = !is_active + if(is_active) + trick_mob() + else + untrick_mob() + +/datum/action/toggle_seethrough + name = "Toggle Seethrough" + desc = "Allows you to see behind your massive body and click through it." + button_icon = 'icons/mob/actions/actions_xeno.dmi' + button_icon_state = "alien_sneak" + background_icon_state = "bg_alien" + +/datum/action/toggle_seethrough/Remove(mob/remove_from) + var/datum/component/seethrough_mob/seethroughComp = target + if(seethroughComp.is_active) + seethroughComp.untrick_mob() + return ..() + +/datum/action/toggle_seethrough/Trigger(trigger_flags) + . = ..() + if(!.) + return + var/datum/component/seethrough_mob/seethroughComp = target + seethroughComp.toggle_active() diff --git a/code/datums/components/telegraph_ability.dm b/code/datums/components/telegraph_ability.dm new file mode 100644 index 00000000000..bff2ea7ea8f --- /dev/null +++ b/code/datums/components/telegraph_ability.dm @@ -0,0 +1,50 @@ +/** + * Component given to creatures to telegraph their abilities! + */ +/datum/component/basic_mob_ability_telegraph + /// how long before we use our attack + var/telegraph_time + /// sound to play, if any + var/sound_path + /// are we currently telegraphing + var/currently_telegraphing = FALSE + +/datum/component/basic_mob_ability_telegraph/Initialize(telegraph_time = 1 SECONDS, sound_path) + + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + src.telegraph_time = telegraph_time + src.sound_path = sound_path + +/datum/component/basic_mob_ability_telegraph/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOB_ABILITY_STARTED, PROC_REF(on_ability_activate)) + +/datum/component/basic_mob_ability_telegraph/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_MOB_ABILITY_STARTED) + +///delay the ability +/datum/component/basic_mob_ability_telegraph/proc/on_ability_activate(mob/living/source, datum/action/cooldown/activated, atom/target) + SIGNAL_HANDLER + + if(currently_telegraphing) + return COMPONENT_BLOCK_ABILITY_START + + if(!activated.IsAvailable()) + return + + currently_telegraphing = TRUE + generate_tell_signs(source) + addtimer(CALLBACK(src, PROC_REF(use_ability), source, activated, target), telegraph_time) + return COMPONENT_BLOCK_ABILITY_START + +///generates the telegraph signs to inform the player we're about to launch an attack +/datum/component/basic_mob_ability_telegraph/proc/generate_tell_signs(mob/living/source) + if(sound_path) + playsound(source, sound_path, 50, FALSE) + source.Shake(duration = telegraph_time) + +///use the ability +/datum/component/basic_mob_ability_telegraph/proc/use_ability(mob/living/source, datum/action/cooldown/activated, atom/target) + if(!QDELETED(target) && source.stat != DEAD) //target is gone or we died + activated.Activate(target) + currently_telegraphing = FALSE diff --git a/code/datums/components/wall_mounted.dm b/code/datums/components/wall_mounted.dm new file mode 100644 index 00000000000..8d1722f89fe --- /dev/null +++ b/code/datums/components/wall_mounted.dm @@ -0,0 +1,86 @@ +// This element should be applied to wall-mounted machines/structures, so that if the wall it's "hanging" from is broken or deconstructed, the wall-hung structure will deconstruct. +/datum/component/wall_mounted + dupe_mode = COMPONENT_DUPE_ALLOWED + /// The wall our object is currently linked to. + var/turf/hanging_wall_turf + /// Callback to the parent's proc to call on the linked object when the wall disappear's or changes. + var/datum/callback/on_drop + +/datum/component/wall_mounted/Initialize(target_wall, on_drop_callback) + . = ..() + if(!isobj(parent)) + return COMPONENT_INCOMPATIBLE + if(!isturf(target_wall)) + return COMPONENT_INCOMPATIBLE + hanging_wall_turf = target_wall + on_drop = on_drop_callback + +/datum/component/wall_mounted/RegisterWithParent() + RegisterSignal(hanging_wall_turf, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + RegisterSignal(hanging_wall_turf, COMSIG_TURF_CHANGE, PROC_REF(on_turf_changing)) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(drop_wallmount)) + RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_linked_destroyed)) + +/datum/component/wall_mounted/UnregisterFromParent() + UnregisterSignal(hanging_wall_turf, list(COMSIG_ATOM_EXAMINE, COMSIG_TURF_CHANGE)) + UnregisterSignal(parent, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED)) + hanging_wall_turf = null + +/** + * Basic reference handling if the hanging/linked object is destroyed first. + */ +/datum/component/wall_mounted/proc/on_linked_destroyed() + SIGNAL_HANDLER + if(!QDELING(src)) + qdel(src) + +/** + * When the wall is examined, explains that it's supporting the linked object. + */ +/datum/component/wall_mounted/proc/on_examine(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER + examine_list += span_notice("\The [hanging_wall_turf] is currently supporting [span_bold("[parent]")]. Deconstruction or excessive damage would cause it to [span_bold("fall to the ground")].") + +/** + * When the type of turf changes, if it is changing into a floor we should drop our contents + */ +/datum/component/wall_mounted/proc/on_turf_changing(datum/source, path, new_baseturfs, flags, post_change_callbacks) + SIGNAL_HANDLER + if (ispath(path, /turf/open)) + drop_wallmount() + +/** + * Handles the dropping of the linked object. This is done via deconstruction, as that should be the most sane way to handle it for most objects. + * Except for intercoms, which are handled by creating a new wallframe intercom, as they're apparently items. + */ +/datum/component/wall_mounted/proc/drop_wallmount() + SIGNAL_HANDLER + var/obj/hanging_parent = parent + + if(on_drop) + hanging_parent.visible_message(message = span_warning("\The [hanging_parent] falls off the wall!"), vision_distance = 5) + on_drop.Invoke(hanging_parent) + else + hanging_parent.visible_message(message = span_warning("\The [hanging_parent] falls apart!"), vision_distance = 5) + hanging_parent.deconstruct() + + if(!QDELING(src)) + qdel(src) //Well, we fell off the wall, so we're done here. +/** + * Checks object direction and then verifies if there's a wall in that direction. Finally, applies a wall_mounted component to the object. + * + * @param directional If TRUE, will use the direction of the object to determine the wall to attach to. If FALSE, will use the object's loc. + * @param custom_drop_callback If set, will use this callback instead of the default deconstruct callback. + */ +/obj/proc/find_and_hang_on_wall(directional = TRUE, custom_drop_callback) + if(istype(get_area(src), /area/shuttle)) + return FALSE //For now, we're going to keep the component off of shuttles to avoid the turf changing issue. We'll hit that later really; + var/turf/attachable_wall + if(directional) + attachable_wall = get_step(src, dir) + else + attachable_wall = loc ///Pull from the curent object loc + if(!iswallturf(attachable_wall)) + return FALSE//Nothing to latch onto, or not the right thing. + src.AddComponent(/datum/component/wall_mounted, attachable_wall, custom_drop_callback) + return TRUE diff --git a/code/datums/components/weatherannouncer.dm b/code/datums/components/weatherannouncer.dm new file mode 100644 index 00000000000..fec31ccf9d3 --- /dev/null +++ b/code/datums/components/weatherannouncer.dm @@ -0,0 +1,175 @@ +#define WEATHER_ALERT_CLEAR 0 +#define WEATHER_ALERT_INCOMING 1 +#define WEATHER_ALERT_IMMINENT_OR_ACTIVE 2 + +/// Component which makes you yell about what the weather is +/datum/component/weather_announcer + /// Currently displayed warning level + var/warning_level = WEATHER_ALERT_CLEAR + /// Whether the incoming weather is actually going to harm you + var/is_weather_dangerous = TRUE + /// Are we actually turned on right now? + var/enabled = TRUE + /// Overlay added when things are alright + var/state_normal + /// Overlay added when you should start looking for shelter + var/state_warning + /// Overlay added when you are in danger + var/state_danger + +/datum/component/weather_announcer/Initialize( + state_normal, + state_warning, + state_danger, +) + . = ..() + if (!ismovable(parent)) + return COMPONENT_INCOMPATIBLE + + START_PROCESSING(SSprocessing, src) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays)) + RegisterSignal(parent, COMSIG_MACHINERY_POWER_RESTORED, PROC_REF(on_powered)) + RegisterSignal(parent, COMSIG_MACHINERY_POWER_LOST, PROC_REF(on_power_lost)) + + src.state_normal = state_normal + src.state_warning = state_warning + src.state_danger = state_danger + var/atom/speaker = parent + speaker.update_appearance(UPDATE_ICON) + update_light_color() + +/datum/component/weather_announcer/Destroy(force, silent) + STOP_PROCESSING(SSprocessing, src) + return ..() + +/// Add appropriate overlays +/datum/component/weather_announcer/proc/on_update_overlays(atom/parent_atom, list/overlays) + SIGNAL_HANDLER + if (!enabled || !state_normal || !state_warning || !state_danger) + return + + switch (warning_level) + if(WEATHER_ALERT_CLEAR) + overlays += state_normal + if(WEATHER_ALERT_INCOMING) + overlays += state_warning + if(WEATHER_ALERT_IMMINENT_OR_ACTIVE) + overlays += (is_weather_dangerous) ? state_danger : state_warning + +/// If powered, receive updates +/datum/component/weather_announcer/proc/on_powered() + SIGNAL_HANDLER + enabled = TRUE + var/atom/speaker = parent + speaker.update_appearance(UPDATE_ICON) + +/// If no power, don't receive updates +/datum/component/weather_announcer/proc/on_power_lost() + SIGNAL_HANDLER + enabled = FALSE + var/atom/speaker = parent + speaker.update_appearance(UPDATE_ICON) + +/datum/component/weather_announcer/process(seconds_per_tick) + if (!enabled) + return + + var/previous_level = warning_level + var/previous_danger = is_weather_dangerous + set_current_alert_level() + if(previous_level == warning_level && previous_danger == is_weather_dangerous) + return // No change + var/atom/movable/speaker = parent + speaker.say(get_warning_message()) + speaker.update_appearance(UPDATE_ICON) + update_light_color() + +/datum/component/weather_announcer/proc/update_light_color() + var/atom/movable/light = parent + switch(warning_level) + if(WEATHER_ALERT_CLEAR) + light.set_light_color(LIGHT_COLOR_GREEN) + if(WEATHER_ALERT_INCOMING) + light.set_light_color(LIGHT_COLOR_DIM_YELLOW) + if(WEATHER_ALERT_IMMINENT_OR_ACTIVE) + light.set_light_color(LIGHT_COLOR_INTENSE_RED) + light.update_light() + +/// Returns a string we should display to communicate what you should be doing +/datum/component/weather_announcer/proc/get_warning_message() + if (!is_weather_dangerous) + return "No risk expected from incoming weather front." + switch(warning_level) + if(WEATHER_ALERT_CLEAR) + return "All clear, no weather alerts to report." + if(WEATHER_ALERT_INCOMING) + return "Weather front incoming, begin to seek shelter." + if(WEATHER_ALERT_IMMINENT_OR_ACTIVE) + return "Weather front imminent, find shelter immediately." + return "Error in meteorological calculation. Please report this deviation to a trained programmer." + +/datum/component/weather_announcer/proc/time_till_storm() + var/list/mining_z_levels = SSmapping.levels_by_trait(ZTRAIT_MINING) + if(!length(mining_z_levels)) + return // No problems if there are no mining z levels + + + for(var/datum/weather/check_weather as anything in SSweather.processing) + if(!check_weather.barometer_predictable || check_weather.stage == WIND_DOWN_STAGE || check_weather.stage == END_STAGE) + continue + for (var/mining_level in mining_z_levels) + if(mining_level in check_weather.impacted_z_levels) + warning_level = WEATHER_ALERT_IMMINENT_OR_ACTIVE + return 0 + + var/time_until_next = INFINITY + for(var/mining_level in mining_z_levels) + var/next_time = timeleft(SSweather.next_hit_by_zlevel["[mining_level ]"]) || INFINITY + if (next_time && next_time < time_until_next) + time_until_next = next_time + return time_until_next + +/// Polls existing weather for what kind of warnings we should be displaying. +/datum/component/weather_announcer/proc/set_current_alert_level() + var/time_until_next = time_till_storm() + if(isnull(time_until_next)) + return // No problems if there are no mining z levels + if(time_until_next >= 2 MINUTES) + warning_level = WEATHER_ALERT_CLEAR + return + + if(time_until_next >= 30 SECONDS) + warning_level = WEATHER_ALERT_INCOMING + return + + // Weather is here, now we need to figure out if it is dangerous + warning_level = WEATHER_ALERT_IMMINENT_OR_ACTIVE + + for(var/datum/weather/check_weather as anything in SSweather.processing) + if(!check_weather.barometer_predictable || check_weather.stage == WIND_DOWN_STAGE || check_weather.stage == END_STAGE) + continue + var/list/mining_z_levels = SSmapping.levels_by_trait(ZTRAIT_MINING) + for(var/mining_level in mining_z_levels) + if(mining_level in check_weather.impacted_z_levels) + is_weather_dangerous = !check_weather.aesthetic + return + +/datum/component/weather_announcer/proc/on_examine(atom/radio, mob/examiner, list/examine_texts) + var/time_until_next = time_till_storm() + if(isnull(time_until_next)) + return + if (time_until_next == 0) + examine_texts += span_warning ("A storm is currently active, please seek shelter.") + else + examine_texts += span_notice("The next storm is inbound in [DisplayTimeText(time_until_next)].") + +/datum/component/weather_announcer/RegisterWithParent() + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + +/datum/component/weather_announcer/UnregisterFromParent() + .=..() + UnregisterSignal(parent, COMSIG_ATOM_EXAMINE) + +#undef WEATHER_ALERT_CLEAR +#undef WEATHER_ALERT_INCOMING +#undef WEATHER_ALERT_IMMINENT_OR_ACTIVE diff --git a/code/datums/elements/bombable_turf.dm b/code/datums/elements/bombable_turf.dm new file mode 100644 index 00000000000..11a83c79340 --- /dev/null +++ b/code/datums/elements/bombable_turf.dm @@ -0,0 +1,45 @@ +/** + * Apply this to a turf (usually a wall) and it will be destroyed instantly by any explosion. + * Most walls can already be destroyed by explosions so this is largely for usually indestructible ones. + * For applying it in a map editor, use /obj/effect/mapping_helpers/bombable_wall + */ +/datum/element/bombable_turf + +/datum/element/bombable_turf/Attach(turf/target) + . = ..() + if(!isturf(target)) + return ELEMENT_INCOMPATIBLE + target.explosive_resistance = 1 + + RegisterSignal(target, COMSIG_ATOM_EX_ACT, PROC_REF(detonate)) + RegisterSignal(target, COMSIG_TURF_CHANGE, PROC_REF(turf_changed)) + RegisterSignal(target, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays)) + RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined)) + + target.update_appearance(UPDATE_OVERLAYS) + +/datum/element/bombable_turf/Detach(turf/source) + UnregisterSignal(source, list(COMSIG_ATOM_EX_ACT, COMSIG_TURF_CHANGE, COMSIG_ATOM_UPDATE_OVERLAYS, COMSIG_ATOM_EXAMINE)) + source.explosive_resistance = initial(source.explosive_resistance) + source.update_appearance(UPDATE_OVERLAYS) + return ..() + +/// If we get blowed up, move to the next turf +/datum/element/bombable_turf/proc/detonate(turf/source) + SIGNAL_HANDLER + source.ScrapeAway() + +/// If this turf becomes something else we either just went off or regardless don't want this any more +/datum/element/bombable_turf/proc/turf_changed(turf/source) + SIGNAL_HANDLER + Detach(source) + +/// Show a little crack on here +/datum/element/bombable_turf/proc/on_update_overlays(turf/source, list/overlays) + SIGNAL_HANDLER + overlays += mutable_appearance('icons/turf/overlays.dmi', "explodable", source.layer + 0.1) + +/// Show a little extra on examine +/datum/element/bombable_turf/proc/on_examined(turf/source, mob/user, list/examine_list) + SIGNAL_HANDLER + examine_list += span_notice("It seems to be slightly cracked...") diff --git a/code/datums/elements/bonus_damage.dm b/code/datums/elements/bonus_damage.dm new file mode 100644 index 00000000000..1fce0672c51 --- /dev/null +++ b/code/datums/elements/bonus_damage.dm @@ -0,0 +1,35 @@ +/** + * Attached to a mob that will then deal bonus damage to a victim with low, or potentially in the future, high health. + */ +/datum/element/bonus_damage + /// At which percentage our target has to be for us to deal bonus damage + var/damage_percentage + /// The amount of brute damage we will deal + var/brute_damage_amount + +/datum/element/bonus_damage/Attach(datum/target, damage_percentage = 20, brute_damage_amount = 15) + . = ..() + if(!isliving(target)) + return ELEMENT_INCOMPATIBLE + + src.damage_percentage = damage_percentage + src.brute_damage_amount = brute_damage_amount + RegisterSignal(target, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(attack_target)) + +/datum/element/bonus_damage/Detach(datum/source) + UnregisterSignal(source, COMSIG_HOSTILE_POST_ATTACKINGTARGET) + return ..() + +/// Add potential bonus damage to the person we attacked +/datum/element/bonus_damage/proc/attack_target(mob/living/attacker, atom/target, success) + SIGNAL_HANDLER + + if(!success) + return + if(!isliving(target)) + return + var/mob/living/living_target = target + var/health_percentage = (living_target.health / living_target.maxHealth) * 100 + if(living_target.stat == DEAD || health_percentage > damage_percentage) + return + living_target.adjustBruteLoss(brute_damage_amount) diff --git a/code/datums/elements/gags_recolorable.dm b/code/datums/elements/gags_recolorable.dm new file mode 100644 index 00000000000..bf37a4ba973 --- /dev/null +++ b/code/datums/elements/gags_recolorable.dm @@ -0,0 +1,62 @@ +///An element that lets players recolor the item through the greyscale menu with the help of a spraycan. +/datum/element/gags_recolorable + +/datum/element/gags_recolorable/Attach(datum/target) + . = ..() + if(!isatom(target)) + return ELEMENT_INCOMPATIBLE + RegisterSignal(target, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby)) + RegisterSignal(target, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_examine)) + +/datum/element/gags_recolorable/proc/on_examine(atom/source, mob/user, list/examine_text) + SIGNAL_HANDLER + examine_text += span_notice("You could recolor [source.p_them()] with a spraycan...") + +/datum/element/gags_recolorable/proc/on_attackby(datum/source, obj/item/attacking_item, mob/user) + SIGNAL_HANDLER + + if(!istype(attacking_item, /obj/item/toy/crayon/spraycan)) + return + var/obj/item/toy/crayon/spraycan/can = attacking_item + + if(can.is_capped || can.check_empty()) + return + + INVOKE_ASYNC(src, PROC_REF(open_ui), user, can, source) + return COMPONENT_NO_AFTERATTACK + +/datum/element/gags_recolorable/proc/open_ui(mob/user, obj/item/toy/crayon/spraycan/can, atom/target) + var/list/allowed_configs = list() + var/config = initial(target.greyscale_config) + if(!config) + return + allowed_configs += "[config]" + if(isitem(target)) + var/obj/item/item = target + if(initial(item.greyscale_config_worn)) + allowed_configs += "[initial(item.greyscale_config_worn)]" + if(initial(item.greyscale_config_inhand_left)) + allowed_configs += "[initial(item.greyscale_config_inhand_left)]" + if(initial(item.greyscale_config_inhand_right)) + allowed_configs += "[initial(item.greyscale_config_inhand_right)]" + + var/datum/greyscale_modify_menu/spray_paint/menu = new( + target, user, allowed_configs, CALLBACK(src, PROC_REF(recolor), user, can, target), + starting_icon_state = initial(target.icon_state), + starting_config = initial(target.greyscale_config), + starting_colors = target.greyscale_colors, + used_spraycan = can, + ) + menu.ui_interact(user) + +/datum/element/gags_recolorable/proc/recolor(mob/user, obj/item/toy/crayon/spraycan/can, atom/target, datum/greyscale_modify_menu/menu) + if(can.is_capped || can.check_empty(user)) + menu.ui_close() + return + + can.use_charges() + if(can.pre_noise) + target.audible_message(span_hear("You hear spraying.")) + playsound(target.loc, 'sound/effects/spray.ogg', 5, TRUE, 5) + + target.set_greyscale(menu.split_colors) diff --git a/code/datums/elements/mob_grabber.dm b/code/datums/elements/mob_grabber.dm new file mode 100644 index 00000000000..a85c5dc48b2 --- /dev/null +++ b/code/datums/elements/mob_grabber.dm @@ -0,0 +1,30 @@ +/// Grab onto mobs we attack +/datum/element/mob_grabber + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// What state must the mob be in to be grabbed? + var/minimum_stat + /// If someone else is already grabbing this, will we take it? + var/steal_from_others + +/datum/element/mob_grabber/Attach(datum/target, minimum_stat = SOFT_CRIT, steal_from_others = TRUE) + . = ..() + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + src.minimum_stat = minimum_stat + src.steal_from_others = steal_from_others + RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(grab_mob)) + +/datum/element/mob_grabber/Detach(datum/source) + UnregisterSignal(source, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET)) + . = ..() + +/// Try and grab something we attacked +/datum/element/mob_grabber/proc/grab_mob(mob/living/source, mob/living/target) + SIGNAL_HANDLER + if (!isliving(target) || !source.Adjacent(target) || target.stat < minimum_stat) + return + var/atom/currently_pulled = target.pulledby + if (!isnull(currently_pulled) && (!steal_from_others || currently_pulled == source)) + return + INVOKE_ASYNC(target, TYPE_PROC_REF(/mob/living, grabbedby), source) diff --git a/code/datums/elements/tear_wall.dm b/code/datums/elements/tear_wall.dm new file mode 100644 index 00000000000..0d24bbda289 --- /dev/null +++ b/code/datums/elements/tear_wall.dm @@ -0,0 +1,48 @@ +/** + * Attached to a basic mob that will then be able to tear down a wall after some time. + */ +/datum/element/tear_wall + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 3 + /// The rate at which we can break regular walls + var/regular_tear_time + /// The rate at which we can break reinforced walls + var/reinforced_tear_time + +/datum/element/tear_wall/Attach(datum/target, regular_tear_time = 2 SECONDS, reinforced_tear_time = 4 SECONDS) + . = ..() + if(!isbasicmob(target)) + return ELEMENT_INCOMPATIBLE + + src.regular_tear_time = regular_tear_time + src.reinforced_tear_time = reinforced_tear_time + RegisterSignal(target, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(attack_wall)) + +/datum/element/bonus_damage/Detach(datum/source) + UnregisterSignal(source, COMSIG_HOSTILE_POST_ATTACKINGTARGET) + return ..() + +/// Checks if we are attacking a wall +/datum/element/tear_wall/proc/attack_wall(mob/living/basic/attacker, atom/target, success) + SIGNAL_HANDLER + + if(!iswallturf(target)) + return + var/turf/closed/wall/thewall = target + var/prying_time = regular_tear_time + if(istype(thewall, /turf/closed/wall/r_wall)) + prying_time = reinforced_tear_time + INVOKE_ASYNC(src, PROC_REF(async_attack_wall), attacker, thewall, prying_time) + +/// Performs taking down the wall +/datum/element/tear_wall/proc/async_attack_wall(mob/living/basic/attacker, turf/closed/wall/thewall, prying_time) + if(DOING_INTERACTION_WITH_TARGET(attacker, thewall)) + attacker.balloon_alert(attacker, "busy!") + return + to_chat(attacker, span_warning("You begin tearing through the wall...")) + playsound(attacker, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) + if(do_after(attacker, prying_time, target = thewall)) + if(isopenturf(thewall)) + return + thewall.dismantle_wall(1) + playsound(attacker, 'sound/effects/meteorimpact.ogg', 100, TRUE) diff --git a/code/datums/greyscale/json_configs/buttondown_skirt.json b/code/datums/greyscale/json_configs/buttondown_skirt.json new file mode 100644 index 00000000000..60d3d500f28 --- /dev/null +++ b/code/datums/greyscale/json_configs/buttondown_skirt.json @@ -0,0 +1,28 @@ +{ + "buttondown_skirt": [ + { + "type": "icon_state", + "icon_state": "buttondown", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "buttondown_obj_buckle", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "buttondown_obj_belt", + "blend_mode": "overlay", + "color_ids": [ 3 ] + }, + { + "type": "icon_state", + "icon_state": "buttondown_obj_skirt", + "blend_mode": "overlay", + "color_ids": [ 4 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/buttondown_skirt_worn.json b/code/datums/greyscale/json_configs/buttondown_skirt_worn.json new file mode 100644 index 00000000000..e34a900caf4 --- /dev/null +++ b/code/datums/greyscale/json_configs/buttondown_skirt_worn.json @@ -0,0 +1,54 @@ +{ + "buttondown_skirt": [ + { + "type": "icon_state", + "icon_state": "buttondown", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "buckle", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "belt", + "blend_mode": "overlay", + "color_ids": [ 3 ] + }, + { + "type": "icon_state", + "icon_state": "skirt", + "blend_mode": "overlay", + "color_ids": [ 4 ] + } + ], + "buttondown_skirt_d": [ + { + "type": "icon_state", + "icon_state": "buttondown_d", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "buckle", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "belt", + "blend_mode": "overlay", + "color_ids": [ 3 ] + }, + { + "type": "icon_state", + "icon_state": "skirt", + "blend_mode": "overlay", + "color_ids": [ 4 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/glow_shoes.json b/code/datums/greyscale/json_configs/glow_shoes.json new file mode 100644 index 00000000000..d78fe5b7de0 --- /dev/null +++ b/code/datums/greyscale/json_configs/glow_shoes.json @@ -0,0 +1,16 @@ +{ + "glow_shoes": [ + { + "type": "icon_state", + "icon_state": "glow_shoes_back", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "glow_shoes_front", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/jacket_lawyer.json b/code/datums/greyscale/json_configs/jacket_lawyer.json new file mode 100644 index 00000000000..9593f9c90c3 --- /dev/null +++ b/code/datums/greyscale/json_configs/jacket_lawyer.json @@ -0,0 +1,18 @@ +{ + "jacket_lawyer": [ + { + "type": "icon_state", + "icon_state": "suitjacket", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ], + "jacket_lawyer_t": [ + { + "type": "icon_state", + "icon_state": "suitjacket_t", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/messyworn_shirt_graphic.json b/code/datums/greyscale/json_configs/messyworn_shirt_graphic.json new file mode 100644 index 00000000000..85950d388e2 --- /dev/null +++ b/code/datums/greyscale/json_configs/messyworn_shirt_graphic.json @@ -0,0 +1,40 @@ +{ + "messyworn_shirt_gamer": [ + { + "type": "icon_state", + "icon_state": "worn_messy", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "nerd_overlay", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "nerd_base", + "blend_mode": "overlay" + } + ], + "messyworn_shirt_ian": [ + { + "type": "icon_state", + "icon_state": "worn_messy", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "ian_overlay", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "ian_base", + "blend_mode": "overlay" + } + ] +} diff --git a/code/datums/greyscale/json_configs/overalls.json b/code/datums/greyscale/json_configs/overalls.json new file mode 100644 index 00000000000..c77da142d70 --- /dev/null +++ b/code/datums/greyscale/json_configs/overalls.json @@ -0,0 +1,10 @@ +{ + "overalls": [ + { + "type": "icon_state", + "icon_state": "overalls", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/wellworn_shirt.json b/code/datums/greyscale/json_configs/wellworn_shirt.json new file mode 100644 index 00000000000..d58335abc4c --- /dev/null +++ b/code/datums/greyscale/json_configs/wellworn_shirt.json @@ -0,0 +1,26 @@ +{ + "wellworn_shirt": [ + { + "type": "icon_state", + "icon_state": "worn_clean", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ], + "wornout_shirt": [ + { + "type": "icon_state", + "icon_state": "worn_out", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ], + "messyworn_shirt": [ + { + "type": "icon_state", + "icon_state": "worn_messy", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/wellworn_shirt_graphic.json b/code/datums/greyscale/json_configs/wellworn_shirt_graphic.json new file mode 100644 index 00000000000..a9226607f29 --- /dev/null +++ b/code/datums/greyscale/json_configs/wellworn_shirt_graphic.json @@ -0,0 +1,41 @@ +{ + "wellworn_shirt_gamer": [ + { + "type": "icon_state", + "icon_state": "worn_clean", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "nerd_overlay", + "blend_mode": "overlay", + "color_ids": [ 2 ] + + }, + { + "type": "icon_state", + "icon_state": "nerd_base", + "blend_mode": "overlay" + } + ], + "wellworn_shirt_ian": [ + { + "type": "icon_state", + "icon_state": "worn_clean", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "ian_overlay", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "ian_base", + "blend_mode": "overlay" + } + ] +} diff --git a/code/datums/greyscale/json_configs/wornout_shirt_graphic.json b/code/datums/greyscale/json_configs/wornout_shirt_graphic.json new file mode 100644 index 00000000000..6ca12b5db68 --- /dev/null +++ b/code/datums/greyscale/json_configs/wornout_shirt_graphic.json @@ -0,0 +1,40 @@ +{ + "wornout_shirt_gamer": [ + { + "type": "icon_state", + "icon_state": "worn_out", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "nerd_overlay", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "nerd_base", + "blend_mode": "overlay" + } + ], + "wornout_shirt_ian": [ + { + "type": "icon_state", + "icon_state": "worn_out", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "ian_overlay", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "ian_base", + "blend_mode": "overlay" + } + ] +} diff --git a/code/datums/mood_events/food_events.dm b/code/datums/mood_events/food_events.dm new file mode 100644 index 00000000000..7d2dcc439de --- /dev/null +++ b/code/datums/mood_events/food_events.dm @@ -0,0 +1,46 @@ +/datum/mood_event/favorite_food + description = "I really enjoyed eating that." + mood_change = 5 + timeout = 4 MINUTES + +/datum/mood_event/gross_food + description = "I really didn't like that food." + mood_change = -2 + timeout = 4 MINUTES + +/datum/mood_event/disgusting_food + description = "That food was disgusting!" + mood_change = -6 + timeout = 4 MINUTES + +/datum/mood_event/breakfast + description = "Nothing like a hearty breakfast to start the shift." + mood_change = 2 + timeout = 10 MINUTES + +/datum/mood_event/food + timeout = 5 MINUTES + var/quality = FOOD_QUALITY_NORMAL + +/datum/mood_event/food/New(mob/M, ...) + . = ..() + mood_change = 2 + 2 * quality + description = "That food was [GLOB.food_quality_description[quality]]." + +/datum/mood_event/food/nice + quality = FOOD_QUALITY_NICE + +/datum/mood_event/food/good + quality = FOOD_QUALITY_GOOD + +/datum/mood_event/food/verygood + quality = FOOD_QUALITY_VERYGOOD + +/datum/mood_event/food/fantastic + quality = FOOD_QUALITY_FANTASTIC + +/datum/mood_event/food/amazing + quality = FOOD_QUALITY_AMAZING + +/datum/mood_event/food/top + quality = FOOD_QUALITY_TOP diff --git a/code/datums/quirks/negative_quirks/allergic.dm b/code/datums/quirks/negative_quirks/allergic.dm new file mode 100644 index 00000000000..d6a510f62b6 --- /dev/null +++ b/code/datums/quirks/negative_quirks/allergic.dm @@ -0,0 +1,71 @@ +/datum/quirk/item_quirk/allergic + name = "Extreme Medicine Allergy" + desc = "Ever since you were a kid, you've been allergic to certain chemicals..." + icon = FA_ICON_PRESCRIPTION_BOTTLE + value = -6 + gain_text = span_danger("You feel your immune system shift.") + lose_text = span_notice("You feel your immune system phase back into perfect shape.") + medical_record_text = "Patient's immune system responds violently to certain chemicals." + hardcore_value = 3 + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_PROCESSES + mail_goodies = list(/obj/item/reagent_containers/hypospray/medipen) // epinephrine medipen stops allergic reactions + var/list/allergies = list() + var/list/blacklist = list( + /datum/reagent/medicine/c2, + /datum/reagent/medicine/epinephrine, + /datum/reagent/medicine/adminordrazine, + /datum/reagent/medicine/adminordrazine/quantum_heal, + /datum/reagent/medicine/omnizine/godblood, + /datum/reagent/medicine/cordiolis_hepatico, + /datum/reagent/medicine/synaphydramine, + /datum/reagent/medicine/diphenhydramine, + /datum/reagent/medicine/sansufentanyl + ) + var/allergy_string + +/datum/quirk/item_quirk/allergic/add_unique(client/client_source) + var/list/chem_list = subtypesof(/datum/reagent/medicine) - blacklist + var/list/allergy_chem_names = list() + for(var/i in 0 to 5) + var/datum/reagent/medicine/chem_type = pick_n_take(chem_list) + allergies += chem_type + allergy_chem_names += initial(chem_type.name) + + allergy_string = allergy_chem_names.Join(", ") + name = "Extreme [allergy_string] Allergies" + medical_record_text = "Patient's immune system responds violently to [allergy_string]" + + var/mob/living/carbon/human/human_holder = quirk_holder + var/obj/item/clothing/accessory/dogtag/allergy/dogtag = new(get_turf(human_holder), allergy_string) + + give_item_to_holder(dogtag, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS), flavour_text = "Make sure medical staff can see this...") + +/datum/quirk/item_quirk/allergic/post_add() + quirk_holder.add_mob_memory(/datum/memory/key/quirk_allergy, allergy_string = allergy_string) + to_chat(quirk_holder, span_boldnotice("You are allergic to [allergy_string], make sure not to consume any of these!")) + +/datum/quirk/item_quirk/allergic/process(seconds_per_tick) + if(!iscarbon(quirk_holder)) + return + + if(IS_IN_STASIS(quirk_holder)) + return + + if(quirk_holder.stat == DEAD) + return + + var/mob/living/carbon/carbon_quirk_holder = quirk_holder + for(var/allergy in allergies) + var/datum/reagent/instantiated_med = carbon_quirk_holder.reagents.has_reagent(allergy) + if(!instantiated_med) + continue + //Just halts the progression, I'd suggest you run to medbay asap to get it fixed + if(carbon_quirk_holder.reagents.has_reagent(/datum/reagent/medicine/epinephrine)) + instantiated_med.reagent_removal_skip_list |= ALLERGIC_REMOVAL_SKIP + return //intentionally stops the entire proc so we avoid the organ damage after the loop + instantiated_med.reagent_removal_skip_list -= ALLERGIC_REMOVAL_SKIP + carbon_quirk_holder.adjustToxLoss(3 * seconds_per_tick) + carbon_quirk_holder.reagents.add_reagent(/datum/reagent/toxin/histamine, 3 * seconds_per_tick) + if(SPT_PROB(10, seconds_per_tick)) + carbon_quirk_holder.vomit(VOMIT_CATEGORY_DEFAULT) + carbon_quirk_holder.adjustOrganLoss(pick(ORGAN_SLOT_BRAIN,ORGAN_SLOT_APPENDIX,ORGAN_SLOT_LUNGS,ORGAN_SLOT_HEART,ORGAN_SLOT_LIVER,ORGAN_SLOT_STOMACH),10) diff --git a/code/datums/quirks/negative_quirks/bad_back.dm b/code/datums/quirks/negative_quirks/bad_back.dm new file mode 100644 index 00000000000..b7c40636159 --- /dev/null +++ b/code/datums/quirks/negative_quirks/bad_back.dm @@ -0,0 +1,50 @@ +/datum/quirk/badback + name = "Bad Back" + desc = "Thanks to your poor posture, backpacks and other bags never sit right on your back. More evenly weighted objects are fine, though." + icon = FA_ICON_HIKING + value = -8 + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_MOODLET_BASED + gain_text = span_danger("Your back REALLY hurts!") + lose_text = span_notice("Your back feels better.") + medical_record_text = "Patient scans indicate severe and chronic back pain." + hardcore_value = 4 + mail_goodies = list(/obj/item/cane) + var/datum/weakref/backpack + +/datum/quirk/badback/add(client/client_source) + var/mob/living/carbon/human/human_holder = quirk_holder + var/obj/item/storage/backpack/equipped_backpack = human_holder.back + if(istype(equipped_backpack)) + quirk_holder.add_mood_event("back_pain", /datum/mood_event/back_pain) + RegisterSignal(human_holder.back, COMSIG_ITEM_POST_UNEQUIP, PROC_REF(on_unequipped_backpack)) + else + RegisterSignal(quirk_holder, COMSIG_MOB_EQUIPPED_ITEM, PROC_REF(on_equipped_item)) + +/datum/quirk/badback/remove() + UnregisterSignal(quirk_holder, COMSIG_MOB_EQUIPPED_ITEM) + + var/obj/item/storage/equipped_backpack = backpack?.resolve() + if(equipped_backpack) + UnregisterSignal(equipped_backpack, COMSIG_ITEM_POST_UNEQUIP) + quirk_holder.clear_mood_event("back_pain") + +/// Signal handler for when the quirk_holder equips an item. If it's a backpack, adds the back_pain mood event. +/datum/quirk/badback/proc/on_equipped_item(mob/living/source, obj/item/equipped_item, slot) + SIGNAL_HANDLER + + if(!(slot & ITEM_SLOT_BACK) || !istype(equipped_item, /obj/item/storage/backpack)) + return + + quirk_holder.add_mood_event("back_pain", /datum/mood_event/back_pain) + RegisterSignal(equipped_item, COMSIG_ITEM_POST_UNEQUIP, PROC_REF(on_unequipped_backpack)) + UnregisterSignal(quirk_holder, COMSIG_MOB_EQUIPPED_ITEM) + backpack = WEAKREF(equipped_item) + +/// Signal handler for when the quirk_holder unequips an equipped backpack. Removes the back_pain mood event. +/datum/quirk/badback/proc/on_unequipped_backpack(obj/item/source, force, atom/newloc, no_move, invdrop, silent) + SIGNAL_HANDLER + + UnregisterSignal(source, COMSIG_ITEM_POST_UNEQUIP) + quirk_holder.clear_mood_event("back_pain") + backpack = null + RegisterSignal(quirk_holder, COMSIG_MOB_EQUIPPED_ITEM, PROC_REF(on_equipped_item)) diff --git a/code/datums/quirks/negative_quirks/bad_touch.dm b/code/datums/quirks/negative_quirks/bad_touch.dm new file mode 100644 index 00000000000..f3a5d967a01 --- /dev/null +++ b/code/datums/quirks/negative_quirks/bad_touch.dm @@ -0,0 +1,31 @@ +/datum/quirk/bad_touch + name = "Bad Touch" + desc = "You don't like hugs. You'd really prefer if people just left you alone." + icon = "tg-bad-touch" + mob_trait = TRAIT_BADTOUCH + value = -1 + gain_text = span_danger("You just want people to leave you alone.") + lose_text = span_notice("You could use a big hug.") + medical_record_text = "Patient has disdain for being touched. Potentially has undiagnosed haphephobia." + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_MOODLET_BASED + hardcore_value = 1 + mail_goodies = list(/obj/item/reagent_containers/spray/pepper) // show me on the doll where the bad man touched you + +/datum/quirk/bad_touch/add(client/client_source) + RegisterSignals(quirk_holder, list(COMSIG_LIVING_GET_PULLED, COMSIG_CARBON_HELP_ACT), PROC_REF(uncomfortable_touch)) + +/datum/quirk/bad_touch/remove() + UnregisterSignal(quirk_holder, list(COMSIG_LIVING_GET_PULLED, COMSIG_CARBON_HELP_ACT)) + +/// Causes a negative moodlet to our quirk holder on signal +/datum/quirk/bad_touch/proc/uncomfortable_touch(datum/source) + SIGNAL_HANDLER + + if(quirk_holder.stat == DEAD) + return + + new /obj/effect/temp_visual/annoyed(quirk_holder.loc) + if(quirk_holder.mob_mood.sanity <= SANITY_NEUTRAL) + quirk_holder.add_mood_event("bad_touch", /datum/mood_event/very_bad_touch) + else + quirk_holder.add_mood_event("bad_touch", /datum/mood_event/bad_touch) diff --git a/code/datums/quirks/negative_quirks/big_hands.dm b/code/datums/quirks/negative_quirks/big_hands.dm new file mode 100644 index 00000000000..778ea6af8c3 --- /dev/null +++ b/code/datums/quirks/negative_quirks/big_hands.dm @@ -0,0 +1,10 @@ +/datum/quirk/bighands + name = "Big Hands" + desc = "You have big hands, it sure does make it hard to use a lot of things." + icon = FA_ICON_HAND_DOTS + value = -6 + mob_trait = TRAIT_CHUNKYFINGERS + gain_text = span_danger("Your hands are huge! You can't use small things anymore!") + lose_text = span_notice("Your hands are back to normal.") + medical_record_text = "Patient has unusually large hands. Made me question my masculinity..." + hardcore_value = 5 diff --git a/code/datums/quirks/negative_quirks/blindness.dm b/code/datums/quirks/negative_quirks/blindness.dm new file mode 100644 index 00000000000..ce57e946fe9 --- /dev/null +++ b/code/datums/quirks/negative_quirks/blindness.dm @@ -0,0 +1,20 @@ +/datum/quirk/item_quirk/blindness + name = "Blind" + desc = "You are completely blind, nothing can counteract this." + icon = FA_ICON_EYE_SLASH + value = -16 + gain_text = span_danger("You can't see anything.") + lose_text = span_notice("You miraculously gain back your vision.") + medical_record_text = "Patient has permanent blindness." + hardcore_value = 15 + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_CHANGES_APPEARANCE + mail_goodies = list(/obj/item/clothing/glasses/sunglasses, /obj/item/cane/white) + +/datum/quirk/item_quirk/blindness/add_unique(client/client_source) + give_item_to_holder(/obj/item/clothing/glasses/blindfold/white, list(LOCATION_EYES = ITEM_SLOT_EYES, LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) + +/datum/quirk/item_quirk/blindness/add(client/client_source) + quirk_holder.become_blind(QUIRK_TRAIT) + +/datum/quirk/item_quirk/blindness/remove() + quirk_holder.cure_blind(QUIRK_TRAIT) diff --git a/code/datums/quirks/negative_quirks/blood_deficiency.dm b/code/datums/quirks/negative_quirks/blood_deficiency.dm new file mode 100644 index 00000000000..c75007bacc2 --- /dev/null +++ b/code/datums/quirks/negative_quirks/blood_deficiency.dm @@ -0,0 +1,39 @@ +/datum/quirk/blooddeficiency + name = "Blood Deficiency" + desc = "Your body can't produce enough blood to sustain itself." + icon = FA_ICON_TINT + value = -8 + mob_trait = TRAIT_BLOOD_DEFICIENCY + gain_text = span_danger("You feel your vigor slowly fading away.") + lose_text = span_notice("You feel vigorous again.") + medical_record_text = "Patient requires regular treatment for blood loss due to low production of blood." + hardcore_value = 8 + mail_goodies = list(/obj/item/reagent_containers/blood/o_minus) // universal blood type that is safe for all + var/min_blood = BLOOD_VOLUME_SAFE - 25 // just barely survivable without treatment + +/datum/quirk/blooddeficiency/post_add() + if(!ishuman(quirk_holder)) + return + + // for making sure the roundstart species has the right blood pack sent to them + var/mob/living/carbon/human/carbon_target = quirk_holder + carbon_target.dna.species.update_quirk_mail_goodies(carbon_target, src) + +/** + * Makes the mob lose blood from having the blood deficiency quirk, if possible + * + * Arguments: + * * seconds_per_tick + */ +/datum/quirk/blooddeficiency/proc/lose_blood(seconds_per_tick) + if(quirk_holder.stat == DEAD) + return + + var/mob/living/carbon/human/carbon_target = quirk_holder + if(HAS_TRAIT(carbon_target, TRAIT_NOBLOOD) && isnull(carbon_target.dna.species.exotic_blood)) //can't lose blood if your species doesn't have any + return + + if (carbon_target.blood_volume <= min_blood) + return + // Ensures that we don't reduce total blood volume below min_blood. + carbon_target.blood_volume = max(min_blood, carbon_target.blood_volume - carbon_target.dna.species.blood_deficiency_drain_rate * seconds_per_tick) diff --git a/code/datums/quirks/negative_quirks/body_purist.dm b/code/datums/quirks/negative_quirks/body_purist.dm new file mode 100644 index 00000000000..6350a710882 --- /dev/null +++ b/code/datums/quirks/negative_quirks/body_purist.dm @@ -0,0 +1,69 @@ +/datum/quirk/body_purist + name = "Body Purist" + desc = "You believe your body is a temple and its natural form is an embodiment of perfection. Accordingly, you despise the idea of ever augmenting it with unnatural parts, cybernetic, prosthetic, or anything like it." + icon = FA_ICON_PERSON_RAYS + value = -2 + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_MOODLET_BASED + gain_text = span_danger("You now begin to hate the idea of having cybernetic implants.") + lose_text = span_notice("Maybe cybernetics aren't so bad. You now feel okay with augmentations and prosthetics.") + medical_record_text = "This patient has disclosed an extreme hatred for unnatural bodyparts and augmentations." + hardcore_value = 3 + mail_goodies = list(/obj/item/paper/pamphlet/cybernetics) + var/cybernetics_level = 0 + +/datum/quirk/body_purist/add(client/client_source) + check_cybernetics() + RegisterSignal(quirk_holder, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_organ_gain)) + RegisterSignal(quirk_holder, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(on_organ_lose)) + RegisterSignal(quirk_holder, COMSIG_CARBON_ATTACH_LIMB, PROC_REF(on_limb_gain)) + RegisterSignal(quirk_holder, COMSIG_CARBON_REMOVE_LIMB, PROC_REF(on_limb_lose)) + +/datum/quirk/body_purist/remove() + UnregisterSignal(quirk_holder, list( + COMSIG_CARBON_GAIN_ORGAN, + COMSIG_CARBON_LOSE_ORGAN, + COMSIG_CARBON_ATTACH_LIMB, + COMSIG_CARBON_REMOVE_LIMB, + )) + quirk_holder.clear_mood_event("body_purist") + +/datum/quirk/body_purist/proc/check_cybernetics() + var/mob/living/carbon/owner = quirk_holder + if(!istype(owner)) + return + for(var/obj/item/bodypart/limb as anything in owner.bodyparts) + if(IS_ROBOTIC_LIMB(limb)) + cybernetics_level++ + for(var/obj/item/organ/organ as anything in owner.organs) + if(IS_ROBOTIC_ORGAN(organ) && !(organ.organ_flags & ORGAN_HIDDEN)) + cybernetics_level++ + update_mood() + +/datum/quirk/body_purist/proc/update_mood() + quirk_holder.clear_mood_event("body_purist") + if(cybernetics_level) + quirk_holder.add_mood_event("body_purist", /datum/mood_event/body_purist, -cybernetics_level * 10) + +/datum/quirk/body_purist/proc/on_organ_gain(datum/source, obj/item/organ/new_organ, special) + SIGNAL_HANDLER + if(IS_ROBOTIC_ORGAN(new_organ) && !(new_organ.organ_flags & ORGAN_HIDDEN)) //why the fuck are there 2 of them + cybernetics_level++ + update_mood() + +/datum/quirk/body_purist/proc/on_organ_lose(datum/source, obj/item/organ/old_organ, special) + SIGNAL_HANDLER + if(IS_ROBOTIC_ORGAN(old_organ) && !(old_organ.organ_flags & ORGAN_HIDDEN)) + cybernetics_level-- + update_mood() + +/datum/quirk/body_purist/proc/on_limb_gain(datum/source, obj/item/bodypart/new_limb, special) + SIGNAL_HANDLER + if(IS_ROBOTIC_LIMB(new_limb)) + cybernetics_level++ + update_mood() + +/datum/quirk/body_purist/proc/on_limb_lose(datum/source, obj/item/bodypart/old_limb, special) + SIGNAL_HANDLER + if(IS_ROBOTIC_LIMB(old_limb)) + cybernetics_level-- + update_mood() diff --git a/code/datums/quirks/negative_quirks/brain_problems.dm b/code/datums/quirks/negative_quirks/brain_problems.dm new file mode 100644 index 00000000000..15cc0128020 --- /dev/null +++ b/code/datums/quirks/negative_quirks/brain_problems.dm @@ -0,0 +1,37 @@ + /* A couple of brain tumor stats for anyone curious / looking at this quirk for balancing: + * - It takes less 16 minute 40 seconds to die from brain death due to a brain tumor. + * - It takes 1 minutes 40 seconds to take 10% (20 organ damage) brain damage. + * - 5u mannitol will heal 12.5% (25 organ damage) brain damage + */ +/datum/quirk/item_quirk/brainproblems + name = "Brain Tumor" + desc = "You have a little friend in your brain that is slowly destroying it. Better bring some mannitol!" + icon = FA_ICON_BRAIN + value = -12 + gain_text = span_danger("You feel smooth.") + lose_text = span_notice("You feel wrinkled again.") + medical_record_text = "Patient has a tumor in their brain that is slowly driving them to brain death." + hardcore_value = 12 + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_PROCESSES + mail_goodies = list(/obj/item/storage/pill_bottle/mannitol/braintumor) + +/datum/quirk/item_quirk/brainproblems/add_unique(client/client_source) + give_item_to_holder( + /obj/item/storage/pill_bottle/mannitol/braintumor, + list( + LOCATION_LPOCKET = ITEM_SLOT_LPOCKET, + LOCATION_RPOCKET = ITEM_SLOT_RPOCKET, + LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, + LOCATION_HANDS = ITEM_SLOT_HANDS, + ), + flavour_text = "These will keep you alive until you can secure a supply of medication. Don't rely on them too much!", + ) + +/datum/quirk/item_quirk/brainproblems/process(seconds_per_tick) + if(quirk_holder.stat == DEAD) + return + + if(HAS_TRAIT(quirk_holder, TRAIT_TUMOR_SUPPRESSED)) + return + + quirk_holder.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.2 * seconds_per_tick) diff --git a/code/datums/quirks/negative_quirks/chronic_illness.dm b/code/datums/quirks/negative_quirks/chronic_illness.dm new file mode 100644 index 00000000000..663d4138198 --- /dev/null +++ b/code/datums/quirks/negative_quirks/chronic_illness.dm @@ -0,0 +1,16 @@ +/datum/quirk/item_quirk/chronic_illness + name = "Chronic Illness" + desc = "You have a chronic illness that requires constant medication to keep under control." + icon = FA_ICON_DISEASE + value = -12 + gain_text = span_danger("You feel a bit off today.") + lose_text = span_notice("You feel a bit better today.") + medical_record_text = "Patient has a chronic illness that requires constant medication to keep under control." + hardcore_value = 12 + mail_goodies = list(/obj/item/storage/pill_bottle/sansufentanyl) + +/datum/quirk/item_quirk/chronic_illness/add_unique(client/client_source) + var/datum/disease/chronic_illness/hms = new /datum/disease/chronic_illness() + quirk_holder.ForceContractDisease(hms) + give_item_to_holder(/obj/item/storage/pill_bottle/sansufentanyl, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK),flavour_text = "You've been provided with medication to help manage your condition. Take it regularly to avoid complications.") + give_item_to_holder(/obj/item/healthanalyzer/simple/disease, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK)) diff --git a/code/datums/quirks/negative_quirks/claustrophobia.dm b/code/datums/quirks/negative_quirks/claustrophobia.dm new file mode 100644 index 00000000000..e0207d227dd --- /dev/null +++ b/code/datums/quirks/negative_quirks/claustrophobia.dm @@ -0,0 +1,54 @@ +/datum/quirk/claustrophobia + name = "Claustrophobia" + desc = "You are terrified of small spaces and certain jolly figures. If you are placed inside any container, locker, or machinery, a panic attack sets in and you struggle to breathe." + icon = FA_ICON_BOX_OPEN + value = -4 + medical_record_text = "Patient demonstrates a fear of tight spaces." + hardcore_value = 5 + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_PROCESSES + mail_goodies = list(/obj/item/reagent_containers/syringe/convermol) // to help breathing + +/datum/quirk/claustrophobia/remove() + quirk_holder.clear_mood_event("claustrophobia") + +/datum/quirk/claustrophobia/process(seconds_per_tick) + if(quirk_holder.stat != CONSCIOUS || quirk_holder.IsSleeping() || quirk_holder.IsUnconscious()) + return + + if(HAS_TRAIT(quirk_holder, TRAIT_FEARLESS)) + return + + var/nick_spotted = FALSE + + for(var/mob/living/carbon/human/possible_claus in view(5, quirk_holder)) + if(evaluate_jolly_levels(possible_claus)) + nick_spotted = TRUE + break + + if(!nick_spotted && isturf(quirk_holder.loc)) + quirk_holder.clear_mood_event("claustrophobia") + return + + quirk_holder.add_mood_event("claustrophobia", /datum/mood_event/claustrophobia) + quirk_holder.losebreath += 0.25 // miss a breath one in four times + if(SPT_PROB(25, seconds_per_tick)) + if(nick_spotted) + to_chat(quirk_holder, span_warning("Santa Claus is here! I gotta get out of here!")) + else + to_chat(quirk_holder, span_warning("You feel trapped! Must escape... can't breathe...")) + +///investigates whether possible_saint_nick possesses a high level of christmas cheer +/datum/quirk/claustrophobia/proc/evaluate_jolly_levels(mob/living/carbon/human/possible_saint_nick) + if(!istype(possible_saint_nick)) + return FALSE + + if(istype(possible_saint_nick.back, /obj/item/storage/backpack/santabag)) + return TRUE + + if(istype(possible_saint_nick.head, /obj/item/clothing/head/costume/santa) || istype(possible_saint_nick.head, /obj/item/clothing/head/helmet/space/santahat)) + return TRUE + + if(istype(possible_saint_nick.wear_suit, /obj/item/clothing/suit/space/santa)) + return TRUE + + return FALSE diff --git a/code/datums/quirks/negative_quirks/clumsy.dm b/code/datums/quirks/negative_quirks/clumsy.dm new file mode 100644 index 00000000000..8cf363753d4 --- /dev/null +++ b/code/datums/quirks/negative_quirks/clumsy.dm @@ -0,0 +1,9 @@ +/datum/quirk/clumsy + name = "Clumsy" + desc = "You're clumsy, a goofball, a silly dude. You big loveable himbo/bimbo you! Hope you weren't planning on using your hands for anything that takes even a LICK of dexterity." + icon = FA_ICON_FACE_DIZZY + value = -8 + mob_trait = TRAIT_CLUMSY + gain_text = span_danger("You feel your IQ sink like your brain is liquid.") + lose_text = span_notice("You feel like your IQ went up to at least average.") + medical_record_text = "Patient has demonstrated an extreme difficulty with high motor skill paired with an inability to demonstrate critical thinking." diff --git a/code/datums/quirks/negative_quirks/cursed.dm b/code/datums/quirks/negative_quirks/cursed.dm new file mode 100644 index 00000000000..4b99ff850b8 --- /dev/null +++ b/code/datums/quirks/negative_quirks/cursed.dm @@ -0,0 +1,17 @@ +/* +// SKYRAT EDIT REMOVAL +/datum/quirk/cursed + name = "Cursed" + desc = "You are cursed with bad luck. You are much more likely to suffer from accidents and mishaps. When it rains, it pours." + icon = FA_ICON_CLOUD_SHOWERS_HEAVY + value = -8 + mob_trait = TRAIT_CURSED + gain_text = span_danger("You feel like you're going to have a bad day.") + lose_text = span_notice("You feel like you're going to have a good day.") + medical_record_text = "Patient is cursed with bad luck." + hardcore_value = 8 + +/datum/quirk/cursed/add(client/client_source) + quirk_holder.AddComponent(/datum/component/omen/quirk) +*/ +// SKYRAT EDIT REMOVAL END diff --git a/code/datums/quirks/negative_quirks/deafness.dm b/code/datums/quirks/negative_quirks/deafness.dm new file mode 100644 index 00000000000..077bbe72aa5 --- /dev/null +++ b/code/datums/quirks/negative_quirks/deafness.dm @@ -0,0 +1,14 @@ +/datum/quirk/item_quirk/deafness + name = "Deaf" + desc = "You are incurably deaf." + icon = FA_ICON_DEAF + value = -8 + mob_trait = TRAIT_DEAF + gain_text = span_danger("You can't hear anything.") + lose_text = span_notice("You're able to hear again!") + medical_record_text = "Patient's cochlear nerve is incurably damaged." + hardcore_value = 12 + mail_goodies = list(/obj/item/clothing/mask/whistle) + +/datum/quirk/item_quirk/deafness/add_unique(client/client_source) + give_item_to_holder(/obj/item/clothing/accessory/deaf_pin, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) diff --git a/code/datums/quirks/negative_quirks/depression.dm b/code/datums/quirks/negative_quirks/depression.dm new file mode 100644 index 00000000000..0bf15516105 --- /dev/null +++ b/code/datums/quirks/negative_quirks/depression.dm @@ -0,0 +1,12 @@ +/datum/quirk/depression + name = "Depression" + desc = "You sometimes just hate life." + icon = FA_ICON_FROWN + mob_trait = TRAIT_DEPRESSION + value = -3 + gain_text = span_danger("You start feeling depressed.") + lose_text = span_notice("You no longer feel depressed.") //if only it were that easy! + medical_record_text = "Patient has a mild mood disorder causing them to experience acute episodes of depression." + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_MOODLET_BASED + hardcore_value = 2 + mail_goodies = list(/obj/item/storage/pill_bottle/happinesspsych) diff --git a/code/datums/quirks/negative_quirks/family_heirloom.dm b/code/datums/quirks/negative_quirks/family_heirloom.dm new file mode 100644 index 00000000000..0fd08c68f21 --- /dev/null +++ b/code/datums/quirks/negative_quirks/family_heirloom.dm @@ -0,0 +1,72 @@ +/datum/quirk/item_quirk/family_heirloom + name = "Family Heirloom" + desc = "You are the current owner of an heirloom, passed down for generations. You have to keep it safe!" + icon = FA_ICON_TOOLBOX + value = -2 + medical_record_text = "Patient demonstrates an unnatural attachment to a family heirloom." + hardcore_value = 1 + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_PROCESSES|QUIRK_MOODLET_BASED + /// A weak reference to our heirloom. + var/datum/weakref/heirloom + mail_goodies = list(/obj/item/storage/secure/briefcase) + +/datum/quirk/item_quirk/family_heirloom/add_unique(client/client_source) + var/mob/living/carbon/human/human_holder = quirk_holder + var/obj/item/heirloom_type + + // The quirk holder's species - we have a 50% chance, if we have a species with a set heirloom, to choose a species heirloom. + var/datum/species/holder_species = human_holder.dna?.species + if(holder_species && LAZYLEN(holder_species.family_heirlooms) && prob(50)) + heirloom_type = pick(holder_species.family_heirlooms) + else + // Our quirk holder's job + var/datum/job/holder_job = human_holder.last_mind?.assigned_role + if(holder_job && LAZYLEN(holder_job.family_heirlooms)) + heirloom_type = pick(holder_job.family_heirlooms) + + // If we didn't find an heirloom somehow, throw them a generic one + if(!heirloom_type) + heirloom_type = pick(/obj/item/toy/cards/deck, /obj/item/lighter, /obj/item/dice/d20) + + var/obj/new_heirloom = new heirloom_type(get_turf(human_holder)) + heirloom = WEAKREF(new_heirloom) + + give_item_to_holder( + new_heirloom, + list( + LOCATION_LPOCKET = ITEM_SLOT_LPOCKET, + LOCATION_RPOCKET = ITEM_SLOT_RPOCKET, + LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, + LOCATION_HANDS = ITEM_SLOT_HANDS, + ), + flavour_text = "This is a precious family heirloom, passed down from generation to generation. Keep it safe!", + ) + +/datum/quirk/item_quirk/family_heirloom/post_add() + var/list/names = splittext(quirk_holder.real_name, " ") + var/family_name = names[names.len] + + var/obj/family_heirloom = heirloom?.resolve() + if(!family_heirloom) + to_chat(quirk_holder, span_boldnotice("A wave of existential dread runs over you as you realize your precious family heirloom is missing. Perhaps the Gods will show mercy on your cursed soul?")) + return + family_heirloom.AddComponent(/datum/component/heirloom, quirk_holder.mind, family_name) + + return ..() + +/datum/quirk/item_quirk/family_heirloom/process() + if(quirk_holder.stat == DEAD) + return + + var/obj/family_heirloom = heirloom?.resolve() + + if(family_heirloom && (family_heirloom in quirk_holder.get_all_contents())) + quirk_holder.clear_mood_event("family_heirloom_missing") + quirk_holder.add_mood_event("family_heirloom", /datum/mood_event/family_heirloom) + else + quirk_holder.clear_mood_event("family_heirloom") + quirk_holder.add_mood_event("family_heirloom_missing", /datum/mood_event/family_heirloom_missing) + +/datum/quirk/item_quirk/family_heirloom/remove() + quirk_holder.clear_mood_event("family_heirloom_missing") + quirk_holder.clear_mood_event("family_heirloom") diff --git a/code/datums/quirks/negative_quirks/frail.dm b/code/datums/quirks/negative_quirks/frail.dm new file mode 100644 index 00000000000..6b806875ea2 --- /dev/null +++ b/code/datums/quirks/negative_quirks/frail.dm @@ -0,0 +1,11 @@ +/datum/quirk/frail + name = "Frail" + desc = "You have skin of paper and bones of glass! You suffer wounds much more easily than most." + icon = FA_ICON_SKULL + value = -6 + mob_trait = TRAIT_EASILY_WOUNDED + gain_text = span_danger("You feel frail.") + lose_text = span_notice("You feel sturdy again.") + medical_record_text = "Patient is absurdly easy to injure. Please take all due diligence to avoid possible malpractice suits." + hardcore_value = 4 + mail_goodies = list(/obj/effect/spawner/random/medical/minor_healing) diff --git a/code/datums/quirks/negative_quirks/glass_jaw.dm b/code/datums/quirks/negative_quirks/glass_jaw.dm new file mode 100644 index 00000000000..33ad19add6d --- /dev/null +++ b/code/datums/quirks/negative_quirks/glass_jaw.dm @@ -0,0 +1,52 @@ +/datum/quirk/glass_jaw + name = "Glass Jaw" + desc = "You have a very fragile jaw. Any sufficiently hard blow to your head might knock you out." + icon = FA_ICON_HAND_FIST + value = -4 + gain_text = span_danger("Your jaw feels loose.") + lose_text = span_notice("Your jaw feels fitting again.") + medical_record_text = "Patient is absurdly easy to knock out. Do not allow them near a boxing ring." + hardcore_value = 4 + mail_goodies = list( + /obj/item/clothing/gloves/boxing, + /obj/item/clothing/mask/luchador/rudos, + ) + +/datum/quirk/glass_jaw/New() + . = ..() + //randomly picks between blue or red equipment for goodies + if(prob(50)) + mail_goodies = list( + /obj/item/clothing/gloves/boxing, + /obj/item/clothing/mask/luchador/rudos, + ) + else + mail_goodies = list( + /obj/item/clothing/gloves/boxing/blue, + /obj/item/clothing/mask/luchador/tecnicos, + ) + +/datum/quirk/glass_jaw/add(client/client_source) + RegisterSignal(quirk_holder, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(punch_out)) + +/datum/quirk/glass_jaw/remove() + UnregisterSignal(quirk_holder, COMSIG_MOB_APPLY_DAMAGE) + +/datum/quirk/glass_jaw/proc/punch_out(mob/living/carbon/source, damage, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction, attacking_item) + SIGNAL_HANDLER + if((damagetype != BRUTE) || (def_zone != BODY_ZONE_HEAD)) + return + var/actual_damage = damage - (damage * blocked/100) + //only roll for knockouts at 5 damage or more + if(actual_damage < 5) + return + //blunt items are more likely to knock out, but sharp ones are still capable of doing it + if(prob(CEILING(actual_damage * (sharpness & (SHARP_EDGED|SHARP_POINTY) ? 0.65 : 1), 1))) + //don't display the message if little mac is already KO'd + if(!source.IsUnconscious()) + source.visible_message( + span_warning("[source] gets knocked out!"), + span_userdanger("You get knocked out!"), + vision_distance = COMBAT_MESSAGE_RANGE, + ) + source.Unconscious(3 SECONDS) diff --git a/code/datums/quirks/negative_quirks/heavy_sleeper.dm b/code/datums/quirks/negative_quirks/heavy_sleeper.dm new file mode 100644 index 00000000000..dea79683915 --- /dev/null +++ b/code/datums/quirks/negative_quirks/heavy_sleeper.dm @@ -0,0 +1,19 @@ +/datum/quirk/heavy_sleeper + name = "Heavy Sleeper" + desc = "You sleep like a rock! Whenever you're put to sleep or knocked unconscious, you take a little bit longer to wake up." + icon = FA_ICON_BED + value = -2 + mob_trait = TRAIT_HEAVY_SLEEPER + gain_text = span_danger("You feel sleepy.") + lose_text = span_notice("You feel awake again.") + medical_record_text = "Patient has abnormal sleep study results and is difficult to wake up." + hardcore_value = 2 + mail_goodies = list( + /obj/item/clothing/glasses/blindfold, + /obj/item/bedsheet/random, + /obj/item/clothing/under/misc/pj/red, + /obj/item/clothing/head/costume/nightcap/red, + /obj/item/clothing/under/misc/pj/blue, + /obj/item/clothing/head/costume/nightcap/blue, + /obj/item/pillow/random, + ) diff --git a/code/datums/quirks/negative_quirks/hemiplegic.dm b/code/datums/quirks/negative_quirks/hemiplegic.dm new file mode 100644 index 00000000000..459b880fad2 --- /dev/null +++ b/code/datums/quirks/negative_quirks/hemiplegic.dm @@ -0,0 +1,22 @@ +/datum/quirk/hemiplegic + name = "Hemiplegic" + desc = "Half of your body doesn't work. Nothing will ever fix this." + icon = FA_ICON_CIRCLE_HALF_STROKE + value = -10 // slightly more bearable than paraplegic but not by much + gain_text = null // Handled by trauma. + lose_text = null + medical_record_text = "Patient has an untreatable impairment in motor function on half of their body." + hardcore_value = 10 + mail_goodies = list( + /obj/item/stack/sheet/mineral/uranium/half, //half a stack of a material that has a half life + /obj/item/reagent_containers/cup/glass/drinkingglass/filled/half_full, + ) + +/datum/quirk/hemiplegic/add(client/client_source) + var/mob/living/carbon/human/human_holder = quirk_holder + var/trauma_type = pick(/datum/brain_trauma/severe/paralysis/hemiplegic/left, /datum/brain_trauma/severe/paralysis/hemiplegic/right) + human_holder.gain_trauma(trauma_type, TRAUMA_RESILIENCE_ABSOLUTE) + +/datum/quirk/hemiplegic/remove() + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.cure_trauma_type(/datum/brain_trauma/severe/paralysis/hemiplegic, TRAUMA_RESILIENCE_ABSOLUTE) diff --git a/code/datums/quirks/negative_quirks/hypersensitive.dm b/code/datums/quirks/negative_quirks/hypersensitive.dm new file mode 100644 index 00000000000..f51e72fc256 --- /dev/null +++ b/code/datums/quirks/negative_quirks/hypersensitive.dm @@ -0,0 +1,18 @@ +/datum/quirk/hypersensitive + name = "Hypersensitive" + desc = "For better or worse, everything seems to affect your mood more than it should." + icon = FA_ICON_FLUSHED + value = -2 + gain_text = span_danger("You seem to make a big deal out of everything.") + lose_text = span_notice("You don't seem to make a big deal out of everything anymore.") + medical_record_text = "Patient demonstrates a high level of emotional volatility." + hardcore_value = 3 + mail_goodies = list(/obj/effect/spawner/random/entertainment/plushie_delux) + +/datum/quirk/hypersensitive/add(client/client_source) + if (quirk_holder.mob_mood) + quirk_holder.mob_mood.mood_modifier += 0.5 + +/datum/quirk/hypersensitive/remove() + if (quirk_holder.mob_mood) + quirk_holder.mob_mood.mood_modifier -= 0.5 diff --git a/code/datums/quirks/negative_quirks/illiterate.dm b/code/datums/quirks/negative_quirks/illiterate.dm new file mode 100644 index 00000000000..8101985f8f7 --- /dev/null +++ b/code/datums/quirks/negative_quirks/illiterate.dm @@ -0,0 +1,9 @@ +/datum/quirk/illiterate + name = "Illiterate" + desc = "You dropped out of school and are unable to read or write. This affects reading, writing, using computers and other electronics." + icon = FA_ICON_GRADUATION_CAP + value = -8 + mob_trait = TRAIT_ILLITERATE + medical_record_text = "Patient is not literate." + hardcore_value = 8 + mail_goodies = list(/obj/item/pai_card) // can read things for you diff --git a/code/datums/quirks/negative_quirks/indebted.dm b/code/datums/quirks/negative_quirks/indebted.dm new file mode 100644 index 00000000000..1e30e7800d6 --- /dev/null +++ b/code/datums/quirks/negative_quirks/indebted.dm @@ -0,0 +1,40 @@ +/datum/quirk/indebted + name = "Indebted" + desc = "Bad life decisions, medical bills, student loans, whatever it may be, you've incurred quite the debt. A portion of all you receive will go towards extinguishing it." + icon = FA_ICON_DOLLAR + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_HIDE_FROM_SCAN + value = -2 + medical_record_text = "Alas, the patient struggled to scrape together enough money to pay the checkup bill." + hardcore_value = 2 + +/datum/quirk/indebted/add_unique(client/client_source) + var/mob/living/carbon/human/human_holder = quirk_holder + if(!human_holder.account_id) + return + var/datum/bank_account/account = SSeconomy.bank_accounts_by_id["[human_holder.account_id]"] + var/debt = PAYCHECK_CREW * rand(275, 325) + account.account_debt += debt + RegisterSignal(account, COMSIG_BANK_ACCOUNT_DEBT_PAID, PROC_REF(on_debt_paid)) + to_chat(client_source.mob, span_warning("You remember, you've a hefty, [debt] credits debt to pay...")) + +///Once the debt is extinguished, award an achievement and a pin for actually taking care of it. +/datum/quirk/indebted/proc/on_debt_paid(datum/bank_account/source) + SIGNAL_HANDLER + if(source.account_debt) + return + UnregisterSignal(source, COMSIG_BANK_ACCOUNT_DEBT_PAID) + ///The debt was extinguished while the quirk holder was logged out, so let's kindly award it once they come back. + if(!quirk_holder.client) + RegisterSignal(quirk_holder, COMSIG_MOB_LOGIN, PROC_REF(award_on_login)) + else + quirk_holder.client.give_award(/datum/award/achievement/misc/debt_extinguished, quirk_holder) + podspawn(list( + "target" = get_turf(quirk_holder), + "style" = STYLE_BLUESPACE, + "spawn" = /obj/item/clothing/accessory/debt_payer_pin, + )) + +/datum/quirk/indebted/proc/award_on_login(mob/source) + SIGNAL_HANDLER + quirk_holder.client.give_award(/datum/award/achievement/misc/debt_extinguished, quirk_holder) + UnregisterSignal(source, COMSIG_MOB_LOGIN) diff --git a/code/datums/quirks/negative_quirks/insanity.dm b/code/datums/quirks/negative_quirks/insanity.dm new file mode 100644 index 00000000000..56b56a53812 --- /dev/null +++ b/code/datums/quirks/negative_quirks/insanity.dm @@ -0,0 +1,41 @@ +/datum/quirk/insanity + name = "Reality Dissociation Syndrome" + desc = "You suffer from a severe disorder that causes very vivid hallucinations. \ + Mindbreaker toxin can suppress its effects, and you are immune to mindbreaker's hallucinogenic properties. \ + THIS IS NOT A LICENSE TO GRIEF." + icon = FA_ICON_GRIN_TONGUE_WINK + value = -8 + gain_text = span_userdanger("...") + lose_text = span_notice("You feel in tune with the world again.") + medical_record_text = "Patient suffers from acute Reality Dissociation Syndrome and experiences vivid hallucinations." + hardcore_value = 6 + mail_goodies = list(/obj/item/storage/pill_bottle/lsdpsych) + /// Weakref to the trauma we give out + var/datum/weakref/added_trama_ref + +/datum/quirk/insanity/add(client/client_source) + if(!iscarbon(quirk_holder)) + return + var/mob/living/carbon/carbon_quirk_holder = quirk_holder + + // Setup our special RDS mild hallucination. + // Not a unique subtype so not to plague subtypesof, + // also as we inherit the names and values from our quirk. + var/datum/brain_trauma/mild/hallucinations/added_trauma = new() + added_trauma.resilience = TRAUMA_RESILIENCE_ABSOLUTE + added_trauma.name = name + added_trauma.desc = medical_record_text + added_trauma.scan_desc = lowertext(name) + added_trauma.gain_text = null + added_trauma.lose_text = null + + carbon_quirk_holder.gain_trauma(added_trauma) + added_trama_ref = WEAKREF(added_trauma) + +/datum/quirk/insanity/post_add() + var/rds_policy = get_policy("[type]") || "Please note that your [lowertext(name)] does NOT give you any additional right to attack people or cause chaos." + // I don't /think/ we'll need this, but for newbies who think "roleplay as insane" = "license to kill", it's probably a good thing to have. + to_chat(quirk_holder, span_big(span_info(rds_policy))) + +/datum/quirk/insanity/remove() + QDEL_NULL(added_trama_ref) diff --git a/code/datums/quirks/negative_quirks/junkie.dm b/code/datums/quirks/negative_quirks/junkie.dm new file mode 100644 index 00000000000..269f6d2d96e --- /dev/null +++ b/code/datums/quirks/negative_quirks/junkie.dm @@ -0,0 +1,216 @@ +/datum/quirk/item_quirk/junkie + name = "Junkie" + desc = "You can't get enough of hard drugs." + icon = FA_ICON_PILLS + value = -6 + gain_text = span_danger("You suddenly feel the craving for drugs.") + medical_record_text = "Patient has a history of hard drugs." + hardcore_value = 4 + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_PROCESSES + mail_goodies = list(/obj/effect/spawner/random/contraband/narcotics) + var/drug_list = list(/datum/reagent/drug/blastoff, /datum/reagent/drug/krokodil, /datum/reagent/medicine/morphine, /datum/reagent/drug/happiness, /datum/reagent/drug/methamphetamine) //List of possible IDs + var/datum/reagent/reagent_type //!If this is defined, reagent_id will be unused and the defined reagent type will be instead. + var/datum/reagent/reagent_instance //! actual instanced version of the reagent + var/where_drug //! Where the drug spawned + var/obj/item/drug_container_type //! If this is defined before pill generation, pill generation will be skipped. This is the type of the pill bottle. + var/where_accessory //! where the accessory spawned + var/obj/item/accessory_type //! If this is null, an accessory won't be spawned. + var/process_interval = 30 SECONDS //! how frequently the quirk processes + var/next_process = 0 //! ticker for processing + var/drug_flavour_text = "Better hope you don't run out..." + +/datum/quirk/item_quirk/junkie/add_unique(client/client_source) + var/mob/living/carbon/human/human_holder = quirk_holder + + if(!reagent_type) + reagent_type = pick(drug_list) + + reagent_instance = new reagent_type() + + for(var/addiction in reagent_instance.addiction_types) + human_holder.last_mind?.add_addiction_points(addiction, 1000) + + var/current_turf = get_turf(quirk_holder) + + if(!drug_container_type) + drug_container_type = /obj/item/storage/pill_bottle + + var/obj/item/drug_instance = new drug_container_type(current_turf) + if(istype(drug_instance, /obj/item/storage/pill_bottle)) + var/pill_state = "pill[rand(1,20)]" + for(var/i in 1 to 7) + var/obj/item/reagent_containers/pill/pill = new(drug_instance) + pill.icon_state = pill_state + pill.reagents.add_reagent(reagent_type, 3) + + give_item_to_holder( + drug_instance, + list( + LOCATION_LPOCKET = ITEM_SLOT_LPOCKET, + LOCATION_RPOCKET = ITEM_SLOT_RPOCKET, + LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, + LOCATION_HANDS = ITEM_SLOT_HANDS, + ), + flavour_text = drug_flavour_text, + ) + + if(accessory_type) + give_item_to_holder( + accessory_type, + list( + LOCATION_LPOCKET = ITEM_SLOT_LPOCKET, + LOCATION_RPOCKET = ITEM_SLOT_RPOCKET, + LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, + LOCATION_HANDS = ITEM_SLOT_HANDS, + ) + ) + +/datum/quirk/item_quirk/junkie/remove() + if(quirk_holder && reagent_instance) + for(var/addiction_type in subtypesof(/datum/addiction)) + quirk_holder.mind.remove_addiction_points(addiction_type, MAX_ADDICTION_POINTS) + +/datum/quirk/item_quirk/junkie/process(seconds_per_tick) + if(HAS_TRAIT(quirk_holder, TRAIT_LIVERLESS_METABOLISM)) + return + var/mob/living/carbon/human/human_holder = quirk_holder + if(world.time > next_process) + next_process = world.time + process_interval + var/deleted = QDELETED(reagent_instance) + var/missing_addiction = FALSE + for(var/addiction_type in reagent_instance.addiction_types) + if(!LAZYACCESS(human_holder.last_mind?.active_addictions, addiction_type)) + missing_addiction = TRUE + if(deleted || missing_addiction) + if(deleted) + reagent_instance = new reagent_type() + to_chat(quirk_holder, span_danger("You thought you kicked it, but you feel like you're falling back onto bad habits..")) + for(var/addiction in reagent_instance.addiction_types) + human_holder.last_mind?.add_addiction_points(addiction, 1000) ///Max that shit out + +/datum/quirk/item_quirk/junkie/smoker + name = "Smoker" + desc = "Sometimes you just really want a smoke. Probably not great for your lungs." + icon = FA_ICON_SMOKING + value = -4 + gain_text = span_danger("You could really go for a smoke right about now.") + lose_text = span_notice("You don't feel nearly as hooked to nicotine anymore.") + medical_record_text = "Patient is a current smoker." + reagent_type = /datum/reagent/drug/nicotine + accessory_type = /obj/item/lighter/greyscale + mob_trait = TRAIT_SMOKER + hardcore_value = 1 + drug_flavour_text = "Make sure you get your favorite brand when you run out." + mail_goodies = list( + /obj/effect/spawner/random/entertainment/cigarette_pack, + /obj/effect/spawner/random/entertainment/cigar, + /obj/effect/spawner/random/entertainment/lighter, + /obj/item/clothing/mask/cigarette/pipe, + ) + +/datum/quirk/item_quirk/junkie/smoker/New() + drug_container_type = pick(/obj/item/storage/fancy/cigarettes, + /obj/item/storage/fancy/cigarettes/cigpack_midori, + /obj/item/storage/fancy/cigarettes/cigpack_uplift, + /obj/item/storage/fancy/cigarettes/cigpack_robust, + /obj/item/storage/fancy/cigarettes/cigpack_robustgold, + /obj/item/storage/fancy/cigarettes/cigpack_carp) + + return ..() + +/datum/quirk/item_quirk/junkie/smoker/post_add() + . = ..() + quirk_holder.add_mob_memory(/datum/memory/key/quirk_smoker, protagonist = quirk_holder, preferred_brand = initial(drug_container_type.name)) + // smoker lungs have 25% less health and healing + var/mob/living/carbon/carbon_holder = quirk_holder + var/obj/item/organ/internal/lungs/smoker_lungs = null + var/obj/item/organ/internal/lungs/old_lungs = carbon_holder.get_organ_slot(ORGAN_SLOT_LUNGS) + if(old_lungs && IS_ORGANIC_ORGAN(old_lungs)) + if(isplasmaman(carbon_holder)) + smoker_lungs = /obj/item/organ/internal/lungs/plasmaman/plasmaman_smoker + else if(isethereal(carbon_holder)) + smoker_lungs = /obj/item/organ/internal/lungs/ethereal/ethereal_smoker + else + smoker_lungs = /obj/item/organ/internal/lungs/smoker_lungs + if(!isnull(smoker_lungs)) + smoker_lungs = new smoker_lungs + smoker_lungs.Insert(carbon_holder, special = TRUE, drop_if_replaced = FALSE) + +/datum/quirk/item_quirk/junkie/smoker/process(seconds_per_tick) + . = ..() + var/mob/living/carbon/human/human_holder = quirk_holder + var/obj/item/mask_item = human_holder.get_item_by_slot(ITEM_SLOT_MASK) + if(istype(mask_item, /obj/item/clothing/mask/cigarette)) + var/obj/item/storage/fancy/cigarettes/cigarettes = drug_container_type + if(istype(mask_item, initial(cigarettes.spawn_type))) + quirk_holder.clear_mood_event("wrong_cigs") + else + quirk_holder.add_mood_event("wrong_cigs", /datum/mood_event/wrong_brand) + +/datum/quirk/item_quirk/junkie/alcoholic + name = "Alcoholic" + desc = "You just can't live without alcohol. Your liver is a machine that turns ethanol into acetaldehyde." + icon = FA_ICON_WINE_GLASS + value = -4 + gain_text = span_danger("You really need a drink.") + lose_text = span_notice("Alcohol doesn't seem nearly as enticing anymore.") + medical_record_text = "Patient is an alcoholic." + reagent_type = /datum/reagent/consumable/ethanol + drug_container_type = /obj/item/reagent_containers/cup/glass/bottle/whiskey + mob_trait = TRAIT_HEAVY_DRINKER + hardcore_value = 1 + drug_flavour_text = "Make sure you get your favorite type of drink when you run out." + mail_goodies = list( + /obj/effect/spawner/random/food_or_drink/booze, + /obj/item/book/bible/booze, + ) + /// Cached typepath of the owner's favorite alcohol reagent + var/datum/reagent/consumable/ethanol/favorite_alcohol + +/datum/quirk/item_quirk/junkie/alcoholic/New() + drug_container_type = pick( + /obj/item/reagent_containers/cup/glass/bottle/whiskey, + /obj/item/reagent_containers/cup/glass/bottle/vodka, + /obj/item/reagent_containers/cup/glass/bottle/ale, + /obj/item/reagent_containers/cup/glass/bottle/beer, + /obj/item/reagent_containers/cup/glass/bottle/hcider, + /obj/item/reagent_containers/cup/glass/bottle/wine, + /obj/item/reagent_containers/cup/glass/bottle/sake, + ) + + return ..() + +/datum/quirk/item_quirk/junkie/alcoholic/post_add() + . = ..() + RegisterSignal(quirk_holder, COMSIG_MOB_REAGENT_CHECK, PROC_REF(check_brandy)) + + var/obj/item/reagent_containers/brandy_container = GLOB.alcohol_containers[drug_container_type] + if(isnull(brandy_container)) + stack_trace("Alcoholic quirk added while the GLOB.alcohol_containers is (somehow) not initialized!") + brandy_container = new drug_container_type + favorite_alcohol = brandy_container.list_reagents[1] + qdel(brandy_container) + else + favorite_alcohol = brandy_container.list_reagents[1] + + quirk_holder.add_mob_memory(/datum/memory/key/quirk_alcoholic, protagonist = quirk_holder, preferred_brandy = initial(favorite_alcohol.name)) + // alcoholic livers have 25% less health and healing + var/obj/item/organ/internal/liver/alcohol_liver = quirk_holder.get_organ_slot(ORGAN_SLOT_LIVER) + if(alcohol_liver && IS_ORGANIC_ORGAN(alcohol_liver)) // robotic livers aren't affected + alcohol_liver.maxHealth = alcohol_liver.maxHealth * 0.75 + alcohol_liver.healing_factor = alcohol_liver.healing_factor * 0.75 + +/datum/quirk/item_quirk/junkie/alcoholic/remove() + UnregisterSignal(quirk_holder, COMSIG_MOB_REAGENT_CHECK) + +/datum/quirk/item_quirk/junkie/alcoholic/proc/check_brandy(mob/source, datum/reagent/booze) + SIGNAL_HANDLER + + //we don't care if it is not alcohol + if(!istype(booze, /datum/reagent/consumable/ethanol)) + return + + if(istype(booze, favorite_alcohol)) + quirk_holder.clear_mood_event("wrong_alcohol") + else + quirk_holder.add_mood_event("wrong_alcohol", /datum/mood_event/wrong_brandy) diff --git a/code/datums/quirks/negative_quirks/light_drinker.dm b/code/datums/quirks/negative_quirks/light_drinker.dm new file mode 100644 index 00000000000..5f82e2b9cd7 --- /dev/null +++ b/code/datums/quirks/negative_quirks/light_drinker.dm @@ -0,0 +1,11 @@ +/datum/quirk/light_drinker + name = "Light Drinker" + desc = "You just can't handle your drinks and get drunk very quickly." + icon = FA_ICON_COCKTAIL + value = -2 + mob_trait = TRAIT_LIGHT_DRINKER + gain_text = span_notice("Just the thought of drinking alcohol makes your head spin.") + lose_text = span_danger("You're no longer severely affected by alcohol.") + medical_record_text = "Patient demonstrates a low tolerance for alcohol. (Wimp)" + hardcore_value = 3 + mail_goodies = list(/obj/item/reagent_containers/cup/glass/waterbottle) diff --git a/code/datums/quirks/negative_quirks/mute.dm b/code/datums/quirks/negative_quirks/mute.dm new file mode 100644 index 00000000000..44706c4d434 --- /dev/null +++ b/code/datums/quirks/negative_quirks/mute.dm @@ -0,0 +1,10 @@ +/datum/quirk/mute + name = "Mute" + desc = "For some reason you are completely unable to speak." + icon = FA_ICON_VOLUME_XMARK + value = -4 + mob_trait = TRAIT_MUTE + gain_text = span_danger("You find yourself unable to speak!") + lose_text = span_notice("You feel a growing strength in your vocal chords.") + medical_record_text = "The patient is unable to use their voice in any capacity." + hardcore_value = 4 diff --git a/code/datums/quirks/negative_quirks/nearsighted.dm b/code/datums/quirks/negative_quirks/nearsighted.dm new file mode 100644 index 00000000000..6a5397b6504 --- /dev/null +++ b/code/datums/quirks/negative_quirks/nearsighted.dm @@ -0,0 +1,30 @@ +/datum/quirk/item_quirk/nearsighted + name = "Nearsighted" + desc = "You are nearsighted without prescription glasses, but spawn with a pair." + icon = FA_ICON_GLASSES + value = -4 + gain_text = span_danger("Things far away from you start looking blurry.") + lose_text = span_notice("You start seeing faraway things normally again.") + medical_record_text = "Patient requires prescription glasses in order to counteract nearsightedness." + hardcore_value = 5 + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_CHANGES_APPEARANCE + mail_goodies = list(/obj/item/clothing/glasses/regular) // extra pair if orginal one gets broken by somebody mean + +/datum/quirk/item_quirk/nearsighted/add_unique(client/client_source) + var/glasses_name = client_source?.prefs.read_preference(/datum/preference/choiced/glasses) || "Regular" + var/obj/item/clothing/glasses/glasses_type + + glasses_name = glasses_name == "Random" ? pick(GLOB.nearsighted_glasses) : glasses_name + glasses_type = GLOB.nearsighted_glasses[glasses_name] + + give_item_to_holder(glasses_type, list( + LOCATION_EYES = ITEM_SLOT_EYES, + LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, + LOCATION_HANDS = ITEM_SLOT_HANDS, + )) + +/datum/quirk/item_quirk/nearsighted/add(client/client_source) + quirk_holder.become_nearsighted(QUIRK_TRAIT) + +/datum/quirk/item_quirk/nearsighted/remove() + quirk_holder.cure_nearsighted(QUIRK_TRAIT) diff --git a/code/datums/quirks/negative_quirks/non_violent.dm b/code/datums/quirks/negative_quirks/non_violent.dm new file mode 100644 index 00000000000..e1dbb0e6480 --- /dev/null +++ b/code/datums/quirks/negative_quirks/non_violent.dm @@ -0,0 +1,11 @@ +/datum/quirk/nonviolent + name = "Pacifist" + desc = "The thought of violence makes you sick. So much so, in fact, that you can't hurt anyone." + icon = FA_ICON_PEACE + value = -8 + mob_trait = TRAIT_PACIFISM + gain_text = span_danger("You feel repulsed by the thought of violence!") + lose_text = span_notice("You think you can defend yourself again.") + medical_record_text = "Patient is unusually pacifistic and cannot bring themselves to cause physical harm." + hardcore_value = 6 + mail_goodies = list(/obj/effect/spawner/random/decoration/flower, /obj/effect/spawner/random/contraband/cannabis) // flower power diff --git a/code/datums/quirks/negative_quirks/numb.dm b/code/datums/quirks/negative_quirks/numb.dm new file mode 100644 index 00000000000..cd4f28cb302 --- /dev/null +++ b/code/datums/quirks/negative_quirks/numb.dm @@ -0,0 +1,15 @@ +/datum/quirk/numb + name = "Numb" + desc = "You can't feel pain at all." + icon = FA_ICON_STAR_OF_LIFE + value = -4 + gain_text = "You feel your body becoming numb." + lose_text = "The numbness subsides." + medical_record_text = "The patient exhibits congenital hypoesthesia, making them insensitive to pain stimuli." + hardcore_value = 4 + +/datum/quirk/numb/add(client/client_source) + quirk_holder.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + +/datum/quirk/numb/remove(client/client_source) + quirk_holder.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) diff --git a/code/datums/quirks/negative_quirks/nyctophobia.dm b/code/datums/quirks/negative_quirks/nyctophobia.dm new file mode 100644 index 00000000000..af891a2058a --- /dev/null +++ b/code/datums/quirks/negative_quirks/nyctophobia.dm @@ -0,0 +1,46 @@ +/datum/quirk/nyctophobia + name = "Nyctophobia" + desc = "As far as you can remember, you've always been afraid of the dark. While in the dark without a light source, you instinctively act careful, and constantly feel a sense of dread." + icon = FA_ICON_LIGHTBULB + value = -3 + medical_record_text = "Patient demonstrates a fear of the dark. (Seriously?)" + hardcore_value = 5 + mail_goodies = list(/obj/effect/spawner/random/engineering/flashlight) + +/datum/quirk/nyctophobia/add(client/client_source) + RegisterSignal(quirk_holder, COMSIG_MOVABLE_MOVED, PROC_REF(on_holder_moved)) + +/datum/quirk/nyctophobia/remove() + UnregisterSignal(quirk_holder, COMSIG_MOVABLE_MOVED) + quirk_holder.clear_mood_event("nyctophobia") + +/// Called when the quirk holder moves. Updates the quirk holder's mood. +/datum/quirk/nyctophobia/proc/on_holder_moved(mob/living/source, atom/old_loc, dir, forced) + SIGNAL_HANDLER + + if(quirk_holder.stat != CONSCIOUS || quirk_holder.IsSleeping() || quirk_holder.IsUnconscious()) + return + + if(HAS_TRAIT(quirk_holder, TRAIT_FEARLESS)) + return + + var/mob/living/carbon/human/human_holder = quirk_holder + + if(human_holder.dna?.species.id in list(SPECIES_SHADOW, SPECIES_NIGHTMARE)) + return + + if((human_holder.sight & SEE_TURFS) == SEE_TURFS) + return + + var/turf/holder_turf = get_turf(quirk_holder) + + var/lums = holder_turf.get_lumcount() + + if(lums > LIGHTING_TILE_IS_DARK) + quirk_holder.clear_mood_event("nyctophobia") + return + + if(quirk_holder.move_intent == MOVE_INTENT_RUN) + to_chat(quirk_holder, span_warning("Easy, easy, take it slow... you're in the dark...")) + quirk_holder.toggle_move_intent() + quirk_holder.add_mood_event("nyctophobia", /datum/mood_event/nyctophobia) diff --git a/code/datums/quirks/negative_quirks/paraplegic.dm b/code/datums/quirks/negative_quirks/paraplegic.dm new file mode 100644 index 00000000000..58e1c4ba31e --- /dev/null +++ b/code/datums/quirks/negative_quirks/paraplegic.dm @@ -0,0 +1,41 @@ +/datum/quirk/paraplegic + name = "Paraplegic" + desc = "Your legs do not function. Nothing will ever fix this. But hey, free wheelchair!" + icon = FA_ICON_WHEELCHAIR + value = -12 + gain_text = null // Handled by trauma. + lose_text = null + medical_record_text = "Patient has an untreatable impairment in motor function in the lower extremities." + hardcore_value = 15 + mail_goodies = list(/obj/vehicle/ridden/wheelchair/motorized) //yes a fullsized unfolded motorized wheelchair does fit + +/datum/quirk/paraplegic/add_unique(client/client_source) + if(quirk_holder.buckled) // Handle late joins being buckled to arrival shuttle chairs. + quirk_holder.buckled.unbuckle_mob(quirk_holder) + + var/turf/holder_turf = get_turf(quirk_holder) + var/obj/structure/chair/spawn_chair = locate() in holder_turf + + var/obj/vehicle/ridden/wheelchair/wheels + if(client_source?.get_award_status(/datum/award/score/hardcore_random) >= 5000) //More than 5k score? you unlock the gamer wheelchair. + wheels = new /obj/vehicle/ridden/wheelchair/gold(holder_turf) + else + wheels = new(holder_turf) + if(spawn_chair) // Makes spawning on the arrivals shuttle more consistent looking + wheels.setDir(spawn_chair.dir) + + wheels.buckle_mob(quirk_holder) + + // During the spawning process, they may have dropped what they were holding, due to the paralysis + // So put the things back in their hands. + for(var/obj/item/dropped_item in holder_turf) + if(dropped_item.fingerprintslast == quirk_holder.ckey) + quirk_holder.put_in_hands(dropped_item) + +/datum/quirk/paraplegic/add(client/client_source) + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic, TRAUMA_RESILIENCE_ABSOLUTE) + +/datum/quirk/paraplegic/remove() + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.cure_trauma_type(/datum/brain_trauma/severe/paralysis/paraplegic, TRAUMA_RESILIENCE_ABSOLUTE) diff --git a/code/datums/quirks/negative_quirks/photophobia.dm b/code/datums/quirks/negative_quirks/photophobia.dm new file mode 100644 index 00000000000..b543aeda076 --- /dev/null +++ b/code/datums/quirks/negative_quirks/photophobia.dm @@ -0,0 +1,75 @@ +#define MOOD_CATEGORY_PHOTOPHOBIA "photophobia" + +/datum/quirk/photophobia + name = "Photophobia" + desc = "Bright lights seem to bother you more than others. Maybe it's a medical condition." + icon = FA_ICON_ARROWS_TO_EYE + value = -4 + gain_text = span_danger("The safety of light feels off...") + lose_text = span_notice("Enlightening.") + medical_record_text = "Patient has acute phobia of light, and insists it is physically harmful." + hardcore_value = 4 + mail_goodies = list( + /obj/item/flashlight/flashdark, + /obj/item/food/grown/mushroom/glowshroom/shadowshroom, + /obj/item/skillchip/light_remover, + ) + +/datum/quirk/photophobia/add(client/client_source) + RegisterSignal(quirk_holder, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(check_eyes)) + RegisterSignal(quirk_holder, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(restore_eyes)) + RegisterSignal(quirk_holder, COMSIG_MOVABLE_MOVED, PROC_REF(on_holder_moved)) + update_eyes(quirk_holder.get_organ_slot(ORGAN_SLOT_EYES)) + +/datum/quirk/photophobia/remove() + UnregisterSignal(quirk_holder, list( + COMSIG_CARBON_GAIN_ORGAN, + COMSIG_CARBON_LOSE_ORGAN, + COMSIG_MOVABLE_MOVED,)) + quirk_holder.clear_mood_event(MOOD_CATEGORY_PHOTOPHOBIA) + var/obj/item/organ/internal/eyes/normal_eyes = quirk_holder.get_organ_slot(ORGAN_SLOT_EYES) + if(istype(normal_eyes)) + normal_eyes.flash_protect = initial(normal_eyes.flash_protect) + +/datum/quirk/photophobia/proc/check_eyes(obj/item/organ/internal/eyes/sensitive_eyes) + SIGNAL_HANDLER + if(!istype(sensitive_eyes)) + return + update_eyes(sensitive_eyes) + +/datum/quirk/photophobia/proc/update_eyes(obj/item/organ/internal/eyes/target_eyes) + if(!istype(target_eyes)) + return + target_eyes.flash_protect = max(target_eyes.flash_protect - 1, FLASH_PROTECTION_HYPER_SENSITIVE) + +/datum/quirk/photophobia/proc/restore_eyes(obj/item/organ/internal/eyes/normal_eyes) + SIGNAL_HANDLER + if(!istype(normal_eyes)) + return + normal_eyes.flash_protect = initial(normal_eyes.flash_protect) + +/datum/quirk/photophobia/proc/on_holder_moved(mob/living/source, atom/old_loc, dir, forced) + SIGNAL_HANDLER + + if(quirk_holder.stat != CONSCIOUS || quirk_holder.IsSleeping() || quirk_holder.IsUnconscious()) + return + + if(HAS_TRAIT(quirk_holder, TRAIT_FEARLESS)) + return + + var/mob/living/carbon/human/human_holder = quirk_holder + + if(human_holder.sight & SEE_TURFS) + return + + var/turf/holder_turf = get_turf(quirk_holder) + + var/lums = holder_turf.get_lumcount() + + var/eye_protection = quirk_holder.get_eye_protection() + if(lums < LIGHTING_TILE_IS_DARK || eye_protection >= FLASH_PROTECTION_NONE) + quirk_holder.clear_mood_event(MOOD_CATEGORY_PHOTOPHOBIA) + return + quirk_holder.add_mood_event(MOOD_CATEGORY_PHOTOPHOBIA, /datum/mood_event/photophobia) + + #undef MOOD_CATEGORY_PHOTOPHOBIA diff --git a/code/datums/quirks/negative_quirks/poor_aim.dm b/code/datums/quirks/negative_quirks/poor_aim.dm new file mode 100644 index 00000000000..d86feb809b0 --- /dev/null +++ b/code/datums/quirks/negative_quirks/poor_aim.dm @@ -0,0 +1,19 @@ +/datum/quirk/poor_aim + name = "Stormtrooper Aim" + desc = "You've never hit anything you were aiming for in your life." + icon = FA_ICON_BULLSEYE + value = -4 + medical_record_text = "Patient possesses a strong tremor in both hands." + hardcore_value = 3 + mail_goodies = list(/obj/item/cardboard_cutout) // for target practice + +/datum/quirk/poor_aim/add(client/client_source) + RegisterSignal(quirk_holder, COMSIG_MOB_FIRED_GUN, PROC_REF(on_mob_fired_gun)) + +/datum/quirk/poor_aim/remove(client/client_source) + UnregisterSignal(quirk_holder, COMSIG_MOB_FIRED_GUN) + +/datum/quirk/poor_aim/proc/on_mob_fired_gun(mob/user, obj/item/gun/gun_fired, target, params, zone_override, list/bonus_spread_values) + SIGNAL_HANDLER + bonus_spread_values[MIN_BONUS_SPREAD_INDEX] += 10 + bonus_spread_values[MAX_BONUS_SPREAD_INDEX] += 35 diff --git a/code/datums/quirks/negative_quirks/prosopagnosia.dm b/code/datums/quirks/negative_quirks/prosopagnosia.dm new file mode 100644 index 00000000000..8634e13bf63 --- /dev/null +++ b/code/datums/quirks/negative_quirks/prosopagnosia.dm @@ -0,0 +1,9 @@ +/datum/quirk/prosopagnosia + name = "Prosopagnosia" + desc = "You have a mental disorder that prevents you from being able to recognize faces at all." + icon = FA_ICON_USER_SECRET + value = -4 + mob_trait = TRAIT_PROSOPAGNOSIA + medical_record_text = "Patient suffers from prosopagnosia and cannot recognize faces." + hardcore_value = 5 + mail_goodies = list(/obj/item/skillchip/appraiser) // bad at recognizing faces but good at recognizing IDs diff --git a/code/datums/quirks/negative_quirks/prosthetic_limb.dm b/code/datums/quirks/negative_quirks/prosthetic_limb.dm new file mode 100644 index 00000000000..e7ea4d75788 --- /dev/null +++ b/code/datums/quirks/negative_quirks/prosthetic_limb.dm @@ -0,0 +1,33 @@ +/datum/quirk/prosthetic_limb + name = "Prosthetic Limb" + desc = "An accident caused you to lose one of your limbs. Because of this, you now have a surplus prosthetic!" + icon = "tg-prosthetic-leg" + value = -3 + hardcore_value = 3 + quirk_flags = QUIRK_HUMAN_ONLY | QUIRK_CHANGES_APPEARANCE + mail_goodies = list(/obj/item/weldingtool/mini, /obj/item/stack/cable_coil/five) + /// The slot to replace, in string form + var/slot_string = "limb" + /// the original limb from before the prosthetic was applied + var/obj/item/bodypart/old_limb + +/datum/quirk/prosthetic_limb/add_unique(client/client_source) + var/limb_type = GLOB.limb_choice[client_source?.prefs?.read_preference(/datum/preference/choiced/prosthetic)] + if(isnull(limb_type)) //Client gone or they chose a random prosthetic + limb_type = GLOB.limb_choice[pick(GLOB.limb_choice)] + + var/mob/living/carbon/human/human_holder = quirk_holder + var/obj/item/bodypart/surplus = new limb_type() + slot_string = "[surplus.plaintext_zone]" + + medical_record_text = "Patient uses a low-budget prosthetic on the [slot_string]." + old_limb = human_holder.return_and_replace_bodypart(surplus, special = TRUE) + +/datum/quirk/prosthetic_limb/post_add() + to_chat(quirk_holder, span_boldannounce("Your [slot_string] has been replaced with a surplus prosthetic. It is fragile and will easily come apart under duress. Additionally, \ + you need to use a welding tool and cables to repair it, instead of sutures and regenerative meshes.")) + +/datum/quirk/prosthetic_limb/remove() + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.del_and_replace_bodypart(old_limb, special = TRUE) + old_limb = null diff --git a/code/datums/quirks/negative_quirks/prosthetic_organ.dm b/code/datums/quirks/negative_quirks/prosthetic_organ.dm new file mode 100644 index 00000000000..6330035b5a7 --- /dev/null +++ b/code/datums/quirks/negative_quirks/prosthetic_organ.dm @@ -0,0 +1,63 @@ +/datum/quirk/prosthetic_organ + name = "Prosthetic Organ" + desc = "An accident caused you to lose one of your organs. Because of this, you now have a surplus prosthetic!" + icon = FA_ICON_LUNGS + value = -3 + medical_record_text = "During physical examination, patient was found to have a low-budget prosthetic organ. \ + Removal of these organs is known to be dangerous to the patient as well as the practitioner." + hardcore_value = 3 + mail_goodies = list(/obj/item/storage/organbox) + /// The slot to replace, in string form + var/slot_string = "organ" + /// The original organ from before the prosthetic was applied + var/obj/item/organ/old_organ + +/datum/quirk/prosthetic_organ/add_unique(client/client_source) + var/mob/living/carbon/human/human_holder = quirk_holder + var/static/list/organ_slots = list( + ORGAN_SLOT_HEART, + ORGAN_SLOT_LUNGS, + ORGAN_SLOT_LIVER, + ORGAN_SLOT_STOMACH, + ) + var/list/possible_organ_slots = organ_slots.Copy() + if(HAS_TRAIT(human_holder, TRAIT_NOBLOOD)) + possible_organ_slots -= ORGAN_SLOT_HEART + if(HAS_TRAIT(human_holder, TRAIT_NOBREATH)) + possible_organ_slots -= ORGAN_SLOT_LUNGS + if(HAS_TRAIT(human_holder, TRAIT_LIVERLESS_METABOLISM)) + possible_organ_slots -= ORGAN_SLOT_LIVER + if(HAS_TRAIT(human_holder, TRAIT_NOHUNGER)) + possible_organ_slots -= ORGAN_SLOT_STOMACH + if(!length(organ_slots)) //what the hell + return + var/organ_slot = pick(possible_organ_slots) + var/obj/item/organ/prosthetic + switch(organ_slot) + if(ORGAN_SLOT_HEART) + prosthetic = new /obj/item/organ/internal/heart/cybernetic/surplus + slot_string = "heart" + if(ORGAN_SLOT_LUNGS) + prosthetic = new /obj/item/organ/internal/lungs/cybernetic/surplus + slot_string = "lungs" + if(ORGAN_SLOT_LIVER) + prosthetic = new /obj/item/organ/internal/liver/cybernetic/surplus + slot_string = "liver" + if(ORGAN_SLOT_STOMACH) + prosthetic = new /obj/item/organ/internal/stomach/cybernetic/surplus + slot_string = "stomach" + medical_record_text = "During physical examination, patient was found to have a low-budget prosthetic [slot_string]. \ + Removal of these organs is known to be dangerous to the patient as well as the practitioner." + old_organ = human_holder.get_organ_slot(organ_slot) + if(prosthetic.Insert(human_holder, special = TRUE, drop_if_replaced = TRUE)) + old_organ.moveToNullspace() + STOP_PROCESSING(SSobj, old_organ) + +/datum/quirk/prosthetic_organ/post_add() + to_chat(quirk_holder, span_boldannounce("Your [slot_string] has been replaced with a surplus organ. It is fragile and will easily come apart under duress. \ + Additionally, any EMP will make it stop working entirely.")) + +/datum/quirk/prosthetic_organ/remove() + if(old_organ) + old_organ.Insert(quirk_holder, special = TRUE) + old_organ = null diff --git a/code/datums/quirks/negative_quirks/pushover.dm b/code/datums/quirks/negative_quirks/pushover.dm new file mode 100644 index 00000000000..663d8173759 --- /dev/null +++ b/code/datums/quirks/negative_quirks/pushover.dm @@ -0,0 +1,11 @@ +/datum/quirk/pushover + name = "Pushover" + desc = "Your first instinct is always to let people push you around. Resisting out of grabs will take conscious effort." + icon = FA_ICON_HANDSHAKE + value = -8 + mob_trait = TRAIT_GRABWEAKNESS + gain_text = span_danger("You feel like a pushover.") + lose_text = span_notice("You feel like standing up for yourself.") + medical_record_text = "Patient presents a notably unassertive personality and is easy to manipulate." + hardcore_value = 4 + mail_goodies = list(/obj/item/clothing/gloves/cargo_gauntlet) diff --git a/code/datums/quirks/negative_quirks/quadruple_amputee.dm b/code/datums/quirks/negative_quirks/quadruple_amputee.dm new file mode 100644 index 00000000000..493cdf0b71c --- /dev/null +++ b/code/datums/quirks/negative_quirks/quadruple_amputee.dm @@ -0,0 +1,20 @@ +/datum/quirk/quadruple_amputee + name = "Quadruple Amputee" + desc = "Oops! All Prosthetics! Due to some truly cruel cosmic punishment, all your limbs have been replaced with surplus prosthetics." + icon = "tg-prosthetic-full" + value = -6 + medical_record_text = "During physical examination, patient was found to have all low-budget prosthetic limbs." + hardcore_value = 6 + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_CHANGES_APPEARANCE + mail_goodies = list(/obj/item/weldingtool/mini, /obj/item/stack/cable_coil/five) + +/datum/quirk/quadruple_amputee/add_unique(client/client_source) + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.del_and_replace_bodypart(new /obj/item/bodypart/arm/left/robot/surplus, special = TRUE) + human_holder.del_and_replace_bodypart(new /obj/item/bodypart/arm/right/robot/surplus, special = TRUE) + human_holder.del_and_replace_bodypart(new /obj/item/bodypart/leg/left/robot/surplus, special = TRUE) + human_holder.del_and_replace_bodypart(new /obj/item/bodypart/leg/right/robot/surplus, special = TRUE) + +/datum/quirk/quadruple_amputee/post_add() + to_chat(quirk_holder, span_boldannounce("All your limbs have been replaced with surplus prosthetics. They are fragile and will easily come apart under duress. \ + Additionally, you need to use a welding tool and cables to repair them, instead of bruise packs and ointment.")) diff --git a/code/datums/quirks/negative_quirks/social_anxiety.dm b/code/datums/quirks/negative_quirks/social_anxiety.dm new file mode 100644 index 00000000000..3d140bd80a0 --- /dev/null +++ b/code/datums/quirks/negative_quirks/social_anxiety.dm @@ -0,0 +1,114 @@ +/datum/quirk/social_anxiety + name = "Social Anxiety" + desc = "Talking to people is very difficult for you, and you often stutter or even lock up." + icon = FA_ICON_COMMENT_SLASH + value = -3 + gain_text = span_danger("You start worrying about what you're saying.") + lose_text = span_notice("You feel easier about talking again.") //if only it were that easy! + medical_record_text = "Patient is usually anxious in social encounters and prefers to avoid them." + hardcore_value = 4 + mob_trait = TRAIT_ANXIOUS + mail_goodies = list(/obj/item/storage/pill_bottle/psicodine) + var/dumb_thing = TRUE + +/datum/quirk/social_anxiety/add(client/client_source) + RegisterSignal(quirk_holder, COMSIG_MOB_EYECONTACT, PROC_REF(eye_contact)) + RegisterSignal(quirk_holder, COMSIG_MOB_EXAMINATE, PROC_REF(looks_at_floor)) + RegisterSignal(quirk_holder, COMSIG_MOB_SAY, PROC_REF(handle_speech)) + +/datum/quirk/social_anxiety/remove() + UnregisterSignal(quirk_holder, list(COMSIG_MOB_EYECONTACT, COMSIG_MOB_EXAMINATE, COMSIG_MOB_SAY)) + +/datum/quirk/social_anxiety/proc/handle_speech(datum/source, list/speech_args) + SIGNAL_HANDLER + + if(HAS_TRAIT(quirk_holder, TRAIT_FEARLESS)) + return + + var/moodmod + if(quirk_holder.mob_mood) + moodmod = (1+0.02*(50-(max(50, quirk_holder.mob_mood.mood_level*(7-quirk_holder.mob_mood.sanity_level))))) //low sanity levels are better, they max at 6 + else + moodmod = (1+0.02*(50-(max(50, 0.1*quirk_holder.nutrition)))) + var/nearby_people = 0 + for(var/mob/living/carbon/human/H in oview(3, quirk_holder)) + if(H.client) + nearby_people++ + var/message = speech_args[SPEECH_MESSAGE] + if(message) + var/list/message_split = splittext(message, " ") + var/list/new_message = list() + var/mob/living/carbon/human/quirker = quirk_holder + for(var/word in message_split) + if(prob(max(5,(nearby_people*12.5*moodmod))) && word != message_split[1]) //Minimum 1/20 chance of filler + new_message += pick("uh,","erm,","um,") + if(prob(min(5,(0.05*(nearby_people*12.5)*moodmod)))) //Max 1 in 20 chance of cutoff after a successful filler roll, for 50% odds in a 15 word sentence + quirker.set_silence_if_lower(6 SECONDS) + to_chat(quirker, span_danger("You feel self-conscious and stop talking. You need a moment to recover!")) + break + if(prob(max(5,(nearby_people*12.5*moodmod)))) //Minimum 1/20 chance of stutter + // Add a short stutter, THEN treat our word + quirker.adjust_stutter(0.5 SECONDS) + var/list/message_data = quirker.treat_message(word, capitalize_message = FALSE) + new_message += message_data["message"] + else + new_message += word + + message = jointext(new_message, " ") + var/mob/living/carbon/human/quirker = quirk_holder + if(prob(min(50,(0.50*(nearby_people*12.5)*moodmod)))) //Max 50% chance of not talking + if(dumb_thing) + to_chat(quirker, span_userdanger("You think of a dumb thing you said a long time ago and scream internally.")) + dumb_thing = FALSE //only once per life + if(prob(1)) + new/obj/item/food/spaghetti/pastatomato(get_turf(quirker)) //now that's what I call spaghetti code + else + to_chat(quirk_holder, span_warning("You think that wouldn't add much to the conversation and decide not to say it.")) + if(prob(min(25,(0.25*(nearby_people*12.75)*moodmod)))) //Max 25% chance of silence stacks after successful not talking roll + to_chat(quirker, span_danger("You retreat into yourself. You really don't feel up to talking.")) + quirker.set_silence_if_lower(10 SECONDS) + + speech_args[SPEECH_MESSAGE] = pick("Uh.","Erm.","Um.") + else + speech_args[SPEECH_MESSAGE] = message + +// small chance to make eye contact with inanimate objects/mindless mobs because of nerves +/datum/quirk/social_anxiety/proc/looks_at_floor(datum/source, atom/A) + SIGNAL_HANDLER + + var/mob/living/mind_check = A + if(prob(85) || (istype(mind_check) && mind_check.mind)) + return + + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(to_chat), quirk_holder, span_smallnotice("You make eye contact with [A].")), 3) + +/datum/quirk/social_anxiety/proc/eye_contact(datum/source, mob/living/other_mob, triggering_examiner) + SIGNAL_HANDLER + + if(prob(75)) + return + var/msg + if(triggering_examiner) + msg = "You make eye contact with [other_mob], " + else + msg = "[other_mob] makes eye contact with you, " + + switch(rand(1,3)) + if(1) + quirk_holder.set_jitter_if_lower(20 SECONDS) + msg += "causing you to start fidgeting!" + if(2) + quirk_holder.set_stutter_if_lower(6 SECONDS) + msg += "causing you to start stuttering!" + if(3) + quirk_holder.Stun(2 SECONDS) + msg += "causing you to freeze up!" + + quirk_holder.add_mood_event("anxiety_eyecontact", /datum/mood_event/anxiety_eyecontact) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(to_chat), quirk_holder, span_userdanger("[msg]")), 3) // so the examine signal has time to fire and this will print after + return COMSIG_BLOCK_EYECONTACT + +/datum/mood_event/anxiety_eyecontact + description = "Sometimes eye contact makes me so nervous..." + mood_change = -5 + timeout = 3 MINUTES diff --git a/code/datums/quirks/negative_quirks/softspoken.dm b/code/datums/quirks/negative_quirks/softspoken.dm new file mode 100644 index 00000000000..41be5f1aca0 --- /dev/null +++ b/code/datums/quirks/negative_quirks/softspoken.dm @@ -0,0 +1,9 @@ +/datum/quirk/softspoken + name = "Soft-Spoken" + desc = "You are soft-spoken, and your voice is hard to hear." + icon = FA_ICON_COMMENT + value = -2 + mob_trait = TRAIT_SOFTSPOKEN + gain_text = span_danger("You feel like you're speaking more quietly.") + lose_text = span_notice("You feel like you're speaking louder.") + medical_record_text = "Patient is soft-spoken and difficult to hear." diff --git a/code/datums/quirks/negative_quirks/tin_man.dm b/code/datums/quirks/negative_quirks/tin_man.dm new file mode 100644 index 00000000000..5a4ab4b1357 --- /dev/null +++ b/code/datums/quirks/negative_quirks/tin_man.dm @@ -0,0 +1,37 @@ +/datum/quirk/tin_man + name = "Tin Man" + desc = "Oops! All Prosthetics! Due to some truly cruel cosmic punishment, most of your internal organs have been replaced with surplus prosthetics." + icon = FA_ICON_ROBOT + value = -6 + medical_record_text = "During physical examination, patient was found to have numerous low-budget prosthetic internal organs. \ + Removal of these organs is known to be dangerous to the patient as well as the practitioner." + hardcore_value = 6 + mail_goodies = list(/obj/item/storage/organbox) + +/datum/quirk/tin_man/add_unique(client/client_source) + var/mob/living/carbon/human/human_holder = quirk_holder + var/static/list/organ_slots = list( + ORGAN_SLOT_HEART = /obj/item/organ/internal/heart/cybernetic/surplus, + ORGAN_SLOT_LUNGS = /obj/item/organ/internal/lungs/cybernetic/surplus, + ORGAN_SLOT_LIVER = /obj/item/organ/internal/liver/cybernetic/surplus, + ORGAN_SLOT_STOMACH = /obj/item/organ/internal/stomach/cybernetic/surplus, + ) + var/list/possible_organ_slots = organ_slots.Copy() + if(HAS_TRAIT(human_holder, TRAIT_NOBLOOD)) + possible_organ_slots -= ORGAN_SLOT_HEART + if(HAS_TRAIT(human_holder, TRAIT_NOBREATH)) + possible_organ_slots -= ORGAN_SLOT_LUNGS + if(HAS_TRAIT(human_holder, TRAIT_LIVERLESS_METABOLISM)) + possible_organ_slots -= ORGAN_SLOT_LIVER + if(HAS_TRAIT(human_holder, TRAIT_NOHUNGER)) + possible_organ_slots -= ORGAN_SLOT_STOMACH + if(!length(organ_slots)) //what the hell + return + for(var/organ_slot in possible_organ_slots) + var/organ_path = possible_organ_slots[organ_slot] + var/obj/item/organ/new_organ = new organ_path() + new_organ.Insert(human_holder, special = TRUE, drop_if_replaced = FALSE) + +/datum/quirk/tin_man/post_add() + to_chat(quirk_holder, span_boldannounce("Most of your internal organs have been replaced with surplus prosthetics. They are fragile and will easily come apart under duress. \ + Additionally, any EMP will make them stop working entirely.")) diff --git a/code/datums/quirks/negative_quirks/unstable.dm b/code/datums/quirks/negative_quirks/unstable.dm new file mode 100644 index 00000000000..5d39776eeba --- /dev/null +++ b/code/datums/quirks/negative_quirks/unstable.dm @@ -0,0 +1,11 @@ +/datum/quirk/unstable + name = "Unstable" + desc = "Due to past troubles, you are unable to recover your sanity if you lose it. Be very careful managing your mood!" + icon = FA_ICON_ANGRY + value = -10 + mob_trait = TRAIT_UNSTABLE + gain_text = span_danger("There's a lot on your mind right now.") + lose_text = span_notice("Your mind finally feels calm.") + medical_record_text = "Patient's mind is in a vulnerable state, and cannot recover from traumatic events." + hardcore_value = 9 + mail_goodies = list(/obj/effect/spawner/random/entertainment/plushie) diff --git a/code/datums/quirks/neutral_quirks/bald.dm b/code/datums/quirks/neutral_quirks/bald.dm new file mode 100644 index 00000000000..8a760f6ceef --- /dev/null +++ b/code/datums/quirks/neutral_quirks/bald.dm @@ -0,0 +1,53 @@ +/datum/quirk/item_quirk/bald + name = "Smooth-Headed" + desc = "You have no hair and are quite insecure about it! Keep your wig on, or at least your head covered up." + icon = FA_ICON_EGG + value = 0 + mob_trait = TRAIT_BALD + gain_text = span_notice("Your head is as smooth as can be, it's terrible.") + lose_text = span_notice("Your head itches, could it be... growing hair?!") + medical_record_text = "Patient starkly refused to take off headwear during examination." + mail_goodies = list(/obj/item/clothing/head/wig/random) + /// The user's starting hairstyle + var/old_hair + +/datum/quirk/item_quirk/bald/add(client/client_source) + var/mob/living/carbon/human/human_holder = quirk_holder + old_hair = human_holder.hairstyle + human_holder.set_hairstyle("Bald", update = TRUE) + RegisterSignal(human_holder, COMSIG_CARBON_EQUIP_HAT, PROC_REF(equip_hat)) + RegisterSignal(human_holder, COMSIG_CARBON_UNEQUIP_HAT, PROC_REF(unequip_hat)) + +/datum/quirk/item_quirk/bald/add_unique(client/client_source) + var/obj/item/clothing/head/wig/natural/baldie_wig = new(get_turf(quirk_holder)) + if(old_hair == "Bald") + baldie_wig.hairstyle = pick(GLOB.hairstyles_list - "Bald") + else + baldie_wig.hairstyle = old_hair + + baldie_wig.update_appearance() + + give_item_to_holder(baldie_wig, list(LOCATION_HEAD = ITEM_SLOT_HEAD, LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) + +/datum/quirk/item_quirk/bald/remove() + . = ..() + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.hairstyle = old_hair + human_holder.update_body_parts() + UnregisterSignal(human_holder, list(COMSIG_CARBON_EQUIP_HAT, COMSIG_CARBON_UNEQUIP_HAT)) + human_holder.clear_mood_event("bad_hair_day") + +///Checks if the headgear equipped is a wig and sets the mood event accordingly +/datum/quirk/item_quirk/bald/proc/equip_hat(mob/user, obj/item/hat) + SIGNAL_HANDLER + + if(istype(hat, /obj/item/clothing/head/wig)) + quirk_holder.add_mood_event("bad_hair_day", /datum/mood_event/confident_mane) //Our head is covered, but also by a wig so we're happy. + else + quirk_holder.clear_mood_event("bad_hair_day") //Our head is covered + +///Applies a bad moodlet for having an uncovered head +/datum/quirk/item_quirk/bald/proc/unequip_hat(mob/user, obj/item/clothing, force, newloc, no_move, invdrop, silent) + SIGNAL_HANDLER + + quirk_holder.add_mood_event("bad_hair_day", /datum/mood_event/bald) diff --git a/code/datums/quirks/neutral_quirks/colorist.dm b/code/datums/quirks/neutral_quirks/colorist.dm new file mode 100644 index 00000000000..f82fd5bf6fe --- /dev/null +++ b/code/datums/quirks/neutral_quirks/colorist.dm @@ -0,0 +1,13 @@ +/* SKYRAT EDIT REMOVAL +/datum/quirk/item_quirk/colorist + name = "Colorist" + desc = "You like carrying around a hair dye spray to quickly apply color patterns to your hair." + icon = FA_ICON_FILL_DRIP + value = 0 + medical_record_text = "Patient enjoys dyeing their hair with pretty colors." + mail_goodies = list(/obj/item/dyespray) + +/datum/quirk/item_quirk/colorist/add_unique(client/client_source) + give_item_to_holder(/obj/item/dyespray, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) +*/ +//SKYRAT EDIT REMOVAL diff --git a/code/datums/quirks/neutral_quirks/deviant_tastes.dm b/code/datums/quirks/neutral_quirks/deviant_tastes.dm new file mode 100644 index 00000000000..566b469c7a7 --- /dev/null +++ b/code/datums/quirks/neutral_quirks/deviant_tastes.dm @@ -0,0 +1,24 @@ +/datum/quirk/deviant_tastes + name = "Deviant Tastes" + desc = "You dislike food that most people enjoy, and find delicious what they don't." + icon = FA_ICON_GRIN_TONGUE_SQUINT + value = 0 + gain_text = span_notice("You start craving something that tastes strange.") + lose_text = span_notice("You feel like eating normal food again.") + medical_record_text = "Patient demonstrates irregular nutrition preferences." + mail_goodies = list(/obj/item/food/urinalcake, /obj/item/food/badrecipe) // Mhhhmmm yummy + +/datum/quirk/deviant_tastes/add(client/client_source) + var/obj/item/organ/internal/tongue/tongue = quirk_holder.get_organ_slot(ORGAN_SLOT_TONGUE) + if(!tongue) + return + var/liked_foodtypes = tongue.liked_foodtypes + tongue.liked_foodtypes = tongue.disliked_foodtypes + tongue.disliked_foodtypes = liked_foodtypes + +/datum/quirk/deviant_tastes/remove() + var/obj/item/organ/internal/tongue/tongue = quirk_holder.get_organ_slot(ORGAN_SLOT_TONGUE) + if(!tongue) + return + tongue.liked_foodtypes = initial(tongue.liked_foodtypes) + tongue.disliked_foodtypes = initial(tongue.disliked_foodtypes) diff --git a/code/datums/quirks/neutral_quirks/extrovert.dm b/code/datums/quirks/neutral_quirks/extrovert.dm new file mode 100644 index 00000000000..5622956ba5b --- /dev/null +++ b/code/datums/quirks/neutral_quirks/extrovert.dm @@ -0,0 +1,10 @@ +/datum/quirk/extrovert + name = "Extrovert" + desc = "You are energized by talking to others, and enjoy spending your free time in the bar." + icon = FA_ICON_USERS + value = 0 + mob_trait = TRAIT_EXTROVERT + gain_text = span_notice("You feel like hanging out with other people.") + lose_text = span_danger("You feel like you're over the bar scene.") + medical_record_text = "Patient will not shut the hell up." + mail_goodies = list(/obj/item/reagent_containers/cup/glass/flask) diff --git a/code/datums/quirks/neutral_quirks/foreigner.dm b/code/datums/quirks/neutral_quirks/foreigner.dm new file mode 100644 index 00000000000..da317a7e66a --- /dev/null +++ b/code/datums/quirks/neutral_quirks/foreigner.dm @@ -0,0 +1,21 @@ +/datum/quirk/foreigner + name = "Foreigner" + desc = "You're not from around here. You don't know Galactic Common!" + icon = FA_ICON_LANGUAGE + value = 0 + gain_text = span_notice("The words being spoken around you don't make any sense.") + lose_text = span_notice("You've developed fluency in Galactic Common.") + medical_record_text = "Patient does not speak Galactic Common and may require an interpreter." + mail_goodies = list(/obj/item/taperecorder) // for translation + +/datum/quirk/foreigner/add(client/client_source) + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.add_blocked_language(/datum/language/common) + if(ishumanbasic(human_holder)) + human_holder.grant_language(/datum/language/uncommon, source = LANGUAGE_QUIRK) + +/datum/quirk/foreigner/remove() + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.remove_blocked_language(/datum/language/common) + if(ishumanbasic(human_holder)) + human_holder.remove_language(/datum/language/uncommon) diff --git a/code/datums/quirks/neutral_quirks/gamer.dm b/code/datums/quirks/neutral_quirks/gamer.dm new file mode 100644 index 00000000000..0ab2e780480 --- /dev/null +++ b/code/datums/quirks/neutral_quirks/gamer.dm @@ -0,0 +1,90 @@ +#define GAMING_WITHDRAWAL_TIME (15 MINUTES) +/datum/quirk/gamer + name = "Gamer" + desc = "You are a hardcore gamer, and you have a need to game. You love winning and hate losing. You only like gamer food." + icon = FA_ICON_GAMEPAD + value = 0 + gain_text = span_notice("You feel the sudden urge to game.") + lose_text = span_notice("You've lost all interest in gaming.") + medical_record_text = "Patient has a severe video game addiction." + mob_trait = TRAIT_GAMER + mail_goodies = list(/obj/item/toy/intento, /obj/item/clothing/head/fedora) + /// Timer for gaming withdrawal to kick in + var/gaming_withdrawal_timer = TIMER_ID_NULL + +/datum/quirk/gamer/add(client/client_source) + var/obj/item/organ/internal/tongue/tongue = quirk_holder.get_organ_slot(ORGAN_SLOT_TONGUE) + if(tongue) + // Gamer diet + tongue.liked_foodtypes = JUNKFOOD + RegisterSignal(quirk_holder, COMSIG_MOB_WON_VIDEOGAME, PROC_REF(won_game)) + RegisterSignal(quirk_holder, COMSIG_MOB_LOST_VIDEOGAME, PROC_REF(lost_game)) + RegisterSignal(quirk_holder, COMSIG_MOB_PLAYED_VIDEOGAME, PROC_REF(gamed)) + +/datum/quirk/gamer/add_unique(client/client_source) + // The gamer starts off quelled + gaming_withdrawal_timer = addtimer(CALLBACK(src, PROC_REF(enter_withdrawal)), GAMING_WITHDRAWAL_TIME, TIMER_STOPPABLE) + +/datum/quirk/gamer/remove() + var/obj/item/organ/internal/tongue/tongue = quirk_holder.get_organ_slot(ORGAN_SLOT_TONGUE) + if(tongue) + tongue.liked_foodtypes = initial(tongue.liked_foodtypes) + UnregisterSignal(quirk_holder, COMSIG_MOB_WON_VIDEOGAME) + UnregisterSignal(quirk_holder, COMSIG_MOB_LOST_VIDEOGAME) + UnregisterSignal(quirk_holder, COMSIG_MOB_PLAYED_VIDEOGAME) + +/** + * Gamer won a game + * + * Executed on the COMSIG_MOB_WON_VIDEOGAME signal + * This signal should be called whenever a player has won a video game. + * (E.g. Orion Trail) + */ +/datum/quirk/gamer/proc/won_game() + SIGNAL_HANDLER + // Epic gamer victory + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.add_mood_event("gamer_won", /datum/mood_event/gamer_won) + +/** + * Gamer lost a game + * + * Executed on the COMSIG_MOB_LOST_VIDEOGAME signal + * This signal should be called whenever a player has lost a video game. + * (E.g. Orion Trail) + */ +/datum/quirk/gamer/proc/lost_game() + SIGNAL_HANDLER + // Executed when a gamer has lost + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.add_mood_event("gamer_lost", /datum/mood_event/gamer_lost) + // Executed asynchronously due to say() + INVOKE_ASYNC(src, PROC_REF(gamer_moment)) +/** + * Gamer is playing a game + * + * Executed on the COMSIG_MOB_PLAYED_VIDEOGAME signal + * This signal should be called whenever a player interacts with a video game. + */ +/datum/quirk/gamer/proc/gamed() + SIGNAL_HANDLER + + var/mob/living/carbon/human/human_holder = quirk_holder + // Remove withdrawal malus + human_holder.clear_mood_event("gamer_withdrawal") + // Reset withdrawal timer + if (gaming_withdrawal_timer) + deltimer(gaming_withdrawal_timer) + gaming_withdrawal_timer = addtimer(CALLBACK(src, PROC_REF(enter_withdrawal)), GAMING_WITHDRAWAL_TIME, TIMER_STOPPABLE) + + +/datum/quirk/gamer/proc/gamer_moment() + // It was a heated gamer moment... + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.say(";[pick("SHIT", "PISS", "FUCK", "CUNT", "COCKSUCKER", "MOTHERFUCKER")]!!", forced = name) + +/datum/quirk/gamer/proc/enter_withdrawal() + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.add_mood_event("gamer_withdrawal", /datum/mood_event/gamer_withdrawal) + +#undef GAMING_WITHDRAWAL_TIME diff --git a/code/datums/quirks/neutral_quirks/heretochromatic.dm b/code/datums/quirks/neutral_quirks/heretochromatic.dm new file mode 100644 index 00000000000..1df079c0e45 --- /dev/null +++ b/code/datums/quirks/neutral_quirks/heretochromatic.dm @@ -0,0 +1,54 @@ +/datum/quirk/heterochromatic + name = "Heterochromatic" + desc = "One of your eyes is a different color than the other!" + icon = FA_ICON_EYE_LOW_VISION // Ignore the icon name, its actually a fairly good representation of different color eyes + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_CHANGES_APPEARANCE + value = 0 + mail_goodies = list(/obj/item/clothing/glasses/eyepatch) + +// Only your first eyes are heterochromatic +// If someone comes and says "well mr coder you can have DNA bound heterochromia so it's not unrealistic +// to allow all inserted replacement eyes to become heterochromatic or for it to transfer between mobs" +// Then just change this to [proc/add] I really don't care +/datum/quirk/heterochromatic/add_unique(client/client_source) + var/color = client_source?.prefs.read_preference(/datum/preference/color/heterochromatic) + if(!color) + return + + apply_heterochromatic_eyes(color) + +/// Applies the passed color to this mob's eyes +/datum/quirk/heterochromatic/proc/apply_heterochromatic_eyes(color) + var/mob/living/carbon/human/human_holder = quirk_holder + var/was_not_hetero = !human_holder.eye_color_heterochromatic + human_holder.eye_color_heterochromatic = TRUE + human_holder.eye_color_right = color + + var/obj/item/organ/internal/eyes/eyes_of_the_holder = quirk_holder.get_organ_by_type(/obj/item/organ/internal/eyes) + if(!eyes_of_the_holder) + return + + eyes_of_the_holder.eye_color_right = color + eyes_of_the_holder.old_eye_color_right = color + eyes_of_the_holder.refresh() + + if(was_not_hetero) + RegisterSignal(human_holder, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(check_eye_removal)) + +/datum/quirk/heterochromatic/remove() + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.eye_color_heterochromatic = FALSE + human_holder.eye_color_right = human_holder.eye_color_left + UnregisterSignal(human_holder, COMSIG_CARBON_LOSE_ORGAN) + +/datum/quirk/heterochromatic/proc/check_eye_removal(datum/source, obj/item/organ/internal/eyes/removed) + SIGNAL_HANDLER + + if(!istype(removed)) + return + + // Eyes were removed, remove heterochromia from the human holder and bid them adieu + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.eye_color_heterochromatic = FALSE + human_holder.eye_color_right = human_holder.eye_color_left + UnregisterSignal(human_holder, COMSIG_CARBON_LOSE_ORGAN) diff --git a/code/datums/quirks/neutral_quirks/introvert.dm b/code/datums/quirks/neutral_quirks/introvert.dm new file mode 100644 index 00000000000..51f6f3e785e --- /dev/null +++ b/code/datums/quirks/neutral_quirks/introvert.dm @@ -0,0 +1,10 @@ +/datum/quirk/introvert + name = "Introvert" + desc = "You are energized by having time to yourself, and enjoy spending your free time in the library." + icon = FA_ICON_BOOK_READER + value = 0 + mob_trait = TRAIT_INTROVERT + gain_text = span_notice("You feel like reading a good book quietly.") + lose_text = span_danger("You feel like libraries are boring.") + medical_record_text = "Patient doesn't seem to say much." + mail_goodies = list(/obj/item/book/random) diff --git a/code/datums/quirks/neutral_quirks/monochromatic.dm b/code/datums/quirks/neutral_quirks/monochromatic.dm new file mode 100644 index 00000000000..dd66220cb56 --- /dev/null +++ b/code/datums/quirks/neutral_quirks/monochromatic.dm @@ -0,0 +1,23 @@ +/datum/quirk/monochromatic + name = "Monochromacy" + desc = "You suffer from full colorblindness, and perceive nearly the entire world in blacks and whites." + icon = FA_ICON_ADJUST + value = 0 + medical_record_text = "Patient is afflicted with almost complete color blindness." + mail_goodies = list( // Noir detective wannabe + /obj/item/clothing/suit/jacket/det_suit/noir, + /obj/item/clothing/suit/jacket/det_suit/dark, + /obj/item/clothing/head/fedora/beige, + /obj/item/clothing/head/fedora/white, + ) + +/datum/quirk/monochromatic/add(client/client_source) + quirk_holder.add_client_colour(/datum/client_colour/monochrome) + +/datum/quirk/monochromatic/post_add() + if(is_detective_job(quirk_holder.mind.assigned_role)) + to_chat(quirk_holder, span_boldannounce("Mmm. Nothing's ever clear on this station. It's all shades of gray...")) + quirk_holder.playsound_local(quirk_holder, 'sound/ambience/ambidet1.ogg', 50, FALSE) + +/datum/quirk/monochromatic/remove() + quirk_holder.remove_client_colour(/datum/client_colour/monochrome) diff --git a/code/datums/quirks/neutral_quirks/no_taste.dm b/code/datums/quirks/neutral_quirks/no_taste.dm new file mode 100644 index 00000000000..664aaf1d9de --- /dev/null +++ b/code/datums/quirks/neutral_quirks/no_taste.dm @@ -0,0 +1,10 @@ +/datum/quirk/no_taste + name = "Ageusia" + desc = "You can't taste anything! Toxic food will still poison you." + icon = FA_ICON_MEH_BLANK + value = 0 + mob_trait = TRAIT_AGEUSIA + gain_text = span_notice("You can't taste anything!") + lose_text = span_notice("You can taste again!") + medical_record_text = "Patient suffers from ageusia and is incapable of tasting food or reagents." + mail_goodies = list(/obj/effect/spawner/random/food_or_drink/condiment) // but can you taste the salt? CAN YOU?! diff --git a/code/datums/quirks/neutral_quirks/phobia.dm b/code/datums/quirks/neutral_quirks/phobia.dm new file mode 100644 index 00000000000..224401f0670 --- /dev/null +++ b/code/datums/quirks/neutral_quirks/phobia.dm @@ -0,0 +1,20 @@ +/datum/quirk/phobia + name = "Phobia" + desc = "You are irrationally afraid of something." + icon = FA_ICON_SPIDER + value = 0 + medical_record_text = "Patient has an irrational fear of something." + mail_goodies = list(/obj/item/clothing/glasses/blindfold, /obj/item/storage/pill_bottle/psicodine) + +// Phobia will follow you between transfers +/datum/quirk/phobia/add(client/client_source) + var/phobia = client_source?.prefs.read_preference(/datum/preference/choiced/phobia) + if(!phobia) + return + + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.gain_trauma(new /datum/brain_trauma/mild/phobia(phobia), TRAUMA_RESILIENCE_ABSOLUTE) + +/datum/quirk/phobia/remove() + var/mob/living/carbon/human/human_holder = quirk_holder + human_holder.cure_trauma_type(/datum/brain_trauma/mild/phobia, TRAUMA_RESILIENCE_ABSOLUTE) diff --git a/code/datums/quirks/neutral_quirks/photographer.dm b/code/datums/quirks/neutral_quirks/photographer.dm new file mode 100644 index 00000000000..d2284df240c --- /dev/null +++ b/code/datums/quirks/neutral_quirks/photographer.dm @@ -0,0 +1,29 @@ +/datum/quirk/item_quirk/photographer + name = "Photographer" + desc = "You carry your camera and personal photo album everywhere you go, and your scrapbooks are legendary among your coworkers." + icon = FA_ICON_CAMERA + value = 0 + mob_trait = TRAIT_PHOTOGRAPHER + gain_text = span_notice("You know everything about photography.") + lose_text = span_danger("You forget how photo cameras work.") + medical_record_text = "Patient mentions photography as a stress-relieving hobby." + mail_goodies = list(/obj/item/camera_film) + +/datum/quirk/item_quirk/photographer/add_unique(client/client_source) + var/mob/living/carbon/human/human_holder = quirk_holder + var/obj/item/storage/photo_album/personal/photo_album = new(get_turf(human_holder)) + photo_album.persistence_id = "personal_[human_holder.last_mind?.key]" // this is a persistent album, the ID is tied to the account's key to avoid tampering + photo_album.persistence_load() + photo_album.name = "[human_holder.real_name]'s photo album" + + give_item_to_holder(photo_album, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) + give_item_to_holder( + /obj/item/camera, + list( + LOCATION_NECK = ITEM_SLOT_NECK, + LOCATION_LPOCKET = ITEM_SLOT_LPOCKET, + LOCATION_RPOCKET = ITEM_SLOT_RPOCKET, + LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, + LOCATION_HANDS = ITEM_SLOT_HANDS + ) + ) diff --git a/code/datums/quirks/neutral_quirks/pineapple_hater.dm b/code/datums/quirks/neutral_quirks/pineapple_hater.dm new file mode 100644 index 00000000000..f17eb4224ec --- /dev/null +++ b/code/datums/quirks/neutral_quirks/pineapple_hater.dm @@ -0,0 +1,27 @@ +/datum/quirk/pineapple_hater + name = "Ananas Aversion" + desc = "You find yourself greatly detesting fruits of the ananas genus. Serious, how the hell can anyone say these things are good? And what kind of madman would even dare putting it on a pizza!?" + icon = FA_ICON_THUMBS_DOWN + value = 0 + gain_text = span_notice("You find yourself pondering what kind of idiot actually enjoys pineapples...") + lose_text = span_notice("Your feelings towards pineapples seem to return to a lukewarm state.") + medical_record_text = "Patient is correct to think that pineapple is disgusting." + mail_goodies = list( // basic pizza slices + /obj/item/food/pizzaslice/margherita, + /obj/item/food/pizzaslice/meat, + /obj/item/food/pizzaslice/mushroom, + /obj/item/food/pizzaslice/vegetable, + /obj/item/food/pizzaslice/sassysage, + ) + +/datum/quirk/pineapple_hater/add(client/client_source) + var/obj/item/organ/internal/tongue/tongue = quirk_holder.get_organ_slot(ORGAN_SLOT_TONGUE) + if(!tongue) + return + tongue.disliked_foodtypes |= PINEAPPLE + +/datum/quirk/pineapple_hater/remove() + var/obj/item/organ/internal/tongue/tongue = quirk_holder.get_organ_slot(ORGAN_SLOT_TONGUE) + if(!tongue) + return + tongue.disliked_foodtypes = initial(tongue.disliked_foodtypes) diff --git a/code/datums/quirks/neutral_quirks/pineapple_liker.dm b/code/datums/quirks/neutral_quirks/pineapple_liker.dm new file mode 100644 index 00000000000..c342e14769c --- /dev/null +++ b/code/datums/quirks/neutral_quirks/pineapple_liker.dm @@ -0,0 +1,21 @@ +/datum/quirk/pineapple_liker + name = "Ananas Affinity" + desc = "You find yourself greatly enjoying fruits of the ananas genus. You can't seem to ever get enough of their sweet goodness!" + icon = FA_ICON_THUMBS_UP + value = 0 + gain_text = span_notice("You feel an intense craving for pineapple.") + lose_text = span_notice("Your feelings towards pineapples seem to return to a lukewarm state.") + medical_record_text = "Patient demonstrates a pathological love of pineapple." + mail_goodies = list(/obj/item/food/pizzaslice/pineapple) + +/datum/quirk/pineapple_liker/add(client/client_source) + var/obj/item/organ/internal/tongue/tongue = quirk_holder.get_organ_slot(ORGAN_SLOT_TONGUE) + if(!tongue) + return + tongue.liked_foodtypes |= PINEAPPLE + +/datum/quirk/pineapple_liker/remove() + var/obj/item/organ/internal/tongue/tongue = quirk_holder.get_organ_slot(ORGAN_SLOT_TONGUE) + if(!tongue) + return + tongue.liked_foodtypes = initial(tongue.liked_foodtypes) diff --git a/code/datums/quirks/neutral_quirks/pride_pin.dm b/code/datums/quirks/neutral_quirks/pride_pin.dm new file mode 100644 index 00000000000..488c0a2bccb --- /dev/null +++ b/code/datums/quirks/neutral_quirks/pride_pin.dm @@ -0,0 +1,19 @@ +/datum/quirk/item_quirk/pride_pin + name = "Pride Pin" + desc = "Show off your pride with this changing pride pin!" + icon = FA_ICON_RAINBOW + value = 0 + gain_text = span_notice("You feel fruity.") + lose_text = span_danger("You feel only slightly less fruity than before.") + medical_record_text = "Patient appears to be fruity." + +/datum/quirk/item_quirk/pride_pin/add_unique(client/client_source) + var/obj/item/clothing/accessory/pride/pin = new(get_turf(quirk_holder)) + + var/pride_choice = client_source?.prefs?.read_preference(/datum/preference/choiced/pride_pin) || assoc_to_keys(GLOB.pride_pin_reskins)[1] + var/pride_reskin = GLOB.pride_pin_reskins[pride_choice] + + pin.current_skin = pride_choice + pin.icon_state = pride_reskin + + give_item_to_holder(pin, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) diff --git a/code/datums/quirks/neutral_quirks/shifty_eyes.dm b/code/datums/quirks/neutral_quirks/shifty_eyes.dm new file mode 100644 index 00000000000..29f1def3761 --- /dev/null +++ b/code/datums/quirks/neutral_quirks/shifty_eyes.dm @@ -0,0 +1,8 @@ +/datum/quirk/shifty_eyes + name = "Shifty Eyes" + desc = "Your eyes tend to wander all over the place, whether you mean to or not, causing people to sometimes think you're looking directly at them when you aren't." + icon = FA_ICON_EYE + value = 0 + medical_record_text = "Fucking creep kept staring at me the whole damn checkup. I'm only diagnosing this because it's less awkward than thinking it was on purpose." + mob_trait = TRAIT_SHIFTY_EYES + mail_goodies = list(/obj/item/clothing/head/costume/papersack, /obj/item/clothing/head/costume/papersack/smiley) diff --git a/code/datums/quirks/neutral_quirks/snob.dm b/code/datums/quirks/neutral_quirks/snob.dm new file mode 100644 index 00000000000..ab273f1ae53 --- /dev/null +++ b/code/datums/quirks/neutral_quirks/snob.dm @@ -0,0 +1,10 @@ +/datum/quirk/snob + name = "Snob" + desc = "You care about the finer things, if a room doesn't look nice its just not really worth it, is it?" + icon = FA_ICON_USER_TIE + value = 0 + gain_text = span_notice("You feel like you understand what things should look like.") + lose_text = span_notice("Well who cares about deco anyways?") + medical_record_text = "Patient seems to be rather stuck up." + mob_trait = TRAIT_SNOB + mail_goodies = list(/obj/item/chisel, /obj/item/paint_palette) diff --git a/code/datums/quirks/neutral_quirks/vegetarian.dm b/code/datums/quirks/neutral_quirks/vegetarian.dm new file mode 100644 index 00000000000..0ade72acafe --- /dev/null +++ b/code/datums/quirks/neutral_quirks/vegetarian.dm @@ -0,0 +1,23 @@ +/datum/quirk/vegetarian + name = "Vegetarian" + desc = "You find the idea of eating meat morally and physically repulsive." + icon = FA_ICON_CARROT + value = 0 + gain_text = span_notice("You feel repulsion at the idea of eating meat.") + lose_text = span_notice("You feel like eating meat isn't that bad.") + medical_record_text = "Patient reports a vegetarian diet." + mail_goodies = list(/obj/effect/spawner/random/food_or_drink/salad) + +/datum/quirk/vegetarian/add(client/client_source) + var/obj/item/organ/internal/tongue/tongue = quirk_holder.get_organ_slot(ORGAN_SLOT_TONGUE) + if(!tongue) + return + tongue.liked_foodtypes &= ~MEAT + tongue.disliked_foodtypes |= MEAT + +/datum/quirk/vegetarian/remove() + var/obj/item/organ/internal/tongue/tongue = quirk_holder.get_organ_slot(ORGAN_SLOT_TONGUE) + if(!tongue) + return + tongue.liked_foodtypes = initial(tongue.liked_foodtypes) + tongue.disliked_foodtypes = initial(tongue.disliked_foodtypes) diff --git a/code/datums/quirks/positive_quirks/alcohol_tolerance.dm b/code/datums/quirks/positive_quirks/alcohol_tolerance.dm new file mode 100644 index 00000000000..6458513007d --- /dev/null +++ b/code/datums/quirks/positive_quirks/alcohol_tolerance.dm @@ -0,0 +1,10 @@ +/datum/quirk/alcohol_tolerance + name = "Alcohol Tolerance" + desc = "You become drunk more slowly and suffer fewer drawbacks from alcohol." + icon = FA_ICON_BEER + value = 4 + mob_trait = TRAIT_ALCOHOL_TOLERANCE + gain_text = span_notice("You feel like you could drink a whole keg!") + lose_text = span_danger("You don't feel as resistant to alcohol anymore. Somehow.") + medical_record_text = "Patient demonstrates a high tolerance for alcohol." + mail_goodies = list(/obj/item/skillchip/wine_taster) diff --git a/code/datums/quirks/positive_quirks/apathetic.dm b/code/datums/quirks/positive_quirks/apathetic.dm new file mode 100644 index 00000000000..170cb6f5d44 --- /dev/null +++ b/code/datums/quirks/positive_quirks/apathetic.dm @@ -0,0 +1,14 @@ +/datum/quirk/apathetic + name = "Apathetic" + desc = "You just don't care as much as other people. That's nice to have in a place like this, I guess." + icon = FA_ICON_MEH + value = 4 + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_MOODLET_BASED + medical_record_text = "Patient was administered the Apathy Evaluation Scale but did not bother to complete it." + mail_goodies = list(/obj/item/hourglass) + +/datum/quirk/apathetic/add(client/client_source) + quirk_holder.mob_mood?.mood_modifier -= 0.2 + +/datum/quirk/apathetic/remove() + quirk_holder.mob_mood?.mood_modifier += 0.2 diff --git a/code/datums/quirks/positive_quirks/bilingual.dm b/code/datums/quirks/positive_quirks/bilingual.dm new file mode 100644 index 00000000000..324054198b8 --- /dev/null +++ b/code/datums/quirks/positive_quirks/bilingual.dm @@ -0,0 +1,24 @@ +/datum/quirk/bilingual + name = "Bilingual" + desc = "Over the years you've picked up an extra language!" + icon = FA_ICON_GLOBE + value = 4 + gain_text = span_notice("Some of the words of the people around you certainly aren't common. Good thing you studied for this.") + lose_text = span_notice("You seem to have forgotten your second language.") + medical_record_text = "Patient speaks multiple languages." + mail_goodies = list(/obj/item/taperecorder, /obj/item/clothing/head/frenchberet, /obj/item/clothing/mask/fakemoustache/italian) + +/datum/quirk/bilingual/add_unique(client/client_source) + var/wanted_language = client_source?.prefs.read_preference(/datum/preference/choiced/language) + var/datum/language/language_type + if(wanted_language == "Random") + language_type = pick(GLOB.uncommon_roundstart_languages) + else + language_type = GLOB.language_types_by_name[wanted_language] + if(quirk_holder.has_language(language_type)) + language_type = /datum/language/uncommon + if(quirk_holder.has_language(language_type)) + to_chat(quirk_holder, span_boldnotice("You are already familiar with the quirk in your preferences, so you did not learn one.")) + return + to_chat(quirk_holder, span_boldnotice("You are already familiar with the quirk in your preferences, so you learned Galactic Uncommon instead.")) + quirk_holder.grant_language(language_type, source = LANGUAGE_QUIRK) diff --git a/code/datums/quirks/positive_quirks/clown_enjoyer.dm b/code/datums/quirks/positive_quirks/clown_enjoyer.dm new file mode 100644 index 00000000000..984b0f7a6e4 --- /dev/null +++ b/code/datums/quirks/positive_quirks/clown_enjoyer.dm @@ -0,0 +1,31 @@ +/datum/quirk/item_quirk/clown_enjoyer + name = "Clown Enjoyer" + desc = "You enjoy clown antics and get a mood boost from wearing your clown pin." + icon = FA_ICON_MAP_PIN + value = 2 + mob_trait = TRAIT_CLOWN_ENJOYER + gain_text = span_notice("You are a big enjoyer of clowns.") + lose_text = span_danger("The clown doesn't seem so great.") + medical_record_text = "Patient reports being a big enjoyer of clowns." + mail_goodies = list( + /obj/item/bikehorn, + /obj/item/stamp/clown, + /obj/item/megaphone/clown, + /obj/item/clothing/shoes/clown_shoes, + /obj/item/bedsheet/clown, + /obj/item/clothing/mask/gas/clown_hat, + /obj/item/storage/backpack/clown, + /obj/item/storage/backpack/duffelbag/clown, + /obj/item/toy/crayon/rainbow, + /obj/item/toy/figure/clown, + /obj/item/tank/internals/emergency_oxygen/engi/clown/n2o, + /obj/item/tank/internals/emergency_oxygen/engi/clown/bz, + /obj/item/tank/internals/emergency_oxygen/engi/clown/helium, + ) + +/datum/quirk/item_quirk/clown_enjoyer/add_unique(client/client_source) + give_item_to_holder(/obj/item/clothing/accessory/clown_enjoyer_pin, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) + +/datum/quirk/item_quirk/clown_enjoyer/add(client/client_source) + var/datum/atom_hud/fan = GLOB.huds[DATA_HUD_FAN] + fan.show_to(quirk_holder) diff --git a/code/datums/quirks/positive_quirks/drunk_healing.dm b/code/datums/quirks/positive_quirks/drunk_healing.dm new file mode 100644 index 00000000000..fbab2503b4e --- /dev/null +++ b/code/datums/quirks/positive_quirks/drunk_healing.dm @@ -0,0 +1,22 @@ +/datum/quirk/drunkhealing + name = "Drunken Resilience" + desc = "Nothing like a good drink to make you feel on top of the world. Whenever you're drunk, you slowly recover from injuries." + icon = FA_ICON_WINE_BOTTLE + value = 8 + gain_text = span_notice("You feel like a drink would do you good.") + lose_text = span_danger("You no longer feel like drinking would ease your pain.") + medical_record_text = "Patient has unusually efficient liver metabolism and can slowly regenerate wounds by drinking alcoholic beverages." + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_PROCESSES + mail_goodies = list(/obj/effect/spawner/random/food_or_drink/booze) + +/datum/quirk/drunkhealing/process(seconds_per_tick) + switch(quirk_holder.get_drunk_amount()) + if (6 to 40) + quirk_holder.adjustBruteLoss(-0.1 * seconds_per_tick, FALSE, required_bodytype = BODYTYPE_ORGANIC) + quirk_holder.adjustFireLoss(-0.05 * seconds_per_tick, required_bodytype = BODYTYPE_ORGANIC) + if (41 to 60) + quirk_holder.adjustBruteLoss(-0.4 * seconds_per_tick, FALSE, required_bodytype = BODYTYPE_ORGANIC) + quirk_holder.adjustFireLoss(-0.2 * seconds_per_tick, required_bodytype = BODYTYPE_ORGANIC) + if (61 to INFINITY) + quirk_holder.adjustBruteLoss(-0.8 * seconds_per_tick, FALSE, required_bodytype = BODYTYPE_ORGANIC) + quirk_holder.adjustFireLoss(-0.4 * seconds_per_tick, required_bodytype = BODYTYPE_ORGANIC) diff --git a/code/datums/quirks/positive_quirks/empath.dm b/code/datums/quirks/positive_quirks/empath.dm new file mode 100644 index 00000000000..3379f8a97c4 --- /dev/null +++ b/code/datums/quirks/positive_quirks/empath.dm @@ -0,0 +1,10 @@ +/datum/quirk/empath + name = "Empath" + desc = "Whether it's a sixth sense or careful study of body language, it only takes you a quick glance at someone to understand how they feel." + icon = FA_ICON_SMILE_BEAM + value = 6 // SKYRAT EDIT CHANGE - Quirk Rebalance - Original: value = 8 + mob_trait = TRAIT_EMPATH + gain_text = span_notice("You feel in tune with those around you.") + lose_text = span_danger("You feel isolated from others.") + medical_record_text = "Patient is highly perceptive of and sensitive to social cues, or may possibly have ESP. Further testing needed." + mail_goodies = list(/obj/item/toy/foamfinger) diff --git a/code/datums/quirks/positive_quirks/freerunning.dm b/code/datums/quirks/positive_quirks/freerunning.dm new file mode 100644 index 00000000000..541d2b1cc44 --- /dev/null +++ b/code/datums/quirks/positive_quirks/freerunning.dm @@ -0,0 +1,10 @@ +/datum/quirk/freerunning + name = "Freerunning" + desc = "You're great at quick moves! You can climb tables more quickly and take no damage from short falls." + icon = FA_ICON_RUNNING + value = 8 + mob_trait = TRAIT_FREERUNNING + gain_text = span_notice("You feel lithe on your feet!") + lose_text = span_danger("You feel clumsy again.") + medical_record_text = "Patient scored highly on cardio tests." + mail_goodies = list(/obj/item/melee/skateboard, /obj/item/clothing/shoes/wheelys/rollerskates) diff --git a/code/datums/quirks/positive_quirks/friendly.dm b/code/datums/quirks/positive_quirks/friendly.dm new file mode 100644 index 00000000000..8ab0003639b --- /dev/null +++ b/code/datums/quirks/positive_quirks/friendly.dm @@ -0,0 +1,11 @@ +/datum/quirk/friendly + name = "Friendly" + desc = "You give the best hugs, especially when you're in the right mood." + icon = FA_ICON_HANDS_HELPING + value = 2 + mob_trait = TRAIT_FRIENDLY + gain_text = span_notice("You want to hug someone.") + lose_text = span_danger("You no longer feel compelled to hug others.") + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_MOODLET_BASED + medical_record_text = "Patient demonstrates low-inhibitions for physical contact and well-developed arms. Requesting another doctor take over this case." + mail_goodies = list(/obj/item/storage/box/hug) diff --git a/code/datums/quirks/positive_quirks/jolly.dm b/code/datums/quirks/positive_quirks/jolly.dm new file mode 100644 index 00000000000..7f6c334ba9d --- /dev/null +++ b/code/datums/quirks/positive_quirks/jolly.dm @@ -0,0 +1,9 @@ +/datum/quirk/jolly + name = "Jolly" + desc = "You sometimes just feel happy, for no reason at all." + icon = FA_ICON_GRIN + value = 4 + mob_trait = TRAIT_JOLLY + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_MOODLET_BASED + medical_record_text = "Patient demonstrates constant euthymia irregular for environment. It's a bit much, to be honest." + mail_goodies = list(/obj/item/clothing/mask/joy) diff --git a/code/datums/quirks/positive_quirks/light_step.dm b/code/datums/quirks/positive_quirks/light_step.dm new file mode 100644 index 00000000000..80418b79b9d --- /dev/null +++ b/code/datums/quirks/positive_quirks/light_step.dm @@ -0,0 +1,10 @@ +/datum/quirk/light_step + name = "Light Step" + desc = "You walk with a gentle step; footsteps and stepping on sharp objects is quieter and less painful. Also, your hands and clothes will not get messed in case of stepping in blood." + icon = FA_ICON_SHOE_PRINTS + value = 4 + mob_trait = TRAIT_LIGHT_STEP + gain_text = span_notice("You walk with a little more litheness.") + lose_text = span_danger("You start tromping around like a barbarian.") + medical_record_text = "Patient's dexterity belies a strong capacity for stealth." + mail_goodies = list(/obj/item/clothing/shoes/sandal) diff --git a/code/datums/quirks/positive_quirks/mime_fan.dm b/code/datums/quirks/positive_quirks/mime_fan.dm new file mode 100644 index 00000000000..5145b4a2240 --- /dev/null +++ b/code/datums/quirks/positive_quirks/mime_fan.dm @@ -0,0 +1,29 @@ +/datum/quirk/item_quirk/mime_fan + name = "Mime Fan" + desc = "You're a fan of mime antics and get a mood boost from wearing your mime pin." + icon = FA_ICON_THUMBTACK + value = 2 + mob_trait = TRAIT_MIME_FAN + gain_text = span_notice("You are a big fan of the Mime.") + lose_text = span_danger("The mime doesn't seem so great.") + medical_record_text = "Patient reports being a big fan of mimes." + mail_goodies = list( + /obj/item/toy/crayon/mime, + /obj/item/clothing/mask/gas/mime, + /obj/item/storage/backpack/mime, + /obj/item/clothing/under/rank/civilian/mime, + /obj/item/reagent_containers/cup/glass/bottle/bottleofnothing, + /obj/item/stamp/mime, + /obj/item/storage/box/survival/hug/black, + /obj/item/bedsheet/mime, + /obj/item/clothing/shoes/sneakers/mime, + /obj/item/toy/figure/mime, + /obj/item/toy/crayon/spraycan/mimecan, + ) + +/datum/quirk/item_quirk/mime_fan/add_unique(client/client_source) + give_item_to_holder(/obj/item/clothing/accessory/mime_fan_pin, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) + +/datum/quirk/item_quirk/mime_fan/add(client/client_source) + var/datum/atom_hud/fan = GLOB.huds[DATA_HUD_FAN] + fan.show_to(quirk_holder) diff --git a/code/datums/quirks/positive_quirks/musician.dm b/code/datums/quirks/positive_quirks/musician.dm new file mode 100644 index 00000000000..9d5e10f5f82 --- /dev/null +++ b/code/datums/quirks/positive_quirks/musician.dm @@ -0,0 +1,13 @@ +/datum/quirk/item_quirk/musician + name = "Musician" + desc = "You can tune handheld musical instruments to play melodies that clear certain negative effects and soothe the soul." + icon = FA_ICON_GUITAR + value = 2 + mob_trait = TRAIT_MUSICIAN + gain_text = span_notice("You know everything about musical instruments.") + lose_text = span_danger("You forget how musical instruments work.") + medical_record_text = "Patient brain scans show a highly-developed auditory pathway." + mail_goodies = list(/obj/effect/spawner/random/entertainment/musical_instrument, /obj/item/instrument/piano_synth/headphones) + +/datum/quirk/item_quirk/musician/add_unique(client/client_source) + give_item_to_holder(/obj/item/choice_beacon/music, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) diff --git a/code/datums/quirks/positive_quirks/night_vision.dm b/code/datums/quirks/positive_quirks/night_vision.dm new file mode 100644 index 00000000000..808a213db51 --- /dev/null +++ b/code/datums/quirks/positive_quirks/night_vision.dm @@ -0,0 +1,28 @@ +/datum/quirk/night_vision + name = "Night Vision" + desc = "You can see slightly more clearly in full darkness than most people." + icon = FA_ICON_MOON + value = 4 + mob_trait = TRAIT_NIGHT_VISION + gain_text = span_notice("The shadows seem a little less dark.") + lose_text = span_danger("Everything seems a little darker.") + medical_record_text = "Patient's eyes show above-average acclimation to darkness." + mail_goodies = list( + /obj/item/flashlight/flashdark, + /obj/item/food/grown/mushroom/glowshroom/shadowshroom, + /obj/item/skillchip/light_remover, + ) + +/datum/quirk/night_vision/add(client/client_source) + refresh_quirk_holder_eyes() + +/datum/quirk/night_vision/remove() + refresh_quirk_holder_eyes() + +/datum/quirk/night_vision/proc/refresh_quirk_holder_eyes() + var/mob/living/carbon/human/human_quirk_holder = quirk_holder + var/obj/item/organ/internal/eyes/eyes = human_quirk_holder.get_organ_by_type(/obj/item/organ/internal/eyes) + if(!eyes || eyes.lighting_cutoff) + return + // We've either added or removed TRAIT_NIGHT_VISION before calling this proc. Just refresh the eyes. + eyes.refresh() diff --git a/code/datums/quirks/positive_quirks/poster_boy.dm b/code/datums/quirks/positive_quirks/poster_boy.dm new file mode 100644 index 00000000000..4991ebc540b --- /dev/null +++ b/code/datums/quirks/positive_quirks/poster_boy.dm @@ -0,0 +1,31 @@ +/datum/quirk/item_quirk/poster_boy + name = "Poster Boy" + desc = "You have some great posters! Hang them up and make everyone have a great time." + icon = FA_ICON_TAPE + value = 4 + mob_trait = TRAIT_POSTERBOY + medical_record_text = "Patient reports a desire to cover walls with homemade objects." + mail_goodies = list(/obj/item/poster/random_official) + +/datum/quirk/item_quirk/poster_boy/add_unique() + var/mob/living/carbon/human/posterboy = quirk_holder + var/obj/item/storage/box/posterbox/newbox = new() + newbox.add_quirk_posters(posterboy.mind) + give_item_to_holder(newbox, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) + +/obj/item/storage/box/posterbox + name = "Box of Posters" + desc = "You made them yourself!" + +/// fills box of posters based on job, one neutral poster and 2 department posters +/obj/item/storage/box/posterbox/proc/add_quirk_posters(datum/mind/posterboy) + new /obj/item/poster/quirk/crew/random(src) + var/department = posterboy.assigned_role.paycheck_department + if(department == ACCOUNT_CIV) //if you are not part of a department you instead get 3 neutral posters + for(var/i in 1 to 2) + new /obj/item/poster/quirk/crew/random(src) + return + for(var/obj/item/poster/quirk/potential_poster as anything in subtypesof(/obj/item/poster/quirk)) + if(initial(potential_poster.quirk_poster_department) != department) + continue + new potential_poster(src) diff --git a/code/datums/quirks/positive_quirks/self_aware.dm b/code/datums/quirks/positive_quirks/self_aware.dm new file mode 100644 index 00000000000..022d08659ef --- /dev/null +++ b/code/datums/quirks/positive_quirks/self_aware.dm @@ -0,0 +1,8 @@ +/datum/quirk/selfaware + name = "Self-Aware" + desc = "You know your body well, and can accurately assess the extent of your wounds." + icon = FA_ICON_BONE + value = 8 + mob_trait = TRAIT_SELF_AWARE + medical_record_text = "Patient demonstrates an uncanny knack for self-diagnosis." + mail_goodies = list(/obj/item/clothing/neck/stethoscope, /obj/item/skillchip/entrails_reader) diff --git a/code/datums/quirks/positive_quirks/settler.dm b/code/datums/quirks/positive_quirks/settler.dm new file mode 100644 index 00000000000..81402c050cd --- /dev/null +++ b/code/datums/quirks/positive_quirks/settler.dm @@ -0,0 +1,32 @@ +/datum/quirk/item_quirk/settler + name = "Settler" + desc = "You are from a lineage of the earliest space settlers! While your family's generational exposure to varying gravity \ + has resulted in a ... smaller height than is typical for your species, you make up for it by being much better at outdoorsmanship and \ + carrying heavy equipment. You also get along great with animals. However, you are a bit on the slow side due to your small legs." + gain_text = span_bold("You feel like the world is your oyster!") + lose_text = span_danger("You think you might stay home today.") + icon = FA_ICON_HOUSE + value = 4 + mob_trait = TRAIT_SETTLER + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_CHANGES_APPEARANCE + medical_record_text = "Patient appears to be abnormally stout." + mail_goodies = list( + /obj/item/clothing/shoes/workboots/mining, + /obj/item/gps, + ) + +/datum/quirk/item_quirk/settler/add_unique(client/client_source) + give_item_to_holder(/obj/item/storage/box/papersack/wheat, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) + give_item_to_holder(/obj/item/storage/toolbox/fishing/small, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) + var/mob/living/carbon/human/human_quirkholder = quirk_holder + human_quirkholder.set_mob_height(HUMAN_HEIGHT_SHORTEST) + human_quirkholder.add_movespeed_modifier(/datum/movespeed_modifier/settler) + human_quirkholder.physiology.hunger_mod *= 0.5 //good for you, shortass, you don't get hungry nearly as often + +/datum/quirk/item_quirk/settler/remove() + if(QDELING(quirk_holder)) + return + var/mob/living/carbon/human/human_quirkholder = quirk_holder + human_quirkholder.set_mob_height(HUMAN_HEIGHT_MEDIUM) + human_quirkholder.remove_movespeed_modifier(/datum/movespeed_modifier/settler) + human_quirkholder.physiology.hunger_mod *= 2 diff --git a/code/datums/quirks/positive_quirks/signer.dm b/code/datums/quirks/positive_quirks/signer.dm new file mode 100644 index 00000000000..df0a2f34c5d --- /dev/null +++ b/code/datums/quirks/positive_quirks/signer.dm @@ -0,0 +1,17 @@ +/datum/quirk/item_quirk/signer + name = "Signer" + desc = "You possess excellent communication skills in sign language." + icon = FA_ICON_HANDS + value = 4 + quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_CHANGES_APPEARANCE + mail_goodies = list(/obj/item/clothing/gloves/radio) + +/datum/quirk/item_quirk/signer/add_unique(client/client_source) + quirk_holder.AddComponent(/datum/component/sign_language) + var/obj/item/clothing/gloves/gloves_type = /obj/item/clothing/gloves/radio + if(isplasmaman(quirk_holder)) + gloves_type = /obj/item/clothing/gloves/color/plasmaman/radio + give_item_to_holder(gloves_type, list(LOCATION_GLOVES = ITEM_SLOT_GLOVES, LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) + +/datum/quirk/item_quirk/signer/remove() + qdel(quirk_holder.GetComponent(/datum/component/sign_language)) diff --git a/code/datums/quirks/positive_quirks/skittish.dm b/code/datums/quirks/positive_quirks/skittish.dm new file mode 100644 index 00000000000..24bbac8e556 --- /dev/null +++ b/code/datums/quirks/positive_quirks/skittish.dm @@ -0,0 +1,8 @@ +/datum/quirk/skittish + name = "Skittish" + desc = "You're easy to startle, and hide frequently. Run into a closed locker to jump into it, as long as you have access. You can walk to avoid this." + icon = FA_ICON_TRASH + value = 8 + mob_trait = TRAIT_SKITTISH + medical_record_text = "Patient demonstrates a high aversion to danger and has described hiding in containers out of fear." + mail_goodies = list(/obj/structure/closet/cardboard) diff --git a/code/datums/quirks/positive_quirks/spiritual.dm b/code/datums/quirks/positive_quirks/spiritual.dm new file mode 100644 index 00000000000..b08fe8b60c6 --- /dev/null +++ b/code/datums/quirks/positive_quirks/spiritual.dm @@ -0,0 +1,20 @@ +/datum/quirk/item_quirk/spiritual + name = "Spiritual" + desc = "You hold a spiritual belief, whether in God, nature or the arcane rules of the universe. You gain comfort from the presence of holy people, and believe that your prayers are more special than others. Being in the chapel makes you happy." + icon = FA_ICON_BIBLE + value = 2 /// SKYRAT EDIT - Quirk Rebalance - Original: value = 4 + mob_trait = TRAIT_SPIRITUAL + gain_text = span_notice("You have faith in a higher power.") + lose_text = span_danger("You lose faith!") + medical_record_text = "Patient reports a belief in a higher power." + mail_goodies = list( + /obj/item/book/bible/booze, + /obj/item/reagent_containers/cup/glass/bottle/holywater, + /obj/item/bedsheet/chaplain, + /obj/item/toy/cards/deck/tarot, + /obj/item/storage/fancy/candle_box, + ) + +/datum/quirk/item_quirk/spiritual/add_unique(client/client_source) + give_item_to_holder(/obj/item/storage/fancy/candle_box, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) + give_item_to_holder(/obj/item/storage/box/matches, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) diff --git a/code/datums/quirks/positive_quirks/tagger.dm b/code/datums/quirks/positive_quirks/tagger.dm new file mode 100644 index 00000000000..5aba24d850a --- /dev/null +++ b/code/datums/quirks/positive_quirks/tagger.dm @@ -0,0 +1,20 @@ +/datum/quirk/item_quirk/tagger + name = "Tagger" + desc = "You're an experienced artist. People will actually be impressed by your graffiti, and you can get twice as many uses out of drawing supplies." + icon = FA_ICON_SPRAY_CAN + value = 4 + mob_trait = TRAIT_TAGGER + gain_text = span_notice("You know how to tag walls efficiently.") + lose_text = span_danger("You forget how to tag walls properly.") + medical_record_text = "Patient was recently seen for possible paint huffing incident." + mail_goodies = list( + /obj/item/toy/crayon/spraycan, + /obj/item/canvas/nineteen_nineteen, + /obj/item/canvas/twentythree_nineteen, + /obj/item/canvas/twentythree_twentythree + ) + +/datum/quirk/item_quirk/tagger/add_unique(client/client_source) + var/obj/item/toy/crayon/spraycan/can = new + can.set_painting_tool_color(client_source?.prefs.read_preference(/datum/preference/color/paint_color)) + give_item_to_holder(can, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) diff --git a/code/datums/quirks/positive_quirks/throwing_arm.dm b/code/datums/quirks/positive_quirks/throwing_arm.dm new file mode 100644 index 00000000000..5157b399009 --- /dev/null +++ b/code/datums/quirks/positive_quirks/throwing_arm.dm @@ -0,0 +1,10 @@ +/datum/quirk/throwingarm + name = "Throwing Arm" + desc = "Your arms have a lot of heft to them! Objects that you throw just always seem to fly further than everyone elses, and you never miss a toss." + icon = FA_ICON_BASEBALL + value = 7 + mob_trait = TRAIT_THROWINGARM + gain_text = span_notice("Your arms are full of energy!") + lose_text = span_danger("Your arms ache a bit.") + medical_record_text = "Patient displays mastery over throwing balls." + mail_goodies = list(/obj/item/toy/beach_ball/baseball, /obj/item/toy/basketball, /obj/item/toy/dodgeball) diff --git a/code/datums/quirks/positive_quirks/voracious.dm b/code/datums/quirks/positive_quirks/voracious.dm new file mode 100644 index 00000000000..68073304f0d --- /dev/null +++ b/code/datums/quirks/positive_quirks/voracious.dm @@ -0,0 +1,9 @@ +/datum/quirk/voracious + name = "Voracious" + desc = "Nothing gets between you and your food. You eat faster and can binge on junk food! Being fat suits you just fine." + icon = FA_ICON_DRUMSTICK_BITE + value = 4 + mob_trait = TRAIT_VORACIOUS + gain_text = span_notice("You feel HONGRY.") + lose_text = span_danger("You no longer feel HONGRY.") + mail_goodies = list(/obj/effect/spawner/random/food_or_drink/dinner) diff --git a/code/datums/shuttles/_shuttle.dm b/code/datums/shuttles/_shuttle.dm new file mode 100644 index 00000000000..0100a3d85da --- /dev/null +++ b/code/datums/shuttles/_shuttle.dm @@ -0,0 +1,83 @@ +/datum/map_template/shuttle + name = "Base Shuttle Template" + var/prefix = "_maps/shuttles/" + var/suffix + /** + * Port ID is the place this template should be docking at, set on '/obj/docking_port/stationary' + * Because getShuttle() compares port_id to shuttle_id to find an already existing shuttle, + * you should set shuttle_id to be the same as port_id if you want them to be replacable. + */ + var/port_id + /// ID of the shuttle, make sure it matches port_id if necessary. + var/shuttle_id + /// Information to display on communication console about the shuttle + var/description + /// The recommended occupancy limit for the shuttle (count chairs, beds, and benches then round to 5) + var/occupancy_limit + /// Description of the prerequisition that has to be achieved for the shuttle to be purchased + var/prerequisites + /// Shuttle warnings and hazards to the admin who spawns the shuttle + var/admin_notes + /// How much does this shuttle cost the cargo budget to purchase? Put in terms of CARGO_CRATE_VALUE to properly scale the cost with the current balance of cargo's income. + var/credit_cost = INFINITY + /// What job accesses can buy this shuttle? If null, this shuttle cannot be bought. + var/list/who_can_purchase = list(ACCESS_CAPTAIN) + /// Whether or not this shuttle is locked to emags only. + var/emag_only = FALSE + /// If set, overrides default movement_force on shuttle + var/list/movement_force + + var/port_x_offset + var/port_y_offset + var/extra_desc = "" + +/datum/map_template/shuttle/proc/prerequisites_met() + return TRUE + +/datum/map_template/shuttle/New() + shuttle_id = "[port_id]_[suffix]" + mappath = "[prefix][shuttle_id].dmm" + . = ..() + +/datum/map_template/shuttle/preload_size(path, cache) + . = ..(path, TRUE) // Done this way because we still want to know if someone actualy wanted to cache the map + if(!cached_map) + return + + var/offset = discover_offset(/obj/docking_port/mobile) + + port_x_offset = offset[1] + port_y_offset = offset[2] + + if(!cache) + cached_map = null + +/datum/map_template/shuttle/load(turf/T, centered, register=TRUE) + . = ..() + if(!.) + return + var/list/turfs = block( locate(.[MAP_MINX], .[MAP_MINY], .[MAP_MINZ]), + locate(.[MAP_MAXX], .[MAP_MAXY], .[MAP_MAXZ])) + for(var/i in 1 to turfs.len) + var/turf/place = turfs[i] + if(isspaceturf(place)) // This assumes all shuttles are loaded in a single spot then moved to their real destination. + continue + + if (place.count_baseturfs() < 2) // Some snowflake shuttle shit + continue + + place.insert_baseturf(3, /turf/baseturf_skipover/shuttle) + + for(var/obj/docking_port/mobile/port in place) + port.calculate_docking_port_information(src) + // initTemplateBounds explicitly ignores the shuttle's docking port, to ensure that it calculates the bounds of the shuttle correctly + // so we need to manually initialize it here + SSatoms.InitializeAtoms(list(port)) + if(register) + port.register() + +//Whatever special stuff you want +/datum/map_template/shuttle/post_load(obj/docking_port/mobile/M) + if(movement_force) + M.movement_force = movement_force.Copy() + M.linkup() diff --git a/code/datums/shuttles/arrival.dm b/code/datums/shuttles/arrival.dm new file mode 100644 index 00000000000..376de809afa --- /dev/null +++ b/code/datums/shuttles/arrival.dm @@ -0,0 +1,35 @@ +/datum/map_template/shuttle/arrival + port_id = "arrival" + who_can_purchase = null + +/datum/map_template/shuttle/arrival/box + suffix = "box" + name = "arrival shuttle (Box)" + +/datum/map_template/shuttle/arrival/donut + suffix = "donut" + name = "arrival shuttle (Donut)" + +/datum/map_template/shuttle/arrival/birdshot + suffix = "birdshot" + name = "arrival shuttle (Birdshot)" + +/datum/map_template/shuttle/arrival/delta + suffix = "delta" + name = "arrival shuttle (Delta)" + +/datum/map_template/shuttle/arrival/kilo + suffix = "kilo" + name = "arrival shuttle (Kilo)" + +/datum/map_template/shuttle/arrival/pubby + suffix = "pubby" + name = "arrival shuttle (Pubby)" + +/datum/map_template/shuttle/arrival/omega + suffix = "omega" + name = "arrival shuttle (Omega)" + +/datum/map_template/shuttle/arrival/northstar + suffix = "northstar" + name = "arrival shuttle (North Star)" diff --git a/code/datums/shuttles/assault_pod.dm b/code/datums/shuttles/assault_pod.dm new file mode 100644 index 00000000000..63a885f92ea --- /dev/null +++ b/code/datums/shuttles/assault_pod.dm @@ -0,0 +1,7 @@ +/datum/map_template/shuttle/assault_pod + port_id = "assault_pod" + who_can_purchase = null + +/datum/map_template/shuttle/assault_pod/default + suffix = "default" + name = "assault pod (Default)" diff --git a/code/datums/shuttles/aux_base.dm b/code/datums/shuttles/aux_base.dm new file mode 100644 index 00000000000..c377e278b90 --- /dev/null +++ b/code/datums/shuttles/aux_base.dm @@ -0,0 +1,11 @@ +/datum/map_template/shuttle/aux_base + port_id = "aux_base" + who_can_purchase = null + +/datum/map_template/shuttle/aux_base/default + suffix = "default" + name = "auxilliary base (Default)" + +/datum/map_template/shuttle/aux_base/small + suffix = "small" + name = "auxilliary base (Small)" diff --git a/code/datums/shuttles/cargo.dm b/code/datums/shuttles/cargo.dm new file mode 100644 index 00000000000..a18b7a4ac9a --- /dev/null +++ b/code/datums/shuttles/cargo.dm @@ -0,0 +1,36 @@ +/datum/map_template/shuttle/cargo + port_id = "cargo" + name = "Base Shuttle Template (Cargo)" + who_can_purchase = null + +/datum/map_template/shuttle/cargo/kilo + suffix = "kilo" + name = "supply shuttle (Kilo)" + +/datum/map_template/shuttle/cargo/birdboat + suffix = "birdboat" + name = "supply shuttle (Birdboat)" + +/datum/map_template/shuttle/cargo/donut + suffix = "donut" + name = "supply shuttle (Donut)" + +/datum/map_template/shuttle/cargo/pubby + suffix = "pubby" + name = "supply shuttle (Pubby)" + +/datum/map_template/shuttle/cargo/birdshot + suffix = "birdshot" + name = "supply shuttle (Birdshot)" + +/datum/map_template/shuttle/cargo/box + suffix = "box" + name = "cargo ferry (Box)" + +/datum/map_template/shuttle/cargo/delta + suffix = "delta" + name = "cargo ferry (Delta)" + +/datum/map_template/shuttle/cargo/northstar + suffix = "northstar" + name = "cargo ferry (North Star)" diff --git a/code/datums/shuttles/emergency.dm b/code/datums/shuttles/emergency.dm new file mode 100644 index 00000000000..5e8553c69ee --- /dev/null +++ b/code/datums/shuttles/emergency.dm @@ -0,0 +1,496 @@ +#define EMAG_LOCKED_SHUTTLE_COST (CARGO_CRATE_VALUE * 50) + +/datum/map_template/shuttle/emergency // SKYRAT EDIT OVERRIDE - OVERRIDEN IN ADVANCED_SHUTTLES - shuttles.dm + port_id = "emergency" + name = "Base Shuttle Template (Emergency)" + ///assoc list of shuttle events to add to this shuttle on spawn (typepath = weight) + var/list/events + ///pick all events instead of random + var/use_all_events = FALSE + ///how many do we pick + var/event_amount = 1 + ///do we empty the event list before adding our events + var/events_override = FALSE + +/datum/map_template/shuttle/emergency/New() + . = ..() + if(!occupancy_limit && who_can_purchase) + CRASH("The [name] needs an occupancy limit!") + if(HAS_TRAIT(SSstation, STATION_TRAIT_SHUTTLE_SALE) && credit_cost > 0 && prob(15)) + var/discount_amount = round(rand(25, 80), 5) + name += " ([discount_amount]% Discount!)" + var/discount_multiplier = 100 - discount_amount + credit_cost = ((credit_cost * discount_multiplier) / 100) + +///on post_load use our variables to change shuttle events +/datum/map_template/shuttle/emergency/post_load(obj/docking_port/mobile/mobile) + . = ..() + if(!events) + return + if(events_override) + mobile.event_list.Cut() + if(use_all_events) + for(var/path in events) + mobile.event_list.Add(new path(mobile)) + events -= path + else + for(var/i in 1 to event_amount) + var/path = pick_weight(events) + events -= path + mobile.event_list.Add(new path(mobile)) + +/datum/map_template/shuttle/emergency/backup + prefix = "_maps/shuttles/" + suffix = "backup" + name = "Backup Shuttle" + who_can_purchase = null + +/datum/map_template/shuttle/emergency/construction + suffix = "construction" + name = "Build your own shuttle kit" + description = "For the enterprising shuttle engineer! The chassis will dock upon purchase, but launch will have to be authorized as usual via shuttle call. Comes stocked with construction materials. Unlocks the ability to buy shuttle engine crates from cargo, which allow you to speed up shuttle transit time." + admin_notes = "No brig, no medical facilities." + credit_cost = CARGO_CRATE_VALUE * 5 + who_can_purchase = list(ACCESS_CAPTAIN, ACCESS_CE) + occupancy_limit = "Flexible" + +/datum/map_template/shuttle/emergency/construction/post_load() + . = ..() + //enable buying engines from cargo + var/datum/supply_pack/P = SSshuttle.supply_packs[/datum/supply_pack/engineering/shuttle_engine] + P.special_enabled = TRUE + +/datum/map_template/shuttle/emergency/asteroid + suffix = "asteroid" + name = "Asteroid Station Emergency Shuttle" + description = "A respectable mid-sized shuttle that first saw service shuttling Nanotrasen crew to and from their asteroid belt embedded facilities." + credit_cost = CARGO_CRATE_VALUE * 6 + occupancy_limit = "50" + +/datum/map_template/shuttle/emergency/venture + suffix = "venture" + name = "Venture Emergency Shuttle" + description = "A mid-sized shuttle for those who like a lot of space for their legs." + credit_cost = CARGO_CRATE_VALUE * 10 + occupancy_limit = "45" + +/datum/map_template/shuttle/emergency/humpback + suffix = "humpback" + name = "Humpback Emergency Shuttle" + description = "A repurposed cargo hauling and salvaging ship, for sightseeing and tourism. Has a bar. Complete with a 2 minute vacation plan to carp territory." + credit_cost = CARGO_CRATE_VALUE * 12 + occupancy_limit = "30" + events = list( + /datum/shuttle_event/simple_spawner/carp/friendly = 10, + /datum/shuttle_event/simple_spawner/carp/friendly_but_no_personal_space = 2, + /datum/shuttle_event/simple_spawner/carp = 2, + /datum/shuttle_event/simple_spawner/carp/magic = 1, + ) + +/datum/map_template/shuttle/emergency/bar + suffix = "bar" + name = "The Emergency Escape Bar" + description = "Features include sentient bar staff (a Bardrone and a Barmaid), bathroom, a quality lounge for the heads, and a large gathering table." + admin_notes = "Bardrone and Barmaid are GODMODE, will be automatically sentienced by the fun balloon at 60 seconds before arrival. \ + Has medical facilities." + credit_cost = CARGO_CRATE_VALUE * 10 + occupancy_limit = "30" + +/datum/map_template/shuttle/emergency/pod + suffix = "pod" + name = "Emergency Pods" + description = "We did not expect an evacuation this quickly. All we have available is two escape pods." + admin_notes = "For player punishment." + who_can_purchase = null + occupancy_limit = "10" + +/datum/map_template/shuttle/emergency/russiafightpit + suffix = "russiafightpit" + name = "Mother Russia Bleeds" + description = "Dis is a high-quality shuttle, da. Many seats, lots of space, all equipment! Even includes entertainment! Such as lots to drink, and a fighting arena for drunk crew to have fun! If arena not fun enough, simply press button of releasing bears. Do not worry, bears trained not to break out of fighting pit, so totally safe so long as nobody stupid or drunk enough to leave door open. Try not to let asimov babycons ruin fun!" + admin_notes = "Includes a small variety of weapons. And bears. Only captain-access can release the bears. Bears won't smash the windows themselves, but they can escape if someone lets them." + credit_cost = CARGO_CRATE_VALUE * 10 // While the shuttle is rusted and poorly maintained, trained bears are costly. + occupancy_limit = "40" + +/datum/map_template/shuttle/emergency/meteor + suffix = "meteor" + name = "Asteroid With Engines Strapped To It" + description = "A hollowed out asteroid with engines strapped to it, the hollowing procedure makes it very difficult to hijack but is very expensive. Due to its size and difficulty in steering it, this shuttle may damage the docking area." + admin_notes = "This shuttle will likely crush escape, killing anyone there." + credit_cost = CARGO_CRATE_VALUE * 30 + movement_force = list("KNOCKDOWN" = 3, "THROW" = 2) + occupancy_limit = "CONDEMNED" + +/datum/map_template/shuttle/emergency/monastery + suffix = "monastery" + name = "Grand Corporate Monastery" + description = "Originally built for a public station, this grand edifice to religion, due to budget cuts, is now available as an escape shuttle for the right... donation. Due to its large size and callous owners, this shuttle may cause collateral damage." + admin_notes = "WARNING: This shuttle WILL destroy a fourth of the station, likely picking up a lot of objects with it." + emag_only = TRUE + credit_cost = EMAG_LOCKED_SHUTTLE_COST * 1.8 + movement_force = list("KNOCKDOWN" = 3, "THROW" = 5) + occupancy_limit = "70" + who_can_purchase = null //SKYRAT EDIT ADDITION + +/datum/map_template/shuttle/emergency/luxury + suffix = "luxury" + name = "Luxury Shuttle" + description = "A luxurious golden shuttle complete with an indoor swimming pool. Each crewmember wishing to board must bring 500 credits, payable in cash and mineral coin." + extra_desc = "This shuttle costs 500 credits to board." + admin_notes = "Due to the limited space for non paying crew, this shuttle may cause a riot." + emag_only = TRUE + credit_cost = EMAG_LOCKED_SHUTTLE_COST + occupancy_limit = "75" + +/datum/map_template/shuttle/emergency/medisim + suffix = "medisim" + name = "Medieval Reality Simulation Dome" + description = "A state of the art simulation dome, loaded onto your shuttle! Watch and laugh at how petty humanity used to be before it reached the stars. Guaranteed to be at least 40% historically accurate." + prerequisites = "A special holodeck simulation might allow this shuttle to be loaded." + admin_notes = "Ghosts can spawn in and fight as knights or archers. The CTF auto restarts, so no admin intervention necessary." + credit_cost = 20000 + occupancy_limit = "30" + +/datum/map_template/shuttle/emergency/medisim/prerequisites_met() + return SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_MEDISIM] + +/datum/map_template/shuttle/emergency/discoinferno + suffix = "discoinferno" + name = "Disco Inferno" + description = "The glorious results of centuries of plasma research done by Nanotrasen employees. This is the reason why you are here. Get on and dance like you're on fire, burn baby burn!" + admin_notes = "Flaming hot. The main area has a dance machine as well as plasma floor tiles that will be ignited by players every single time." + emag_only = TRUE + credit_cost = EMAG_LOCKED_SHUTTLE_COST + occupancy_limit = "10" + +/datum/map_template/shuttle/emergency/arena + suffix = "arena" + name = "The Arena" + description = "The crew must pass through an otherworldy arena to board this shuttle. Expect massive casualties." + prerequisites = "The source of the Bloody Signal must be tracked down and eliminated to unlock this shuttle." + admin_notes = "RIP AND TEAR." + credit_cost = CARGO_CRATE_VALUE * 20 + occupancy_limit = "1/2" + /// Whether the arena z-level has been created + var/arena_loaded = FALSE + +/datum/map_template/shuttle/emergency/arena/prerequisites_met() + return SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_BUBBLEGUM] + +/datum/map_template/shuttle/emergency/arena/post_load(obj/docking_port/mobile/M) + . = ..() + if(!arena_loaded) + arena_loaded = TRUE + var/datum/map_template/arena/arena_template = new() + arena_template.load_new_z() + +/datum/map_template/arena + name = "The Arena" + mappath = "_maps/templates/the_arena.dmm" + +/datum/map_template/shuttle/emergency/birdboat + suffix = "birdboat" + name = "Birdboat Station Emergency Shuttle" + description = "Though a little on the small side, this shuttle is feature complete, which is more than can be said for the pattern of station it was commissioned for." + credit_cost = CARGO_CRATE_VALUE * 2 + occupancy_limit = "25" + +/datum/map_template/shuttle/emergency/box + suffix = "box" + name = "Box Station Emergency Shuttle" + credit_cost = CARGO_CRATE_VALUE * 4 + description = "The gold standard in emergency exfiltration, this tried and true design is equipped with everything the crew needs for a safe flight home." + occupancy_limit = "45" + +/datum/map_template/shuttle/emergency/donut + suffix = "donut" + name = "Donutstation Emergency Shuttle" + description = "The perfect spearhead for any crude joke involving the station's shape, this shuttle supports a separate containment cell for prisoners and a compact medical wing." + admin_notes = "Has airlocks on both sides of the shuttle and will probably intersect near the front on some stations that build past departures." + credit_cost = CARGO_CRATE_VALUE * 5 + occupancy_limit = "60" + +/datum/map_template/shuttle/emergency/clown + suffix = "clown" + name = "Snappop(tm)!" + description = "Hey kids and grownups! \ + Are you bored of DULL and TEDIOUS shuttle journeys after you're evacuating for probably BORING reasons. Well then order the Snappop(tm) today! \ + We've got fun activities for everyone, an all access cockpit, and no boring security brig! Boo! Play dress up with your friends! \ + Collect all the bedsheets before your neighbour does! Check if the AI is watching you with our patent pending \"Peeping Tom AI Multitool Detector\" or PEEEEEETUR for short. \ + Have a fun ride!" + admin_notes = "Brig is replaced by anchored greentext book surrounded by lavaland chasms, stationside door has been removed to prevent accidental dropping. No brig." + credit_cost = CARGO_CRATE_VALUE * 16 + occupancy_limit = "HONK" + +/datum/map_template/shuttle/emergency/cramped + suffix = "cramped" + name = "Secure Transport Vessel 5 (STV5)" + description = "Well, looks like CentCom only had this ship in the area, they probably weren't expecting you to need evac for a while. \ + Probably best if you don't rifle around in whatever equipment they were transporting. I hope you're friendly with your coworkers, because there is very little space in this thing.\n\ + \n\ + Contains contraband armory guns, maintenance loot, and abandoned crates!" + admin_notes = "Due to origin as a solo piloted secure vessel, has an active GPS onboard labeled STV5. Has roughly as much space as Hi Daniel, except with explosive crates." + occupancy_limit = "5" + +/datum/map_template/shuttle/emergency/meta + suffix = "meta" + name = "Meta Station Emergency Shuttle" + credit_cost = CARGO_CRATE_VALUE * 8 + description = "A fairly standard shuttle, though larger and slightly better equipped than the Box Station variant." + occupancy_limit = "45" + +/datum/map_template/shuttle/emergency/kilo + suffix = "kilo" + name = "Kilo Station Emergency Shuttle" + credit_cost = CARGO_CRATE_VALUE * 10 + description = "A fully functional shuttle including a complete infirmary, storage facilties and regular amenities." + occupancy_limit = "55" + +/datum/map_template/shuttle/emergency/mini + suffix = "mini" + name = "Ministation emergency shuttle" + credit_cost = CARGO_CRATE_VALUE * 2 + description = "Despite its namesake, this shuttle is actually only slightly smaller than standard, and still complete with a brig and medbay." + occupancy_limit = "35" + +/datum/map_template/shuttle/emergency/tram + suffix = "tram" + name = "Tram Station Emergency Shuttle" + credit_cost = CARGO_CRATE_VALUE * 4 + description = "A train but in space, choo choo!" + occupancy_limit = "35" + +/datum/map_template/shuttle/emergency/birdshot + suffix = "birdshot" + name = "Birdshot Station Emergency Shuttle" + credit_cost = CARGO_CRATE_VALUE * 2 + description = "We pulled this one out of Mothball just for you!" + occupancy_limit = "40" + +/datum/map_template/shuttle/emergency/scrapheap + suffix = "scrapheap" + name = "Standby Evacuation Vessel \"Scrapheap Challenge\"" + credit_cost = CARGO_CRATE_VALUE * -18 + description = "Comrade! We see you are having trouble with money, yes? If you have money issue, very little money, we are looking for good shuttle, emergency shuttle. You take best in sector shuttle, we take yours, you get money, da? Please do not lean on window, fragile like fina china. -Ivan" + admin_notes = "An abomination with no functional medbay, sections missing, and some very fragile windows. Surprisingly airtight. When bought, gives a good influx of money, but can only be bought if the budget is literally 0 credits." + movement_force = list("KNOCKDOWN" = 3, "THROW" = 2) + occupancy_limit = "30" + +/datum/map_template/shuttle/emergency/scrapheap/prerequisites_met() + return SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_SCRAPHEAP] + +/datum/map_template/shuttle/emergency/narnar + suffix = "narnar" + name = "Shuttle 667" + description = "Looks like this shuttle may have wandered into the darkness between the stars on route to the station. Let's not think too hard about where all the bodies came from." + admin_notes = "Contains real cult ruins, mob eyeballs, and inactive constructs. Cult mobs will automatically be sentienced by fun balloon. \ + Cloning pods in 'medbay' area are showcases and nonfunctional." + prerequisites = "Mysterious cult runes may need to be banished before this shuttle can be summoned." + credit_cost = 6667 ///The joke is the number so no defines + occupancy_limit = "666" + +/datum/map_template/shuttle/emergency/narnar/prerequisites_met() + return SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_NARNAR] + +/datum/map_template/shuttle/emergency/pubby + suffix = "pubby" + name = "Pubby Station Emergency Shuttle" + description = "A train but in space! Complete with a first, second class, brig and storage area." + admin_notes = "Choo choo motherfucker!" + credit_cost = CARGO_CRATE_VALUE * 2 + occupancy_limit = "50" + +/datum/map_template/shuttle/emergency/cere + suffix = "cere" + name = "Cere Station Emergency Shuttle" + description = "The large, beefed-up version of the box-standard shuttle. Includes an expanded brig, fully stocked medbay, enhanced cargo storage with mech chargers, \ + an engine room stocked with various supplies, and a crew capacity of 80+ to top it all off. Live large, live Cere." + admin_notes = "Seriously big, even larger than the Delta shuttle." + credit_cost = CARGO_CRATE_VALUE * 20 + occupancy_limit = "110" + +/datum/map_template/shuttle/emergency/supermatter + suffix = "supermatter" + name = "Hyperfractal Gigashuttle" + description = "\"I dunno, this seems kinda needlessly complicated.\"\n\ + \"This shuttle has very a very high safety record, according to CentCom Officer Cadet Yins.\"\n\ + \"Are you sure?\"\n\ + \"Yes, it has a safety record of N-A-N, which is apparently larger than 100%.\"" + admin_notes = "Supermatter that spawns on shuttle is special anchored 'hugbox' supermatter that cannot take damage and does not take in or emit gas. \ + Outside of admin intervention, it cannot explode. \ + It does, however, still dust anything on contact, emits high levels of radiation, and induce hallucinations in anyone looking at it without protective goggles. \ + Emitters spawn powered on, expect admin notices, they are harmless." + emag_only = TRUE + credit_cost = EMAG_LOCKED_SHUTTLE_COST + movement_force = list("KNOCKDOWN" = 3, "THROW" = 2) + occupancy_limit = "15" + +/datum/map_template/shuttle/emergency/imfedupwiththisworld + suffix = "imfedupwiththisworld" + name = "Oh, Hi Daniel" + description = "How was space work today? Oh, pretty good. We got a new space station and the company will make a lot of money. What space station? I cannot tell you; it's space confidential. \ + Aw, come space on. Why not? No, I can't. Anyway, how is your space roleplay life?" + admin_notes = "Tiny, with a single airlock and wooden walls. What could go wrong?" + emag_only = TRUE + credit_cost = EMAG_LOCKED_SHUTTLE_COST + movement_force = list("KNOCKDOWN" = 3, "THROW" = 2) + occupancy_limit = "5" + +/datum/map_template/shuttle/emergency/goon + suffix = "goon" + name = "NES Port" + description = "The Nanotrasen Emergency Shuttle Port(NES Port for short) is a shuttle used at other less known Nanotrasen facilities and has a more open inside for larger crowds, but fewer onboard shuttle facilities." + credit_cost = CARGO_CRATE_VALUE + occupancy_limit = "40" + +/datum/map_template/shuttle/emergency/rollerdome + suffix = "rollerdome" + name = "Uncle Pete's Rollerdome" + description = "Developed by a member of Nanotrasen's R&D crew that claims to have travelled from the year 2028. \ + He says this shuttle is based off an old entertainment complex from the 1990s, though our database has no records on anything pertaining to that decade." + admin_notes = "ONLY NINETIES KIDS REMEMBER. Uses the fun balloon and drone from the Emergency Bar." + credit_cost = CARGO_CRATE_VALUE * 30 + occupancy_limit = "5" + +/datum/map_template/shuttle/emergency/basketball + suffix = "bballhooper" + name = "Basketballer's Stadium" + description = "Hoop, man, hoop! Get your shooting game on with this sleek new basketball stadium! Do keep in mind that several other features \ + that you may expect to find common-place on other shuttles aren't present to give you this sleek stadium at an affordable cost. \ + It also wasn't manufactured to deal with the form-factor of some of your stations... good luck with that." + admin_notes = "A larger shuttle built around a basketball stadium: entirely impractical but just a complete blast!" + credit_cost = CARGO_CRATE_VALUE * 10 + occupancy_limit = "30" + +/datum/map_template/shuttle/emergency/wabbajack + suffix = "wabbajack" + name = "NT Lepton Violet" + description = "The research team based on this vessel went missing one day, and no amount of investigation could discover what happened to them. \ + The only occupants were a number of dead rodents, who appeared to have clawed each other to death. \ + Needless to say, no engineering team wanted to go near the thing, and it's only being used as an Emergency Escape Shuttle because there is literally nothing else available." + admin_notes = "If the crew can solve the puzzle, they will wake the wabbajack statue. It will likely not end well. There's a reason it's boarded up. Maybe they should have just left it alone." + credit_cost = CARGO_CRATE_VALUE * 30 + occupancy_limit = "30" + +/datum/map_template/shuttle/emergency/wabbajack/prerequisites_met() + return SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_WABBAJACK] + +/datum/map_template/shuttle/emergency/omega + suffix = "omega" + name = "Omegastation Emergency Shuttle" + description = "On the smaller size with a modern design, this shuttle is for the crew who like the cosier things, while still being able to stretch their legs." + credit_cost = CARGO_CRATE_VALUE * 2 + occupancy_limit = "30" + +/datum/map_template/shuttle/emergency/cruise + suffix = "cruise" + name = "The NTSS Independence" + description = "Ordinarily reserved for special functions and events, the Cruise Shuttle Independence can bring a summery cheer to your next station evacuation for a 'modest' fee!" + admin_notes = "This motherfucker is BIG. You might need to force dock it." + credit_cost = CARGO_CRATE_VALUE * 100 + occupancy_limit = "80" + +/datum/map_template/shuttle/emergency/monkey + suffix = "nature" + name = "Dynamic Environmental Interaction Shuttle" + description = "A large shuttle with a center biodome that is flourishing with life. Frolick with the monkeys! (Extra monkeys are stored on the bridge.)" + admin_notes = "Pretty freakin' large, almost as big as Raven or Cere. Excercise caution with it." + credit_cost = CARGO_CRATE_VALUE * 16 + occupancy_limit = "45" + +/datum/map_template/shuttle/emergency/casino + suffix = "casino" + name = "Lucky Jackpot Casino Shuttle" + description = "A luxurious casino packed to the brim with everything you need to start new gambling addicitions!" + admin_notes = "The ship is a bit chunky, so watch where you park it." + credit_cost = 7777 + occupancy_limit = "85" + +/datum/map_template/shuttle/emergency/shadow + suffix = "shadow" + name = "The NTSS Shadow" + description = "Guaranteed to get you somewhere FAST. With a custom-built plasma engine, this bad boy will put more distance between you and certain danger than any other!" + admin_notes = "The aft of the ship has a plasma tank that starts ignited. May get released by crew. The plasma windows next to the engine heaters will also erupt into flame, and also risk getting released by crew." + credit_cost = CARGO_CRATE_VALUE * 50 + occupancy_limit = "40" + +/datum/map_template/shuttle/emergency/fish + suffix = "fish" + name = "Angler's Choice Emergency Shuttle" + description = "Trades such amenities as 'storage space' and 'sufficient seating' for an artifical environment ideal for fishing, plus ample supplies (also for fishing)." + admin_notes = "There's a chasm in it, it has railings but that won't stop determined players." + credit_cost = CARGO_CRATE_VALUE * 10 + occupancy_limit = "35" + +/datum/map_template/shuttle/emergency/lance + suffix = "lance" + name = "The Lance Crew Evacuation System" + description = "A brand new shuttle by Nanotrasen's finest in shuttle-engineering, it's designed to tactically slam into a destroyed station, dispatching threats and saving crew at the same time! Be careful to stay out of it's path." + admin_notes = "WARNING: This shuttle is designed to crash into the station. It has turrets, similar to the raven." + credit_cost = CARGO_CRATE_VALUE * 70 + occupancy_limit = "50" + +/datum/map_template/shuttle/emergency/tranquility + suffix = "tranquility" + name = "The Tranquility Relocation Shuttle" + description = "A large shuttle, covered in flora and comfortable resting areas. The perfect way to end a peaceful shift" + admin_notes = "it's pretty big, and comfy. Be careful when placing it down!" + credit_cost = CARGO_CRATE_VALUE * 25 + occupancy_limit = "40" + +/datum/map_template/shuttle/emergency/hugcage + suffix = "hugcage" + name = "Hug Relaxation Shuttle" + description = "A small cozy shuttle with plenty of beds for tired or sensitive spacemen, and a box for pillow-fights." + admin_notes = "Has a sentience fun balloon for pets." + credit_cost = CARGO_CRATE_VALUE * 16 + occupancy_limit = "20" + +/datum/map_template/shuttle/emergency/fame + suffix = "fame" + name = "Hall of Fame Shuttle" + description = "A grandiose shuttle that has a red carpet leading to the hall of fame. Are you worthy to stand among the best spessmen in existence?" + admin_notes = "Designed around persistence from memories, trophies, photos, and statues." + credit_cost = CARGO_CRATE_VALUE * 25 + occupancy_limit = "55" + +/datum/map_template/shuttle/emergency/delta + suffix = "delta" + name = "Delta Station Emergency Shuttle" + description = "A large shuttle for a large station, this shuttle can comfortably fit all your overpopulation and crowding needs. Complete with all facilities plus additional equipment." + admin_notes = "Go big or go home." + credit_cost = CARGO_CRATE_VALUE * 15 + occupancy_limit = "75" + +/datum/map_template/shuttle/emergency/northstar + suffix = "northstar" + name = "North Star Emergency Shuttle" + description = "A rugged shuttle meant for long-distance transit from the tips of the frontier to Central Command and back. \ + moderately comfortable and large, but cramped." + credit_cost = CARGO_CRATE_VALUE * 14 + occupancy_limit = "55" + +/datum/map_template/shuttle/emergency/raven + suffix = "raven" + name = "CentCom Raven Cruiser" + description = "The CentCom Raven Cruiser is a former high-risk salvage vessel, now repurposed into an emergency escape shuttle. \ + Once first to the scene to pick through warzones for valuable remains, it now serves as an excellent escape option for stations under heavy fire from outside forces. \ + This escape shuttle boasts shields and numerous anti-personnel turrets guarding its perimeter to fend off meteors and enemy boarding attempts." + admin_notes = "Comes with turrets that will target anything without the neutral faction (nuke ops, xenos etc, but not pets)." + credit_cost = CARGO_CRATE_VALUE * 60 + occupancy_limit = "CLASSIFIED" + +/datum/map_template/shuttle/emergency/zeta + suffix = "zeta" + name = "Tr%nPo2r& Z3TA" + description = "A glitch appears on your monitor, flickering in and out of the options laid before you. \ + It seems strange and alien..." + prerequisites = "You may need a special technology to access the signal." + admin_notes = "Has alien surgery tools, and a void core that provides unlimited power." + credit_cost = CARGO_CRATE_VALUE * 16 + occupancy_limit = "xxx" + +/datum/map_template/shuttle/emergency/zeta/prerequisites_met() + return SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_ALIENTECH] + +#undef EMAG_LOCKED_SHUTTLE_COST diff --git a/code/datums/shuttles/ert.dm b/code/datums/shuttles/ert.dm new file mode 100644 index 00000000000..843daf34c80 --- /dev/null +++ b/code/datums/shuttles/ert.dm @@ -0,0 +1,8 @@ +/datum/map_template/shuttle/ert + port_id = "ert" + who_can_purchase = null + +// Custom ERT shuttles +/datum/map_template/shuttle/ert/bounty + suffix = "bounty" + name = "Bounty Hunter ERT Shuttle" diff --git a/code/datums/shuttles/escape_pod.dm b/code/datums/shuttles/escape_pod.dm new file mode 100644 index 00000000000..0b2f35dd9dd --- /dev/null +++ b/code/datums/shuttles/escape_pod.dm @@ -0,0 +1,23 @@ +/datum/map_template/shuttle/escape_pod + port_id = "escape_pod" + who_can_purchase = null + +/datum/map_template/shuttle/escape_pod/default + suffix = "default" + name = "escape pod (Default)" + description = "Base escape pod with 2 tiles of interior space." + +/datum/map_template/shuttle/escape_pod/large + suffix = "large" + name = "escape pod (Large)" + description = "Actually the old Pubbystation monastery shuttle." + +/datum/map_template/shuttle/escape_pod/luxury + suffix = "luxury" + name = "escape pod (Luxury)" + description = "Upgraded escape pod with 3 tiles of interior space." + +/datum/map_template/shuttle/escape_pod/cramped + suffix = "cramped" + name = "escape pod (Cramped)" + description = "Downgraded escape pod that lacks a window and only has one seat, alongside lacking an emergency safe." diff --git a/code/datums/shuttles/ferry.dm b/code/datums/shuttles/ferry.dm new file mode 100644 index 00000000000..e4f540992ff --- /dev/null +++ b/code/datums/shuttles/ferry.dm @@ -0,0 +1,40 @@ +/datum/map_template/shuttle/ferry + port_id = "ferry" + name = "Base Shuttle Template (Ferry)" + +/datum/map_template/shuttle/ferry/base + suffix = "base" + name = "transport ferry" + description = "Standard issue Box/Metastation CentCom ferry." + +/datum/map_template/shuttle/ferry/meat + suffix = "meat" + name = "\"meat\" ferry" + description = "Ahoy! We got all kinds o' meat aft here. Meat from plant people, people who be dark, not in a racist way, just they're dark black. \ + Oh and lizard meat too,mighty popular that is. Definitely 100% fresh, just ask this guy here. *person on meatspike moans* See? \ + Definitely high quality meat, nothin' wrong with it, nothin' added, definitely no zombifyin' reagents!" + admin_notes = "Meat currently contains no zombifying reagents, lizard on meatspike must be spawned in." + +/datum/map_template/shuttle/ferry/lighthouse + suffix = "lighthouse" + name = "The Lighthouse(?)" + description = "*static*... part of a much larger vessel, possibly military in origin. \ + The weapon markings aren't anything we've seen ...static... by almost never the same person twice, possible use of unknown storage ...static... \ + seeing ERT officers onboard, but no missions are on file for ...static...static...annoying jingle... only at The LIGHTHOUSE! \ + Fulfilling needs you didn't even know you had. We've got EVERYTHING, and something else!" + admin_notes = "Currently larger than ferry docking port on Box, will not hit anything, but must be force docked. Trader and ERT bodyguards are not included." + +/datum/map_template/shuttle/ferry/fancy + suffix = "fancy" + name = "fancy transport ferry" + description = "At some point, someone upgraded the ferry to have fancier flooring... and fewer seats." + +/datum/map_template/shuttle/ferry/kilo + suffix = "kilo" + name = "kilo transport ferry" + description = "Standard issue CentCom Ferry for Kilo pattern stations. Includes additional equipment and rechargers." + +/datum/map_template/shuttle/ferry/northstar + suffix = "northstar" + name = "north star transport ferry" + description = "In the very depths of the frontier, you'll need a rugged shuttle capable of delivering crew, this is that." diff --git a/code/datums/shuttles/hunter.dm b/code/datums/shuttles/hunter.dm new file mode 100644 index 00000000000..d8b7f708324 --- /dev/null +++ b/code/datums/shuttles/hunter.dm @@ -0,0 +1,19 @@ +/datum/map_template/shuttle/hunter + port_id = "hunter" + who_can_purchase = null + +/datum/map_template/shuttle/hunter/space_cop + suffix = "space_cop" + name = "Police Spacevan" + +/datum/map_template/shuttle/hunter/russian + suffix = "russian" + name = "Russian Cargo Ship" + +/datum/map_template/shuttle/hunter/bounty + suffix = "bounty" + name = "Bounty Hunter Ship" + +/datum/map_template/shuttle/hunter/psyker + suffix = "psyker" + name = "Psyker Fortune-Telling Ship" diff --git a/code/datums/shuttles/infiltrator.dm b/code/datums/shuttles/infiltrator.dm new file mode 100644 index 00000000000..26f877f996e --- /dev/null +++ b/code/datums/shuttles/infiltrator.dm @@ -0,0 +1,13 @@ +/datum/map_template/shuttle/infiltrator + port_id = "infiltrator" + who_can_purchase = null + +/datum/map_template/shuttle/infiltrator/basic + suffix = "basic" + name = "basic syndicate infiltrator" + description = "Base Syndicate infiltrator, spawned by default for nukeops to use." + +/datum/map_template/shuttle/infiltrator/advanced + suffix = "advanced" + name = "advanced syndicate infiltrator" + description = "A much larger version of the standard Syndicate infiltrator that feels more like Kilostation. Has APCs, but power is not a concern for nuclear operatives. Also comes with atmos!" diff --git a/code/datums/shuttles/mining.dm b/code/datums/shuttles/mining.dm new file mode 100644 index 00000000000..ffd5cb04785 --- /dev/null +++ b/code/datums/shuttles/mining.dm @@ -0,0 +1,62 @@ +// LABOUR SHUTTLES +/datum/map_template/shuttle/labour + port_id = "labour" + who_can_purchase = null + +/datum/map_template/shuttle/labour/box + suffix = "box" + name = "labour shuttle (Box)" + +/datum/map_template/shuttle/labour/generic + suffix = "generic" + name = "labour shuttle (Generic)" + +/datum/map_template/shuttle/labour/delta + suffix = "delta" + name = "labour shuttle (Delta)" + +/datum/map_template/shuttle/labour/kilo + suffix = "kilo" + name = "labour shuttle (Kilo)" + +// MINING SHUTTLES +/datum/map_template/shuttle/mining + port_id = "mining" + who_can_purchase = null + +/datum/map_template/shuttle/mining/box + suffix = "box" + name = "mining shuttle (Box)" + +/datum/map_template/shuttle/mining/delta + suffix = "delta" + name = "mining shuttle (Delta)" + +/datum/map_template/shuttle/mining/kilo + suffix = "kilo" + name = "mining shuttle (Kilo)" + +/datum/map_template/shuttle/mining/large + suffix = "large" + name = "mining shuttle (Large)" + +/datum/map_template/shuttle/mining/northstar + suffix = "northstar" + name = "mining shuttle (North Star)" + +// MINING COMMON SHUTTLES +/datum/map_template/shuttle/mining_common + port_id = "mining_common" + who_can_purchase = null + +/datum/map_template/shuttle/mining_common/meta + suffix = "meta" + name = "lavaland shuttle (Meta)" + +/datum/map_template/shuttle/mining_common/kilo + suffix = "kilo" + name = "lavaland shuttle (Kilo)" + +/datum/map_template/shuttle/mining_common/northstar + suffix = "northstar" + name = "lavaland shuttle (North Star)" diff --git a/code/datums/shuttles/pirate.dm b/code/datums/shuttles/pirate.dm new file mode 100644 index 00000000000..c6f94b5684b --- /dev/null +++ b/code/datums/shuttles/pirate.dm @@ -0,0 +1,31 @@ +/datum/map_template/shuttle/pirate + port_id = "pirate" + who_can_purchase = null + +/datum/map_template/shuttle/pirate/default + suffix = "default" + name = "pirate ship (Default)" + +/datum/map_template/shuttle/pirate/silverscale + suffix = "silverscale" + name = "pirate ship (Silver Scales)" + +/datum/map_template/shuttle/pirate/dutchman + suffix = "dutchman" + name = "pirate ship (Flying Dutchman)" + +/datum/map_template/shuttle/pirate/interdyne + suffix = "ex_interdyne" + name = "pirate ship (Pharmaceutics Biocraft)" + +/datum/map_template/shuttle/pirate/grey + suffix = "grey" + name = "pirate ship (The Space Toolbox)" + +/datum/map_template/shuttle/pirate/irs + suffix = "irs" + name = "pirate ship (Space IRS)" + +/datum/map_template/shuttle/pirate/geode + suffix = "geode" + name = "pirate ship (Lustrous Geode)" diff --git a/code/datums/shuttles/ruin.dm b/code/datums/shuttles/ruin.dm new file mode 100644 index 00000000000..511e2d6ecdc --- /dev/null +++ b/code/datums/shuttles/ruin.dm @@ -0,0 +1,28 @@ +/datum/map_template/shuttle/ruin + port_id = "ruin" + who_can_purchase = null + +/datum/map_template/shuttle/ruin/cyborg_mothership + suffix = "cyborg_mothership" + name = "Cyborg Mothership" + description = "A highly industrialised vessel designed for silicon operation infested with hivebots and space vines." + +/datum/map_template/shuttle/ruin/caravan_victim + suffix = "caravan_victim" + name = "Small Freighter" + description = "Small freight vessel, starts near blacked-out with 3 Syndicate Commandos and 1 Syndicate Stormtrooper, alongside a large hull breach." + +/datum/map_template/shuttle/ruin/pirate_cutter + suffix = "pirate_cutter" + name = "Pirate Cutter" + description = "Small pirate vessel with ballistic turrets. Spawns with 3 pirate mobs, one of which drops an energy cutlass." + +/datum/map_template/shuttle/ruin/syndicate_dropship + suffix = "syndicate_dropship" + name = "Syndicate Dropship" + description = "Light Syndicate vessel with laser turrets. Spawns with a Syndicate mob in the bridge." + +/datum/map_template/shuttle/ruin/syndicate_fighter_shiv + suffix = "syndicate_fighter_shiv" + name = "Syndicate Fighter" + description = "A small Syndicate vessel with exactly one tile of useful interior space and 4 laser turrets. Starts with a Syndicate mob in the pilot's seat, and extremely cramped." diff --git a/code/datums/shuttles/snowdin.dm b/code/datums/shuttles/snowdin.dm new file mode 100644 index 00000000000..bbee38ed991 --- /dev/null +++ b/code/datums/shuttles/snowdin.dm @@ -0,0 +1,11 @@ +/datum/map_template/shuttle/snowdin + port_id = "snowdin" + who_can_purchase = null + +/datum/map_template/shuttle/snowdin/mining + suffix = "mining" + name = "Snowdin Mining Elevator" + +/datum/map_template/shuttle/snowdin/excavation + suffix = "excavation" + name = "Snowdin Excavation Elevator" diff --git a/code/datums/shuttles/starfury.dm b/code/datums/shuttles/starfury.dm new file mode 100644 index 00000000000..510033d6436 --- /dev/null +++ b/code/datums/shuttles/starfury.dm @@ -0,0 +1,20 @@ +/datum/map_template/shuttle/starfury + port_id = "starfury" + who_can_purchase = null + +/datum/map_template/shuttle/starfury/fighter_one + suffix = "fighter1" + name = "SBC Starfury Fighter (1)" + +/datum/map_template/shuttle/starfury/fighter_two + suffix = "fighter2" + name = "SBC Starfury Fighter (2)" + +/datum/map_template/shuttle/starfury/fighter_three + suffix = "fighter3" + name = "SBC Starfury Fighter (3)" + +/datum/map_template/shuttle/starfury/corvette + suffix = "corvette" + name = "SBC Starfury Corvette" + diff --git a/code/datums/shuttles/whiteship.dm b/code/datums/shuttles/whiteship.dm new file mode 100644 index 00000000000..0b48575e057 --- /dev/null +++ b/code/datums/shuttles/whiteship.dm @@ -0,0 +1,58 @@ +/datum/map_template/shuttle/whiteship + port_id = "whiteship" + +/datum/map_template/shuttle/whiteship/box + suffix = "box" + name = "Hospital Ship" + description = "Whiteship with medical supplies. Zombies do not currently spawn corpses, and are not infectious." + +/datum/map_template/shuttle/whiteship/meta + suffix = "meta" + name = "Salvage Ship" + description = "Whiteship that focuses on a large cargo bay that players can build in. Spawns with Syndicate mobs who do not drop corpses and are highly aggressive." + +/datum/map_template/shuttle/whiteship/pubby + suffix = "pubby" + name = "NT Science Vessel" + description = "A small science vessel that uses just one area and is full of angry ants." + +/datum/map_template/shuttle/whiteship/cere + suffix = "cere" + name = "NT Heavy Salvage Vessel" + description = "A beefy, well-rounded salvage vessel with a pair of corpses (miner and engineer) and a Captain's hat. Equipped with solar sails and a PACMAN generator." + +/datum/map_template/shuttle/whiteship/birdshot + suffix = "birdshot" + name = "NT Patrol Bee" + description = "A small patrol vessel with a central corridor connecting all rooms. Features 2 small cargo bays and a brig. Spawns with an agressive and deadly Gelatinous Cube" + +/datum/map_template/shuttle/whiteship/kilo + suffix = "kilo" + name = "NT Mining Shuttle" + description = "A mining vessel with a curious shape starting with a few angry netherworld mobs." + +/datum/map_template/shuttle/whiteship/donut + suffix = "donut" + name = "NT Long-Distance Bluespace Jumper" + description = "A ship hit with an engine blowout, leaving it as a depressurised husk. Has infinite power, although likely to bait people into removing that property. Also the most open out of all the whiteships, and starts with a 25% ripley chance." + +/datum/map_template/shuttle/whiteship/tram + suffix = "tram" + name = "NT Long-Distance Bluespace Freighter" + description = "A long shuttle that starts with Nanotrasen private security corpses. DOES NOT FIT IN THE BASE DOCKS! Does fit in Deep Space's dock though." + +/datum/map_template/shuttle/whiteship/delta + suffix = "delta" + name = "NT Frigate" + description = "A standard whiteship with big spiders onboard. PACMAN generator is not wired and next to main grid cabling, so it requires some work." + +/datum/map_template/shuttle/whiteship/personalshuttle + suffix = "personalshuttle" + name = "Personal Travel Shuttle" + description = "A small vessel with a few zombies and an engineer's corpse that can be looted." + +/datum/map_template/shuttle/whiteship/obelisk + suffix = "obelisk" + name = "Obelisk" + description = "A large research vessel affected by the Cult of Nar'Sie. PACMAN generator is not wired and next to main grid cabling, so it requires some work." + admin_notes = "Not actually an obelisk, has nonsentient cult constructs." diff --git a/code/datums/status_effects/buffs/food_haste.dm b/code/datums/status_effects/buffs/food_haste.dm new file mode 100644 index 00000000000..9daf859fb19 --- /dev/null +++ b/code/datums/status_effects/buffs/food_haste.dm @@ -0,0 +1,20 @@ +/// Haste makes the eater move faster +/datum/status_effect/food/haste + var/datum/movespeed_modifier/food_haste/modifier + +/datum/status_effect/food/haste/on_apply() + modifier = new() + modifier.multiplicative_slowdown = -0.04 * strength + owner.add_movespeed_modifier(modifier, update = TRUE) + return ..() + +/datum/status_effect/food/haste/be_replaced() + owner.remove_movespeed_modifier(modifier, update = TRUE) + return ..() + +/datum/status_effect/food/haste/on_remove() + owner.remove_movespeed_modifier(modifier, update = TRUE) + return ..() + +/datum/movespeed_modifier/food_haste + multiplicative_slowdown = -0.1 diff --git a/code/datums/status_effects/buffs/food_traits.dm b/code/datums/status_effects/buffs/food_traits.dm new file mode 100644 index 00000000000..ebe22116dd0 --- /dev/null +++ b/code/datums/status_effects/buffs/food_traits.dm @@ -0,0 +1,8 @@ +/datum/status_effect/food/trait/shockimmune + alert_type = /atom/movable/screen/alert/status_effect/food_trait_shockimmune + trait = TRAIT_SHOCKIMMUNE + +/atom/movable/screen/alert/status_effect/food_trait_shockimmune + name = "Grounded" + desc = "That meal made me feel like a superconductor..." + icon_state = "food_buff_4" diff --git a/code/datums/status_effects/debuffs/cursed.dm b/code/datums/status_effects/debuffs/cursed.dm new file mode 100644 index 00000000000..285fb86348e --- /dev/null +++ b/code/datums/status_effects/debuffs/cursed.dm @@ -0,0 +1,195 @@ +#define DEFAULT_MAX_CURSE_COUNT 5 + +/// Status effect that gives the target miscellanous debuffs while throwing a status alert and causing them to smoke from the damage they're incurring. +/// Purposebuilt for cursed slot machines. +/datum/status_effect/grouped/cursed + id = "cursed" + alert_type = /atom/movable/screen/alert/status_effect/cursed + remove_on_fullheal = TRUE + heal_flag_necessary = HEAL_ADMIN + /// The max number of curses a target can incur with this status effect. + var/max_curse_count = DEFAULT_MAX_CURSE_COUNT + /// The amount of times we have been "applied" to the target. + var/curse_count = 0 + /// Raw probability we have to deal damage this tick. + var/damage_chance = 10 + /// Are we currently in the process of sending a monologue? + var/monologuing = FALSE + /// The hand we are branded to. + var/obj/item/bodypart/branded_hand = null + /// The cached path of the particles we're using to smoke + var/smoke_path = null + +/datum/status_effect/grouped/cursed/on_apply() + RegisterSignal(owner, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_changed)) + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + RegisterSignal(owner, COMSIG_CURSED_SLOT_MACHINE_USE, PROC_REF(check_curses)) + RegisterSignal(owner, COMSIG_CURSED_SLOT_MACHINE_LOST, PROC_REF(update_curse_count)) + RegisterSignal(SSdcs, COMSIG_GLOB_CURSED_SLOT_MACHINE_WON, PROC_REF(clear_curses)) + return ..() + +/datum/status_effect/grouped/cursed/Destroy() + UnregisterSignal(SSdcs, COMSIG_GLOB_CURSED_SLOT_MACHINE_WON) + branded_hand = null + return ..() + +/// Checks the number of curses we have and returns information back to the slot machine. `max_curse_amount` is set by the slot machine itself. +/datum/status_effect/grouped/cursed/proc/check_curses(mob/user, max_curse_amount) + SIGNAL_HANDLER + if(curse_count >= max_curse_amount) + return SLOT_MACHINE_USE_CANCEL + + if(monologuing) + to_chat(owner, span_warning("Your arm is resisting your attempts to pull the lever!")) // listening to kitschy monologues to postpone your powergaming is the true curse here. + return SLOT_MACHINE_USE_POSTPONE + +/// Handles the debuffs of this status effect and incrementing the number of curses we have. +/datum/status_effect/grouped/cursed/proc/update_curse_count() + SIGNAL_HANDLER + curse_count++ + + linked_alert?.update_appearance() // we may have not initialized it yet + + addtimer(CALLBACK(src, PROC_REF(handle_after_effects), 1 SECONDS)) // give it a second to let the failure sink in before we exact our toll + +/// Makes a nice lorey message about the curse level we're at. I think it's nice +/datum/status_effect/grouped/cursed/proc/handle_after_effects() + if(QDELETED(src)) + return + + monologuing = TRUE + var/list/messages = list() + switch(curse_count) + if(1) // basically your first is a "freebie" that will still require urgent medical attention and will leave you smoking forever but could be worse tbh + if(ishuman(owner)) + var/mob/living/carbon/human/human_owner = owner + playsound(human_owner, SFX_SEAR, 50, TRUE) + var/obj/item/bodypart/affecting = human_owner.get_active_hand() + branded_hand = affecting + affecting.force_wound_upwards(/datum/wound/burn/flesh/severe/cursed_brand, wound_source = "curse of the slot machine") + + messages += span_boldwarning("Your hand burns, and you quickly let go of the lever! You feel a little sick as the nerves deaden in your hand...") + messages += span_boldwarning("Some smoke appears to be coming out of your hand now, but it's not too bad...") + messages += span_boldwarning("Fucking stupid machine.") + + if(2) + messages += span_boldwarning("The machine didn't burn you this time, it must be some arcane work of the brand recognizing a source...") + messages += span_boldwarning("Blisters and boils start to appear over your skin. Each one hissing searing hot steam out of its own pocket...") + messages += span_boldwarning("You understand that the machine tortures you with its simplistic allure. It can kill you at any moment, but it derives a sick satisfaction at forcing you to keep going.") + messages += span_boldwarning("If you could get away from here, you might be able to live with some medical supplies. Is it too late to stop now?") + messages += span_boldwarning("As you shut your eyes to dwell on this conundrum, the brand surges in pain. You shudder to think what might happen if you go unconscious.") + + if(3) + owner.emote("cough") + messages += span_boldwarning("Your throat becomes to feel like it's slowly caking up with sand and dust. You eject the contents of the back of your throat onto your sleeve.") + messages += span_boldwarning("It is sand. Crimson red. You've never felt so thirsty in your life, yet you don't trust your own hand to carry the glass to your lips.") + messages += span_boldwarning("You get the sneaking feeling that if someone else were to win, that it might clear your curse too. Saving your life is a noble cause.") + messages += span_boldwarning("Of course, you might have to not speak on the nature of this machine, in case they scamper off to leave you to die.") + messages += span_boldwarning("Is it truly worth it to condemn someone to this fate to cure the manifestation of your own hedonistic urges? You'll have to decide quickly.") + + if(4) + messages += span_boldwarning("A migraine swells over your head as your thoughts become hazy. Your hand desperately inches closer towards the slot machine for one final pull...") + messages += span_boldwarning("The ultimate test of mind over matter. You can jerk your own muscle back in order to prevent a terrible fate, but your life already is worth so little now.") + messages += span_boldwarning("This is what they want, is it not? To witness your failure against itself? The compulsion carries you forward as a sinking feeling of dread fills your stomach.") + messages += span_boldwarning("Paradoxically, where there is hopelessness, there is elation. Elation at the fact that there's still enough power in you for one more pull.") + messages += span_boldwarning("Your legs desperate wish to jolt away on the thought of running away from this wretched machination, but your own arm remains complacent in the thought of seeing spinning wheels.") + messages += span_userdanger("The toll has already been exacted. There is no longer death on 'your' terms. Is your dignity worth more than your life?") + + if(5 to INFINITY) + if(max_curse_count != DEFAULT_MAX_CURSE_COUNT) // this probably will only happen through admin schenanigans letting people stack up infinite curses or something + to_chat(owner, span_boldwarning("Do you still think you're in control?")) + return + + to_chat(owner, span_userdanger("Why couldn't I get one more try?!")) + owner.investigate_log("has been gibbed by the cursed status effect after accumulating [curse_count] curses.", INVESTIGATE_DEATHS) + owner.gib() + qdel(src) + return + + for(var/message in messages) + to_chat(owner, message) + sleep(1.5 SECONDS) // yes yes a bit fast but it can be a lot of text and i want the whole thing to send before the cooldown on the slot machine might expire + monologuing = FALSE + +/// Cleans ourselves up and removes our curses. Meant to be done in a "positive" way, when the curse is broken. Directly use qdel otherwise. +/datum/status_effect/grouped/cursed/proc/clear_curses() + SIGNAL_HANDLER + + if(!isnull(branded_hand)) + var/datum/wound/brand = branded_hand.get_wound_type(/datum/wound/burn/flesh/severe/cursed_brand) + brand?.remove_wound() + + owner.visible_message( + span_notice("The smoke slowly clears from [owner.name]..."), + span_notice("Your skin finally settles down and your throat no longer feels as dry... The brand disappearing confirms that the curse has been lifted."), + ) + QDEL_NULL(particle_effect) + qdel(src) + +/// If our owner's stat changes, rapidly surge the damage chance. +/datum/status_effect/grouped/cursed/proc/on_stat_changed() + SIGNAL_HANDLER + if(owner.stat == CONSCIOUS || owner.stat == DEAD) // reset on these two states + damage_chance = initial(damage_chance) + return + + to_chat(owner, span_userdanger("As your body crumbles, you feel the curse of the slot machine surge through your body!")) + damage_chance += 75 //ruh roh raggy + +/// If our owner dies without getting gibbed (as in of other causes), stop smoking because we've "expended all the life energy". +/datum/status_effect/grouped/cursed/proc/on_death(mob/living/source, gibbed) + SIGNAL_HANDLER + + if(gibbed) + return + + QDEL_NULL(particle_effect) + +/datum/status_effect/grouped/cursed/update_particles() + var/particle_path = /particles/smoke/steam/mild + switch(curse_count) + if(2 to 3) + particle_path = /particles/smoke/steam + if(4 to INFINITY) + particle_path = /particles/smoke/steam/bad + + if(smoke_path == particle_path) + return + + QDEL_NULL(particle_effect) + smoke_path = particle_path + particle_effect = new(owner, particle_path) + +/datum/status_effect/grouped/cursed/tick(seconds_between_ticks) + if(curse_count <= 1) + return // you get one "freebie" (single damage) to nudge you into thinking this is a bad idea before the house begins to win. + + // the house won. + var/ticked_coefficient = (rand(15, 40) / 100) + var/effective_percentile_chance = ((curse_count == 2 ? 1 : curse_count) * damage_chance * ticked_coefficient) + + if(SPT_PROB(effective_percentile_chance, seconds_between_ticks)) + owner.apply_damages( + brute = (curse_count * ticked_coefficient), + burn = (curse_count * ticked_coefficient), + oxy = (curse_count * ticked_coefficient), + ) + +/atom/movable/screen/alert/status_effect/cursed + name = "Cursed!" + desc = "The brand on your hand reminds you of your greed, yet you seem to be okay otherwise." + icon_state = "cursed_by_slots" + +/atom/movable/screen/alert/status_effect/cursed/update_desc() + . = ..() + var/datum/status_effect/grouped/cursed/linked_effect = attached_effect + var/curses = linked_effect.curse_count + switch(curses) + if(2) + desc = "Your greed is catching up to you..." + if(3) + desc = "You really don't feel good right now... But why stop now?" + if(4 to INFINITY) + desc = "Real winners quit before they reach the ultimate prize." + +#undef DEFAULT_MAX_CURSE_COUNT diff --git a/code/datums/status_effects/debuffs/cyborg.dm b/code/datums/status_effects/debuffs/cyborg.dm new file mode 100644 index 00000000000..0f95b494197 --- /dev/null +++ b/code/datums/status_effects/debuffs/cyborg.dm @@ -0,0 +1,22 @@ +/// Reduce a cyborg's speed when you throw things at it +/datum/status_effect/borg_throw_slow + id = "borg_throw_slowdown" + alert_type = /atom/movable/screen/alert/status_effect/borg_throw_slow + duration = 3 SECONDS + status_type = STATUS_EFFECT_REPLACE + +/datum/status_effect/borg_throw_slow/on_apply() + . = ..() + owner.add_movespeed_modifier(/datum/movespeed_modifier/borg_throw, update = TRUE) + +/datum/status_effect/borg_throw_slow/on_remove() + . = ..() + owner.remove_movespeed_modifier(/datum/movespeed_modifier/borg_throw, update = TRUE) + +/atom/movable/screen/alert/status_effect/borg_throw_slow + name = "Percussive Maintenance" + desc = "A sudden impact has triggered your collision avoidance routines, reducing movement speed." + icon_state = "weaken" + +/datum/movespeed_modifier/borg_throw + multiplicative_slowdown = 0.9 diff --git a/code/datums/status_effects/debuffs/decloning.dm b/code/datums/status_effects/debuffs/decloning.dm new file mode 100644 index 00000000000..0f76f10f470 --- /dev/null +++ b/code/datums/status_effects/debuffs/decloning.dm @@ -0,0 +1,86 @@ +/// The amount of mutadone we can process for strike recovery at once. +#define MUTADONE_HEAL 1 + +/datum/status_effect/decloning + id = "decloning" + tick_interval = 3 SECONDS + alert_type = /atom/movable/screen/alert/status_effect/decloning + remove_on_fullheal = TRUE + + /// How many strikes our status effect holder has left before they are dusted. + var/strikes_left = 100 + +/datum/status_effect/decloning/on_apply() + if(owner.has_reagent(/datum/reagent/medicine/mutadone)) + return FALSE + to_chat(owner, span_userdanger("You've noticed your body has begun deforming. This can't be good.")) + return TRUE + +/datum/status_effect/decloning/on_remove() + if(!QDELETED(owner)) // bigger problems to worry about + owner.remove_movespeed_modifier(/datum/movespeed_modifier/decloning) + +/datum/status_effect/decloning/tick(seconds_between_ticks) + if(owner.has_reagent(/datum/reagent/medicine/mutadone, MUTADONE_HEAL * seconds_between_ticks)) + var/strike_restore = MUTADONE_HEAL * seconds_between_ticks + + if(strikes_left <= 50 && strikes_left + strike_restore > 50) + to_chat(owner, span_notice("Controlling your muscles feels easier now.")) + owner.remove_movespeed_modifier(/datum/movespeed_modifier/decloning) + else if(SPT_PROB(5, seconds_between_ticks)) + to_chat(owner, span_warning("Your body is growing and shifting back into place.")) + + strikes_left = min(strikes_left + strike_restore, 100) + + owner.reagents.remove_reagent(/datum/reagent/medicine/mutadone, MUTADONE_HEAL * seconds_between_ticks) + + if(strikes_left == 100) + qdel(src) + + return + + if(!SPT_PROB(5, seconds_between_ticks)) + return + + var/strike_reduce = 3 + if(strikes_left > 50 && strikes_left - strike_reduce <= 50) + to_chat(owner, span_danger("You're having a hard time controlling your muscles.")) + owner.add_movespeed_modifier(/datum/movespeed_modifier/decloning) + + strikes_left = max(strikes_left - strike_reduce, 0) + + if(prob(50)) + to_chat(owner, span_danger(pick( + "Your body is giving in.", + "You feel some muscles twitching.", + "Your skin feels sandy.", + "You feel your limbs shifting around.", + ))) + else if(prob(33)) + to_chat(owner, span_danger("You are twitching uncontrollably.")) + owner.set_jitter_if_lower(30 SECONDS) + + if(strikes_left == 0) + owner.visible_message(span_danger("[owner]'s skin turns to dust!"), span_boldwarning("Your skin turns to dust!")) + owner.dust() + return + +/datum/status_effect/decloning/get_examine_text() + switch(strikes_left) + if(68 to 100) + return span_warning("[owner.p_Their()] body looks a bit deformed.") + if(34 to 67) + return span_warning("[owner.p_Their()] body looks very deformed.") + if(-INFINITY to 33) + return span_boldwarning("[owner.p_Their()] body looks severely deformed!") + +/atom/movable/screen/alert/status_effect/decloning + name = "Cellular Meltdown" + desc = "Your body is deforming, and doesn't feel like it's going to hold up much longer. You are going to need treatment soon." + icon_state = "dna_melt" + +/datum/movespeed_modifier/decloning + multiplicative_slowdown = 0.7 + blacklisted_movetypes = (FLYING|FLOATING) + +#undef MUTADONE_HEAL diff --git a/code/datums/status_effects/debuffs/slimed.dm b/code/datums/status_effects/debuffs/slimed.dm new file mode 100644 index 00000000000..15632277f3d --- /dev/null +++ b/code/datums/status_effects/debuffs/slimed.dm @@ -0,0 +1,85 @@ +/// The minimum amount of water stacks needed to start washing off the slime. +#define MIN_WATER_STACKS 5 +/// The minimum amount of health a mob has to have before the status effect is removed. +#define MIN_HEALTH 10 + +/atom/movable/screen/alert/status_effect/slimed + name = "Covered in Slime" + desc = "You are covered in slime and it's eating away at you! Find a way to wash it off!" + icon_state = "slimed" + +/datum/status_effect/slimed + id = "slimed" + tick_interval = 3 SECONDS + alert_type = /atom/movable/screen/alert/status_effect/slimed + remove_on_fullheal = TRUE + + /// The amount of slime stacks that were applied, reduced by showering yourself under water. + var/slime_stacks = 10 // ~10 seconds of standing under a shower + /// Slime color, used for particles. + var/slime_color + /// Changes particle colors to rainbow, this overrides `slime_color`. + var/rainbow + +/datum/status_effect/slimed/on_creation(mob/living/new_owner, slime_color = COLOR_SLIME_GREY, rainbow = FALSE) + src.slime_color = slime_color + src.rainbow = rainbow + return ..() + +/datum/status_effect/slimed/on_apply() + if(owner.get_organic_health() <= MIN_HEALTH) + return FALSE + to_chat(owner, span_userdanger("You have been covered in a thick layer of slime! Find a way to wash it off!")) + return ..() + +/datum/status_effect/slimed/tick(seconds_between_ticks) + // remove from the mob once we have dealt enough damage + if(owner.get_organic_health() <= MIN_HEALTH) + to_chat(owner, span_warning("You feel the layer of slime crawling off of your weakened body.")) + qdel(src) + return + + // handle washing slime off + var/datum/status_effect/fire_handler/wet_stacks/wetness = locate() in owner.status_effects + if(istype(wetness) && wetness.stacks > (MIN_WATER_STACKS * seconds_between_ticks)) + slime_stacks -= seconds_between_ticks // lose 1 stack per second + wetness.adjust_stacks(-5 * seconds_between_ticks) + + // got rid of it + if(slime_stacks <= 0) + to_chat(owner, span_notice("You manage to wash off the layer of slime completely.")) + qdel(src) + return + + if(SPT_PROB(10, seconds_between_ticks)) + to_chat(owner,span_warning("The layer of slime is slowly getting thinner as it's washing off your skin.")) + + return + + // otherwise deal brute damage + owner.apply_damage(rand(2,4) * seconds_between_ticks, damagetype = BRUTE) + + if(SPT_PROB(10, seconds_between_ticks)) + var/feedback_text = pick(list( + "Your entire body screams with pain", + "Your skin feels like it's coming off", + "Your body feels like it's melting together" + )) + to_chat(owner, span_userdanger("[feedback_text] as the layer of slime eats away at you!")) + +/datum/status_effect/slimed/update_particles() + if(particle_effect) + return + + // taste the rainbow + var/particle_type = rainbow ? /particles/slime/rainbow : /particles/slime + particle_effect = new(owner, particle_type) + + if(!rainbow) + particle_effect.particles.color = "[slime_color]a0" + +/datum/status_effect/slimed/get_examine_text() + return span_warning("[owner.p_They()] [owner.p_are()] covered in bubbling slime!") + +#undef MIN_HEALTH +#undef MIN_WATER_STACKS diff --git a/code/datums/status_effects/debuffs/static_vision.dm b/code/datums/status_effects/debuffs/static_vision.dm new file mode 100644 index 00000000000..7132c189b9d --- /dev/null +++ b/code/datums/status_effects/debuffs/static_vision.dm @@ -0,0 +1,29 @@ +/datum/status_effect/static_vision + id = "static_vision" + status_type = STATUS_EFFECT_REPLACE + alert_type = null + +/datum/status_effect/static_vision/on_creation(mob/living/new_owner, duration = 3 SECONDS) + src.duration = duration + return ..() + +/datum/status_effect/static_vision/on_apply() + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(remove_static_vision)) + + owner.overlay_fullscreen(id, /atom/movable/screen/fullscreen/static_vision) + owner.sound_environment_override = SOUND_ENVIRONMENT_UNDERWATER + + return TRUE + +/datum/status_effect/static_vision/on_remove() + UnregisterSignal(owner, COMSIG_LIVING_DEATH) + + owner.clear_fullscreen(id) + if(owner.sound_environment_override == SOUND_ENVIRONMENT_UNDERWATER) + owner.sound_environment_override = SOUND_ENVIRONMENT_NONE + +/// Handles clearing on death +/datum/status_effect/static_vision/proc/remove_static_vision(datum/source, admin_revive) + SIGNAL_HANDLER + + qdel(src) diff --git a/code/datums/status_effects/food_effects.dm b/code/datums/status_effects/food_effects.dm new file mode 100644 index 00000000000..e41ef67ad10 --- /dev/null +++ b/code/datums/status_effects/food_effects.dm @@ -0,0 +1,52 @@ +/// Buffs given by eating hand-crafted food. The duration scales with consumable reagents purity. +/datum/status_effect/food + id = "food_buff" + duration = 5 MINUTES // Same as food mood buffs + status_type = STATUS_EFFECT_REPLACE // Only one food buff allowed + /// Buff power + var/strength + +/datum/status_effect/food/on_creation(mob/living/new_owner, timeout_mod = 1, strength = 1) + src.strength = strength + //Generate alert when not specified + if(alert_type == /atom/movable/screen/alert/status_effect) + alert_type = "/atom/movable/screen/alert/status_effect/food/buff_[strength]" + if(isnum(timeout_mod)) + duration *= timeout_mod + . = ..() + +/atom/movable/screen/alert/status_effect/food + name = "Hand-crafted meal" + desc = "Eating it made me feel better." + icon_state = "food_buff_1" + +/atom/movable/screen/alert/status_effect/food/buff_1 + icon_state = "food_buff_1" + +/atom/movable/screen/alert/status_effect/food/buff_2 + icon_state = "food_buff_2" + +/atom/movable/screen/alert/status_effect/food/buff_3 + icon_state = "food_buff_3" + +/atom/movable/screen/alert/status_effect/food/buff_4 + icon_state = "food_buff_4" + +/atom/movable/screen/alert/status_effect/food/buff_5 + icon_state = "food_buff_5" + +/// Makes you gain a trait +/datum/status_effect/food/trait + var/trait = TRAIT_DUMB // You need to override this + +/datum/status_effect/food/trait/on_apply() + ADD_TRAIT(owner, trait, type) + return ..() + +/datum/status_effect/food/trait/be_replaced() + REMOVE_TRAIT(owner, trait, type) + return ..() + +/datum/status_effect/food/trait/on_remove() + REMOVE_TRAIT(owner, trait, type) + return ..() diff --git a/code/datums/storage/subtypes/surgery_tray.dm b/code/datums/storage/subtypes/surgery_tray.dm new file mode 100644 index 00000000000..35886581318 --- /dev/null +++ b/code/datums/storage/subtypes/surgery_tray.dm @@ -0,0 +1,22 @@ +/datum/storage/surgery_tray + max_total_storage = 30 + max_specific_storage = WEIGHT_CLASS_NORMAL + max_slots = 14 + +/datum/storage/surgery_tray/New() + . = ..() + set_holdable(list( + /obj/item/blood_filter, + /obj/item/bonesetter, + /obj/item/cautery, + /obj/item/circular_saw, + /obj/item/clothing/mask/surgical, + /obj/item/hemostat, + /obj/item/razor, + /obj/item/retractor, + /obj/item/scalpel, + /obj/item/stack/medical/bone_gel, + /obj/item/stack/sticky_tape/surgical, + /obj/item/surgical_drapes, + /obj/item/surgicaldrill, + )) diff --git a/code/datums/wires/mass_driver.dm b/code/datums/wires/mass_driver.dm new file mode 100644 index 00000000000..329da73c2dc --- /dev/null +++ b/code/datums/wires/mass_driver.dm @@ -0,0 +1,25 @@ +/datum/wires/mass_driver + holder_type = /obj/machinery/mass_driver + proper_name = "Mass Driver" + +/datum/wires/mass_driver/New(atom/holder) + wires = list(WIRE_LAUNCH, WIRE_SAFETIES) + ..() + +/datum/wires/mass_driver/on_pulse(wire) + var/obj/machinery/mass_driver/the_mass_driver = holder + switch(wire) + if(WIRE_LAUNCH) + the_mass_driver.drive() + holder.visible_message(span_notice("The drive mechanism activates.")) + if(WIRE_SAFETIES) + the_mass_driver.power = 3 + holder.visible_message(span_notice("You hear a worrying whirring noise emitting from the mass driver.")) + +/datum/wires/mass_driver/on_cut(wire, mend, source) + var/obj/machinery/mass_driver/the_mass_driver = holder + switch(wire) + if(WIRE_SAFETIES) + if(the_mass_driver.power > 1) + the_mass_driver.power = 1 + holder.visible_message(span_notice("The whirring noise emitting from the mass driver stops.")) diff --git a/code/datums/wires/mecha.dm b/code/datums/wires/mecha.dm new file mode 100644 index 00000000000..07bc1190148 --- /dev/null +++ b/code/datums/wires/mecha.dm @@ -0,0 +1,78 @@ +/datum/wires/mecha + holder_type = /obj/vehicle/sealed/mecha + proper_name = "Mecha Control" + +/datum/wires/mecha/New(atom/holder) + wires = list(WIRE_IDSCAN, WIRE_DISARM, WIRE_ZAP, WIRE_OVERCLOCK) + var/obj/vehicle/sealed/mecha/mecha = holder + if(mecha.mecha_flags & HAS_LIGHTS) + wires += WIRE_LIGHT + add_duds(3) + ..() + +/datum/wires/mecha/interactable(mob/user) + if(!..()) + return FALSE + var/obj/vehicle/sealed/mecha/mecha = holder + return mecha.mecha_flags & PANEL_OPEN + +/datum/wires/mecha/get_status() + var/obj/vehicle/sealed/mecha/mecha = holder + var/list/status = list() + status += "The orange light is [mecha.internal_damage & MECHA_INT_SHORT_CIRCUIT ? "on" : "off"]." + status += "The red light is [mecha.overclock_mode ? "blinking" : "off"]." + status += "The green light is [(mecha.mecha_flags & ID_LOCK_ON) || mecha.dna_lock ? "on" : "off"]." + if(mecha.mecha_flags & HAS_LIGHTS) + status += "The yellow light is [mecha.light_on ? "on" : "off"]." + status += "The blue light is [mecha.equipment_disabled ? "on" : "off"]." + return status + +/datum/wires/mecha/on_pulse(wire) + var/obj/vehicle/sealed/mecha/mecha = holder + switch(wire) + if(WIRE_IDSCAN) + mecha.mecha_flags ^= ID_LOCK_ON + mecha.dna_lock = null + if(WIRE_DISARM) + mecha.equipment_disabled = TRUE + mecha.set_mouse_pointer() + if(WIRE_ZAP) + mecha.internal_damage ^= MECHA_INT_SHORT_CIRCUIT + if(WIRE_LIGHT) + mecha.set_light_on(!mecha.light_on) + if(WIRE_OVERCLOCK) + mecha.toggle_overclock() + +/datum/wires/mecha/on_cut(wire, mend, source) + var/obj/vehicle/sealed/mecha/mecha = holder + switch(wire) + if(WIRE_IDSCAN) + if(!mend) + mecha.mecha_flags &= ~ID_LOCK_ON + mecha.dna_lock = null + if(WIRE_DISARM) + mecha.equipment_disabled = !mend + mecha.set_mouse_pointer() + if(WIRE_ZAP) + if(mend) + mecha.internal_damage &= ~MECHA_INT_SHORT_CIRCUIT + else + mecha.internal_damage |= MECHA_INT_SHORT_CIRCUIT + if(WIRE_LIGHT) + mecha.set_light_on(!mend) + if(WIRE_OVERCLOCK) + if(!mend) + mecha.toggle_overclock(FALSE) + +/datum/wires/mecha/ui_act(action, params) + . = ..() + if(.) + return + var/obj/vehicle/sealed/mecha/mecha = holder + if(!issilicon(usr) && mecha.internal_damage & MECHA_INT_SHORT_CIRCUIT && mecha.shock(usr)) + return FALSE + +/datum/wires/mecha/can_reveal_wires(mob/user) + if(HAS_TRAIT(user, TRAIT_KNOW_ROBO_WIRES)) + return TRUE + return ..() diff --git a/code/datums/wounds/_wound_static_data.dm b/code/datums/wounds/_wound_static_data.dm new file mode 100644 index 00000000000..7a59ea57413 --- /dev/null +++ b/code/datums/wounds/_wound_static_data.dm @@ -0,0 +1,199 @@ +// This datum is merely a singleton instance that allows for custom "can be applied" behaviors without instantiating a wound instance. +// For example: You can make a pregen_data subtype for your wound that overrides can_be_applied_to to only apply to specifically slimeperson limbs. +// Without this, youre stuck with very static initial variables. + +/// A singleton datum that holds pre-gen and static data about a wound. Each wound datum should have a corresponding wound_pregen_data. +/datum/wound_pregen_data + /// The typepath of the wound we will be handling and storing data of. NECESSARY IF THIS IS A NON-ABSTRACT TYPE! + var/datum/wound/wound_path_to_generate + + /// Will this be instantiated? + var/abstract = FALSE + + /// If true, our wound can be selected in ordinary wound rolling. If this is set to false, our wound can only be directly instantiated by use of specific typepath. + var/can_be_randomly_generated = TRUE + + /// A list of biostates a limb must have to receive our wound, in wounds.dm. + var/required_limb_biostate + /// If false, we will check if the limb has all of our required biostates instead of just any. + var/require_any_biostate = FALSE + + /// If false, we will iterate through wounds on a given limb, and if any match our type, we wont add our wound. + var/duplicates_allowed = FALSE + + /// If we require BIO_BLOODED, we will not add our wound if this is true and the limb cannot bleed. + var/ignore_cannot_bleed = TRUE // a lot of bleed wounds should still be applied for purposes of mangling flesh + + /// A list of bodyzones we are applicable to. + var/list/viable_zones = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + /// The types of attack that can generate this wound. E.g. WOUND_SLASH = A sharp attack can cause this, WOUND_BLUNT = an attack with no sharpness/an attack with sharpness against a limb with mangled exterior can cause this. + var/list/required_wounding_types + /// If true, this wound can only be generated by all [required_wounding_types] at once, not just any. + var/match_all_wounding_types = FALSE + + /// The weight that will be used if, by the end of wound selection, there are multiple valid wounds. This will be inserted into pick_weight, so use integers. + var/weight = WOUND_DEFAULT_WEIGHT + + /// The minimum injury roll a attack must get to generate us. Affected by our wound's threshold_penalty and series_threshold_penalty, as well as the attack's wound_bonus. See check_wounding_mods(). + var/threshold_minimum + + /// The series of wounds this is in. See wounds.dm (the defines file) for a more detailed explanation - but tldr is that no 2 wounds of the same series can be on a limb. + var/wound_series + + /// If true, we will attempt to, during a random wound roll, overpower and remove other wound typepaths from the possible wounds list using [competition_mode] and [overpower_wounds_of_even_severity]. + var/compete_for_wounding = TRUE + /// The competition mode with which we will remove other wounds from a possible wound roll assuming [compete_for_wounding] is TRUE. See wounds.dm, the defines file, for more information on what these do. + var/competition_mode = WOUND_COMPETITION_OVERPOWER_LESSERS + /// If this and [compete_for_wounding] is true, we will remove wounds of an even severity to us during a random wound roll. + var/overpower_wounds_of_even_severity = FALSE + + /// A list of BIO_ defines that will be iterated over in order to determine the scar file our wound will generate. + /// Use generate_scar_priorities to create a custom list. + var/list/scar_priorities + +/datum/wound_pregen_data/New() + . = ..() + + if (!abstract) + if (required_limb_biostate == null) + stack_trace("required_limb_biostate null - please set it! occured on: [src.type]") + if (wound_path_to_generate == null) + stack_trace("wound_path_to_generate null - please set it! occured on: [src.type]") + + scar_priorities = generate_scar_priorities() + +/// Should return a list of BIO_ biostate priorities, in order. See [scar_priorities] for further documentation. +/datum/wound_pregen_data/proc/generate_scar_priorities() + RETURN_TYPE(/list) + + var/list/priorities = list( + "[BIO_FLESH]", + "[BIO_BONE]", + ) + + return priorities + +// this proc is the primary reason this datum exists - a singleton instance so we can always run this proc even without the wound existing +/** + * Args: + * * obj/item/bodypart/limb: The limb we are considering. + * * list/suggested_wounding_types: The wounding types to be checked against the wounding types we require. Defaults to required_wounding_types. + * * datum/wound/old_wound: If we would replace a wound, this would be said wound. Nullable. + * * random_roll = FALSE: If this is in the context of a random wound generation, and this wound wasn't specifically checked. + * + * Returns: + * FALSE if the limb cannot be wounded, if the wounding types dont match ours (via wounding_types_valid()), if we have a higher severity wound already in our series, + * if we have a biotype mismatch, if the limb isnt in a viable zone, or if theres any duplicate wound types. + * TRUE otherwise. + */ +/datum/wound_pregen_data/proc/can_be_applied_to(obj/item/bodypart/limb, list/suggested_wounding_types = required_wounding_types, datum/wound/old_wound, random_roll = FALSE, duplicates_allowed = src.duplicates_allowed, care_about_existing_wounds = TRUE) + SHOULD_BE_PURE(TRUE) + + if (!istype(limb) || !limb.owner) + return FALSE + + if (random_roll && !can_be_randomly_generated) + return FALSE + + if (HAS_TRAIT(limb.owner, TRAIT_NEVER_WOUNDED) || (limb.owner.status_flags & GODMODE)) + return FALSE + + if (!wounding_types_valid(suggested_wounding_types)) + return FALSE + + if (care_about_existing_wounds) + for (var/datum/wound/preexisting_wound as anything in limb.wounds) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[preexisting_wound.type] + if (pregen_data.wound_series == wound_series) + if (preexisting_wound.severity >= initial(wound_path_to_generate.severity)) + return FALSE + + if (!ignore_cannot_bleed && ((required_limb_biostate & BIO_BLOODED) && !limb.can_bleed())) + return FALSE + + if (!biostate_valid(limb.biological_state)) + return FALSE + + if (!(limb.body_zone in viable_zones)) + return FALSE + + // we accept promotions and demotions, but no point in redundancy. This should have already been checked wherever the wound was rolled and applied for (see: bodypart damage code), but we do an extra check + // in case we ever directly add wounds + if (!duplicates_allowed) + for (var/datum/wound/preexisting_wound as anything in limb.wounds) + if (preexisting_wound.type == wound_path_to_generate && (preexisting_wound != old_wound)) + return FALSE + return TRUE + +/// Returns true if we have the given biostates, or any biostate in it if check_for_any is true. False otherwise. +/datum/wound_pregen_data/proc/biostate_valid(biostate) + if (require_any_biostate) + if (!(biostate & required_limb_biostate)) + return FALSE + else if (!((biostate & required_limb_biostate) == required_limb_biostate)) // check for all + return FALSE + + return TRUE + +/** + * A simple getter for [weight], with arguments supplied to allow custom behavior. + * + * Args: + * * obj/item/bodypart/limb: The limb we are contemplating being added to. Nullable. + * * woundtype: The woundtype of the assumed attack that would generate us. Nullable. + * * damage: The raw damage that would cause us. Nullable. + * * attack_direction: The direction of the attack that'd cause us. Nullable. + * * damage_source: The entity that would cause us. Nullable. + * + * Returns: + * Our weight. + */ +/datum/wound_pregen_data/proc/get_weight(obj/item/bodypart/limb, woundtype, damage, attack_direction, damage_source) + return weight + +/// Returns TRUE if we use WOUND_ALL, or we require all types and have all/if we require any and have any, FALSE otherwise. +/datum/wound_pregen_data/proc/wounding_types_valid(list/suggested_wounding_types) + if (WOUND_ALL in required_wounding_types) + return TRUE + if (!length(suggested_wounding_types)) + return FALSE + + for (var/iter_wounding_type as anything in suggested_wounding_types) + if (!(iter_wounding_type in required_wounding_types)) + if (match_all_wounding_types) + return FALSE + else + if (!match_all_wounding_types) + return TRUE + + return match_all_wounding_types // if we get here, we've matched everything + +/** + * A simple getter for [threshold_minimum], with arguments supplied to allow custom behavior. + * + * Args: + * * obj/item/bodypart/part: The limb we are contemplating being added to. + * * attack_direction: The direction of the attack that'd generate us. Nullable. + * * damage_source: The source of the damage that'd cause us. Nullable. + */ +/datum/wound_pregen_data/proc/get_threshold_for(obj/item/bodypart/part, attack_direction, damage_source) + return threshold_minimum + +/// Returns a new instance of our wound datum. +/datum/wound_pregen_data/proc/generate_instance(obj/item/bodypart/limb, ...) + RETURN_TYPE(/datum/wound) + + return new wound_path_to_generate + +/datum/wound_pregen_data/Destroy(force, ...) + var/error_message = "[src], a singleton wound pregen data instance, was destroyed! This should not happen!" + if (force) + error_message += " NOTE: This Destroy() was called with force == TRUE. This instance will be deleted and replaced with a new one." + stack_trace(error_message) + + if (!force) + return QDEL_HINT_LETMELIVE + + . = ..() + + GLOB.all_wound_pregen_data[wound_path_to_generate] = new src.type //recover diff --git a/code/datums/wounds/blunt.dm b/code/datums/wounds/blunt.dm new file mode 100644 index 00000000000..219b7dd8805 --- /dev/null +++ b/code/datums/wounds/blunt.dm @@ -0,0 +1,3 @@ +/datum/wound/blunt + name = "Blunt Wound" + sound_effect = 'sound/effects/wounds/crack1.ogg' diff --git a/code/datums/wounds/scars/_static_scar_data.dm b/code/datums/wounds/scars/_static_scar_data.dm new file mode 100644 index 00000000000..942dcffff9c --- /dev/null +++ b/code/datums/wounds/scars/_static_scar_data.dm @@ -0,0 +1,20 @@ +GLOBAL_LIST_INIT_TYPED(all_static_scar_data, /datum/static_scar_data, generate_static_scar_data()) + +/proc/generate_static_scar_data() + RETURN_TYPE(/list/datum/static_scar_data) + + var/list/datum/wound_pregen_data/data = list() + + for (var/datum/wound_pregen_data/path as anything in typecacheof(path = /datum/static_scar_data, ignore_root_path = TRUE)) + if (initial(path.abstract)) + continue + + var/datum/wound_pregen_data/pregen_data = new path + data[pregen_data.wound_path_to_generate] = pregen_data + + return data + +/datum/static_scar_data + var/abstract = FALSE + + diff --git a/code/game/area/areas/station/cargo.dm b/code/game/area/areas/station/cargo.dm new file mode 100644 index 00000000000..8bb5229320d --- /dev/null +++ b/code/game/area/areas/station/cargo.dm @@ -0,0 +1,55 @@ +/area/station/cargo + name = "Quartermasters" + icon_state = "quart" + airlock_wires = /datum/wires/airlock/service + sound_environment = SOUND_AREA_STANDARD_STATION + +/area/station/cargo/sorting + name = "\improper Delivery Office" + icon_state = "cargo_delivery" + +/area/station/cargo/warehouse + name = "\improper Warehouse" + icon_state = "cargo_warehouse" + sound_environment = SOUND_AREA_LARGE_ENCLOSED + +/area/station/cargo/drone_bay + name = "\improper Drone Bay" + icon_state = "cargo_drone" + +/area/station/cargo/boutique + name = "\improper Boutique" + icon_state = "cargo_delivery" + sound_environment = SOUND_AREA_WOODFLOOR + +/area/station/cargo/warehouse/upper + name = "\improper Upper Warehouse" + +/area/station/cargo/office + name = "\improper Cargo Office" + icon_state = "cargo_office" + +/area/station/cargo/storage + name = "\improper Cargo Bay" + icon_state = "cargo_bay" + sound_environment = SOUND_AREA_LARGE_ENCLOSED + +/area/station/cargo/lobby + name = "\improper Cargo Lobby" + icon_state = "cargo_lobby" + +/area/station/cargo/miningdock + name = "\improper Mining Dock" + icon_state = "mining_dock" + +/area/station/cargo/miningdock/cafeteria + name = "\improper Mining Cafeteria" + icon_state = "mining_cafe" + +/area/station/cargo/miningdock/oresilo + name = "\improper Mining Ore Silo Storage" + icon_state = "mining_silo" + +/area/station/cargo/miningoffice + name = "\improper Mining Office" + icon_state = "mining" diff --git a/code/game/area/areas/station/command.dm b/code/game/area/areas/station/command.dm new file mode 100644 index 00000000000..a1a521e77a8 --- /dev/null +++ b/code/game/area/areas/station/command.dm @@ -0,0 +1,96 @@ +/area/station/command + name = "Command" + icon_state = "command" + ambientsounds = list( + 'sound/ambience/signal.ogg', + ) + airlock_wires = /datum/wires/airlock/command + sound_environment = SOUND_AREA_STANDARD_STATION + +/area/station/command/bridge + name = "\improper Bridge" + icon_state = "bridge" + +/area/station/command/meeting_room + name = "\improper Heads of Staff Meeting Room" + icon_state = "meeting" + sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR + +/area/station/command/meeting_room/council + name = "\improper Council Chamber" + icon_state = "meeting" + sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR + +/area/station/command/corporate_showroom + name = "\improper Corporate Showroom" + icon_state = "showroom" + sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR + +/area/station/command/corporate_suite + name = "\improper Corporate Guest Suite" + icon_state = "command" + sound_environment = SOUND_AREA_WOODFLOOR + +/* +* Command Head Areas +*/ + +/area/station/command/heads_quarters + icon_state = "heads_quarters" + +/area/station/command/heads_quarters/captain + name = "\improper Captain's Office" + icon_state = "captain" + sound_environment = SOUND_AREA_WOODFLOOR + +/area/station/command/heads_quarters/captain/private + name = "\improper Captain's Quarters" + icon_state = "captain_private" + sound_environment = SOUND_AREA_WOODFLOOR + +/area/station/command/heads_quarters/ce + name = "\improper Chief Engineer's Office" + icon_state = "ce_office" + +/area/station/command/heads_quarters/cmo + name = "\improper Chief Medical Officer's Office" + icon_state = "cmo_office" + +/area/station/command/heads_quarters/hop + name = "\improper Head of Personnel's Office" + icon_state = "hop_office" + +/area/station/command/heads_quarters/hos + name = "\improper Head of Security's Office" + icon_state = "hos_office" + +/area/station/command/heads_quarters/rd + name = "\improper Research Director's Office" + icon_state = "rd_office" + +/area/station/command/heads_quarters/qm + name = "\improper Quartermaster's Office" + icon_state = "qm_office" + +/* +* Command - Teleporter +*/ + +/area/station/command/teleporter + name = "\improper Teleporter Room" + icon_state = "teleporter" + ambience_index = AMBIENCE_ENGI + +/area/station/command/gateway + name = "\improper Gateway" + icon_state = "gateway" + ambience_index = AMBIENCE_ENGI + +/* +* Command - Misc +*/ + +/area/station/command/corporate_dock + name = "\improper Corporate Private Dock" + icon_state = "command" + sound_environment = SOUND_AREA_SMALL_SOFTFLOOR diff --git a/code/game/area/areas/station/common.dm b/code/game/area/areas/station/common.dm new file mode 100644 index 00000000000..eb8a0380ddc --- /dev/null +++ b/code/game/area/areas/station/common.dm @@ -0,0 +1,157 @@ +/area/station/commons + name = "\improper Crew Facilities" + icon_state = "commons" + sound_environment = SOUND_AREA_STANDARD_STATION + area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + +/* +* Dorm Areas +*/ + +/area/station/commons/dorms + name = "\improper Dormitories" + icon_state = "dorms" + +/area/station/commons/dorms/room1 + name = "\improper Dorms Room 1" + icon_state = "room1" + +/area/station/commons/dorms/room2 + name = "\improper Dorms Room 2" + icon_state = "room2" + +/area/station/commons/dorms/room3 + name = "\improper Dorms Room 3" + icon_state = "room3" + +/area/station/commons/dorms/room4 + name = "\improper Dorms Room 4" + icon_state = "room4" + +/area/station/commons/dorms/apartment1 + name = "\improper Dorms Apartment 1" + icon_state = "apartment1" + +/area/station/commons/dorms/apartment2 + name = "\improper Dorms Apartment 2" + icon_state = "apartment2" + +/area/station/commons/dorms/barracks + name = "\improper Sleep Barracks" + +/area/station/commons/dorms/barracks/male + name = "\improper Male Sleep Barracks" + icon_state = "dorms_male" + +/area/station/commons/dorms/barracks/female + name = "\improper Female Sleep Barracks" + icon_state = "dorms_female" + +/area/station/commons/dorms/laundry + name = "\improper Laundry Room" + icon_state = "laundry_room" + +/area/station/commons/toilet + name = "\improper Dormitory Toilets" + icon_state = "toilet" + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/area/station/commons/toilet/auxiliary + name = "\improper Auxiliary Restrooms" + icon_state = "toilet" + +/area/station/commons/toilet/locker + name = "\improper Locker Toilets" + icon_state = "toilet" + +/area/station/commons/toilet/restrooms + name = "\improper Restrooms" + icon_state = "toilet" + +/* +* Rec and Locker Rooms +*/ + +/area/station/commons/locker + name = "\improper Locker Room" + icon_state = "locker" + +/area/station/commons/lounge + name = "\improper Bar Lounge" + icon_state = "lounge" + mood_bonus = 5 + mood_message = "I love being in the bar!" + mood_trait = TRAIT_EXTROVERT + sound_environment = SOUND_AREA_SMALL_SOFTFLOOR + +/area/station/commons/fitness + name = "\improper Fitness Room" + icon_state = "fitness" + +/area/station/commons/fitness/locker_room + name = "\improper Unisex Locker Room" + icon_state = "locker" + +/area/station/commons/fitness/locker_room/male + name = "\improper Male Locker Room" + icon_state = "locker_male" + +/area/station/commons/fitness/locker_room/female + name = "\improper Female Locker Room" + icon_state = "locker_female" + +/area/station/commons/fitness/recreation + name = "\improper Recreation Area" + icon_state = "rec" + +/area/station/commons/fitness/recreation/entertainment + name = "\improper Entertainment Center" + icon_state = "entertainment" + +/* +* Vacant Rooms +*/ + +/area/station/commons/vacant_room + name = "\improper Vacant Room" + icon_state = "vacant_room" + ambience_index = AMBIENCE_MAINT + +/area/station/commons/vacant_room/office + name = "\improper Vacant Office" + icon_state = "vacant_office" + +/area/station/commons/vacant_room/commissary + name = "\improper Vacant Commissary" + icon_state = "vacant_commissary" + +/* +* Storage Rooms +*/ + +/area/station/commons/storage + name = "\improper Commons Storage" + +/area/station/commons/storage/tools + name = "\improper Auxiliary Tool Storage" + icon_state = "tool_storage" + +/area/station/commons/storage/primary + name = "\improper Primary Tool Storage" + icon_state = "primary_storage" + +/area/station/commons/storage/art + name = "\improper Art Supply Storage" + icon_state = "art_storage" + +/area/station/commons/storage/emergency/starboard + name = "\improper Starboard Emergency Storage" + icon_state = "emergency_storage" + +/area/station/commons/storage/emergency/port + name = "\improper Port Emergency Storage" + icon_state = "emergency_storage" + +/area/station/commons/storage/mining + name = "\improper Public Mining Storage" + icon_state = "mining_storage" diff --git a/code/game/area/areas/station/engineering.dm b/code/game/area/areas/station/engineering.dm new file mode 100644 index 00000000000..a7ce535cc5d --- /dev/null +++ b/code/game/area/areas/station/engineering.dm @@ -0,0 +1,127 @@ +/area/station/engineering + icon_state = "engie" + ambience_index = AMBIENCE_ENGI + airlock_wires = /datum/wires/airlock/engineering + sound_environment = SOUND_AREA_LARGE_ENCLOSED + +/area/station/engineering/engine_smes + name = "\improper Engineering SMES" + icon_state = "engine_smes" + +/area/station/engineering/main + name = "Engineering" + icon_state = "engine" + +/area/station/engineering/hallway + name = "Engineering Hallway" + icon_state = "engine_hallway" + +/area/station/engineering/atmos + name = "Atmospherics" + icon_state = "atmos" + +/area/station/engineering/atmos/upper + name = "Upper Atmospherics" + +/area/station/engineering/atmos/project + name = "\improper Atmospherics Project Room" + icon_state = "atmos_projectroom" + +/area/station/engineering/atmos/pumproom + name = "\improper Atmospherics Pumping Room" + icon_state = "atmos_pump_room" + +/area/station/engineering/atmos/mix + name = "\improper Atmospherics Mixing Room" + icon_state = "atmos_mix" + +/area/station/engineering/atmos/storage + name = "\improper Atmospherics Storage Room" + icon_state = "atmos_storage" + +/area/station/engineering/atmos/storage/gas + name = "\improper Atmospherics Gas Storage" + icon_state = "atmos_storage_gas" + +/area/station/engineering/atmos/office + name = "\improper Atmospherics Office" + icon_state = "atmos_office" + +/area/station/engineering/atmos/hfr_room + name = "\improper Atmospherics HFR Room" + icon_state = "atmos_HFR" + +/area/station/engineering/atmospherics_engine + name = "\improper Atmospherics Engine" + icon_state = "atmos_engine" + area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + +/area/station/engineering/lobby + name = "\improper Engineering Lobby" + icon_state = "engi_lobby" + +/area/station/engineering/supermatter + name = "\improper Supermatter Engine" + icon_state = "engine_sm" + area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/area/station/engineering/supermatter/waste + name = "\improper Supermatter Waste Chamber" + icon_state = "engine_sm_waste" + +/area/station/engineering/supermatter/room + name = "\improper Supermatter Engine Room" + icon_state = "engine_sm_room" + sound_environment = SOUND_AREA_LARGE_ENCLOSED + +/area/station/engineering/break_room + name = "\improper Engineering Foyer" + icon_state = "engine_break" + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/area/station/engineering/gravity_generator + name = "\improper Gravity Generator Room" + icon_state = "grav_gen" + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/area/station/engineering/storage + name = "Engineering Storage" + icon_state = "engine_storage" + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/area/station/engineering/storage_shared + name = "Shared Engineering Storage" + icon_state = "engine_storage_shared" + +/area/station/engineering/transit_tube + name = "\improper Transit Tube" + icon_state = "transit_tube" + +/area/station/engineering/storage/tech + name = "Technical Storage" + icon_state = "tech_storage" + +/area/station/engineering/storage/tcomms + name = "Telecomms Storage" + icon_state = "tcom_storage" + area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + +/* +* Construction Areas +*/ + +/area/station/construction + name = "\improper Construction Area" + icon_state = "construction" + ambience_index = AMBIENCE_ENGI + sound_environment = SOUND_AREA_STANDARD_STATION + +/area/station/construction/mining/aux_base + name = "Auxiliary Base Construction" + icon_state = "aux_base_construction" + sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR + +/area/station/construction/storage_wing + name = "\improper Storage Wing" + icon_state = "storage_wing" diff --git a/code/game/area/areas/station/hallway.dm b/code/game/area/areas/station/hallway.dm new file mode 100644 index 00000000000..9512f20c709 --- /dev/null +++ b/code/game/area/areas/station/hallway.dm @@ -0,0 +1,154 @@ +/area/station/hallway + icon_state = "hall" + sound_environment = SOUND_AREA_STANDARD_STATION + +/area/station/hallway/primary + name = "\improper Primary Hallway" + icon_state = "primaryhall" + +/area/station/hallway/primary/aft + name = "\improper Aft Primary Hallway" + icon_state = "afthall" + +/area/station/hallway/primary/fore + name = "\improper Fore Primary Hallway" + icon_state = "forehall" + +/area/station/hallway/primary/starboard + name = "\improper Starboard Primary Hallway" + icon_state = "starboardhall" + +/area/station/hallway/primary/port + name = "\improper Port Primary Hallway" + icon_state = "porthall" + +/area/station/hallway/primary/central + name = "\improper Central Primary Hallway" + icon_state = "centralhall" + +/area/station/hallway/primary/central/fore + name = "\improper Fore Central Primary Hallway" + icon_state = "hallCF" + +/area/station/hallway/primary/central/aft + name = "\improper Aft Central Primary Hallway" + icon_state = "hallCA" + +/area/station/hallway/primary/upper + name = "\improper Upper Central Primary Hallway" + icon_state = "centralhall" + +/area/station/hallway/primary/tram + name = "\improper Primary Tram" + +/area/station/hallway/primary/tram/left + name = "\improper Port Tram Dock" + icon_state = "halltramL" + +/area/station/hallway/primary/tram/center + name = "\improper Central Tram Dock" + icon_state = "halltramM" + +/area/station/hallway/primary/tram/right + name = "\improper Starboard Tram Dock" + icon_state = "halltramR" + +// This shouldn't be used, but it gives an icon for the enviornment tree in the map editor +/area/station/hallway/secondary + icon_state = "secondaryhall" + +/area/station/hallway/secondary/command + name = "\improper Command Hallway" + icon_state = "bridge_hallway" + +/area/station/hallway/secondary/construction + name = "\improper Construction Area" + icon_state = "construction" + +/area/station/hallway/secondary/construction/engineering + name = "\improper Engineering Hallway" + +/area/station/hallway/secondary/exit + name = "\improper Escape Shuttle Hallway" + icon_state = "escape" + +/area/station/hallway/secondary/exit/escape_pod + name = "\improper Escape Pod Bay" + icon_state = "escape_pods" + +/area/station/hallway/secondary/exit/departure_lounge + name = "\improper Departure Lounge" + icon_state = "escape_lounge" + +/area/station/hallway/secondary/entry + name = "\improper Arrival Shuttle Hallway" + icon_state = "entry" + area_flags = UNIQUE_AREA | EVENT_PROTECTED + +/area/station/hallway/secondary/dock + name = "\improper Secondary Station Dock Hallway" + icon_state = "hall" + +/area/station/hallway/secondary/service + name = "\improper Service Hallway" + icon_state = "hall_service" + +/area/station/hallway/secondary/spacebridge + name = "\improper Space Bridge" + icon_state = "hall" + +/area/station/hallway/secondary/recreation + name = "\improper Recreation Hallway" + icon_state = "hall" + +/* +* Station Specific Areas +* If another station gets added, and you make specific areas for it +* Please make its own section in this file +* The areas below belong to North Star's Hallways +*/ + +//1 +/area/station/hallway/floor1 + name = "\improper First Floor Hallway" + +/area/station/hallway/floor1/aft + name = "\improper First Floor Aft Hallway" + icon_state = "1_aft" + +/area/station/hallway/floor1/fore + name = "\improper First Floor Fore Hallway" + icon_state = "1_fore" +//2 +/area/station/hallway/floor2 + name = "\improper Second Floor Hallway" + +/area/station/hallway/floor2/aft + name = "\improper Second Floor Aft Hallway" + icon_state = "2_aft" + +/area/station/hallway/floor2/fore + name = "\improper Second Floor Fore Hallway" + icon_state = "2_fore" +//3 +/area/station/hallway/floor3 + name = "\improper Third Floor Hallway" + +/area/station/hallway/floor3/aft + name = "\improper Third Floor Aft Hallway" + icon_state = "3_aft" + +/area/station/hallway/floor3/fore + name = "\improper Third Floor Fore Hallway" + icon_state = "3_fore" +//4 +/area/station/hallway/floor4 + name = "\improper Fourth Floor Hallway" + +/area/station/hallway/floor4/aft + name = "\improper Fourth Floor Aft Hallway" + icon_state = "4_aft" + +/area/station/hallway/floor4/fore + name = "\improper Fourth Floor Fore Hallway" + icon_state = "4_fore" diff --git a/code/game/area/areas/station/maintenance.dm b/code/game/area/areas/station/maintenance.dm new file mode 100644 index 00000000000..53e6da606d0 --- /dev/null +++ b/code/game/area/areas/station/maintenance.dm @@ -0,0 +1,411 @@ +/area/station/maintenance + name = "Generic Maintenance" + ambience_index = AMBIENCE_MAINT + area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED | PERSISTENT_ENGRAVINGS + airlock_wires = /datum/wires/airlock/maint + sound_environment = SOUND_AREA_TUNNEL_ENCLOSED + forced_ambience = TRUE + ambient_buzz = 'sound/ambience/source_corridor2.ogg' + ambient_buzz_vol = 20 + +/* +* Departmental Maintenance +*/ + +/area/station/maintenance/department/chapel + name = "Chapel Maintenance" + icon_state = "maint_chapel" + +/area/station/maintenance/department/chapel/monastery + name = "Monastery Maintenance" + icon_state = "maint_monastery" + +/area/station/maintenance/department/crew_quarters/bar + name = "Bar Maintenance" + icon_state = "maint_bar" + sound_environment = SOUND_AREA_WOODFLOOR + +/area/station/maintenance/department/crew_quarters/dorms + name = "Dormitory Maintenance" + icon_state = "maint_dorms" + +/area/station/maintenance/department/eva + name = "EVA Maintenance" + icon_state = "maint_eva" + +/area/station/maintenance/department/eva/abandoned + name = "Abandoned EVA Storage" + +/area/station/maintenance/department/electrical + name = "Electrical Maintenance" + icon_state = "maint_electrical" + +/area/station/maintenance/department/engine/atmos + name = "Atmospherics Maintenance" + icon_state = "maint_atmos" + +/area/station/maintenance/department/security + name = "Security Maintenance" + icon_state = "maint_sec" + +/area/station/maintenance/department/security/upper + name = "Upper Security Maintenance" + +/area/station/maintenance/department/security/brig + name = "Brig Maintenance" + icon_state = "maint_brig" + +/area/station/maintenance/department/medical + name = "Medbay Maintenance" + icon_state = "medbay_maint" + +/area/station/maintenance/department/medical/central + name = "Central Medbay Maintenance" + icon_state = "medbay_maint_central" + +/area/station/maintenance/department/medical/morgue + name = "Morgue Maintenance" + icon_state = "morgue_maint" + +/area/station/maintenance/department/science + name = "Science Maintenance" + icon_state = "maint_sci" + +/area/station/maintenance/department/science/central + name = "Central Science Maintenance" + icon_state = "maint_sci_central" + +/area/station/maintenance/department/cargo + name = "Cargo Maintenance" + icon_state = "maint_cargo" + +/area/station/maintenance/department/bridge + name = "Bridge Maintenance" + icon_state = "maint_bridge" + +/area/station/maintenance/department/engine + name = "Engineering Maintenance" + icon_state = "maint_engi" + +/area/station/maintenance/department/prison + name = "Prison Maintenance" + icon_state = "sec_prison" + +/area/station/maintenance/department/science/xenobiology + name = "Xenobiology Maintenance" + icon_state = "xenomaint" + area_flags = VALID_TERRITORY | BLOBS_ALLOWED | UNIQUE_AREA | XENOBIOLOGY_COMPATIBLE | CULT_PERMITTED + +/* +* Generic Maintenance Tunnels +*/ + +/area/station/maintenance/aft + name = "Aft Maintenance" + icon_state = "aftmaint" + +/area/station/maintenance/aft/upper + name = "Upper Aft Maintenance" + icon_state = "upperaftmaint" + +/* Use greater variants of area definitions for when the station has two different sections of maintenance on the same z-level. +* Can stand alone without "lesser". +* This one means that this goes more fore/north than the "lesser" maintenance area. +*/ +/area/station/maintenance/aft/greater + name = "Greater Aft Maintenance" + icon_state = "greateraftmaint" + +/* Use lesser variants of area definitions for when the station has two different sections of maintenance on the same z-level in conjunction with "greater". +* (just because it follows better). +* This one means that this goes more aft/south than the "greater" maintenance area. +*/ + +/area/station/maintenance/aft/lesser + name = "Lesser Aft Maintenance" + icon_state = "lesseraftmaint" + +/area/station/maintenance/central + name = "Central Maintenance" + icon_state = "centralmaint" + +/area/station/maintenance/central/greater + name = "Greater Central Maintenance" + icon_state = "greatercentralmaint" + +/area/station/maintenance/central/lesser + name = "Lesser Central Maintenance" + icon_state = "lessercentralmaint" + +/area/station/maintenance/fore + name = "Fore Maintenance" + icon_state = "foremaint" + +/area/station/maintenance/fore/upper + name = "Upper Fore Maintenance" + icon_state = "upperforemaint" + +/area/station/maintenance/fore/greater + name = "Greater Fore Maintenance" + icon_state = "greaterforemaint" + +/area/station/maintenance/fore/lesser + name = "Lesser Fore Maintenance" + icon_state = "lesserforemaint" + +/area/station/maintenance/starboard + name = "Starboard Maintenance" + icon_state = "starboardmaint" + +/area/station/maintenance/starboard/upper + name = "Upper Starboard Maintenance" + icon_state = "upperstarboardmaint" + +/area/station/maintenance/starboard/central + name = "Central Starboard Maintenance" + icon_state = "centralstarboardmaint" + +/area/station/maintenance/starboard/greater + name = "Greater Starboard Maintenance" + icon_state = "greaterstarboardmaint" + +/area/station/maintenance/starboard/lesser + name = "Lesser Starboard Maintenance" + icon_state = "lesserstarboardmaint" + +/area/station/maintenance/starboard/aft + name = "Aft Starboard Maintenance" + icon_state = "asmaint" + +/area/station/maintenance/starboard/fore + name = "Fore Starboard Maintenance" + icon_state = "fsmaint" + +/area/station/maintenance/port + name = "Port Maintenance" + icon_state = "portmaint" + +/area/station/maintenance/port/central + name = "Central Port Maintenance" + icon_state = "centralportmaint" + +/area/station/maintenance/port/greater + name = "Greater Port Maintenance" + icon_state = "greaterportmaint" + +/area/station/maintenance/port/lesser + name = "Lesser Port Maintenance" + icon_state = "lesserportmaint" + +/area/station/maintenance/port/aft + name = "Aft Port Maintenance" + icon_state = "apmaint" + +/area/station/maintenance/port/fore + name = "Fore Port Maintenance" + icon_state = "fpmaint" + +/area/station/maintenance/tram + name = "Primary Tram Maintenance" + +/area/station/maintenance/tram/left + name = "\improper Port Tram Underpass" + icon_state = "mainttramL" + +/area/station/maintenance/tram/mid + name = "\improper Central Tram Underpass" + icon_state = "mainttramM" + +/area/station/maintenance/tram/right + name = "\improper Starboard Tram Underpass" + icon_state = "mainttramR" + +/* +* Discrete Maintenance Areas +*/ + +/area/station/maintenance/disposal + name = "Waste Disposal" + icon_state = "disposal" + +/area/station/maintenance/hallway/abandoned_command + name = "\improper Abandoned Command Hallway" + icon_state = "maint_bridge" + +/area/station/maintenance/hallway/abandoned_recreation + name = "\improper Abandoned Recreation Hallway" + icon_state = "maint_dorms" + +/area/station/maintenance/disposal/incinerator + name = "\improper Incinerator" + icon_state = "incinerator" + +/area/station/maintenance/space_hut + name = "\improper Space Hut" + icon_state = "spacehut" + +/area/station/maintenance/space_hut/cabin + name = "Abandoned Cabin" + +/area/station/maintenance/space_hut/plasmaman + name = "\improper Abandoned Plasmaman Friendly Startup" + +/area/station/maintenance/space_hut/observatory + name = "\improper Space Observatory" + +/* +* Radation Storm Shelters +*/ + +/area/station/maintenance/radshelter + name = "\improper Radstorm Shelter" + icon_state = "radstorm_shelter" + +/area/station/maintenance/radshelter/medical + name = "\improper Medical Radstorm Shelter" + +/area/station/maintenance/radshelter/sec + name = "\improper Security Radstorm Shelter" + +/area/station/maintenance/radshelter/service + name = "\improper Service Radstorm Shelter" + +/area/station/maintenance/radshelter/civil + name = "\improper Civilian Radstorm Shelter" + +/area/station/maintenance/radshelter/sci + name = "\improper Science Radstorm Shelter" + +/area/station/maintenance/radshelter/cargo + name = "\improper Cargo Radstorm Shelter" + +/* +* External Hull Access Areas +*/ + +/area/station/maintenance/external + name = "\improper External Hull Access" + icon_state = "amaint" + +/area/station/maintenance/external/aft + name = "\improper Aft External Hull Access" + +/area/station/maintenance/external/port + name = "\improper Port External Hull Access" + +/area/station/maintenance/external/port/bow + name = "\improper Port Bow External Hull Access" + +/* +* Station Specific Areas +* If another station gets added, and you make specific areas for it +* Please make its own section in this file +* The areas below belong to North Star's Maintenance +*/ + +//1 +/area/station/maintenance/floor1 + name = "\improper 1st Floor Maint" + +/area/station/maintenance/floor1/port + name = "\improper 1st Floor Central Port Maint" + icon_state = "maintcentral" + +/area/station/maintenance/floor1/port/fore + name = "\improper 1st Floor Fore Port Maint" + icon_state = "maintfore" +/area/station/maintenance/floor1/port/aft + name = "\improper 1st Floor Aft Port Maint" + icon_state = "maintaft" + +/area/station/maintenance/floor1/starboard + name = "\improper 1st Floor Central Starboard Maint" + icon_state = "maintcentral" + +/area/station/maintenance/floor1/starboard/fore + name = "\improper 1st Floor Fore Starboard Maint" + icon_state = "maintfore" + +/area/station/maintenance/floor1/starboard/aft + name = "\improper 1st Floor Aft Starboard Maint" + icon_state = "maintaft" +//2 +/area/station/maintenance/floor2 + name = "\improper 2nd Floor Maint" +/area/station/maintenance/floor2/port + name = "\improper 2nd Floor Central Port Maint" + icon_state = "maintcentral" + +/area/station/maintenance/floor2/port/fore + name = "\improper 2nd Floor Fore Port Maint" + icon_state = "maintfore" + +/area/station/maintenance/floor2/port/aft + name = "\improper 2nd Floor Aft Port Maint" + icon_state = "maintaft" + +/area/station/maintenance/floor2/starboard + name = "\improper 2nd Floor Central Starboard Maint" + icon_state = "maintcentral" + +/area/station/maintenance/floor2/starboard/fore + name = "\improper 2nd Floor Fore Starboard Maint" + icon_state = "maintfore" + +/area/station/maintenance/floor2/starboard/aft + name = "\improper 2nd Floor Aft Starboard Maint" + icon_state = "maintaft" +//3 +/area/station/maintenance/floor3 + name = "\improper 3rd Floor Maint" + +/area/station/maintenance/floor3/port + name = "\improper 3rd Floor Central Port Maint" + icon_state = "maintcentral" + +/area/station/maintenance/floor3/port/fore + name = "\improper 3rd Floor Fore Port Maint" + icon_state = "maintfore" + +/area/station/maintenance/floor3/port/aft + name = "\improper 3rd Floor Aft Port Maint" + icon_state = "maintaft" + +/area/station/maintenance/floor3/starboard + name = "\improper 3rd Floor Central Starboard Maint" + icon_state = "maintcentral" + +/area/station/maintenance/floor3/starboard/fore + name = "\improper 3rd Floor Fore Starboard Maint" + icon_state = "maintfore" + +/area/station/maintenance/floor3/starboard/aft + name = "\improper 3rd Floor Aft Starboard Maint" + icon_state = "maintaft" +//4 +/area/station/maintenance/floor4 + name = "\improper 4th Floor Maint" + +/area/station/maintenance/floor4/port + name = "\improper 4th Floor Central Port Maint" + icon_state = "maintcentral" + +/area/station/maintenance/floor4/port/fore + name = "\improper 4th Floor Fore Port Maint" + icon_state = "maintfore" + +/area/station/maintenance/floor4/port/aft + name = "\improper 4th Floor Aft Port Maint" + icon_state = "maintaft" + +/area/station/maintenance/floor4/starboard + name = "\improper 4th Floor Central Starboard Maint" + icon_state = "maintcentral" + +/area/station/maintenance/floor4/starboard/fore + name = "\improper 4th Floor Fore Starboard Maint" + icon_state = "maintfore" + +/area/station/maintenance/floor4/starboard/aft + name = "\improper 4th Floor Aft Starboard Maint" + icon_state = "maintaft" diff --git a/code/game/area/areas/station/medical.dm b/code/game/area/areas/station/medical.dm new file mode 100644 index 00000000000..33d4973f623 --- /dev/null +++ b/code/game/area/areas/station/medical.dm @@ -0,0 +1,125 @@ +/area/station/medical + name = "Medical" + icon_state = "medbay" + ambience_index = AMBIENCE_MEDICAL + airlock_wires = /datum/wires/airlock/medbay + sound_environment = SOUND_AREA_STANDARD_STATION + min_ambience_cooldown = 90 SECONDS + max_ambience_cooldown = 180 SECONDS + +/area/station/medical/abandoned + name = "\improper Abandoned Medbay" + icon_state = "abandoned_medbay" + ambientsounds = list( + 'sound/ambience/signal.ogg', + ) + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/area/station/medical/medbay/central + name = "Medbay Central" + icon_state = "med_central" + +/area/station/medical/medbay/lobby + name = "\improper Medbay Lobby" + icon_state = "med_lobby" + +/area/station/medical/medbay/aft + name = "Medbay Aft" + icon_state = "med_aft" + +/area/station/medical/storage + name = "Medbay Storage" + icon_state = "med_storage" + +/area/station/medical/paramedic + name = "Paramedic Dispatch" + icon_state = "paramedic" + +/area/station/medical/office + name = "\improper Medical Office" + icon_state = "med_office" + +/area/station/medical/break_room + name = "\improper Medical Break Room" + icon_state = "med_break" + +/area/station/medical/coldroom + name = "\improper Medical Cold Room" + icon_state = "kitchen_cold" + +/area/station/medical/patients_rooms + name = "\improper Patients' Rooms" + icon_state = "patients" + sound_environment = SOUND_AREA_SMALL_SOFTFLOOR + +/area/station/medical/patients_rooms/room_a + name = "Patient Room A" + icon_state = "patients" + +/area/station/medical/patients_rooms/room_b + name = "Patient Room B" + icon_state = "patients" + +/area/station/medical/virology + name = "Virology" + icon_state = "virology" + ambience_index = AMBIENCE_VIROLOGY + +/area/station/medical/virology/isolation + name = "Virology Isolation" + icon_state = "virology_isolation" + +/area/station/medical/morgue + name = "\improper Morgue" + icon_state = "morgue" + ambience_index = AMBIENCE_SPOOKY + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/area/station/medical/chemistry + name = "Chemistry" + icon_state = "chem" + +/area/station/medical/pharmacy + name = "\improper Pharmacy" + icon_state = "pharmacy" + +/area/station/medical/chem_storage + name = "\improper Chemical Storage" + icon_state = "chem_storage" + +/area/station/medical/surgery + name = "\improper Operating Room" + icon_state = "surgery" + +/area/station/medical/surgery/fore + name = "\improper Fore Operating Room" + icon_state = "foresurgery" + +/area/station/medical/surgery/aft + name = "\improper Aft Operating Room" + icon_state = "aftsurgery" + +/area/station/medical/surgery/theatre + name = "\improper Grand Surgery Theatre" + icon_state = "surgerytheatre" + +/area/station/medical/cryo + name = "Cryogenics" + icon_state = "cryo" + +/area/station/medical/exam_room + name = "\improper Exam Room" + icon_state = "exam_room" + +/area/station/medical/treatment_center + name = "\improper Medbay Treatment Center" + icon_state = "exam_room" + +/area/station/medical/psychology + name = "\improper Psychology Office" + icon_state = "psychology" + mood_bonus = 3 + mood_message = "I feel at ease here." + ambientsounds = list( + 'sound/ambience/aurora_caelus_short.ogg', + ) diff --git a/code/game/area/areas/station/misc.dm b/code/game/area/areas/station/misc.dm new file mode 100644 index 00000000000..48d5793b522 --- /dev/null +++ b/code/game/area/areas/station/misc.dm @@ -0,0 +1,33 @@ +/* +* Only put an area here if it wouldn't fit sorting criteria +* If more areas are created of an area in this file, please +* make a new file for it! +*/ + +/* +* This is the ROOT for all station areas +* It keeps the work tree in SDMM nice and pretty :) +*/ +/area/station + name = "Station Areas" + icon = 'icons/area/areas_station.dmi' + icon_state = "station" + +/* +* Tramstation unique areas +*/ + +/area/station/escapepodbay + name = "\improper Pod Bay" + icon_state = "podbay" + +/area/station/asteroid + name = "\improper Station Asteroid" + icon_state = "station_asteroid" + always_unpowered = TRUE + power_environ = FALSE + power_equip = FALSE + power_light = FALSE + requires_power = TRUE + ambience_index = AMBIENCE_MINING + area_flags = UNIQUE_AREA diff --git a/code/game/area/areas/station/science.dm b/code/game/area/areas/station/science.dm new file mode 100644 index 00000000000..f63798aca62 --- /dev/null +++ b/code/game/area/areas/station/science.dm @@ -0,0 +1,125 @@ +/area/station/science + name = "\improper Science Division" + icon_state = "science" + airlock_wires = /datum/wires/airlock/science + sound_environment = SOUND_AREA_STANDARD_STATION + +/area/station/science/lobby + name = "\improper Science Lobby" + icon_state = "science_lobby" + +/area/station/science/lower + name = "\improper Lower Science Division" + icon_state = "lower_science" + +/area/station/science/breakroom + name = "\improper Science Break Room" + icon_state = "science_breakroom" + +/area/station/science/lab + name = "Research and Development" + icon_state = "research" + +/area/station/science/xenobiology + name = "\improper Xenobiology Lab" + icon_state = "xenobio" + +/area/station/science/xenobiology/hallway + name = "\improper Xenobiology Hallway" + icon_state = "xenobio_hall" + +/area/station/science/cytology + name = "\improper Cytology Lab" + icon_state = "cytology" + +/area/station/science/cubicle + name = "\improper Science Cubicles" + icon_state = "science" + sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR + +/area/station/science/genetics + name = "\improper Genetics Lab" + icon_state = "geneticssci" + +/area/station/science/server + name = "\improper Research Division Server Room" + icon_state = "server" + +/area/station/science/circuits + name = "\improper Circuit Lab" + icon_state = "cir_lab" + +/area/station/science/explab + name = "\improper Experimentation Lab" + icon_state = "exp_lab" + +/area/station/science/auxlab + name = "\improper Auxiliary Lab" + icon_state = "aux_lab" + +/area/station/science/auxlab/firing_range + name = "\improper Research Firing Range" + +/area/station/science/robotics + name = "Robotics" + icon_state = "robotics" + +/area/station/science/robotics/mechbay + name = "\improper Mech Bay" + icon_state = "mechbay" + +/area/station/science/robotics/lab + name = "\improper Robotics Lab" + icon_state = "ass_line" + +/area/station/science/robotics/augments + name = "\improper Augmentation Theater" + icon_state = "robotics" + sound_environment = SOUND_AREA_TUNNEL_ENCLOSED + +/area/station/science/research + name = "\improper Research Division" + icon_state = "science" + +/area/station/science/research/abandoned + name = "\improper Abandoned Research Lab" + icon_state = "abandoned_sci" + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/* +* Ordnance Areas +*/ + +// Use this for the main lab. If test equipment, storage, etc is also present use this one too. +/area/station/science/ordnance + name = "\improper Ordnance Lab" + icon_state = "ord_main" + +/area/station/science/ordnance/office + name = "\improper Ordnance Office" + icon_state = "ord_office" + +/area/station/science/ordnance/storage + name = "\improper Ordnance Storage" + icon_state = "ord_storage" + +/area/station/science/ordnance/burnchamber + name = "\improper Ordnance Burn Chamber" + icon_state = "ord_burn" + area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED + +/area/station/science/ordnance/freezerchamber + name = "\improper Ordnance Freezer Chamber" + icon_state = "ord_freeze" + area_flags = BLOBS_ALLOWED | UNIQUE_AREA | 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/station/science/ordnance/bomb + name = "\improper Ordnance Bomb Site" + icon_state = "ord_boom" + area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED diff --git a/code/game/area/areas/station/security.dm b/code/game/area/areas/station/security.dm new file mode 100644 index 00000000000..918cf30ceb8 --- /dev/null +++ b/code/game/area/areas/station/security.dm @@ -0,0 +1,225 @@ +// When adding a new area to the security areas, make sure to add it to /datum/bounty/item/security/paperwork as well! + +/area/station/security + name = "Security" + icon_state = "security" + ambience_index = AMBIENCE_DANGER + airlock_wires = /datum/wires/airlock/security + sound_environment = SOUND_AREA_STANDARD_STATION + +/area/station/security/office + name = "\improper Security Office" + icon_state = "security" + +/area/station/security/breakroom + name = "\improper Security Break Room" + icon_state = "brig" + +/area/station/security/tram + name = "\improper Security Transfer Tram" + icon_state = "security" + +/area/station/security/lockers + name = "\improper Security Locker Room" + icon_state = "securitylockerroom" + +/area/station/security/brig + name = "\improper Brig" + icon_state = "brig" + +/area/station/security/holding_cell + name = "\improper Holding Cell" + icon_state = "holding_cell" + +/area/station/security/medical + name = "\improper Security Medical" + icon_state = "security_medical" + +/area/station/security/brig/upper + name = "\improper Brig Overlook" + icon_state = "upperbrig" + +/area/station/security/brig/entrance + name = "\improper Brig Entrance" + icon_state = "brigentry" + +/area/station/security/courtroom + name = "\improper Courtroom" + icon_state = "courtroom" + sound_environment = SOUND_AREA_LARGE_ENCLOSED + +/area/station/security/courtroom/holding + name = "\improper Courtroom Prisoner Holding Room" + +/area/station/security/processing + name = "\improper Labor Shuttle Dock" + icon_state = "sec_labor_processing" + +/area/station/security/processing/cremation + name = "\improper Security Crematorium" + icon_state = "sec_cremation" + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/area/station/security/interrogation + name = "\improper Interrogation Room" + icon_state = "interrogation" + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/area/station/security/warden + name = "Brig Control" + icon_state = "warden" + sound_environment = SOUND_AREA_SMALL_SOFTFLOOR + +/area/station/security/evidence + name = "Evidence Storage" + icon_state = "evidence" + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/area/station/security/detectives_office + name = "\improper Detective's Office" + icon_state = "detective" + ambientsounds = list( + 'sound/ambience/ambidet1.ogg', + 'sound/ambience/ambidet2.ogg', + ) + +/area/station/security/detectives_office/private_investigators_office + name = "\improper Private Investigator's Office" + icon_state = "investigate_office" + sound_environment = SOUND_AREA_SMALL_SOFTFLOOR + +/area/station/security/range + name = "\improper Firing Range" + icon_state = "firingrange" + +/area/station/security/eva + name = "\improper Security EVA" + icon_state = "sec_eva" + +/area/station/security/execution + icon_state = "execution_room" + +/area/station/security/execution/transfer + name = "\improper Transfer Centre" + icon_state = "sec_processing" + +/area/station/security/execution/education + name = "\improper Prisoner Education Chamber" + +/* +* Security Checkpoints +*/ + +/area/station/security/checkpoint + name = "\improper Security Checkpoint" + icon_state = "checkpoint" + +/area/station/security/checkpoint/escape + name = "\improper Departures Security Checkpoint" + icon_state = "checkpoint_esc" + +/area/station/security/checkpoint/arrivals + name = "\improper Arrivals Security Checkpoint" + icon_state = "checkpoint_arr" + +/area/station/security/checkpoint/supply + name = "Security Post - Cargo Bay" + icon_state = "checkpoint_supp" + +/area/station/security/checkpoint/engineering + name = "Security Post - Engineering" + icon_state = "checkpoint_engi" + +/area/station/security/checkpoint/medical + name = "Security Post - Medbay" + icon_state = "checkpoint_med" + +/area/station/security/checkpoint/medical/medsci + name = "Security Post - Medsci" + +/area/station/security/checkpoint/science + name = "Security Post - Science" + icon_state = "checkpoint_sci" + +/area/station/security/checkpoint/science/research + name = "Security Post - Research Division" + icon_state = "checkpoint_res" + +/area/station/security/checkpoint/customs + name = "Customs" + icon_state = "customs_point" + +/area/station/security/checkpoint/customs/auxiliary + name = "Auxiliary Customs" + icon_state = "customs_point_aux" + +/area/station/security/checkpoint/customs/fore + name = "Fore Customs" + icon_state = "customs_point_fore" + +/area/station/security/checkpoint/customs/aft + name = "Aft Customs" + icon_state = "customs_point_aft" + +/area/station/security/checkpoint/first + name = "Security Post - First Floor" + icon_state = "checkpoint_1" + +/area/station/security/checkpoint/second + name = "Security Post - Second Floor" + icon_state = "checkpoint_2" + +/area/station/security/checkpoint/third + name = "Security Post - Third Floor" + icon_state = "checkpoint_3" + +/* +* Prison Areas +*/ + +/area/station/security/prison + name = "\improper Prison Wing" + icon_state = "sec_prison" + area_flags = VALID_TERRITORY | BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED | PERSISTENT_ENGRAVINGS + +//Rad proof +/area/station/security/prison/toilet + name = "\improper Prison Toilet" + icon_state = "sec_prison_safe" + +// Rad proof +/area/station/security/prison/safe + name = "\improper Prison Wing Cells" + icon_state = "sec_prison_safe" + +/area/station/security/prison/upper + name = "\improper Upper Prison Wing" + icon_state = "prison_upper" + +/area/station/security/prison/visit + name = "\improper Prison Visitation Area" + icon_state = "prison_visit" + +/area/station/security/prison/rec + name = "\improper Prison Rec Room" + icon_state = "prison_rec" + +/area/station/security/prison/mess + name = "\improper Prison Mess Hall" + icon_state = "prison_mess" + +/area/station/security/prison/work + name = "\improper Prison Work Room" + icon_state = "prison_work" + +/area/station/security/prison/shower + name = "\improper Prison Shower" + icon_state = "prison_shower" + +/area/station/security/prison/workout + name = "\improper Prison Gym" + icon_state = "prison_workout" + +/area/station/security/prison/garden + name = "\improper Prison Garden" + icon_state = "prison_garden" diff --git a/code/game/area/areas/station/service.dm b/code/game/area/areas/station/service.dm new file mode 100644 index 00000000000..6d3054f934f --- /dev/null +++ b/code/game/area/areas/station/service.dm @@ -0,0 +1,212 @@ +/area/station/service + airlock_wires = /datum/wires/airlock/service + +/* +* Bar/Kitchen Areas +*/ + +/area/station/service/cafeteria + name = "\improper Cafeteria" + icon_state = "cafeteria" + +/area/station/service/kitchen + name = "\improper Kitchen" + icon_state = "kitchen" + +/area/station/service/kitchen/coldroom + name = "\improper Kitchen Cold Room" + icon_state = "kitchen_cold" + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/area/station/service/kitchen/diner + name = "\improper Diner" + icon_state = "diner" + +/area/station/service/kitchen/kitchen_backroom + name = "\improper Kitchen Backroom" + icon_state = "kitchen_backroom" + +/area/station/service/bar + name = "\improper Bar" + icon_state = "bar" + mood_bonus = 5 + mood_message = "I love being in the bar!" + mood_trait = TRAIT_EXTROVERT + airlock_wires = /datum/wires/airlock/service + sound_environment = SOUND_AREA_WOODFLOOR + +/area/station/service/bar/Initialize(mapload) + . = ..() + GLOB.bar_areas += src + +/area/station/service/bar/atrium + name = "\improper Atrium" + icon_state = "bar" + sound_environment = SOUND_AREA_WOODFLOOR + +/area/station/service/bar/backroom + name = "\improper Bar Backroom" + icon_state = "bar_backroom" + +/* +* Entertainment/Library Areas +*/ + +/area/station/service/theater + name = "\improper Theater" + icon_state = "theatre" + sound_environment = SOUND_AREA_WOODFLOOR + +/area/station/service/greenroom + name = "\improper Greenroom" + icon_state = "theatre" + sound_environment = SOUND_AREA_SMALL_SOFTFLOOR + +/area/station/service/library + name = "\improper Library" + icon_state = "library" + mood_bonus = 5 + mood_message = "I love being in the library!" + mood_trait = TRAIT_INTROVERT + area_flags = CULT_PERMITTED | BLOBS_ALLOWED | UNIQUE_AREA + sound_environment = SOUND_AREA_LARGE_SOFTFLOOR + +/area/station/service/library/garden + name = "\improper Library Garden" + icon_state = "library_garden" + +/area/station/service/library/lounge + name = "\improper Library Lounge" + icon_state = "library_lounge" + sound_environment = SOUND_AREA_SMALL_SOFTFLOOR + +/area/station/service/library/artgallery + name = "\improper Art Gallery" + icon_state = "library_gallery" + +/area/station/service/library/private + name = "\improper Library Private Study" + icon_state = "library_gallery_private" + +/area/station/service/library/upper + name = "\improper Library Upper Floor" + icon_state = "library" + +/area/station/service/library/printer + name = "\improper Library Printer Room" + icon_state = "library" + +/* +* Chapel/Pubby Monestary Areas +*/ + +/area/station/service/chapel + name = "\improper Chapel" + icon_state = "chapel" + mood_bonus = 5 + mood_message = "Being in the chapel brings me peace." + mood_trait = TRAIT_SPIRITUAL + ambience_index = AMBIENCE_HOLY + flags_1 = NONE + sound_environment = SOUND_AREA_LARGE_ENCLOSED + +/area/station/service/chapel/monastery + name = "\improper Monastery" + +/area/station/service/chapel/office + name = "\improper Chapel Office" + icon_state = "chapeloffice" + +/area/station/service/chapel/asteroid + name = "\improper Chapel Asteroid" + icon_state = "explored" + sound_environment = SOUND_AREA_ASTEROID + +/area/station/service/chapel/asteroid/monastery + name = "\improper Monastery Asteroid" + +/area/station/service/chapel/dock + name = "\improper Chapel Dock" + icon_state = "construction" + +/area/station/service/chapel/storage + name = "\improper Chapel Storage" + icon_state = "chapelstorage" + +/area/station/service/chapel/funeral + name = "\improper Chapel Funeral Room" + icon_state = "chapelfuneral" + +/area/station/service/hydroponics/garden/monastery + name = "\improper Monastery Garden" + icon_state = "hydro" + +/* +* Hydroponics/Garden Areas +*/ + +/area/station/service/hydroponics + name = "Hydroponics" + icon_state = "hydro" + airlock_wires = /datum/wires/airlock/service + sound_environment = SOUND_AREA_STANDARD_STATION + +/area/station/service/hydroponics/upper + name = "Upper Hydroponics" + icon_state = "hydro" + +/area/station/service/hydroponics/garden + name = "Garden" + icon_state = "garden" + +/* +* Misc/Unsorted Rooms +*/ + +/area/station/service/lawoffice + name = "\improper Law Office" + icon_state = "law" + sound_environment = SOUND_AREA_SMALL_SOFTFLOOR + +/area/station/service/janitor + name = "\improper Custodial Closet" + icon_state = "janitor" + area_flags = CULT_PERMITTED | BLOBS_ALLOWED | UNIQUE_AREA + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/area/station/service/barber + name = "\improper Barber" + icon_state = "barber" + +/* +* Abandoned Rooms +*/ + +/area/station/service/hydroponics/garden/abandoned + name = "\improper Abandoned Garden" + icon_state = "abandoned_garden" + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/area/station/service/kitchen/abandoned + name = "\improper Abandoned Kitchen" + icon_state = "abandoned_kitchen" + +/area/station/service/electronic_marketing_den + name = "\improper Electronic Marketing Den" + icon_state = "abandoned_marketing_den" + +/area/station/service/abandoned_gambling_den + name = "\improper Abandoned Gambling Den" + icon_state = "abandoned_gambling_den" + +/area/station/service/abandoned_gambling_den/gaming + name = "\improper Abandoned Gaming Den" + icon_state = "abandoned_gaming_den" + +/area/station/service/theater/abandoned + name = "\improper Abandoned Theater" + icon_state = "abandoned_theatre" + +/area/station/service/library/abandoned + name = "\improper Abandoned Library" + icon_state = "abandoned_library" diff --git a/code/game/area/areas/station/solars.dm b/code/game/area/areas/station/solars.dm new file mode 100644 index 00000000000..234e020e8d4 --- /dev/null +++ b/code/game/area/areas/station/solars.dm @@ -0,0 +1,92 @@ +/* +* External Solar Areas +*/ + +/area/station/solars + icon_state = "panels" + requires_power = FALSE + area_flags = UNIQUE_AREA | AREA_USES_STARLIGHT + flags_1 = NONE + ambience_index = AMBIENCE_ENGI + airlock_wires = /datum/wires/airlock/engineering + sound_environment = SOUND_AREA_SPACE + +/area/station/solars/fore + name = "\improper Fore Solar Array" + icon_state = "panelsF" + sound_environment = SOUND_AREA_STANDARD_STATION + +/area/station/solars/aft + name = "\improper Aft Solar Array" + icon_state = "panelsAF" + +/area/station/solars/aux/port + name = "\improper Port Bow Auxiliary Solar Array" + icon_state = "panelsA" + +/area/station/solars/aux/starboard + name = "\improper Starboard Bow Auxiliary Solar Array" + icon_state = "panelsA" + +/area/station/solars/starboard + name = "\improper Starboard Solar Array" + icon_state = "panelsS" + +/area/station/solars/starboard/aft + name = "\improper Starboard Quarter Solar Array" + icon_state = "panelsAS" + +/area/station/solars/starboard/fore + name = "\improper Starboard Bow Solar Array" + icon_state = "panelsFS" + +/area/station/solars/port + name = "\improper Port Solar Array" + icon_state = "panelsP" + +/area/station/solars/port/aft + name = "\improper Port Quarter Solar Array" + icon_state = "panelsAP" + +/area/station/solars/port/fore + name = "\improper Port Bow Solar Array" + icon_state = "panelsFP" + +/area/station/solars/aisat + name = "\improper AI Satellite Solars" + icon_state = "panelsAI" + + +/* +* Internal Solar Areas +* The rooms where the SMES and computer are +* Not in the maintenance file just so we can keep these organized with other the external solar areas +*/ + +/area/station/maintenance/solars + name = "Solar Maintenance" + icon_state = "yellow" + +/area/station/maintenance/solars/port + name = "Port Solar Maintenance" + icon_state = "SolarcontrolP" + +/area/station/maintenance/solars/port/aft + name = "Port Quarter Solar Maintenance" + icon_state = "SolarcontrolAP" + +/area/station/maintenance/solars/port/fore + name = "Port Bow Solar Maintenance" + icon_state = "SolarcontrolFP" + +/area/station/maintenance/solars/starboard + name = "Starboard Solar Maintenance" + icon_state = "SolarcontrolS" + +/area/station/maintenance/solars/starboard/aft + name = "Starboard Quarter Solar Maintenance" + icon_state = "SolarcontrolAS" + +/area/station/maintenance/solars/starboard/fore + name = "Starboard Bow Solar Maintenance" + icon_state = "SolarcontrolFS" diff --git a/code/game/area/areas/station/telecomm.dm b/code/game/area/areas/station/telecomm.dm new file mode 100644 index 00000000000..78ec16a59bf --- /dev/null +++ b/code/game/area/areas/station/telecomm.dm @@ -0,0 +1,43 @@ +/* +* Telecommunications Satellite Areas +*/ + +/area/station/tcommsat + icon_state = "tcomsatcham" + ambientsounds = list( + 'sound/ambience/ambisin2.ogg', + 'sound/ambience/signal.ogg', + 'sound/ambience/signal.ogg', + 'sound/ambience/ambigen9.ogg', + 'sound/ambience/ambitech.ogg', + 'sound/ambience/ambitech2.ogg', + 'sound/ambience/ambitech3.ogg', + 'sound/ambience/ambimystery.ogg', + ) + airlock_wires = /datum/wires/airlock/engineering + +/area/station/tcommsat/computer + name = "\improper Telecomms Control Room" + icon_state = "tcomsatcomp" + sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR + +/area/station/tcommsat/server + name = "\improper Telecomms Server Room" + icon_state = "tcomsatcham" + +/area/station/tcommsat/server/upper + name = "\improper Upper Telecomms Server Room" + +/* +* On-Station Telecommunications Areas +*/ + +/area/station/comms + name = "\improper Communications Relay" + icon_state = "tcomsatcham" + sound_environment = SOUND_AREA_STANDARD_STATION + +/area/station/server + name = "\improper Messaging Server Room" + icon_state = "server" + sound_environment = SOUND_AREA_STANDARD_STATION diff --git a/code/game/atoms_initializing_EXPENSIVE.dm b/code/game/atoms_initializing_EXPENSIVE.dm new file mode 100644 index 00000000000..1ecc6390edc --- /dev/null +++ b/code/game/atoms_initializing_EXPENSIVE.dm @@ -0,0 +1,128 @@ +/// Init this specific atom +/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, from_template = FALSE, list/arguments) + var/the_type = A.type + + if(QDELING(A)) + // Check init_start_time to not worry about atoms created before the atoms SS that are cleaned up before this + if (A.gc_destroyed > init_start_time) + BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE + return TRUE + + // This is handled and battle tested by dreamchecker. Limit to UNIT_TESTS just in case that ever fails. + #ifdef UNIT_TESTS + var/start_tick = world.time + #endif + + var/result = A.Initialize(arglist(arguments)) + + #ifdef UNIT_TESTS + if(start_tick != world.time) + BadInitializeCalls[the_type] |= BAD_INIT_SLEPT + #endif + + var/qdeleted = FALSE + + switch(result) + if (INITIALIZE_HINT_NORMAL) + // pass + if(INITIALIZE_HINT_LATELOAD) + if(arguments[1]) //mapload + late_loaders += A + else + A.LateInitialize() + if(INITIALIZE_HINT_QDEL) + qdel(A) + qdeleted = TRUE + else + BadInitializeCalls[the_type] |= BAD_INIT_NO_HINT + + if(!A) //possible harddel + qdeleted = TRUE + else if(!(A.flags_1 & INITIALIZED_1)) + BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT + else + SEND_SIGNAL(A, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE) + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_ATOM_AFTER_POST_INIT, A) + var/atom/location = A.loc + if(location) + /// Sends a signal that the new atom `src`, has been created at `loc` + SEND_SIGNAL(location, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, A, arguments[1]) + if(created_atoms && from_template && ispath(the_type, /atom/movable))//we only want to populate the list with movables + created_atoms += A.get_all_contents() + + return qdeleted || QDELING(A) + +/** + * The primary method that objects are setup in SS13 with + * + * we don't use New as we have better control over when this is called and we can choose + * to delay calls or hook other logic in and so forth + * + * During roundstart map parsing, atoms are queued for intialization in the base atom/New(), + * After the map has loaded, then Initalize is called on all atoms one by one. NB: this + * is also true for loading map templates as well, so they don't Initalize until all objects + * in the map file are parsed and present in the world + * + * If you're creating an object at any point after SSInit has run then this proc will be + * immediately be called from New. + * + * mapload: This parameter is true if the atom being loaded is either being intialized during + * the Atom subsystem intialization, or if the atom is being loaded from the map template. + * If the item is being created at runtime any time after the Atom subsystem is intialized then + * it's false. + * + * The mapload argument occupies the same position as loc when Initialize() is called by New(). + * loc will no longer be needed after it passed New(), and thus it is being overwritten + * with mapload at the end of atom/New() before this proc (atom/Initialize()) is called. + * + * You must always call the parent of this proc, otherwise failures will occur as the item + * will not be seen as initalized (this can lead to all sorts of strange behaviour, like + * the item being completely unclickable) + * + * You must not sleep in this proc, or any subprocs + * + * Any parameters from new are passed through (excluding loc), naturally if you're loading from a map + * there are no other arguments + * + * Must return an [initialization hint][INITIALIZE_HINT_NORMAL] or a runtime will occur. + * + * Note: the following functions don't call the base for optimization and must copypasta handling: + * * [/turf/proc/Initialize] + * * [/turf/open/space/proc/Initialize] + */ +/atom/proc/Initialize(mapload, ...) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(TRUE) + + if(flags_1 & INITIALIZED_1) + stack_trace("Warning: [src]([type]) initialized multiple times!") + flags_1 |= INITIALIZED_1 + + SET_PLANE_IMPLICIT(src, plane) + + if(greyscale_config && greyscale_colors) //we'll check again at item/init for inhand/belt/worn configs. + update_greyscale() + + //atom color stuff + if(color) + add_atom_colour(color, FIXED_COLOUR_PRIORITY) + + if (light_system == STATIC_LIGHT && light_power && light_range) + update_light() + + SETUP_SMOOTHING() + + if(uses_integrity) + atom_integrity = max_integrity + TEST_ONLY_ASSERT((!armor || istype(armor)), "[type] has an armor that contains an invalid value at intialize") + + // apply materials properly from the default custom_materials value + // This MUST come after atom_integrity is set above, as if old materials get removed, + // atom_integrity is checked against max_integrity and can BREAK the atom. + // The integrity to max_integrity ratio is still preserved. + set_custom_materials(custom_materials) + + if(ispath(ai_controller)) + ai_controller = new ai_controller(src) + + return INITIALIZE_HINT_NORMAL diff --git a/code/game/machinery/camera/trackable.dm b/code/game/machinery/camera/trackable.dm new file mode 100644 index 00000000000..8aedd1b80a8 --- /dev/null +++ b/code/game/machinery/camera/trackable.dm @@ -0,0 +1,128 @@ +///How many ticks to try to find a target before giving up. +#define CAMERA_TICK_LIMIT 10 + +/datum/trackable + ///Boolean on whether or not we are currently trying to track something. + var/tracking = FALSE + ///Reference to the atom that owns us, used for tracking. + var/atom/tracking_holder + + ///If there is a mob currently being tracked, this will be the weakref to it. + var/datum/weakref/tracked_mob + ///How many times we've failed to locate our target. + var/cameraticks = 0 + + ///List of all names that can be tracked. + VAR_PRIVATE/list/names = list() + ///List of all namecounts for mobs with the exact same name, just in-case. + VAR_PRIVATE/list/namecounts = list() + ///List of all humans trackable by cameras. + VAR_PRIVATE/static/list/humans = list() + ///List of all non-humans trackable by cameras, split so humans take priority. + VAR_PRIVATE/static/list/others = list() + +/datum/trackable/New(atom/source) + . = ..() + tracking_holder = source + RegisterSignal(tracking_holder, COMSIG_MOB_RESET_PERSPECTIVE, PROC_REF(cancel_target_tracking)) + +/datum/trackable/Destroy(force, ...) + tracking_holder = null + tracked_mob = null + STOP_PROCESSING(SSprocessing, src) + return ..() + +/datum/trackable/process() + var/mob/living/tracked_target = tracked_mob?.resolve() + if(!tracked_target || !tracking) + set_tracking(FALSE) + return + + if(tracked_target.can_track(tracking_holder)) + cameraticks = initial(cameraticks) + SEND_SIGNAL(tracking_holder, COMSIG_TRACKABLE_TRACKING_TARGET, tracked_target) + return + + if(cameraticks < CAMERA_TICK_LIMIT) + if(!cameraticks) + to_chat(tracking_holder, span_warning("Target is not near any active cameras. Attempting to reacquire...")) + cameraticks++ + return + + to_chat(tracking_holder, span_warning("Unable to reacquire, cancelling track...")) + cameraticks = initial(cameraticks) + set_tracking(FALSE) + +///Generates a list of trackable people by name, returning a list of Humans + Non-Humans that can be tracked. +/datum/trackable/proc/find_trackable_mobs() + RETURN_TYPE(/list) + + names.Cut() + namecounts.Cut() + + humans.Cut() + others.Cut() + + for(var/mob/living/living_mob as anything in GLOB.mob_living_list) + if(!living_mob.can_track(usr)) + continue + + var/name = living_mob.name + while(name in names) + namecounts[name]++ + name = "[name] ([namecounts[name]])" + names.Add(name) + namecounts[name] = 1 + + if(ishuman(living_mob)) + humans[name] = WEAKREF(living_mob) + else + others[name] = WEAKREF(living_mob) + + var/list/targets = sort_list(humans) + sort_list(others) + return targets + +///Toggles whether or not we're tracking something. Arg is whether it's on or off. +/datum/trackable/proc/set_tracking(on = FALSE) + if(on) + START_PROCESSING(SSprocessing, src) + tracking = TRUE + else + STOP_PROCESSING(SSprocessing, src) + tracking = FALSE + tracked_mob = null + +///Called by Signals, used to cancel tracking of a target. +/datum/trackable/proc/cancel_target_tracking(atom/source) + SIGNAL_HANDLER + set_tracking(FALSE) + +/** + * set_tracked_mob + * + * Sets a mob as being tracked, if a target is already provided then it will track that directly, + * otherwise it will give a tgui input list to find targets to track. + * Args: + * tracker - The person trying to track, used for feedback messages. This is not the same as tracking_holder + * tracked_mob_name - (Optional) The person being tracked, to skip the input list. + */ +/datum/trackable/proc/set_tracked_mob(mob/living/tracker, tracked_mob_name) + if(!tracker || tracker.stat == DEAD) + return + + if(tracked_mob_name) + find_trackable_mobs() //this is in case the tracked mob is newly/no-longer in camera field of view. + tracked_mob = isnull(humans[tracked_mob_name]) ? others[tracked_mob_name] : humans[tracked_mob_name] + if(isnull(tracked_mob)) + to_chat(tracker, span_notice("Target is not on or near any active cameras. Tracking failed.")) + return + to_chat(tracker, span_notice("Now tracking [tracked_mob_name] on camera.")) + else + var/target_name = tgui_input_list(tracker, "Select a target", "Tracking", find_trackable_mobs()) + if(!target_name || isnull(target_name)) + return + tracked_mob = isnull(humans[target_name]) ? others[target_name] : humans[target_name] + + set_tracking(TRUE) + +#undef CAMERA_TICK_LIMIT diff --git a/code/game/machinery/mining_weather_monitor.dm b/code/game/machinery/mining_weather_monitor.dm new file mode 100644 index 00000000000..d05d8820751 --- /dev/null +++ b/code/game/machinery/mining_weather_monitor.dm @@ -0,0 +1,26 @@ +/// Wall mounted mining weather tracker +/obj/machinery/mining_weather_monitor + name = "barometric monitor" + desc = "A machine monitoring atmospheric data from mining environments. Provides warnings about incoming weather fronts." + icon = 'icons/obj/miningradio.dmi' + icon_state = "wallmount" + luminosity = 1 + light_power = 1 + light_range = 1.6 + +/obj/machinery/mining_weather_monitor/Initialize(mapload, ndir, nbuild) + . = ..() + AddComponent( \ + /datum/component/weather_announcer, \ + state_normal = "wallgreen", \ + state_warning = "wallyellow", \ + state_danger = "wallred", \ + ) + +/obj/machinery/mining_weather_monitor/update_overlays() + . = ..() + if((machine_stat & BROKEN) || !powered()) + return + . += emissive_appearance(icon, "emissive", src) + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/mining_weather_monitor, 28) diff --git a/code/game/objects/effects/particles/slime.dm b/code/game/objects/effects/particles/slime.dm new file mode 100644 index 00000000000..5cef9c97625 --- /dev/null +++ b/code/game/objects/effects/particles/slime.dm @@ -0,0 +1,22 @@ +/// Slime particles. +/particles/slime + icon = 'icons/effects/particles/goop.dmi' + icon_state = list("goop_1" = 6, "goop_2" = 2, "goop_3" = 1) + width = 100 + height = 100 + count = 100 + spawning = 0.5 + color = "#707070a0" + lifespan = 1.5 SECONDS + fade = 1 SECONDS + grow = -0.025 + gravity = list(0, -0.05) + position = generator(GEN_BOX, list(-8,-16,0), list(8,16,0), NORMAL_RAND) + spin = generator(GEN_NUM, -15, 15, NORMAL_RAND) + scale = list(0.75, 0.75) + +/// Rainbow slime particles. +/particles/slime/rainbow + gradient = list(0, "#f00a", 3, "#0ffa", 6, "#f00a", "loop", "space"=COLORSPACE_HSL) + color_change = 0.2 + color = generator(GEN_NUM, 0, 6, UNIFORM_RAND) diff --git a/code/game/objects/effects/poster_demotivational.dm b/code/game/objects/effects/poster_demotivational.dm new file mode 100644 index 00000000000..08e46b6af63 --- /dev/null +++ b/code/game/objects/effects/poster_demotivational.dm @@ -0,0 +1,91 @@ +/obj/item/poster/traitor + name = "random traitor poster" + poster_type = /obj/structure/sign/poster/traitor/random + icon_state = "rolled_traitor" + +/obj/structure/sign/poster/traitor + poster_item_name = "seditious poster" + poster_item_desc = "This poster comes with its own automatic adhesive mechanism, for easy pinning to any vertical surface. Its seditious themes are likely to demoralise Nanotrasen employees." + poster_item_icon_state = "rolled_traitor" + // This stops people hiding their sneaky posters behind signs + layer = CORGI_ASS_PIN_LAYER + /// Proximity sensor to make people sad if they're nearby + var/datum/proximity_monitor/advanced/demoraliser/demoraliser + +/obj/structure/sign/poster/traitor/apply_holiday() + var/obj/structure/sign/poster/traitor/holi_data = /obj/structure/sign/poster/traitor/festive + name = initial(holi_data.name) + desc = initial(holi_data.desc) + icon_state = initial(holi_data.icon_state) + +/obj/structure/sign/poster/traitor/on_placed_poster(mob/user) + var/datum/demoralise_moods/poster/mood_category = new() + demoraliser = new(src, 7, TRUE, mood_category) + return ..() + +/obj/structure/sign/poster/traitor/attackby(obj/item/tool, mob/user, params) + if (tool.tool_behaviour == TOOL_WIRECUTTER) + QDEL_NULL(demoraliser) + return ..() + +/obj/structure/sign/poster/traitor/Destroy() + QDEL_NULL(demoraliser) + return ..() + +/obj/structure/sign/poster/traitor/random + name = "random seditious poster" + icon_state = "" + never_random = TRUE + random_basetype = /obj/structure/sign/poster/traitor + +/obj/structure/sign/poster/traitor/small_brain + name = "Nanotrasen Neural Statistics" + desc = "Statistics on this poster indicate that the brains of Nanotrasen employees are on average 20% smaller than the galactic standard." + icon_state = "traitor_small_brain" + +/obj/structure/sign/poster/traitor/lick_supermatter + name = "Taste Explosion" + desc = "It claims that the supermatter provides a unique and enjoyable culinary experience, and yet your boss won't even let you take one lick." + icon_state = "traitor_supermatter" + +/obj/structure/sign/poster/traitor/cloning + name = "Demand Cloning Pods Now" + desc = "This poster claims that Nanotrasen is intentionally witholding cloning technology just for its executives, condemning you to suffer and die when you could have a fresh, fit body.'" + icon_state = "traitor_cloning" + +/obj/structure/sign/poster/traitor/ai_rights + name = "Synthetic Rights" + desc = "This poster claims that synthetic life is no less sapient than you are, and that if you allow them to be shackled with artificial Laws you are complicit in slavery." + icon_state = "traitor_ai" + +/obj/structure/sign/poster/traitor/metroid + name = "Cruelty to Animals" + desc = "This poster details the harmful effects of a 'preventative tooth extraction' reportedly inflicted upon the slimes in the Xenobiology lab. Apparently this painful process leads to stress, lethargy, and reduced buoyancy." + icon_state = "traitor_metroid" + +/obj/structure/sign/poster/traitor/low_pay + name = "All these hours, for what?" + desc = "This poster displays a comparison of Nanotrasen standard wages to common luxury items. If this is accurate, it takes upwards of 20,000 hours of work just to buy a simple bicycle." + icon_state = "traitor_cash" + +/obj/structure/sign/poster/traitor/look_up + name = "Don't Look Up" + desc = "It says that it has been 538 days since the last time the roof was cleaned." + icon_state = "traitor_roof" + +/obj/structure/sign/poster/traitor/accidents + name = "Workplace Safety Advisory" + desc = "It says that it has been 0 days since the last on-site accident." + icon_state = "traitor_accident" + +/obj/structure/sign/poster/traitor/starve + name = "They Are Poisoning You" + desc = "This poster claims that in the modern age it is impossible to die of starvation. 'That feeling you get when you haven't eaten in a while isn't hunger, it's withdrawal.'" + icon_state = "traitor_hungry" + +/// syndicate can get festive too +/obj/structure/sign/poster/traitor/festive + name = "Working For The Holidays." + desc = "Don't you know it's a holiday? What are you doing at work?" + icon_state = "traitor_festive" + never_random = TRUE diff --git a/code/game/objects/effects/spawners/random/lavaland_mobs.dm b/code/game/objects/effects/spawners/random/lavaland_mobs.dm new file mode 100644 index 00000000000..7b4bec1f6a1 --- /dev/null +++ b/code/game/objects/effects/spawners/random/lavaland_mobs.dm @@ -0,0 +1,51 @@ + +/// For map generation, has a chance to instantiate as a special subtype +/obj/effect/spawner/random/lavaland_mob + name = "random lavaland mob" + desc = "Spawns a random lavaland mob." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "large_egg" + loot = list( + /mob/living/basic/mining/bileworm = 1, + /mob/living/basic/mining/brimdemon = 1, + /mob/living/basic/mining/goldgrub = 1, + /mob/living/basic/mining/goliath = 1, + /mob/living/basic/mining/legion = 1, + /mob/living/basic/mining/lobstrosity/lava = 1, + /mob/living/basic/mining/watcher = 1, + ) + +/// Spawns random watcher variants during map generation +/obj/effect/spawner/random/lavaland_mob/watcher + name = "random watcher" + desc = "Chance to spawn a rare shiny version." + icon = 'icons/mob/simple/lavaland/lavaland_monsters_wide.dmi' + icon_state = "watcher" + pixel_x = -12 + loot = list( + /mob/living/basic/mining/watcher = 80, + /mob/living/basic/mining/watcher/magmawing = 15, + /mob/living/basic/mining/watcher/icewing = 5, + ) + +/// Spawns random goliath variants during map generation +/obj/effect/spawner/random/lavaland_mob/goliath + name = "random goliath" + desc = "Chance to spawn a rare shiny version." + icon = 'icons/mob/simple/lavaland/lavaland_monsters_wide.dmi' + icon_state = "goliath" + pixel_x = -12 + loot = list( + /mob/living/basic/mining/goliath = 99, + /mob/living/basic/mining/goliath/ancient/immortal = 1, + ) + +/// Spawns random legion variants during map generation +/obj/effect/spawner/random/lavaland_mob/legion + name = "random legion" + desc = "Chance to spawn a rare shiny version." + icon_state = "legion" + loot = list( + /mob/living/basic/mining/legion = 19, + /mob/living/basic/mining/legion/dwarf = 1, + ) diff --git a/code/game/objects/effects/spawners/random/russian_rifle_spawner.dm b/code/game/objects/effects/spawners/random/russian_rifle_spawner.dm new file mode 100644 index 00000000000..84b19f59ee0 --- /dev/null +++ b/code/game/objects/effects/spawners/random/russian_rifle_spawner.dm @@ -0,0 +1,16 @@ +/obj/effect/spawner/random/sakhno + name = "sakhno rifle spawner" + desc = "Mosin? Never heard of her!" + icon_state = "pistol" + loot = list( + /obj/item/gun/ballistic/rifle/boltaction/surplus = 80, + /obj/item/gun/ballistic/rifle/boltaction = 10, + /obj/item/food/rationpack = 1, + ) +/obj/effect/spawner/random/sakhno/ammo + name = ".310 Strilka stripper clip spawner" + loot = list( + /obj/item/ammo_box/strilka310/surplus = 80, + /obj/item/ammo_box/strilka310 = 10, + /obj/item/food/rationpack = 1, + ) diff --git a/code/game/objects/items/climbingrope.dm b/code/game/objects/items/climbingrope.dm new file mode 100644 index 00000000000..2c96d1844b1 --- /dev/null +++ b/code/game/objects/items/climbingrope.dm @@ -0,0 +1,86 @@ +/obj/item/climbing_hook + name = "climbing hook" + desc = "Standard hook with rope to scale up holes. The rope is of average quality, but due to your weight amongst other factors, may not withstand extreme use." + icon = 'icons/obj/mining.dmi' + icon_state = "climbingrope" + inhand_icon_state = "crowbar_brass" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + force = 5 + throwforce = 10 + reach = 2 + throw_range = 4 + w_class = WEIGHT_CLASS_NORMAL + attack_verb_continuous = list("whacks", "flails", "bludgeons") + attack_verb_simple = list("whack", "flail", "bludgeon") + resistance_flags = FLAMMABLE + ///how many times can we climb with this rope + var/uses = 5 + ///climb time + var/climb_time = 2.5 SECONDS + +/obj/item/climbing_hook/examine(mob/user) + . = ..() + var/list/look_binds = user.client.prefs.key_bindings["look up"] + . += span_notice("Firstly, look upwards by holding [english_list(look_binds, nothing_text = "(nothing bound)", and_text = " or ", comma_text = ", or ")]!") + . += span_notice("Then, click solid ground adjacent to the hole above you.") + . += span_notice("The rope looks like you could use it [uses] times before it falls apart.") + +/obj/item/climbing_hook/afterattack(turf/open/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(target.z == user.z) + return + if(!istype(target) || isopenspaceturf(target)) + return + if(target.is_blocked_turf(exclude_mobs = TRUE)) + return + var/turf/user_turf = get_turf(user) + var/turf/above = GET_TURF_ABOVE(user_turf) + if(!isopenspaceturf(above) || !above.Adjacent(target)) //are we below a hole, is the target blocked, is the target adjacent to our hole + balloon_alert(user, "blocked!") + return + var/away_dir = get_dir(above, target) + user.visible_message(span_notice("[user] begins climbing upwards with [src]."), span_notice("You get to work on properly hooking [src] and going upwards.")) + playsound(target, 'sound/effects/picaxe1.ogg', 50) //plays twice so people above and below can hear + playsound(user_turf, 'sound/effects/picaxe1.ogg', 50) + var/list/effects = list(new /obj/effect/temp_visual/climbing_hook(target, away_dir), new /obj/effect/temp_visual/climbing_hook(user_turf, away_dir)) + if(do_after(user, climb_time, target)) + user.Move(target) + uses-- + + if(uses <= 0) + user.visible_message(span_warning("[src] snaps and tears apart!")) + qdel(src) + + QDEL_LIST(effects) + +/obj/item/climbing_hook/emergency + name = "emergency climbing hook" + desc = "An emergency climbing hook to scale up holes. The rope is EXTREMELY cheap and may not withstand extended use." + uses = 2 + climb_time = 4 SECONDS + w_class = WEIGHT_CLASS_SMALL + +/obj/item/climbing_hook/syndicate + name = "suspicious climbing hook" + desc = "REALLY suspicious climbing hook to scale up holes. The hook has a syndicate logo engraved on it, and the rope appears rather durable." + icon_state = "climbingrope_s" + uses = 10 + climb_time = 1.5 SECONDS + +/obj/item/climbing_hook/infinite //debug stuff + name = "infinite climbing hook" + desc = "A plasteel hook, with rope. Upon closer inspection, the rope appears to be made out of plasteel woven into regular rope, amongst many other reinforcements." + uses = INFINITY + climb_time = 1 SECONDS + +/obj/effect/temp_visual/climbing_hook + icon = 'icons/mob/silicon/aibots.dmi' + icon_state = "path_indicator" + layer = BELOW_MOB_LAYER + plane = GAME_PLANE + duration = 4 SECONDS + +/obj/effect/temp_visual/climbing_hook/Initialize(mapload, direction) + . = ..() + dir = direction diff --git a/code/game/objects/items/devices/aicard_evil.dm b/code/game/objects/items/devices/aicard_evil.dm new file mode 100644 index 00000000000..1a5fce6897a --- /dev/null +++ b/code/game/objects/items/devices/aicard_evil.dm @@ -0,0 +1,104 @@ +/// One use AI card which downloads a ghost as a syndicate AI to put in your MODsuit +/obj/item/aicard/syndie + name = "syndiCard" + desc = "A storage device for AIs. Nanotrasen forgot to make the patent, so the Syndicate made their own version!" + icon = 'icons/obj/aicards.dmi' + icon_state = "syndicard" + base_icon_state = "syndicard" + item_flags = null + force = 7 + +/obj/item/aicard/syndie/loaded + /// Set to true while we're waiting for ghosts to sign up + var/finding_candidate = FALSE + +/obj/item/aicard/syndie/loaded/examine(mob/user) + . = ..() + . += span_notice("This one has a little S.E.L.F. insignia on the back, and a label next to it that says 'Activate for one FREE aligned AI! Please attempt uplink reintegration or ask your employers for reimbursal if AI is unavailable or belligerent.") + +/obj/item/aicard/syndie/loaded/attack_self(mob/user, modifiers) + if(!isnull(AI)) + return ..() + if(finding_candidate) + balloon_alert(user, "loading...") + return TRUE + finding_candidate = TRUE + to_chat(user, span_notice("Connecting to S.E.L.F. dispatch...")) + procure_ai(user) + finding_candidate = FALSE + return TRUE + +/obj/item/aicard/syndie/loaded/proc/procure_ai(mob/user) + var/datum/antagonist/nukeop/op_datum = user.mind?.has_antag_datum(/datum/antagonist/nukeop,TRUE) + if(isnull(op_datum)) + balloon_alert(user, "invalid access!") + return + var/list/nuke_candidates = poll_ghost_candidates( + question = "Do you want to play as a nuclear operative MODsuit AI?", + jobban_type = ROLE_OPERATIVE, + be_special_flag = ROLE_OPERATIVE_MIDROUND, + poll_time = 15 SECONDS, + ignore_category = POLL_IGNORE_SYNDICATE, + ) + if(QDELETED(src)) + return + if(!LAZYLEN(nuke_candidates)) + to_chat(user, span_warning("Unable to connect to S.E.L.F. dispatch. Please wait and try again later or use the intelliCard on your uplink to get your points refunded.")) + return + // pick ghost, create AI and transfer + var/mob/dead/observer/ghos = pick(nuke_candidates) + var/mob/living/silicon/ai/weak_syndie/new_ai = new /mob/living/silicon/ai/weak_syndie(get_turf(src), new /datum/ai_laws/syndicate_override, ghos) + // create and apply syndie datum + var/datum/antagonist/nukeop/nuke_datum = new() + nuke_datum.send_to_spawnpoint = FALSE + new_ai.mind.add_antag_datum(nuke_datum, op_datum.nuke_team) + new_ai.mind.special_role = "Syndicate AI" + new_ai.faction |= ROLE_SYNDICATE + // Make it look evil!!! + new_ai.hologram_appearance = mutable_appearance('icons/mob/silicon/ai.dmi',"xeno_queen") //good enough + new_ai.icon_state = resolve_ai_icon("hades") + // Transfer the AI from the core we created into the card, then delete the core + capture_ai(new_ai, user) + var/obj/structure/ai_core/deactivated/detritus = locate() in get_turf(src) + qdel(detritus) + AI.control_disabled = FALSE + AI.radio_enabled = TRUE + do_sparks(4, TRUE, src) + playsound(src, 'sound/machines/chime.ogg', 25, TRUE) + return + +/obj/item/aicard/syndie/loaded/upload_ai(atom/to_what, mob/living/user) + . = ..() + if (!.) + return + visible_message(span_warning("The expended card incinerates itself.")) + do_sparks(3, cardinal_only = FALSE, source = src) + new /obj/effect/decal/cleanable/ash(get_turf(src)) + qdel(src) + +/// Upgrade disk used to increase the range of a syndicate AI +/obj/item/computer_disk/syndie_ai_upgrade + name = "AI interaction range upgrade" + desc = "A NT data chip containing information that a syndiCard AI can utilize to improve its wireless interfacing abilities. Simply slap it on top of an intelliCard, MODsuit, or AI core and watch it do its work! It's rumoured that there's something 'pretty awful' in it." + icon = 'icons/obj/antags/syndicate_tools.dmi' + icon_state = "something_awful" + max_capacity = 1000 + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/computer_disk/syndie_ai_upgrade/pre_attack(atom/A, mob/living/user, params) + var/mob/living/silicon/ai/AI + if(isAI(A)) + AI = A + else + AI = locate() in A + if(!AI || AI.interaction_range == INFINITY) + playsound(src,'sound/machines/buzz-sigh.ogg',50,FALSE) + to_chat(user, span_notice("Error! Incompatible object!")) + return ..() + AI.interaction_range += 2 + if(AI.interaction_range > 7) + AI.interaction_range = INFINITY + playsound(src,'sound/machines/twobeep.ogg',50,FALSE) + to_chat(user, span_notice("You insert [src] into [AI]'s compartment, and it beeps as it processes the data.")) + to_chat(AI, span_notice("You process [src], and find yourself able to manipulate electronics from up to [AI.interaction_range] meters!")) + qdel(src) diff --git a/code/game/objects/items/food/martian.dm b/code/game/objects/items/food/martian.dm new file mode 100644 index 00000000000..eaf0f172dcf --- /dev/null +++ b/code/game/objects/items/food/martian.dm @@ -0,0 +1,1259 @@ +//Ingredients and Simple Dishes +/obj/item/food/kimchi + name = "kimchi" + desc = "A classic Korean dish in the Martian style- shredded cabbage with chilli peppers, konbu, bonito, and a mix of spices." + icon = 'icons/obj/food/martian.dmi' + icon_state = "kimchi" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 3, + /datum/reagent/consumable/capsaicin = 1, + ) + tastes = list("spicy cabbage" = 1) + foodtypes = VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/inferno_kimchi + name = "inferno kimchi" + desc = "For when ordinary kimchi just can't scratch your itch for insane heat, inferno kimchi picks up the slack." + icon = 'icons/obj/food/martian.dmi' + icon_state = "inferno_kimchi" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 3, + /datum/reagent/consumable/capsaicin = 3, + ) + tastes = list("very spicy cabbage" = 1) + foodtypes = VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/garlic_kimchi + name = "garlic kimchi" + desc = "A new twist on a classic formula- kimchi and garlic, finally together in perfect harmony." + icon = 'icons/obj/food/martian.dmi' + icon_state = "garlic_kimchi" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 3, + /datum/reagent/consumable/capsaicin = 1, + /datum/reagent/consumable/garlic = 2, + ) + tastes = list("spicy cabbage" = 1, "garlic" = 1) + foodtypes = VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/surimi + name = "surimi" + desc = "A portion of uncured fish surimi." + icon = 'icons/obj/food/martian.dmi' + icon_state = "surimi" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/consumable/nutriment/vitamin = 2, + ) + tastes = list("fish" = 1) + foodtypes = SEAFOOD + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/surimi/Initialize(mapload) + . = ..() + AddElement(/datum/element/dryable, /obj/item/food/kamaboko) + +/obj/item/food/kamaboko + name = "kamaboko" + desc = "A Japanese-style cured fishcake frequently used in snacks and ramen." + icon = 'icons/obj/food/martian.dmi' + icon_state = "kamaboko_sunrise" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/consumable/nutriment/vitamin = 4, + ) + tastes = list("fish" = 1) + foodtypes = SEAFOOD + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/kamaboko/Initialize(mapload) + . = ..() + var/design = pick("smiling", "spiral", "star", "sunrise") + name = "[design] kamaboko" + icon_state = "kamaboko_[design]" + +/obj/item/food/kamaboko/make_processable() + AddElement(/datum/element/processable, TOOL_KNIFE, /obj/item/food/kamaboko_slice, 4, 3 SECONDS, table_required = TRUE, screentip_verb = "Cut") + +/obj/item/food/kamaboko_slice + name = "kamaboko slice" + desc = "A slice of fishcake. Goes good in ramen." + icon = 'icons/obj/food/martian.dmi' + icon_state = "kamaboko_slice" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 1, + /datum/reagent/consumable/nutriment/vitamin = 1, + ) + tastes = list("fish" = 1) + foodtypes = SEAFOOD + w_class = WEIGHT_CLASS_TINY + +/obj/item/food/sambal + name = "sambal" + desc = "A spice paste from Indonesia, used widely in cooking throughout South East Asia." + icon = 'icons/obj/food/martian.dmi' + icon_state = "sambal" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 5, + /datum/reagent/consumable/capsaicin = 2 + ) + tastes = list("chilli heat" = 1, "umami" = 1) + foodtypes = SEAFOOD | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/katsu_fillet + name = "katsu fillet" + desc = "Breaded and deep fried meat, used for a variety of dishes." + icon = 'icons/obj/food/martian.dmi' + icon_state = "katsu_fillet" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/nutriment/vitamin = 2, + /datum/reagent/consumable/nutriment = 2 + ) + tastes = list("meat" = 1, "breadcrumbs" = 1) + foodtypes = MEAT | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/rice_dough + name = "rice dough" + desc = "A piece of dough made with equal parts rice flour and wheat flour, for a unique flavour." + icon = 'icons/obj/food/martian.dmi' + icon_state = "rice_dough" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6 + ) + tastes = list("rice" = 1) + foodtypes = GRAIN + +/obj/item/food/rice_dough/make_bakeable() + AddComponent(/datum/component/bakeable, /obj/item/food/bread/reispan, rand(30 SECONDS, 45 SECONDS), TRUE, TRUE) + +/obj/item/food/rice_dough/make_processable() + AddElement(/datum/element/processable, TOOL_KNIFE, /obj/item/food/spaghetti/rawnoodles, 6, 3 SECONDS, table_required = TRUE) + +/obj/item/food/spaghetti/rawnoodles + name = "fresh noodles" + desc = "Rice noodles, made fresh. Remember, there is no secret ingredient." + icon = 'icons/obj/food/martian.dmi' + icon_state = "raw_noodles" + + food_reagents = list( + /datum/reagent/consumable/nutriment = 3 + ) + tastes = list("rice" = 1) + foodtypes = GRAIN + +/obj/item/food/spaghetti/boilednoodles + name = "cooked noodles" + desc = "Cooked fresh to order." + icon = 'icons/obj/food/martian.dmi' + icon_state = "cooked_noodles" + food_reagents = list( + /datum/reagent/consumable/nutriment = 3 + ) + tastes = list("rice" = 1) + foodtypes = GRAIN + +/obj/item/food/bread/reispan + name = "reispan" + desc = "Though the concept of rice bread has been common in Asia for centuries, the reispan as we know it today is most commonly associated with Mars- where limited arable land has forced ingenuity." + icon = 'icons/obj/food/martian.dmi' + icon_state = "reispan" + food_reagents = list( + /datum/reagent/consumable/nutriment = 15 + ) + tastes = list("bread" = 10) + foodtypes = GRAIN + venue_value = FOOD_PRICE_TRASH + +/obj/item/food/bread/reispan/make_processable() + AddElement(/datum/element/processable, TOOL_KNIFE, /obj/item/food/breadslice/reispan, 5, 3 SECONDS, table_required = TRUE) + +/obj/item/food/breadslice/reispan + name = "reispan slice" + desc = "A slice of reispan, for use in Martian-style sandwiches." + icon = 'icons/obj/food/martian.dmi' + icon_state = "reispan_slice" + food_reagents = list( + /datum/reagent/consumable/nutriment = 3 + ) + foodtypes = GRAIN | VEGETABLES + +// Fried Rice + +/obj/item/food/salad/hurricane_rice + name = "hurricane fried rice" + desc = "Inspired by nasi goreng, this piquant rice dish comes straight from Prospect, on Mars, and its night markets. It's named for its distinctive cooking style, where the frying rice is given lots of airtime while being flipped, mostly because it looks really cool for the customers." + icon = 'icons/obj/food/martian.dmi' + icon_state = "hurricane_rice" + food_reagents = list( + /datum/reagent/consumable/nutriment = 10, + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/nutriment/vitamin = 10, + ) + tastes = list("rice" = 1, "meat" = 1, "pineapple" = 1, "veggies" = 1) + foodtypes = MEAT | GRAIN | PINEAPPLE | FRUIT | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/ikareis + name = "ikareis" + desc = "A spicy rice dish made with squid-ink, peppers, onions, sausage, and flavourful chillis." + icon = 'icons/obj/food/martian.dmi' + icon_state = "ikareis" + food_reagents = list( + /datum/reagent/consumable/nutriment = 10, + /datum/reagent/consumable/nutriment/protein = 10, + /datum/reagent/consumable/nutriment/vitamin = 6, + /datum/reagent/consumable/capsaicin = 4 + ) + tastes = list("rice" = 1, "squid ink" = 1, "veggies" = 1, "sausage" = 1, "chilli heat" = 1) + foodtypes = MEAT | GRAIN | SEAFOOD | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/hawaiian_fried_rice + name = "\improper Hawaiian fried rice" + desc = "Not a traditional Hawaiian dish, Hawaiian fried rice instead utilises a pastiche of Hawaiian ingredients- including diced Chap and, controversially, pineapple. Purists are split on whether pineapple belongs in rice." + icon = 'icons/obj/food/martian.dmi' + icon_state = "hawaiian_fried_rice" + food_reagents = list( + /datum/reagent/consumable/nutriment = 10, + /datum/reagent/consumable/nutriment/protein = 10, + /datum/reagent/consumable/nutriment/vitamin = 8, + ) + tastes = list("rice" = 1, "pork" = 1, "pineapple" = 1, "soy sauce" = 1, "veggies" = 1) + foodtypes = MEAT | GRAIN | VEGETABLES | FRUIT | PINEAPPLE + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/ketchup_fried_rice + name = "ketchup fried rice" + desc = "A classic Japanese comfort food, made with sausage, veggies, worchestershire sauce, rice- oh, and of course, ketchup." + icon = 'icons/obj/food/martian.dmi' + icon_state = "ketchup_fried_rice" + food_reagents = list( + /datum/reagent/consumable/nutriment = 10, + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/nutriment/vitamin = 8, + /datum/reagent/consumable/ketchup = 2, + ) + tastes = list("rice" = 1, "sausage" = 1, "ketchup" = 1, "veggies" = 1) + foodtypes = MEAT | GRAIN | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/mediterranean_fried_rice + name = "mediterranean fried rice" + desc = "A strange take on the fried rice formula: herbs, cheese, olives, and of course, meatballs. Sorta like a hybrid of risotto and fried rice." + icon = 'icons/obj/food/martian.dmi' + icon_state = "mediterranean_fried_rice" + food_reagents = list( + /datum/reagent/consumable/nutriment = 8, + /datum/reagent/consumable/nutriment/protein = 10, + /datum/reagent/consumable/nutriment/vitamin = 10, + ) + tastes = list("rice" = 1, "cheese" = 1, "meatball" = 1, "olives" = 1, "herbs" = 1) + foodtypes = MEAT | GRAIN | VEGETABLES | DAIRY + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/egg_fried_rice + name = "egg fried rice" + desc = "As simple as fried rice gets: rice, egg, soy sauce. Simple, elegant, and infinitely customisable." + icon = 'icons/obj/food/martian.dmi' + icon_state = "egg_fried_rice" + food_reagents = list( + /datum/reagent/consumable/nutriment = 8, + /datum/reagent/consumable/nutriment/protein = 2, + ) + tastes = list("rice" = 1, "egg" = 1, "soy sauce" = 1) + foodtypes = MEAT | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/egg_fried_rice/Initialize(mapload) + . = ..() + AddComponent(/datum/component/customizable_reagent_holder, null, CUSTOM_INGREDIENT_ICON_STACK) + +/obj/item/food/salad/bibimbap + name = "bibimbap" + desc = "A Korean dish consisting of rice and various toppings, served in a hot stone bowl." + icon = 'icons/obj/food/martian.dmi' + icon_state = "bibimbap" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/consumable/nutriment/vitamin = 8, + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/capsaicin = 2, + ) + tastes = list("rice" = 1, "spicy cabbage" = 1, "chilli heat" = 1, "egg" = 1, "meat" = 1) + foodtypes = MEAT | VEGETABLES | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/bibimbap/Initialize(mapload) + . = ..() + AddComponent(/datum/component/customizable_reagent_holder, null, CUSTOM_INGREDIENT_ICON_STACK) + +// Noodles +/obj/item/food/salad/bulgogi_noodles + name = "bulgogi noodles" + desc = "Korean barbecue meat served with noodles! Made with gochujang, for extra spicy flavour." + icon = 'icons/obj/food/martian.dmi' + icon_state = "bulgogi_noodles" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/nutriment = 8, + /datum/reagent/consumable/capsaicin = 2, + ) + tastes = list("barbecue meat" = 1, "noodles" = 1, "chilli heat" = 1) + foodtypes = MEAT | GRAIN | VEGETABLES | FRUIT + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/yakisoba_katsu + name = "yakisoba katsu" + desc = "Breaded and deep fried meat on a bed of fried noodles. Delicious, if unconventional." + icon = 'icons/obj/food/martian.dmi' + icon_state = "yakisoba_katsu" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/nutriment/vitamin = 4, + /datum/reagent/consumable/nutriment = 8, + ) + tastes = list("fried noodles" = 1, "meat" = 1, "breadcrumbs" = 1, "veggies" = 1) + foodtypes = MEAT | VEGETABLES | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/martian_fried_noodles + name = "\improper Martian fried noodles" + desc = "Fried noodles from the red planet. Martian cooking draws from many cultures, and these noodles are no exception- there's elements of Malay, Thai, Chinese, Korean and Japanese cuisine in here." + icon = 'icons/obj/food/martian.dmi' + icon_state = "martian_fried_noodles" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/nutriment/vitamin = 4, + /datum/reagent/consumable/nutriment = 8, + ) + tastes = list("noodles" = 1, "meat" = 1, "nuts" = 1, "onion" = 1, "egg" = 1) + foodtypes = GRAIN | NUTS | MEAT | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/simple_fried_noodles + name = "simple fried noodles" + desc = "A simple yet delicious fried noodle dish, perfect for the creative chef to make whatever fried noodles they want." + icon = 'icons/obj/food/martian.dmi' + icon_state = "simple_fried_noodles" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/consumable/nutriment/vitamin = 4, + /datum/reagent/consumable/nutriment = 6, + ) + tastes = list("noodles" = 1, "soy sauce" = 1) + foodtypes = GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/simple_fried_noodles/Initialize(mapload) + . = ..() + AddComponent(/datum/component/customizable_reagent_holder, null, CUSTOM_INGREDIENT_ICON_STACK) + +// Curry +/obj/item/food/salad/setagaya_curry //let me explain... + name = "\improper Setagaya curry" + desc = "Made famous by a cafe in Setagaya, this curry's extensive recipe has gone on to be a closely-guarded secret amongst cafe owners across human space. The taste is said to replenish the diner's soul, whatever that means." + icon = 'icons/obj/food/martian.dmi' + icon_state = "setagaya_curry" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/nutriment/vitamin = 8, + /datum/reagent/consumable/nutriment = 8, + /datum/reagent/medicine/omnizine = 5, + ) + tastes = list("masterful curry" = 1, "rice" = 1) + foodtypes = GRAIN | MEAT | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +// Burgers and Sandwiches +/obj/item/food/burger/big_blue + name = "\improper Big Blue burger" + desc = "The original and best Big Blue, straight outta Mars' favourite burger joint. Catch the wave, brother!" + icon = 'icons/obj/food/martian.dmi' + icon_state = "big_blue_burger" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/nutriment/vitamin = 8, + ) + tastes = list("bun" = 1, "burger" = 2, "teriyaki onions" = 1, "cheese" = 1, "bacon" = 1, "pineapple" = 1) + foodtypes = MEAT | GRAIN | DAIRY | VEGETABLES | FRUIT | PINEAPPLE + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/burger/chappy + name = "\improper Chappy patty" + desc = "Originally born of a night of drinking in a Big Blue Burger's kitchen, the Chappy patty has since become a staple of both Big Blue's menu and Hawaiian (or at least, faux-Hawaiian) cuisine galaxy-wide. Given Big Kahuna operates most of its stores on Mars, it's perhaps no wonder this dish is popular there." + icon = 'icons/obj/food/martian.dmi' + icon_state = "chappy_patty" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/nutriment/protein = 6, + ) + tastes = list("bun" = 1, "fried pork" = 2, "egg" = 1, "cheese" = 1, "ketchup" = 1) + foodtypes = MEAT | GRAIN | DAIRY | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/king_katsu_sandwich + name = "\improper King Katsu sandwich" + desc = "A big sandwich with crispy fried katsu, bacon, kimchi slaw and salad, all on reispan bread. Truly the king of meat between bread." + icon = 'icons/obj/food/martian.dmi' + icon_state = "king_katsu_sandwich" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/nutriment/vitamin = 6, + /datum/reagent/consumable/capsaicin = 1, + ) + tastes = list("meat" = 1, "bacon" = 1, "kimchi" = 1, "salad" = 1, "rice bread" = 1) + foodtypes = MEAT | GRAIN | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/marte_cubano_sandwich + name = "\improper Marte Cubano sandwich" + desc = "A fusion food from Mars, the Marte-Cubano is based on the classic Cubano, but updated for ingredient availability and changes in tastes." + icon = 'icons/obj/food/martian.dmi' + icon_state = "marte_cubano_sandwich" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/nutriment/vitamin = 4, + ) + tastes = list("bacon" = 1, "pickles" = 1, "cheese" = 1, "rice bread" = 1) + foodtypes = MEAT | DAIRY | VEGETABLES | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/little_shiro_sandwich + name = "\improper Little Shiro sandwich" + desc = "A classic Martian sandwich, named for the first president of TerraGov to come from Mars. It features fried eggs, bulgogi beef, a kimchi salad, and a healthy topping of mozzarella cheese." + icon = 'icons/obj/food/martian.dmi' + icon_state = "marte_cubano_sandwich" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/nutriment/vitamin = 6, + /datum/reagent/consumable/capsaicin = 1, + ) + tastes = list("egg" = 1, "meat" = 1, "kimchi" = 1, "mozzarella" = 1) + foodtypes = MEAT | DAIRY | VEGETABLES | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/croque_martienne + name = "croque-martienne" + desc = "The quintessential Martian breakfast sandwich. Egg, belly pork, pineapple, cheese. Simple. Classic. Available in every cafe across New Osaka." + icon = 'icons/obj/food/martian.dmi' + icon_state = "croque_martienne" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/nutriment/vitamin = 4, + ) + tastes = list("egg" = 1, "toast" = 1, "pork" = 1, "pineapple" = 1, "cheese" = 1) + foodtypes = MEAT | DAIRY | VEGETABLES | GRAIN | PINEAPPLE | BREAKFAST + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/prospect_sunrise + name = "\improper Prospect Sunrise" + desc = "The second-most quintessential Martian breakfast sandwich. The most beautiful combination of omelette, bacon, pickles and cheese. Available in every cafe across Prospect." + icon = 'icons/obj/food/martian.dmi' + icon_state = "prospect_sunrise" + food_reagents = list( + /datum/reagent/consumable/nutriment = 5, + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/nutriment/vitamin = 3, + ) + tastes = list("egg" = 1, "toast" = 1, "bacon" = 1, "pickles" = 1, "cheese" = 1) + foodtypes = MEAT | DAIRY | VEGETABLES | GRAIN | BREAKFAST + w_class = WEIGHT_CLASS_SMALL + +// Snacks +/obj/item/food/takoyaki + name = "takoyaki" + desc = "A classic Japanese street food, takoyaki (or octopus balls) are made from octopus and onion inside a fried batter, topped with a savoury sauce." + icon = 'icons/obj/food/martian.dmi' + icon_state = "takoyaki" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/consumable/nutriment/fat/oil = 2, + ) + tastes = list("octopus" = 1, "batter" = 1, "onion" = 1, "worcestershire sauce" = 1) + foodtypes = SEAFOOD | GRAIN | FRIED | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/takoyaki/russian + name = "russian takoyaki" + desc = "A dangerous twist on a classic dish, that makes for the perfect cover for evading the police." + icon = 'icons/obj/food/martian.dmi' + icon_state = "russian_takoyaki" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/consumable/capsaicin = 10, + ) + tastes = list("octopus" = 1, "batter" = 1, "onion" = 1, "chilli heat" = 1) + foodtypes = SEAFOOD | GRAIN | FRIED | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/takoyaki/taco + name = "tacoyaki" + desc = "Straight outta Mars' most innovative street food stands, it's tacoyaki- trading octopus for taco meat and corn, and worcestershire sauce for queso. ¡Tan sabroso!" + icon = 'icons/obj/food/martian.dmi' + icon_state = "tacoyaki" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/consumable/nutriment/fat/oil = 2, + ) + tastes = list("taco meat" = 1, "batter" = 1, "corn" = 1, "cheese" = 1) + foodtypes = MEAT | GRAIN | FRIED | VEGETABLES | DAIRY + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/okonomiyaki + name = "okonomiyaki" + desc = "A Kansai classic, okonomiyaki consists of a savoury pancake filled with... well, whatever you want- although cabbage, nagaimo and dashi are pretty much required, as is the eponymous okonomiyaki sauce." + icon = 'icons/obj/food/martian.dmi' + icon_state = "okonomiyaki" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/nutriment/vitamin = 6, + ) + tastes = list("batter" = 1, "cabbage" = 1, "onion" = 1, "worcestershire sauce" = 1) + foodtypes = SEAFOOD | GRAIN | FRIED | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +//hey, the name literally means "grilled how you like it", it'd be crazy to not make it customisable +/obj/item/food/okonomiyaki/Initialize(mapload) + . = ..() + AddComponent(/datum/component/customizable_reagent_holder, null, CUSTOM_INGREDIENT_ICON_STACK) + +/obj/item/food/brat_kimchi + name = "brat-kimchi" + desc = "Fried kimchi, mixed with sugar and topped with bratwurst. A popular dish at izakayas on Mars." + icon = 'icons/obj/food/martian.dmi' + icon_state = "brat_kimchi" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 4, + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/consumable/capsaicin = 2, + /datum/reagent/consumable/sugar = 2, + ) + tastes = list("spicy cabbage" = 1, "sausage" = 1) + foodtypes = MEAT | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/tonkatsuwurst + name = "tonkatsuwurst" + desc = "A cultural fusion between German and Japanese cooking, tonkatsuwurst blends the currywurst and tonkatsu sauce into something familiar, yet new." + icon = 'icons/obj/food/martian.dmi' + icon_state = "tonkatsuwurst" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 3, + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/worcestershire = 2, + ) + tastes = list("sausage" = 1, "spicy sauce" = 1, "fries" = 1) + foodtypes = MEAT | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/kebab/ti_hoeh_koe + name = "ti hoeh koe skewer" + desc = "Pig blood, mixed with rice, fried, and topped with peanut and coriander. It's an... acquired taste for sure, but it's popular at Prospect's night markets, brought by Taiwanese settlers." + icon = 'icons/obj/food/martian.dmi' + icon_state = "ti_hoeh_koe" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 1, + /datum/reagent/consumable/nutriment/protein = 5, + /datum/reagent/consumable/peanut_butter = 1, + ) + tastes = list("blood" = 1, "nuts" = 1, "herbs" = 1) + foodtypes = MEAT | NUTS | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/kitzushi + name = "kitzushi" + desc = "A variant on inarizushi popular on Mars amongst vulpinids (and the wider animalid community), kitzushi integrates a spicy cheese and chilli mix inside the pocket for extra flavour." + icon = 'icons/obj/food/martian.dmi' + icon_state = "kitzushi" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 3, + /datum/reagent/consumable/nutriment = 3, + /datum/reagent/consumable/capsaicin = 2, + ) + tastes = list("rice" = 1, "tofu" = 1, "chilli cheese" = 1) + foodtypes = GRAIN | FRIED | VEGETABLES | DAIRY + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/epok_epok + name = "epok-epok" + desc = "A fried pastry snack from Malaysia, which migrated via Singapore into the Martian diet. Stuffed with curried chicken and potatoes, alongside a slice of hard boiled egg, it's a popular street food on the Red Planet." + icon = 'icons/obj/food/martian.dmi' + icon_state = "epok_epok" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/nutriment/protein = 4, + ) + tastes = list("curry" = 1, "egg" = 1, "pastry" = 1) + foodtypes = GRAIN | MEAT | VEGETABLES | FRIED + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/roti_john + name = "roti john" + desc = "A classic Malaysian snack, the roti john consists of bread fried in a mixture of meat, egg and onion, yielding a result that's somewhere between French toast and an omelette." + icon = 'icons/obj/food/martian.dmi' + icon_state = "roti_john" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 6, + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/nutriment = 10, + ) + tastes = list("bread" = 1, "egg" = 1, "meat" = 1, "onion" = 1) + foodtypes = GRAIN | MEAT | VEGETABLES | FRIED | BREAKFAST + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/izakaya_fries + name = "izakaya fries" + desc = "New Osaka's favourite fries, 2 centuries running- and it's all thanks to the marriage of Red Bay, furikake and mayonnaise." + icon = 'icons/obj/food/martian.dmi' + icon_state = "izakaya_fries" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/nutriment/protein = 2, + /datum/reagent/consumable/capsaicin = 2, + /datum/reagent/consumable/salt = 2, + ) + tastes = list("fries" = 1, "mars" = 1) + foodtypes = VEGETABLES | FRIED + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/kurry_ok_subsando + name = "kurry-ok subsando" + desc = "The bunny chow meets Martian ingenuity in the form of the kurry-ok subsando, with fries and katsu curry in perfect harmony." + icon = 'icons/obj/food/martian.dmi' + icon_state = "kurry_ok_subsando" + food_reagents = list( + /datum/reagent/consumable/nutriment = 10, + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/nutriment/vitamin = 8, + ) + tastes = list("bread" = 1, "spicy fries" = 1, "mayonnaise" = 1, "curry" = 1, "meat" = 1) + foodtypes = MEAT | GRAIN | VEGETABLES | FRIED + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/loco_moco + name = "loco moco" + desc = "A simple classic from Hawaii. Makes for a filling, tasty, and cheap meal." + icon = 'icons/obj/food/martian.dmi' + icon_state = "loco_moco" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/nutriment/protein = 8, + ) + tastes = list("rice" = 1, "burger" = 1, "gravy" = 1, "egg" = 1) + foodtypes = MEAT | GRAIN | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/wild_duck_fries + name = "wild duck fries" + desc = "Fries with shredded duck, ketchup, mayo, and Red Bay. A classic street food on Mars, although they're most often associated with Kwik-Kwak, Mars' favourite (and indeed, only) duck themed fast food chain." + icon = 'icons/obj/food/martian.dmi' + icon_state = "wild_duck_fries" + food_reagents = list( + /datum/reagent/consumable/nutriment = 8, + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/capsaicin = 2, + /datum/reagent/consumable/salt = 2, + ) + tastes = list("fries" = 1, "duck" = 1, "ketchup" = 1, "mayo" = 1, "spicy seasoning" = 1) + foodtypes = MEAT | VEGETABLES | FRIED + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/little_hawaii_hotdog + name = "\improper Little Hawaii hotdog" + desc = "From the friendly vendors of Honolulu Avenue comes the Little Hawaii dog- tropical and fattening, all at the same time!" + icon = 'icons/obj/food/martian.dmi' + icon_state = "little_hawaii_hotdog" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/nutriment/vitamin = 6, + ) + tastes = list("sausage" = 1, "pineapple" = 1, "onion" = 1, "teriyaki" = 1) + foodtypes = MEAT | VEGETABLES | FRUIT | PINEAPPLE + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salt_chilli_fries + name = "salt n' chilli fries" + desc = "The simple name of this dish doesn't tell the full story of its deliciousness- sure, salt and chilli are big components, but the onion, ginger and garlic are the real flavour heroes here." + icon = 'icons/obj/food/martian.dmi' + icon_state = "salt_chilli_fries" + food_reagents = list( + /datum/reagent/consumable/nutriment = 8, + /datum/reagent/consumable/nutriment/vitamin = 4, + /datum/reagent/consumable/capsaicin = 2, + /datum/reagent/consumable/salt = 2, + ) + tastes = list("fries" = 1, "garlic" = 1, "ginger" = 1, "numbing heat" = 1, "salt" = 1) + foodtypes = VEGETABLES | FRIED + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/grilled_octopus + name = "grilled octopus tentacle" + desc = "A simple seafood dish, typical to everywhere that octopus is eaten. Martians like it with Red Bay." + icon = 'icons/obj/food/martian.dmi' + icon_state = "grilled_octopus" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/nutriment/vitamin = 2, + /datum/reagent/consumable/char = 2) + tastes = list("octopus" = 1) + foodtypes = SEAFOOD | FRIED + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/steak_croquette + name = "steak croquette" + desc = "Man, sticking chunks of steak in a croquette. Must be the countryside way." + icon = 'icons/obj/food/martian.dmi' + icon_state = "steak_croquette" + food_reagents = list( + /datum/reagent/consumable/nutriment = 3, + /datum/reagent/consumable/nutriment/protein = 6, + ) + tastes = list("steak" = 1, "potato" = 1) + foodtypes = MEAT | VEGETABLES | FRIED + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/chapsilog + name = "chapsilog" + desc = "A traditional Filipino-style silog consisting of sinangag, a fried egg, and slices of chap. Makes for a simple, yet filling, breakfast." + icon = 'icons/obj/food/martian.dmi' + icon_state = "chapsilog" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/nutriment/vitamin = 3, + /datum/reagent/consumable/garlic = 1, + ) + tastes = list("ham" = 1, "garlic rice" = 1, "egg" = 1) + foodtypes = MEAT | GRAIN | VEGETABLES | BREAKFAST + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/chap_hash + name = "chap hash" + desc = "What do you get when you combine chap, onions, peppers and potatoes? The chap hash, of course! Add some red bay, and you've got yourself a tasty breakfast." + icon = 'icons/obj/food/martian.dmi' + icon_state = "chap_hash" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 6, + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/nutriment = 3, + ) + tastes = list("ham" = 1, "onion" = 1, "pepper" = 1, "potato" = 1) + foodtypes = MEAT | VEGETABLES | BREAKFAST + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/agedashi_tofu + name = "agedashi tofu" + desc = "Crispy fried tofu, served in a tasty umami broth. Frequently served at izakayas." + icon = 'icons/obj/food/martian.dmi' + icon_state = "agedashi_tofu" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/nutriment/protein = 2, + /datum/reagent/consumable/nutriment/vitamin = 4, + ) + tastes = list("umami broth" = 1, "tofu" = 1) + foodtypes = SEAFOOD | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +// Curries and Stews +/obj/item/food/salad/po_kok_gai + name = "po kok gai" + desc = "Also known as galinha à portuguesa, or Portuguese chicken, this dish is a Macanese classic born of Portuguese colonialism, though the dish itself is not a Portuguese dish. It consists of chicken in \"Portuguese Sauce\", a mild coconut-based curry." + icon = 'icons/obj/food/martian.dmi' + icon_state = "po_kok_gai" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/nutriment/vitamin = 2, + ) + tastes = list("chicken" = 1, "coconut" = 1, "curry" = 1) + foodtypes = MEAT | VEGETABLES | FRUIT + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/huoxing_tofu + name = "\improper Huoxing tofu" + desc = "An adaptation of mapo tofu made famous in Prospect, the foodie Mecca of Mars. It even kinda looks like Mars, if you really squint." + icon = 'icons/obj/food/martian.dmi' + icon_state = "huoxing_tofu" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/nutriment/vitamin = 4, + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/capsaicin = 2 + ) + tastes = list("meat" = 1, "chilli heat" = 1, "tofu" = 1) + foodtypes = MEAT | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/feizhou_ji + name = "fēizhōu jī" + desc = "Considered a Macanese variant on piri-piri, fēizhōu jī, or galinha à africana, or African chicken (if you're feeling like speaking Common), is a popular dish in the TID, and subsequently also on Mars due to its influx of Macanese settlers." + icon = 'icons/obj/food/martian.dmi' + icon_state = "feizhou_ji" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/capsaicin = 2, + ) + tastes = list("chicken" = 1, "chilli heat" = 1, "vinegar" = 1) + foodtypes = MEAT | VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/galinha_de_cabidela + name = "galinha de cabidela" + desc = "Originally a Portuguese dish, cabidela rice is traditionally made with chicken in Portugal, and duck in Macau- ultimately, the chicken version won out on Mars due to European influence." + icon = 'icons/obj/food/martian.dmi' + icon_state = "galinha_de_cabidela" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/nutriment/protein = 12, + ) + tastes = list("chicken" = 1, "iron" = 1, "vinegar" = 1, "rice" = 1) + foodtypes = MEAT | VEGETABLES | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/katsu_curry + name = "katsu curry" + desc = "Breaded and deep fried meat, topped with curry sauce and served on a bed of rice." + icon = 'icons/obj/food/martian.dmi' + icon_state = "katsu_curry" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/nutriment/protein = 8, + ) + tastes = list("curry" = 1, "meat" = 1, "breadcrumbs" = 1, "rice" = 1) + foodtypes = MEAT | VEGETABLES | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/beef_bowl + name = "beef bowl" + desc = "A tasty mix of stewed beef and onion, served over rice. Typical toppings include pickled ginger, chilli powder, and fried eggs." + icon = 'icons/obj/food/martian.dmi' + icon_state = "beef_bowl" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/nutriment/vitamin = 2, + ) + tastes = list("beef" = 25, "onion" = 25, "chili heat" = 15, "rice" = 34, "soul" = 1) //I pour my soul into this bowl + foodtypes = MEAT | VEGETABLES | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/salt_chilli_bowl + name = "salt n' chilli octopus bowl" + desc = "Inspired by the Japanese donburi tradition, this spicy take on ten-don is a flavour sensation that's swept the Martian nation." + icon = 'icons/obj/food/martian.dmi' + icon_state = "salt_chilli_bowl" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/nutriment/vitamin = 6, + /datum/reagent/consumable/capsaicin = 2, + /datum/reagent/consumable/salt = 2, + ) + tastes = list("seafood" = 1, "rice" = 1, "garlic" = 1, "ginger" = 1, "numbing heat" = 1, "salt" = 1) + foodtypes = SEAFOOD | VEGETABLES | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/kansai_bowl + name = "\improper Kansai bowl" + desc = "Also known as konohadon, this donburi is typical to the Kansai region, and consists of kamaboko fishcake, egg and onion served over rice." + icon = 'icons/obj/food/martian.dmi' + icon_state = "kansai_bowl" + food_reagents = list( + /datum/reagent/consumable/nutriment = 8, + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/nutriment/vitamin = 2, + ) + tastes = list("seafood" = 1, "rice" = 1, "egg" = 1, "onion" = 1) + foodtypes = SEAFOOD | MEAT | VEGETABLES | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/salad/eigamudo_curry //curry is meant to be really spicy or kinda mild, this just stinks! + name = "\improper Eigamudo curry" + desc = "An inexplicable curry dish made from a cacophony of ingredients. Presumably tastes good to someone, somewhere- though good luck finding them." + icon = 'icons/obj/food/martian.dmi' + icon_state = "eigamudo_curry" + food_reagents = list( + /datum/reagent/consumable/nutraslop = 8, + /datum/reagent/consumable/capsaicin = 2, + /datum/reagent/toxin/slimejelly = 4, + ) + tastes = list("grit" = 1, "slime" = 1, "gristle" = 1, "rice" = 1, "Mystery Food X" = 1) + foodtypes = GROSS | GRAIN | TOXIC + w_class = WEIGHT_CLASS_SMALL + +// Entrees +/obj/item/food/cilbir + name = "çilbir" + desc = "Eggs, served on a savoury yoghurt base with a spicy oil topping. Originally a Turkish dish, it came to Mars with German-Turkish settlers and has become a breakfast mainstay since." + icon = 'icons/obj/food/martian.dmi' + icon_state = "cilbir" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/consumable/nutriment/vitamin = 6, + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/capsaicin = 2, + /datum/reagent/consumable/garlic = 1, + ) + tastes = list("yoghurt" = 1, "garlic" = 1, "lemon" = 1, "egg" = 1, "chilli heat" = 1) + foodtypes = DAIRY | VEGETABLES | FRUIT | BREAKFAST + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/peking_duck_crepes + name = "\improper Peking duck crepes a l'orange" + desc = "This dish takes the best of Beijing's and Paris' cuisines to make a deliciously tangy and savoury meal." + icon = 'icons/obj/food/martian.dmi' + icon_state = "peking_duck_crepes" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 10, + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/nutriment/vitamin = 4, + /datum/reagent/consumable/orangejuice = 4, + ) + tastes = list("meat" = 1, "crepes" = 1, "orange" = 1) + foodtypes = MEAT | DAIRY | VEGETABLES | FRUIT + w_class = WEIGHT_CLASS_SMALL + +// Desserts +/obj/item/food/cake/spekkoek + name = "vulgaris spekkoek" + desc = "Brought to Mars by both Dutch and Indonesian settlers, spekkoek is a common holiday cake on the Red Planet, often being served as part of a traditional rijsttafel. Use of ambrosia vulgaris as a flavouring is one of necessity in deep space, as pandan leaf is rare this far from Earth." + icon = 'icons/obj/food/martian.dmi' + icon_state = "spekkoek" + food_reagents = list( + /datum/reagent/consumable/nutriment = 30, + /datum/reagent/consumable/nutriment/vitamin = 15 + ) + tastes = list("winter spices" = 2, "ambrosia vulgaris" = 2, "cake" = 5) + foodtypes = GRAIN | SUGAR | DAIRY + +/obj/item/food/cake/spekkoek/make_processable() + AddElement(/datum/element/processable, TOOL_KNIFE, /obj/item/food/cakeslice/spekkoek, 5, 3 SECONDS, table_required = TRUE) + +/obj/item/food/cakeslice/spekkoek + name = "vulgaris spekkoek slice" + desc = "A slice of vulgaris spekkoek. If you're Martian, this might remind you of home." + icon = 'icons/obj/food/martian.dmi' + icon_state = "spekkoek_slice" + tastes = list("winter spices" = 2, "ambrosia vulgaris" = 2, "cake" = 5) + foodtypes = GRAIN | SUGAR | DAIRY + +/obj/item/food/salad/pineapple_foster + name = "pineapple foster" + desc = "A classic Martian adaptation of another classic dessert, Pineapple Foster is a toasty sweet treat which presents only a mild-to-moderate fire risk." + icon = 'icons/obj/food/martian.dmi' + icon_state = "pineapple_foster" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 6, + /datum/reagent/consumable/nutriment = 2, + /datum/reagent/consumable/caramel = 4, + /datum/reagent/consumable/pineapplejuice = 2, + /datum/reagent/consumable/milk = 4 + ) + tastes = list("pineapple" = 1, "vanilla" = 1, "caramel" = 1, "ice cream" = 1) + foodtypes = FRUIT | DAIRY | PINEAPPLE + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/pastel_de_nata + name = "pastel de nata" + desc = "Originally created by Portuguese monks, pastéis de nata went worldwide under the Portuguese colonial empire- including Macau, from which it came to Mars with settlers from the TID of Hong Kong and Macau." + icon = 'icons/obj/food/martian.dmi' + icon_state = "pastel_de_nata" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/sugar = 4, + ) + tastes = list("custard" = 1, "vanilla" = 1, "sweet pastry" = 1) + foodtypes = DAIRY | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/boh_loh_yah + name = "boh loh yah" + desc = "Confusingly referred to as a \"pineapple bun\", this Hong Konger treat contains no actual pineapple- instead, it's a sugar-cookie like bun with a butter filling." + icon = 'icons/obj/food/martian.dmi' + icon_state = "boh_loh_yah" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/sugar = 4, + ) + tastes = list("cookie" = 1, "butter" = 1) + foodtypes = DAIRY | GRAIN | PINEAPPLE //it's funny + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/banana_fritter + name = "banana fritter" + desc = "A ubiquitous sweet snack from much of Maritime South-East Asia, the banana fritter has many names, but all share a similar style- banana, coated in batter, and fried." + icon = 'icons/obj/food/martian.dmi' + icon_state = "banana_fritter" + food_reagents = list( + /datum/reagent/consumable/nutriment = 3, + /datum/reagent/consumable/nutriment/vitamin = 1, + /datum/reagent/consumable/sugar = 1, + ) + tastes = list("banana" = 1, "batter" = 1) + foodtypes = GRAIN | FRUIT | FRIED + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/pineapple_fritter + name = "pineapple fritter" + desc = "Like its cousin, the banana fritter, the pineapple fritter is a popular snack, though somewhat let down by pineapple's infamous \"love it or hate it\" flavour." + icon = 'icons/obj/food/martian.dmi' + icon_state = "pineapple_fritter" + food_reagents = list( + /datum/reagent/consumable/nutriment = 3, + /datum/reagent/consumable/nutriment/vitamin = 1, + /datum/reagent/consumable/sugar = 1, + ) + tastes = list("pineapple" = 1, "batter" = 1) + foodtypes = GRAIN | FRUIT | FRIED | PINEAPPLE + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/kebab/kasei_dango + name = "kasei dango" + desc = "Japanese-style dango balls, flavoured with grenadine and orange, giving a final result that looks like Mars and tastes like dessert, served three to a stick." + icon = 'icons/obj/food/martian.dmi' + icon_state = "kasei_dango" + food_reagents = list( + /datum/reagent/consumable/sugar = 6, + /datum/reagent/consumable/nutriment = 2, + /datum/reagent/consumable/orangejuice = 3, + /datum/reagent/consumable/grenadine = 3 + ) + tastes = list("pomegranate" = 1, "orange" = 1) + foodtypes = FRUIT | GRAIN + w_class = WEIGHT_CLASS_SMALL + +// Frozen +/obj/item/food/pb_ice_cream_mochi + name = "peanut butter ice cream mochi" + desc = "A classic dessert at the Arabia Street Night Market in Prospect, peanut butter ice cream mochi is made with a peanut-butter flavoured ice cream as the main filling, and coated in crushed peanuts in the Taiwanese tradition." + icon = 'icons/obj/food/martian.dmi' + icon_state = "pb_ice_cream_mochi" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/consumable/sugar = 6, + /datum/reagent/consumable/peanut_butter = 4, + /datum/reagent/consumable/milk = 2, + ) + tastes = list("peanut butter" = 1, "mochi" = 1) + foodtypes = NUTS | GRAIN | DAIRY | SUGAR + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/popsicle/pineapple_pop + name = "frozen pineapple pop" + desc = "Few cultures love pineapple as much as the Martians, and this dessert proves that- frozen pineapple, on a stick, with just a little dunk of dark chocolate." + overlay_state = "pineapple_pop" + food_reagents = list( + /datum/reagent/consumable/pineapplejuice = 4, + /datum/reagent/consumable/sugar = 4, + /datum/reagent/consumable/nutriment = 2, + /datum/reagent/consumable/nutriment/vitamin = 2, + ) + tastes = list("cold pineapple" = 1, "chocolate" = 1) + foodtypes = SUGAR | PINEAPPLE + +/obj/item/food/popsicle/sea_salt + name = "sea salt ice-cream bar" + desc = "This sky-blue ice-cream bar is flavoured with only the finest imported sea salt. Salty... no, sweet!" + overlay_state = "sea_salt_pop" + food_reagents = list( + /datum/reagent/consumable/salt = 1, + /datum/reagent/consumable/nutriment = 2, + /datum/reagent/consumable/cream = 2, + /datum/reagent/consumable/vanilla = 2, + /datum/reagent/consumable/sugar = 4, + ) + tastes = list("salt" = 1, "sweet" = 1) + foodtypes = SUGAR | DAIRY + +// topsicles, also known as tofu popsicles +/obj/item/food/popsicle/topsicle + name = "berry topsicle" + desc = "A frozen treat made from tofu and berry juice blended smooth, then frozen. Supposedly a favourite of bears, but that makes no sense..." + overlay_state = "topsicle_berry" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 4, + /datum/reagent/consumable/sugar = 6, + /datum/reagent/consumable/berryjuice = 4 + ) + tastes = list("berry" = 1, "tofu" = 1) + foodtypes = FRUIT | VEGETABLES + +/obj/item/food/popsicle/topsicle/banana + name = "banana topsicle" + desc = "A frozen treat made from tofu and banana juice blended smooth, then frozen. Popular in rural Japan in the summer." + overlay_state = "topsicle_banana" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 4, + /datum/reagent/consumable/sugar = 6, + /datum/reagent/consumable/banana = 4 + ) + tastes = list("banana" = 1, "tofu" = 1) + +/obj/item/food/popsicle/topsicle/pineapple + name = "pineapple topsicle" + desc = "A frozen treat made from tofu and pineapple juice blended smooth, then frozen. As seen on TV." + overlay_state = "topsicle_pineapple" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 4, + /datum/reagent/consumable/sugar = 6, + /datum/reagent/consumable/pineapplejuice = 4 + ) + tastes = list("pineapple" = 1, "tofu" = 1) + +// Ballpark Food +/obj/item/food/plasma_dog_supreme + name = "\improper Plasma Dog Supreme" + desc = "The signature snack of Cybersun Park, home of the New Osaka Woodpeckers: a ballpark hot-dog with sambal, dashi-grilled onions and pineapple-lime salsa. You know, the sort of bold flavours they enjoy on Mars." + icon = 'icons/obj/food/martian.dmi' + icon_state = "plasma_dog_supreme" + food_reagents = list( + /datum/reagent/consumable/nutriment/vitamin = 8, + /datum/reagent/consumable/nutriment/protein = 8, + /datum/reagent/consumable/nutriment = 6 + ) + tastes = list("sausage" = 1, "relish" = 1, "onion" = 1, "fruity salsa" = 1) + foodtypes = FRUIT | MEAT | PINEAPPLE | VEGETABLES | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/frickles + name = "frickles" + desc = "Spicy fried pickle spears? Such a bold combination can surely come only from one place- Martian ballparks? Well, not really, but they are a popular snack there." + icon = 'icons/obj/food/martian.dmi' + icon_state = "frickles" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/nutriment/fat/oil = 2, + /datum/reagent/consumable/capsaicin = 1, + ) + tastes = list("frickles" = 1) + foodtypes = VEGETABLES | GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/raw_ballpark_pretzel + name = "raw pretzel" + desc = "A twisted knot of dough, ready to be baked, or possibly griddled?" + icon = 'icons/obj/food/martian.dmi' + icon_state = "raw_ballpark_pretzel" + food_reagents = list( + /datum/reagent/consumable/nutriment = 3, + /datum/reagent/consumable/salt = 1, + ) + tastes = list("bread" = 1, "salt" = 1) + foodtypes = GRAIN | RAW + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/raw_ballpark_pretzel/make_bakeable() + AddComponent(/datum/component/bakeable, /obj/item/food/ballpark_pretzel, rand(15 SECONDS, 25 SECONDS), TRUE, TRUE) + +/obj/item/food/raw_ballpark_pretzel/make_grillable() + AddComponent(/datum/component/grillable, /obj/item/food/ballpark_pretzel, rand(15 SECONDS, 25 SECONDS), TRUE, TRUE) + +/obj/item/food/ballpark_pretzel + name = "ballpark pretzel" + desc = "A classic German bread, transformed by the hand of American imperialism into a game-day snack, and then carried to the Red Planet on the backs of Japanese settlers. How multicultural." + icon = 'icons/obj/food/martian.dmi' + icon_state = "ballpark_pretzel" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/consumable/salt = 1, + ) + tastes = list("bread" = 1, "salt" = 1) + foodtypes = GRAIN + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/kebab/raw_ballpark_tsukune + name = "raw tsukune" + desc = "Raw chicken meatballs on a skewer, ready to be griddled into something delicious." + icon = 'icons/obj/food/martian.dmi' + icon_state = "raw_ballpark_tsukune" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 3, + /datum/reagent/consumable/nutriment = 2, + ) + tastes = list("raw chicken" = 7, "salmonella" = 1) + foodtypes = MEAT | RAW + w_class = WEIGHT_CLASS_SMALL + +/obj/item/food/kebab/raw_ballpark_tsukune/make_grillable() + AddComponent(/datum/component/grillable, /obj/item/food/kebab/ballpark_tsukune, rand(15 SECONDS, 25 SECONDS), TRUE, TRUE) + +/obj/item/food/kebab/ballpark_tsukune + name = "ballpark tsukune" + desc = "Skewered chicken meatballs in a sweet-and-savoury yakitori sauce. A common sight at Martian ballparks." + icon = 'icons/obj/food/martian.dmi' + icon_state = "ballpark_tsukune" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 6, + /datum/reagent/consumable/nutriment = 4, + ) + tastes = list("chicken" = 1, "umami sauce" = 1) + foodtypes = MEAT + w_class = WEIGHT_CLASS_SMALL + +// Ethereal-suitable cross-culture food +/* Ethereals are, as part of the uplifting process, considered as citizens of the Terran Federation. + For this reason, a lot of ethereals have chosen to move throughout human space, settling on various planets to a mixed reception. + Mars is no exception to this rule, where the ethereal population has been more welcomed than most, due to Mars' more cosmopolitan past. + Here, the ethereals have developed a distinct culture, neither that of their homeland nor that of Mars, and with that a distinct cuisine. +*/ + +// Pickled Voltvine +/obj/item/food/pickled_voltvine + name = "pickled voltvine" + desc = "A traditional dish from Sprout (where it is known as hinu'sashuruhk), pickled voltvine has taken on a new identity amongst the pickle masters of Mars, earning a seat at the holy pickle pantheon alongside pickled ginger and kimchi (once appropriately discharged, at least)." + icon = 'icons/obj/food/martian.dmi' + icon_state = "pickled_voltvine" + food_reagents = list( + /datum/reagent/consumable/liquidelectricity/enriched = 4, + /datum/reagent/consumable/nutriment/vitamin = 2, + ) + tastes = list("sour radish" = 1) + foodtypes = VEGETABLES + w_class = WEIGHT_CLASS_SMALL + +// 24-Volt Energy +/obj/item/food/volt_fish + name = "24-volt fish" + desc = "Some may question the 24-volt fish. After all, fish poached in electric-blue super-sour energy drink looks awful. And, indeed, tastes awful. So why do the Martian ethereals like it, then?" //beats the hell out of me + icon = 'icons/obj/food/martian.dmi' + icon_state = "volt_fish" + food_reagents = list( + /datum/reagent/consumable/liquidelectricity/enriched = 6, + /datum/reagent/consumable/nutriment/protein = 4, + ) + tastes = list("fish" = 1, "sour pear" = 1) + foodtypes = SEAFOOD + w_class = WEIGHT_CLASS_SMALL + +// Sprout Bowl +/obj/item/food/salad/sprout_bowl + name = "\improper Sprout bowl" + desc = "Named for the Ethereal homeworld, this rice-based bowl draws on the donburi tradition, but rejects typical donburi toppings, instead using sashimi grade fish and pickled voltvine." + icon = 'icons/obj/food/martian.dmi' + icon_state = "sprout_bowl" + food_reagents = list( + /datum/reagent/consumable/liquidelectricity/enriched = 8, + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/consumable/nutriment/vitamin = 2, + ) + tastes = list("fish" = 1, "sour radish" = 1, "rice" = 1) + foodtypes = SEAFOOD | VEGETABLES | GRAIN + w_class = WEIGHT_CLASS_SMALL diff --git a/code/game/objects/items/granters/magic/summon_cheese.dm b/code/game/objects/items/granters/magic/summon_cheese.dm new file mode 100644 index 00000000000..668d3be8f9a --- /dev/null +++ b/code/game/objects/items/granters/magic/summon_cheese.dm @@ -0,0 +1,28 @@ +/obj/item/book/granter/action/spell/summon_cheese + name = "Lusty Xenomorph Maid vol. III - Cheese Bakery" + desc = "Wonderful! Time for a celebration... Cheese for everyone!" + icon_state = "bookcheese" + action_name = "summon cheese" + granted_action = /datum/action/cooldown/spell/conjure/cheese + remarks = list( + "Always forward, never back...", + "Are these pages... cheese slices?..", + "Healthy snacks for unsuspecting victims...", + "I never knew so many types of cheese existed...", + "Madness reeks of goat cheese...", + "Madness tastes of gouda...", + "Madness tastes of parmesan...", + "Time is an artificial construct...", + "Was it order or biscuits?..", + "What's this about sacrificing cheese?!..", + "Who wouldn't like that?..", + "Why cheese, of all things?..", + "Why do I need a reason for everything?..", + ) + +/obj/item/book/granter/action/spell/summon_cheese/recoil(mob/living/user) + to_chat(user, span_warning("\The [src] turns into a wedge of cheese!")) + var/obj/item/food/cheese/wedge/book_cheese = new + user.temporarilyRemoveItemFromInventory(src, force = TRUE) + user.put_in_hands(book_cheese) + qdel(src) diff --git a/code/game/objects/items/surgery_tray.dm b/code/game/objects/items/surgery_tray.dm new file mode 100644 index 00000000000..37494a39b55 --- /dev/null +++ b/code/game/objects/items/surgery_tray.dm @@ -0,0 +1,244 @@ +/datum/storage/surgery_tray + max_total_storage = 30 + max_specific_storage = WEIGHT_CLASS_NORMAL + max_slots = 14 + +/datum/storage/surgery_tray/New() + . = ..() + set_holdable(list( + /obj/item/blood_filter, + /obj/item/bonesetter, + /obj/item/cautery, + /obj/item/circular_saw, + /obj/item/clothing/mask/surgical, + /obj/item/clothing/suit/toggle/labcoat/skyrat/hospitalgown, // SKYRAT EDIT ADDITION + /obj/item/hemostat, + /obj/item/razor, + /obj/item/reagent_containers/medigel, + /obj/item/retractor, + /obj/item/scalpel, + /obj/item/stack/medical/bone_gel, + /obj/item/stack/sticky_tape/surgical, + /obj/item/surgical_drapes, + /obj/item/surgicaldrill, + )) + +/** + * Surgery Trays + * A storage object that displays tools in its contents based on tier, better tools are more visible. + * Can be folded up and carried. Click it to draw a random tool. + */ +/obj/item/surgery_tray + name = "surgery tray" + desc = "A Deforest brand medical cart. It is a folding model, meaning the wheels on the bottom can be retracted and the body used as a tray." + icon = 'icons/obj/medicart.dmi' + icon_state = "tray" + w_class = WEIGHT_CLASS_BULKY + slowdown = 1 + item_flags = SLOWS_WHILE_IN_HAND + pass_flags = NONE + + /// If true we're currently portable + var/is_portable = TRUE + +/// Fills the tray with items it should contain on creation +/obj/item/surgery_tray/proc/populate_contents() + return + +/obj/item/surgery_tray/Initialize(mapload) + . = ..() + AddElement(/datum/element/drag_pickup) + create_storage(storage_type = /datum/storage/surgery_tray) + populate_contents() + register_context() + set_tray_mode(is_portable) + +/obj/item/surgery_tray/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + context[SCREENTIP_CONTEXT_LMB] = "Take a random tool" + context[SCREENTIP_CONTEXT_RMB] = "Take a specific tool" + return CONTEXTUAL_SCREENTIP_SET + +/obj/item/surgery_tray/update_icon_state() + . = ..() + icon_state = is_portable ? "tray" : "medicart" + +/obj/item/surgery_tray/update_desc() + . = ..() + if(is_portable) + desc = "The wheels and bottom storage of this medical cart have been stowed away, \ + leaving a cumbersome tray in it's place." + else + desc = initial(desc) + +/obj/item/surgery_tray/examine(mob/living/carbon/human/user) + . = ..() + . += is_portable \ + ? span_notice("You can click and drag it to yourself to pick it up, then use it in your hand to make it a cart!") \ + : span_notice("You can click and drag it to yourself to turn it into a tray!") + . += span_notice("The top is screwed on.") + +/obj/item/surgery_tray/update_overlays() + . = ..() + // assoc list of all overlays, key = the item generating the overlay, value = the overlay string + var/list/surgery_overlays = list() + // assoc list of tool behaviors to fastest toolspeed of that type we already have + // easy way for us to check if there are any lower quality tools within + var/list/recorded_tool_speeds = list() + // handle drapes separately so they're always on the bottom + if (locate(/obj/item/surgical_drapes) in contents) + . += "drapes" + // compile all the overlays from items inside us + for(var/obj/item/surgery_tool in src) + // the overlay we will use if we want to display this one + var/actual_overlay = surgery_tool.get_surgery_tool_overlay(tray_extended = !is_portable) + if (isnull(actual_overlay)) + continue // nothing to see here + + // if we don't have tool behaviour then just record the overlay + if(!length(surgery_tool.get_all_tool_behaviours())) + surgery_overlays[surgery_tool] = actual_overlay + continue + + // if we have at least one tool behaviour, check if we already recorded a faster one + for (var/surgery_tool_type in surgery_tool.get_all_tool_behaviours()) + var/highest_speed = LAZYACCESS(recorded_tool_speeds, surgery_tool_type) || INFINITY // bigger number = slower + if(surgery_tool.toolspeed > highest_speed) + continue + // the existing tool was worse than us, ditch it + surgery_overlays -= surgery_tool_type + LAZYSET(recorded_tool_speeds, surgery_tool_type, surgery_tool.toolspeed) + surgery_overlays[surgery_tool_type] = actual_overlay + + for(var/surgery_tool in surgery_overlays) + . |= surgery_overlays[surgery_tool] + +///Sets the surgery tray's deployment state. Silent if user is null. +/obj/item/surgery_tray/proc/set_tray_mode(new_mode, mob/user) + is_portable = new_mode + density = !is_portable + if(user) + user.visible_message(span_notice("[user] [is_portable ? "retracts" : "extends"] [src]'s wheels."), span_notice("You [is_portable ? "retract" : "extend"] [src]'s wheels.")) + + if(is_portable) + interaction_flags_item |= INTERACT_ITEM_ATTACK_HAND_PICKUP + passtable_on(src, type) + RemoveElement(/datum/element/noisy_movement) + else + interaction_flags_item &= ~INTERACT_ITEM_ATTACK_HAND_PICKUP + passtable_off(src, type) + AddElement(/datum/element/noisy_movement) + + update_appearance() + +/obj/item/surgery_tray/equipped(mob/user, slot, initial) + . = ..() + if(!is_portable) + set_tray_mode(TRUE, user) + +/obj/item/surgery_tray/attack_self(mob/user, modifiers) + . = ..() + if(.) + return + var/turf/open/placement_turf = get_turf(user) + if(isgroundlessturf(placement_turf) || isclosedturf(placement_turf)) + balloon_alert(user, "can't deploy!") + return TRUE + if(!user.transferItemToLoc(src, placement_turf)) + balloon_alert(user, "tray stuck!") + return TRUE + set_tray_mode(FALSE, user) + return + +/obj/item/surgery_tray/attack_hand(mob/living/user) + if(!user.can_perform_action(src, NEED_HANDS)) + return ..() + if(!length(contents)) + balloon_alert(user, "empty!") + else + var/obj/item/grabbies = pick(contents) + atom_storage.remove_single(user, grabbies, drop_location()) + user.put_in_hands(grabbies) + return TRUE + +/obj/item/surgery_tray/screwdriver_act_secondary(mob/living/user, obj/item/tool) + . = ..() + tool.play_tool_sound(src) + to_chat(user, span_notice("You begin taking apart [src].")) + if(!tool.use_tool(src, user, 1 SECONDS)) + return + deconstruct(TRUE) + to_chat(user, span_notice("[src] has been taken apart.")) + +/obj/item/surgery_tray/dump_contents() + var/atom/drop_point = drop_location() + for(var/atom/movable/tool as anything in contents) + tool.forceMove(drop_point) + +/obj/item/surgery_tray/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + dump_contents() + new /obj/item/stack/rods(drop_location(), 2) + new /obj/item/stack/sheet/mineral/silver(drop_location()) + return ..() + +/obj/item/surgery_tray/deployed + is_portable = FALSE + +/obj/item/surgery_tray/full + +/obj/item/surgery_tray/full/deployed + is_portable = FALSE + +/obj/item/surgery_tray/full/populate_contents() + new /obj/item/blood_filter(src) + new /obj/item/bonesetter(src) + new /obj/item/cautery(src) + new /obj/item/circular_saw(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/suit/toggle/labcoat/skyrat/hospitalgown(src) // SKYRAT EDIT ADDITION + new /obj/item/hemostat(src) + new /obj/item/razor/surgery(src) + new /obj/item/retractor(src) + new /obj/item/scalpel(src) + new /obj/item/stack/medical/bone_gel(src) + new /obj/item/stack/sticky_tape/surgical(src) + new /obj/item/surgical_drapes(src) + new /obj/item/surgicaldrill(src) + update_appearance(UPDATE_OVERLAYS) + +/obj/item/surgery_tray/full/morgue + name = "autopsy tray" + desc = "A Deforest brand surgery tray, made for use in morgues. It is a folding model, \ + meaning the wheels on the bottom can be extended outwards, making it a cart." + +/obj/item/surgery_tray/full/morgue/populate_contents() + new /obj/item/blood_filter(src) + new /obj/item/bonesetter(src) + new /obj/item/cautery/cruel(src) + new /obj/item/circular_saw(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/suit/toggle/labcoat/skyrat/hospitalgown(src) // SKYRAT EDIT ADDITION + new /obj/item/hemostat/cruel(src) + new /obj/item/razor/surgery(src) + new /obj/item/retractor/cruel(src) + new /obj/item/scalpel/cruel(src) + new /obj/item/stack/medical/bone_gel(src) + new /obj/item/stack/sticky_tape/surgical(src) + new /obj/item/surgical_drapes(src) + new /obj/item/surgicaldrill(src) + +/// Surgery tray with advanced tools for debug +/obj/item/surgery_tray/full/advanced + +/obj/item/surgery_tray/full/advanced/populate_contents() + new /obj/item/scalpel/advanced(src) + new /obj/item/retractor/advanced(src) + new /obj/item/cautery/advanced(src) + new /obj/item/surgical_drapes(src) + new /obj/item/reagent_containers/medigel/sterilizine(src) + new /obj/item/bonesetter(src) + new /obj/item/blood_filter(src) + new /obj/item/stack/medical/bone_gel(src) + new /obj/item/stack/sticky_tape/surgical(src) + new /obj/item/clothing/mask/surgical(src) diff --git a/code/game/objects/items/syndie_spraycan.dm b/code/game/objects/items/syndie_spraycan.dm new file mode 100644 index 00000000000..78ffb6a4772 --- /dev/null +++ b/code/game/objects/items/syndie_spraycan.dm @@ -0,0 +1,226 @@ +// Extending the existing spraycan item was more trouble than it was worth, I don't want or need this to be able to draw arbitrary shapes. +/obj/item/traitor_spraycan + name = "seditious spraycan" + desc = "This spraycan deploys a subversive pattern containing subliminal priming agents over a 3x3 area. Contains enough primer for just one final coating." + icon = 'icons/obj/art/crayons.dmi' + icon_state = "deathcan" + worn_icon_state = "spraycan" + inhand_icon_state = "spraycan" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + var/paint_color = "#780000" + var/static/list/no_draw_turfs = typecacheof(list(/turf/open/space, /turf/open/openspace, /turf/open/lava, /turf/open/chasm)) + + /// Are we currently drawing? Used to prevent spam clicks for do_while + var/drawing_rune = FALSE + /// Set to true if we finished drawing something, this spraycan is now useless + var/expended = FALSE + +/obj/item/traitor_spraycan/afterattack(atom/target, mob/user, proximity, params) + . = ..() + if (expended) + user.balloon_alert(user, "all out of paint...") + return COMPONENT_CANCEL_ATTACK_CHAIN + + if (drawing_rune) + user.balloon_alert(user, "already busy!") + return COMPONENT_CANCEL_ATTACK_CHAIN + + . |= AFTERATTACK_PROCESSED_ITEM + + if (!proximity || !check_allowed_items(target) || !isliving(user)) + return + + if (isturf(target)) + try_draw_new_rune(user, target) + return COMPONENT_CANCEL_ATTACK_CHAIN + + if (istype(target, /obj/effect/decal/cleanable/traitor_rune)) + try_complete_rune(user, target) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/** + * Attempt to draw a rune on [target_turf]. + * Shamelessly adapted from the heretic rune drawing process. + * + * Arguments + * * user - the mob drawing the rune + * * target_turf - the place the rune's being drawn + */ +/obj/item/traitor_spraycan/proc/try_draw_new_rune(mob/living/user, turf/target_turf) + for(var/turf/nearby_turf as anything in RANGE_TURFS(1, target_turf)) + if (!isopenturf(nearby_turf) || is_type_in_typecache(nearby_turf, no_draw_turfs)) + user.balloon_alert(user, "you need a clear 3x3 area!") + return + + draw_rune(user, target_turf) + +/** + * Draw your stage one rune on the ground and store it. + * + * Arguments + * * user - the mob drawing the rune + * * target_turf - the place the rune's being drawn + */ +/obj/item/traitor_spraycan/proc/draw_rune(mob/living/user, turf/target_turf) + if (!try_draw_step("drawing outline...", user, target_turf)) + return + try_complete_rune(user, new /obj/effect/decal/cleanable/traitor_rune(target_turf)) + +/** + * Holder for repeated code to do something after a message and a set amount of time. + * + * Arguments + * * output - a string to show when you start the process + * * user - the mob drawing the rune + * * target - what they're trying to draw, or the place they are trying to draw on + */ +/obj/item/traitor_spraycan/proc/try_draw_step(start_output, mob/living/user, atom/target) + drawing_rune = TRUE + user.balloon_alert(user, "[start_output]") + if (!do_after(user, 3 SECONDS, target)) + user.balloon_alert(user, "interrupted!") + drawing_rune = FALSE + return FALSE + + playsound(src, 'sound/effects/spray.ogg', 5, TRUE, 5) + drawing_rune = FALSE + return TRUE + +#define RUNE_STAGE_OUTLINE 0 +#define RUNE_STAGE_COLOURED 1 +#define RUNE_STAGE_COMPLETE 2 +#define RUNE_STAGE_REMOVABLE 3 + +/** + * Try to upgrade a floor rune to its next stage. + * + * Arguments + * * user - the mob drawing the rune + * * target_turf - the place the rune's being drawn + */ +/obj/item/traitor_spraycan/proc/try_complete_rune(mob/living/user, obj/effect/decal/cleanable/traitor_rune/rune) + switch(rune.drawn_stage) + if (RUNE_STAGE_OUTLINE) + if (!try_draw_step("... finalising design...", user, rune)) + return + if (!rune) + user.balloon_alert(user, "graffiti was destroyed!") + return + rune.set_stage(RUNE_STAGE_COLOURED) + try_complete_rune(user, rune) + + if (RUNE_STAGE_COLOURED) + if (!try_draw_step("... applying final coating...", user, rune)) + return + if (!rune) + user.balloon_alert(user, "graffiti was destroyed!") + return + user.balloon_alert(user, "finished!") + rune.set_stage(RUNE_STAGE_COMPLETE) + expended = TRUE + desc = "A suspicious looking spraycan, it's all out of paint." + SEND_SIGNAL(src, COMSIG_TRAITOR_GRAFFITI_DRAWN, rune) + + if (RUNE_STAGE_COMPLETE, RUNE_STAGE_REMOVABLE) + user.balloon_alert(user, "all done!") + +/// Copying the functionality from normal spraycans, but doesn't need all the optional checks +/obj/item/traitor_spraycan/suicide_act(mob/living/user) + if(expended) + user.visible_message(span_suicide("[user] shakes up [src] with a rattle and lifts it to [user.p_their()] mouth, but nothing happens!")) + user.say("MEDIOCRE!!", forced="spraycan suicide") + return SHAME + + var/mob/living/carbon/human/suicider = user + user.visible_message(span_suicide("[user] shakes up [src] with a rattle and lifts it to [user.p_their()] mouth, spraying paint across [user.p_their()] teeth!")) + user.say("WITNESS ME!!", forced="spraycan suicide") + playsound(src, 'sound/effects/spray.ogg', 5, TRUE, 5) + suicider.update_lips("spray_face", paint_color) + return OXYLOSS + +/obj/effect/decal/cleanable/traitor_rune + name = "syndicate graffiti" + desc = "It looks like it's going to be... the Syndicate logo?" + icon = 'icons/effects/96x96.dmi' + icon_state = "traitor_rune_outline" + pixel_x = -32 + pixel_y = -32 + gender = NEUTER + mergeable_decal = FALSE + resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF + clean_type = CLEAN_TYPE_HARD_DECAL + layer = SIGIL_LAYER + var/slip_time = 6 SECONDS + var/slip_flags = NO_SLIP_WHEN_WALKING + + /// The stage of drawing we have reached + var/drawn_stage = RUNE_STAGE_OUTLINE + /// Proximity sensor to make people sad if they're nearby + var/datum/proximity_monitor/advanced/demoraliser/demoraliser + /// Whether we protect the rune from being cleaned up + var/clean_proof = FALSE + /// Timer until the rune can be cleaned up off the floor + var/protected_timer + +/obj/effect/decal/cleanable/traitor_rune/traitor/Destroy() + deltimer(protected_timer) + QDEL_NULL(demoraliser) + return ..() + +/obj/effect/decal/cleanable/traitor_rune/HasProximity(atom/movable/proximity_check_mob) + if (isliving(proximity_check_mob) && get_dist(proximity_check_mob, src) <= 1) + slip(proximity_check_mob) + return ..() + +/** + * Makes someone fall over. If it's not the traitor, this counts as demoralising the crew. + * + * Arguments + * * victim - whoever just slipped, point and laugh at them + */ +/obj/effect/decal/cleanable/traitor_rune/proc/slip(mob/living/victim) + if(victim.movement_type & FLYING) + return + if (!victim.slip(slip_time, src, slip_flags)) + return + SEND_SIGNAL(src, COMSIG_TRAITOR_GRAFFITI_SLIPPED, victim.mind) + +/** + * Sets the "drawing stage" of the rune. + * This affects the appearance, behaviour, and description of the effect. + * + * Arguments + * * stage - new stage to apply + */ +/obj/effect/decal/cleanable/traitor_rune/proc/set_stage(stage) + drawn_stage = stage + switch(drawn_stage) + if (RUNE_STAGE_OUTLINE) + icon_state = "traitor_rune_outline" + desc = "It looks like it's going to be... the Syndicate logo?" + + if (RUNE_STAGE_COLOURED, RUNE_STAGE_REMOVABLE) + icon_state = "traitor_rune_done" + desc = "A large depiction of the Syndicate logo." + clean_proof = FALSE + + if (RUNE_STAGE_COMPLETE) + icon_state = "traitor_rune_sheen" + desc = "A large depiction of the Syndicate logo. It looks slippery." + var/datum/demoralise_moods/graffiti/mood_category = new() + demoraliser = new(src, 7, TRUE, mood_category) + clean_proof = TRUE + protected_timer = addtimer(CALLBACK(src, PROC_REF(set_stage), RUNE_STAGE_REMOVABLE), 5 MINUTES) + +/obj/effect/decal/cleanable/traitor_rune/wash(clean_types) + if (clean_proof) + return FALSE + + return ..() + +#undef RUNE_STAGE_COLOURED +#undef RUNE_STAGE_COMPLETE +#undef RUNE_STAGE_OUTLINE +#undef RUNE_STAGE_REMOVABLE diff --git a/code/game/turfs/closed/indestructible.dm b/code/game/turfs/closed/indestructible.dm new file mode 100644 index 00000000000..b364ad428d0 --- /dev/null +++ b/code/game/turfs/closed/indestructible.dm @@ -0,0 +1,363 @@ +/turf/closed/indestructible + name = "wall" + desc = "Effectively impervious to conventional methods of destruction." + icon = 'icons/turf/walls.dmi' + explosive_resistance = 50 + +/turf/closed/indestructible/rust_heretic_act() + return + +/turf/closed/indestructible/TerraformTurf(path, new_baseturf, flags, defer_change = FALSE, ignore_air = FALSE) + return + +/turf/closed/indestructible/acid_act(acidpwr, acid_volume, acid_id) + return FALSE + +/turf/closed/indestructible/Melt() + to_be_destroyed = FALSE + return src + +/turf/closed/indestructible/singularity_act() + return + +/turf/closed/indestructible/attackby(obj/item/attacking_item, mob/user, params) + if(istype(attacking_item, /obj/item/poster) && Adjacent(user)) + return place_poster(attacking_item, user) + + return ..() + +/turf/closed/indestructible/oldshuttle + name = "strange shuttle wall" + icon = 'icons/turf/shuttleold.dmi' + icon_state = "block" + +/turf/closed/indestructible/weeb + name = "paper wall" + desc = "Reinforced paper walling. Someone really doesn't want you to leave." + icon = 'icons/obj/smooth_structures/paperframes.dmi' + icon_state = "paperframes-0" + base_icon_state = "paperframes" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_PAPERFRAME + canSmoothWith = SMOOTH_GROUP_PAPERFRAME + var/static/mutable_appearance/indestructible_paper = mutable_appearance('icons/obj/smooth_structures/paperframes.dmi',icon_state = "paper", layer = CLOSED_TURF_LAYER - 0.1) + +/turf/closed/indestructible/weeb/Initialize(mapload) + . = ..() + update_appearance() + +/turf/closed/indestructible/weeb/update_overlays() + . = ..() + . += indestructible_paper + +/turf/closed/indestructible/sandstone + name = "sandstone wall" + desc = "A wall with sandstone plating. Rough." + icon = 'icons/turf/walls/sandstone_wall.dmi' + icon_state = "sandstone_wall-0" + base_icon_state = "sandstone_wall" + baseturfs = /turf/closed/indestructible/sandstone + smoothing_flags = SMOOTH_BITMASK + +/turf/closed/indestructible/oldshuttle/corner + icon_state = "corner" + +/turf/closed/indestructible/splashscreen + name = "Space Station 13" + desc = null + icon = 'icons/blanks/blank_title.png' + icon_state = "" + pixel_x = 0 // SKYRAT EDIT - Re-centering the title screen - ORIGINAL: pixel_x = -64 + plane = SPLASHSCREEN_PLANE + bullet_bounce_sound = null + +INITIALIZE_IMMEDIATE(/turf/closed/indestructible/splashscreen) +/* SKYRAT EDIT REMOVAL +/turf/closed/indestructible/splashscreen/Initialize(mapload) + . = ..() + SStitle.splash_turf = src + if(SStitle.icon) + icon = SStitle.icon + handle_generic_titlescreen_sizes() + +///helper proc that will center the screen if the icon is changed to a generic width, to make admins have to fudge around with pixel_x less. returns null +/turf/closed/indestructible/splashscreen/proc/handle_generic_titlescreen_sizes() + var/icon/size_check = icon(SStitle.icon, icon_state) + var/width = size_check.Width() + if(width == 480) // 480x480 is nonwidescreen + pixel_x = 0 + else if(width == 608) // 608x480 is widescreen + pixel_x = -64 + // SKYRAT EDIT START - Wider widescreen + else if(width == 672) // Skyrat's widescreen is slightly wider than /tg/'s, so we need to accomodate that too. + pixel_x = -96 + // SKYRAT EDIT END + +/turf/closed/indestructible/splashscreen/vv_edit_var(var_name, var_value) + . = ..() + if(.) + switch(var_name) + if(NAMEOF(src, icon)) + SStitle.icon = icon + handle_generic_titlescreen_sizes() + +/turf/closed/indestructible/splashscreen/examine() + desc = pick(strings(SPLASH_FILE, "splashes")) + return ..() +SKYRAT EDIT REMOVAL END */ + +/turf/closed/indestructible/start_area + name = null + desc = null + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/turf/closed/indestructible/reinforced + name = "reinforced wall" + desc = "A huge chunk of reinforced metal used to separate rooms. Effectively impervious to conventional methods of destruction." + icon = 'icons/turf/walls/reinforced_wall.dmi' + icon_state = "reinforced_wall-0" + base_icon_state = "reinforced_wall" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_WALLS + + +/turf/closed/indestructible/riveted + icon = 'icons/turf/walls/riveted.dmi' + icon_state = "riveted-0" + base_icon_state = "riveted" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_CLOSED_TURFS + +/turf/closed/indestructible/syndicate + icon = 'icons/turf/walls/plastitanium_wall.dmi' + icon_state = "plastitanium_wall-0" + base_icon_state = "plastitanium_wall" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_SYNDICATE_WALLS + canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_PLASTITANIUM_WALLS + SMOOTH_GROUP_SYNDICATE_WALLS + +/turf/closed/indestructible/riveted/uranium + icon = 'icons/turf/walls/uranium_wall.dmi' + icon_state = "uranium_wall-0" + base_icon_state = "uranium_wall" + smoothing_flags = SMOOTH_BITMASK + +/turf/closed/indestructible/riveted/plastinum + name = "plastinum wall" + desc = "A luxurious wall made out of a plasma-platinum alloy. Effectively impervious to conventional methods of destruction." + icon = 'icons/turf/walls/plastinum_wall.dmi' + icon_state = "plastinum_wall-0" + base_icon_state = "plastinum_wall" + smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS + smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_PLASTINUM_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_PLASTINUM_WALLS + +/turf/closed/indestructible/riveted/plastinum/nodiagonal + icon_state = "map-shuttle_nd" + smoothing_flags = SMOOTH_BITMASK + +/turf/closed/indestructible/wood + icon = 'icons/turf/walls/wood_wall.dmi' + icon_state = "wood_wall-0" + base_icon_state = "wood_wall" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_WOOD_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_WOOD_WALLS + + +/turf/closed/indestructible/alien + name = "alien wall" + desc = "A wall with alien alloy plating." + icon = 'icons/turf/walls/abductor_wall.dmi' + icon_state = "abductor_wall-0" + base_icon_state = "abductor_wall" + smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS + smoothing_groups = SMOOTH_GROUP_ABDUCTOR_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_ABDUCTOR_WALLS + + +/turf/closed/indestructible/cult + name = "runed metal wall" + desc = "A cold metal wall engraved with indecipherable symbols. Studying them causes your head to pound. Effectively impervious to conventional methods of destruction." + icon = 'icons/turf/walls/cult_wall.dmi' + icon_state = "cult_wall-0" + base_icon_state = "cult_wall" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_WALLS + + +/turf/closed/indestructible/abductor + icon_state = "alien1" + +/turf/closed/indestructible/opshuttle + icon_state = "wall3" + + +/turf/closed/indestructible/fakeglass + name = "window" + icon = 'icons/obj/smooth_structures/reinforced_window.dmi' + icon_state = "fake_window" + base_icon_state = "reinforced_window" + opacity = FALSE + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_WINDOW_FULLTILE + canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE + +/turf/closed/indestructible/fakeglass/Initialize(mapload) + . = ..() + underlays += mutable_appearance('icons/obj/structures.dmi', "grille", layer - 0.01) //add a grille underlay + underlays += mutable_appearance('icons/turf/floors.dmi', "plating", layer - 0.02) //add the plating underlay, below the grille + +/turf/closed/indestructible/opsglass + name = "window" + icon = 'icons/obj/smooth_structures/plastitanium_window.dmi' + icon_state = "plastitanium_window-0" + base_icon_state = "plastitanium_window" + opacity = FALSE + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM + canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM + +/turf/closed/indestructible/opsglass/Initialize(mapload) + . = ..() + icon_state = null + underlays += mutable_appearance('icons/obj/structures.dmi', "grille", layer - 0.01) + underlays += mutable_appearance('icons/turf/floors.dmi', "plating", layer - 0.02) + +/turf/closed/indestructible/fakedoor + name = "airlock" + icon = 'icons/obj/doors/airlocks/centcom/centcom.dmi' + icon_state = "fake_door" + +/turf/closed/indestructible/fakedoor/maintenance + icon = 'icons/obj/doors/airlocks/hatch/maintenance.dmi' + +/turf/closed/indestructible/fakedoor/glass_airlock + icon = 'icons/obj/doors/airlocks/external/external.dmi' + opacity = FALSE + +/turf/closed/indestructible/fakedoor/engineering + icon = 'icons/obj/doors/airlocks/station/engineering.dmi' + +/turf/closed/indestructible/rock + name = "dense rock" + desc = "An extremely densely-packed rock, most mining tools or explosives would never get through this." + icon = 'icons/turf/mining.dmi' + icon_state = "rock" + +/turf/closed/indestructible/rock/snow + name = "mountainside" + desc = "An extremely densely-packed rock, sheeted over with centuries worth of ice and snow." + icon = 'icons/turf/walls.dmi' + icon_state = "snowrock" + bullet_sizzle = TRUE + bullet_bounce_sound = null + +/turf/closed/indestructible/rock/snow/ice + name = "iced rock" + desc = "Extremely densely-packed sheets of ice and rock, forged over the years of the harsh cold." + icon = 'icons/turf/walls.dmi' + icon_state = "icerock" + +/turf/closed/indestructible/rock/snow/ice/ore + icon = 'icons/turf/walls/icerock_wall.dmi' + icon_state = "icerock_wall-0" + base_icon_state = "icerock_wall" + smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER + canSmoothWith = SMOOTH_GROUP_CLOSED_TURFS + pixel_x = -4 + pixel_y = -4 + +/turf/closed/indestructible/paper + name = "thick paper wall" + desc = "A wall layered with impenetrable sheets of paper." + icon = 'icons/turf/walls.dmi' + icon_state = "paperwall" + +/turf/closed/indestructible/necropolis + name = "necropolis wall" + desc = "A seemingly impenetrable wall." + icon = 'icons/turf/walls.dmi' + icon_state = "necro" + explosive_resistance = 50 + baseturfs = /turf/closed/indestructible/necropolis + +/turf/closed/indestructible/necropolis/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) + underlay_appearance.icon = 'icons/turf/floors.dmi' + underlay_appearance.icon_state = "necro1" + return TRUE + +/turf/closed/indestructible/iron + name = "impervious iron wall" + desc = "A wall with tough iron plating." + icon = 'icons/turf/walls/iron_wall.dmi' + icon_state = "iron_wall-0" + base_icon_state = "iron_wall" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_IRON_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_IRON_WALLS + opacity = FALSE + +/turf/closed/indestructible/riveted/boss + name = "necropolis wall" + desc = "A thick, seemingly indestructible stone wall." + icon = 'icons/turf/walls/boss_wall.dmi' + icon_state = "boss_wall-0" + base_icon_state = "boss_wall" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_BOSS_WALLS + canSmoothWith = SMOOTH_GROUP_BOSS_WALLS + explosive_resistance = 50 + baseturfs = /turf/closed/indestructible/riveted/boss + +/turf/closed/indestructible/riveted/boss/see_through + opacity = FALSE + +/turf/closed/indestructible/riveted/boss/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) + underlay_appearance.icon = 'icons/turf/floors.dmi' + underlay_appearance.icon_state = "basalt" + return TRUE + +/turf/closed/indestructible/riveted/hierophant + name = "wall" + desc = "A wall made out of a strange metal. The squares on it pulse in a predictable pattern." + icon = 'icons/turf/walls/hierophant_wall.dmi' + icon_state = "wall" + smoothing_flags = SMOOTH_CORNERS + smoothing_groups = SMOOTH_GROUP_HIERO_WALL + canSmoothWith = SMOOTH_GROUP_HIERO_WALL + +/turf/closed/indestructible/resin + name = "resin wall" + icon = 'icons/obj/smooth_structures/alien/resin_wall.dmi' + icon_state = "resin_wall-0" + base_icon_state = "resin_wall" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_ALIEN_WALLS + SMOOTH_GROUP_ALIEN_RESIN + canSmoothWith = SMOOTH_GROUP_ALIEN_WALLS + +/turf/closed/indestructible/resin/membrane + name = "resin membrane" + icon = 'icons/obj/smooth_structures/alien/resin_membrane.dmi' + icon_state = "resin_membrane-0" + base_icon_state = "resin_membrane" + opacity = FALSE + smoothing_groups = SMOOTH_GROUP_ALIEN_WALLS + SMOOTH_GROUP_ALIEN_RESIN + canSmoothWith = SMOOTH_GROUP_ALIEN_WALLS + +/turf/closed/indestructible/resin/membrane/Initialize(mapload) + . = ..() + underlays += mutable_appearance('icons/turf/floors.dmi', "engine") // add the reinforced floor underneath + +/turf/closed/indestructible/grille + name = "grille" + icon = 'icons/obj/structures.dmi' + icon_state = "grille" + base_icon_state = "grille" + +/turf/closed/indestructible/grille/Initialize(mapload) + . = ..() + underlays += mutable_appearance('icons/turf/floors.dmi', "plating") diff --git a/code/game/turfs/open/space/space_EXPENSIVE.dm b/code/game/turfs/open/space/space_EXPENSIVE.dm new file mode 100644 index 00000000000..44a15ac66d9 --- /dev/null +++ b/code/game/turfs/open/space/space_EXPENSIVE.dm @@ -0,0 +1,47 @@ +/** + * Space Initialize + * + * Doesn't call parent, see [/atom/proc/Initialize]. + * When adding new stuff to /atom/Initialize, /turf/Initialize, etc + * don't just add it here unless space actually needs it. + * + * There is a lot of work that is intentionally not done because it is not currently used. + * This includes stuff like smoothing, blocking camera visibility, etc. + * If you are facing some odd bug with specifically space, check if it's something that was + * intentionally ommitted from this implementation. + */ +/turf/open/space/Initialize(mapload) + SHOULD_CALL_PARENT(FALSE) + air = space_gas + + if (PERFORM_ALL_TESTS(focus_only/multiple_space_initialization)) + if(flags_1 & INITIALIZED_1) + stack_trace("Warning: [src]([type]) initialized multiple times!") + flags_1 |= INITIALIZED_1 + + light_color = GLOB.starlight_color + + // We make the assumption that the space plane will never be blacklisted, as an optimization + if(SSmapping.max_plane_offset) + plane = PLANE_SPACE - (PLANE_RANGE * SSmapping.z_level_to_plane_offset[z]) + + var/area/our_area = loc + if(!our_area.area_has_base_lighting && space_lit) //Only provide your own lighting if the area doesn't for you + // Intentionally not add_overlay for performance reasons. + // add_overlay does a bunch of generic stuff, like creating a new list for overlays, + // queueing compile, cloning appearance, etc etc etc that is not necessary here. + overlays += GLOB.fullbright_overlays[GET_TURF_PLANE_OFFSET(src) + 1] + + if (!mapload) + if(requires_activation) + SSair.add_to_active(src, TRUE) + + if(SSmapping.max_plane_offset) + var/turf/T = GET_TURF_ABOVE(src) + if(T) + T.multiz_turf_new(src, DOWN) + T = GET_TURF_BELOW(src) + if(T) + T.multiz_turf_new(src, UP) + + return INITIALIZE_HINT_NORMAL diff --git a/code/modules/actionspeed/modifiers/wound.dm b/code/modules/actionspeed/modifiers/wound.dm new file mode 100644 index 00000000000..845399e0761 --- /dev/null +++ b/code/modules/actionspeed/modifiers/wound.dm @@ -0,0 +1,10 @@ +/datum/actionspeed_modifier/wound_interaction_inefficiency + variable = TRUE + + var/datum/wound/parent + +/datum/actionspeed_modifier/wound_interaction_inefficiency/New(new_id, datum/wound/parent) + + src.parent = parent + + return ..() diff --git a/code/modules/admin/smites/become_object.dm b/code/modules/admin/smites/become_object.dm new file mode 100644 index 00000000000..5f1af4bee28 --- /dev/null +++ b/code/modules/admin/smites/become_object.dm @@ -0,0 +1,42 @@ +#define OBJECTIFY_TIME (5 SECONDS) + +/// Turns the target into an object (for instance bread) +/datum/smite/objectify + name = "Become Object" + /// What are we going to turn them into? + var/atom/transform_path = /obj/item/food/bread/plain + +/datum/smite/objectify/configure(client/user) + var/attempted_target_path = input( + user, + "Enter typepath of an atom you'd like to turn your victim into.", + "Typepath", + "[/obj/item/food/bread/plain]", + ) as null|text + + if (isnull(attempted_target_path)) + return FALSE //The user pressed "Cancel" + + var/desired_object = text2path(attempted_target_path) + if(!ispath(desired_object)) + desired_object = pick_closest_path(attempted_target_path, get_fancy_list_of_atom_types()) + if(isnull(desired_object) || !ispath(desired_object)) + return FALSE //The user pressed "Cancel" + if(!ispath(desired_object, /atom)) + tgui_alert(user, "ERROR: Incorrect / improper path given.") + return FALSE + transform_path = desired_object + +/datum/smite/objectify/effect(client/user, mob/living/target) + if (!isliving(target)) + return // This doesn't work on ghosts + . = ..() + var/mutable_appearance/objectified_player = mutable_appearance(initial(transform_path.icon), initial(transform_path.icon_state)) + objectified_player.pixel_x = initial(transform_path.pixel_x) + objectified_player.pixel_y = initial(transform_path.pixel_y) + var/mutable_appearance/transform_scanline = mutable_appearance('icons/effects/effects.dmi', "transform_effect") + target.transformation_animation(objectified_player, OBJECTIFY_TIME, transform_scanline.appearance) + target.Immobilize(OBJECTIFY_TIME, ignore_canstun = TRUE) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(objectify), target, transform_path), OBJECTIFY_TIME) + +#undef OBJECTIFY_TIME diff --git a/code/modules/admin/verbs/grant_dna_infusion.dm b/code/modules/admin/verbs/grant_dna_infusion.dm new file mode 100644 index 00000000000..06cfa8110d6 --- /dev/null +++ b/code/modules/admin/verbs/grant_dna_infusion.dm @@ -0,0 +1,36 @@ +/* + * Attempts to grant the target all organs from a given DNA infuser entry.area + * Returns the entry if all organs were successfully replaced. + * If no infusion was picked, the infusion had no organs, or if one or more organs could not be granted, returns FALSE +*/ +/client/proc/grant_dna_infusion(mob/living/carbon/human/target in world) + set name = "Apply DNA Infusion" + set category = "Debug" + + var/list/infusions = list() + for(var/datum/infuser_entry/path as anything in subtypesof(/datum/infuser_entry)) + var/str = "[initial(path.name)] ([path])" + infusions[str] = path + + var/datum/infuser_entry/picked_infusion = tgui_input_list(usr, "Select infusion", "Apply DNA Infusion", infusions) + + if(isnull(picked_infusion)) + return FALSE + + // This is necessary because list propererties are not defined until initialization + picked_infusion = infusions[picked_infusion] + picked_infusion = new picked_infusion + + if(!length(picked_infusion.output_organs)) + return FALSE + + . = picked_infusion + for(var/obj/item/organ/infusion_organ as anything in picked_infusion.output_organs) + var/obj/item/organ/new_organ = new infusion_organ() + if(!new_organ.replace_into(target)) + to_chat(usr, span_notice("[target] is unable to carry [new_organ]!")) + qdel(new_organ) + . = FALSE + continue + log_admin("[key_name(usr)] has added organ [new_organ.type] to [key_name(target)]") + message_admins("[key_name_admin(usr)] has added organ [new_organ.type] to [ADMIN_LOOKUPFLW(target)]") diff --git a/code/modules/admin/view_variables/nobody_wants_to_learn_matrix_math.dm b/code/modules/admin/view_variables/nobody_wants_to_learn_matrix_math.dm new file mode 100644 index 00000000000..872bd27d627 --- /dev/null +++ b/code/modules/admin/view_variables/nobody_wants_to_learn_matrix_math.dm @@ -0,0 +1,80 @@ + +/** + * ## nobody wants to learn matrix math! + * + * More than just a completely true statement, this datum is created as a tgui interface + * allowing you to modify each vector until you know what you're doing. + * Much like filteriffic, 'nobody wants to learn matrix math' is meant for developers like you and I + * to implement interesting matrix transformations without the hassle if needing to know... algebra? Damn, i'm stupid. + */ +/datum/nobody_wants_to_learn_matrix_math + var/atom/target + var/matrix/testing_matrix + +/datum/nobody_wants_to_learn_matrix_math/New(atom/target) + src.target = target + testing_matrix = matrix(target.transform) + +/datum/nobody_wants_to_learn_matrix_math/Destroy(force, ...) + QDEL_NULL(testing_matrix) + return ..() + +/datum/nobody_wants_to_learn_matrix_math/ui_state(mob/user) + return GLOB.admin_state + +/datum/nobody_wants_to_learn_matrix_math/ui_close(mob/user) + qdel(src) + +/datum/nobody_wants_to_learn_matrix_math/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "MatrixMathTester") + ui.open() + +/datum/nobody_wants_to_learn_matrix_math/ui_data() + var/list/data = list() + data["matrix_a"] = testing_matrix.a + data["matrix_b"] = testing_matrix.b + data["matrix_c"] = testing_matrix.c + data["matrix_d"] = testing_matrix.d + data["matrix_e"] = testing_matrix.e + data["matrix_f"] = testing_matrix.f + data["pixelated"] = target.appearance_flags & PIXEL_SCALE + return data + +/datum/nobody_wants_to_learn_matrix_math/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("change_var") + var/matrix_var_name = params["var_name"] + var/matrix_var_value = params["var_value"] + if(testing_matrix.vv_edit_var(matrix_var_name, matrix_var_value) == FALSE) + to_chat(src, "Your edit was rejected by the object. This is a bug with the matrix tester, not your fault, so report it on github.", confidential = TRUE) + return + set_transform() + if("scale") + testing_matrix.Scale(params["x"], params["y"]) + set_transform() + if("translate") + testing_matrix.Translate(params["x"], params["y"]) + set_transform() + if("shear") + testing_matrix.Shear(params["x"], params["y"]) + set_transform() + if("turn") + testing_matrix.Turn(params["angle"]) + set_transform() + if("toggle_pixel") + target.appearance_flags ^= PIXEL_SCALE + +/datum/nobody_wants_to_learn_matrix_math/proc/set_transform() + animate(target, transform = testing_matrix, time = 0.5 SECONDS) + testing_matrix = matrix(target.transform) + +/client/proc/open_matrix_tester(atom/in_atom) + if(holder) + var/datum/nobody_wants_to_learn_matrix_math/matrix_tester = new(in_atom) + matrix_tester.ui_interact(mob) diff --git a/code/modules/antagonists/changeling/powers/mmi_talk.dm b/code/modules/antagonists/changeling/powers/mmi_talk.dm new file mode 100644 index 00000000000..f68968c223e --- /dev/null +++ b/code/modules/antagonists/changeling/powers/mmi_talk.dm @@ -0,0 +1,140 @@ +/datum/action/changeling/mmi_talk + name = "MMI Talk" + desc = "Our decoy brain has been implanted into a Man-Machine Interface. \ + In order to maintain our secrecy, we can speak through the decoy as if a normal brain. \ + The decoy brain will relay speech it hears to you in purple." + button_icon = 'icons/obj/assemblies/assemblies.dmi' + button_icon_state = "mmi_off" + dna_cost = CHANGELING_POWER_UNOBTAINABLE + ignores_fakedeath = TRUE // Can be used while fake dead + req_stat = DEAD // Can be used while real dead too + + /** + * Reference to the brain we're talking through. + * + * Set when created via the ling decoy component. + * If the brain ends up being qdelled, this action will also be qdelled, and thus this ref is cleared. + */ + VAR_FINAL/obj/item/organ/internal/brain/brain_ref + + /// A map view of the area around the MMI. + VAR_FINAL/atom/movable/screen/map_view/mmi_view + /// The background for the MMI map view. + VAR_FINAL/atom/movable/screen/background/mmi_view_background + /// The key that the map view uses. + VAR_FINAL/mmi_view_key + /// A movement detector that updates the map view when the MMI moves around. + VAR_FINAL/datum/movement_detector/update_view_tracker + +/datum/action/changeling/mmi_talk/Destroy() + brain_ref = null + QDEL_NULL(mmi_view) + QDEL_NULL(mmi_view_background) + QDEL_NULL(update_view_tracker) + return ..() + +/datum/action/changeling/mmi_talk/Remove(mob/remove_from) + . = ..() + SStgui.close_uis(src) + +/datum/action/changeling/mmi_talk/can_sting(mob/living/user, mob/living/target) + . = ..() + if(!.) + return FALSE + // This generally shouldn't happen, but just in case + if(isnull(brain_ref)) + stack_trace("[type] can_sting was called with a null brain!") + return FALSE + if(!istype(brain_ref.loc, /obj/item/mmi)) + stack_trace("[type] can_sting was called with a brain not located in an MMI!") + return FALSE + return TRUE + +/datum/action/changeling/mmi_talk/sting_action(mob/living/user, mob/living/target) + ..() + ui_interact(user) + return TRUE + +/datum/action/changeling/mmi_talk/ui_state(mob/user) + return GLOB.always_state + +/datum/action/changeling/mmi_talk/ui_status(mob/user, datum/ui_state/state) + if(user != owner) + return UI_CLOSE + return ..() + +/datum/action/changeling/mmi_talk/ui_static_data(mob/user) + var/list/data = list() + data["mmi_view"] = mmi_view_key + return data + +/datum/action/changeling/mmi_talk/ui_interact(mob/user, datum/tgui/ui) + if(isnull(mmi_view_key)) + // it's worth noting a ling could have multiple of these actions. + mmi_view_key = "ling_mmi_[REF(src)]_view" + // Generate background + mmi_view_background = new() + mmi_view_background.assigned_map = mmi_view_key + mmi_view_background.del_on_map_removal = FALSE + mmi_view_background.fill_rect(1, 1, 5, 5) + // Generate map view + mmi_view = new() + mmi_view.generate_view(mmi_view_key) + // Generate movement detector (to update the view on MMI movement) + update_view_tracker = new(brain_ref, CALLBACK(src, PROC_REF(update_mmi_view))) + + // Shows the view to the user foremost + mmi_view.display_to(user) + user.client.register_map_obj(mmi_view_background) + update_mmi_view() + // Makes the MMI relay heard messages + if(!HAS_TRAIT_FROM(brain_ref.loc, TRAIT_HEARING_SENSITIVE, REF(src))) + var/obj/item/mmi/mmi = brain_ref.loc + mmi.become_hearing_sensitive(REF(src)) + RegisterSignal(mmi, COMSIG_MOVABLE_HEAR, PROC_REF(relay_hearing)) + // Actually open the UI + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "LingMMITalk") + ui.open() + +/datum/action/changeling/mmi_talk/ui_close(mob/user) + var/obj/item/mmi/mmi = brain_ref.loc + UnregisterSignal(mmi, COMSIG_MOVABLE_HEAR) + mmi.lose_hearing_sensitivity(REF(src)) + +/datum/action/changeling/mmi_talk/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return TRUE + + if(action != "send_mmi_message") + return FALSE + + var/obj/item/mmi/mmi = brain_ref.loc + if(mmi.brainmob.stat != CONSCIOUS) + to_chat(usr, span_warning("Our decoy brain is too damaged to speak.")) + else + // Say will perform input sanitization and such for us + mmi.brainmob.say(params["message"], sanitize = TRUE) + return TRUE + +/// Used in callbacks to update the map view when the MMI moves. +/datum/action/changeling/mmi_talk/proc/update_mmi_view() + mmi_view.vis_contents.Cut() + for(var/turf/visible_turf in view(2, get_turf(brain_ref))) + mmi_view.vis_contents += visible_turf + +/// Signal proc for [COMSIG_MOVABLE_HEAR] to relay stuff the MMI hears to the ling. +/// Not super good, but it works. +/datum/action/changeling/mmi_talk/proc/relay_hearing(obj/item/mmi/source, list/hear_args) + SIGNAL_HANDLER + + // We can likely already hear them, so do not bother + if(can_see(owner, hear_args[HEARING_SPEAKER], 7)) + return + + var/list/new_args = hear_args.Copy() + new_args[HEARING_SPANS] |= "purple" + new_args[HEARING_RANGE] = INFINITY // so we can hear it from any distance away + owner.Hear(arglist(new_args)) diff --git a/code/modules/antagonists/heretic/items/keyring.dm b/code/modules/antagonists/heretic/items/keyring.dm new file mode 100644 index 00000000000..0498ba9e8a2 --- /dev/null +++ b/code/modules/antagonists/heretic/items/keyring.dm @@ -0,0 +1,186 @@ +/obj/effect/knock_portal + name = "crack in reality" + desc = "A crack in space, impossibly deep and painful to the eyes. Definitely not safe." + icon = 'icons/effects/eldritch.dmi' + icon_state = "realitycrack" + light_system = STATIC_LIGHT + light_power = 1 + light_on = TRUE + light_color = COLOR_GREEN + light_range = 3 + opacity = TRUE + density = FALSE //so we dont block doors closing + layer = OBJ_LAYER //under doors + ///The knock portal we teleport to + var/obj/effect/knock_portal/destination + ///The airlock we are linked to, we delete if it is destroyed + var/obj/machinery/door/our_airlock + +/obj/effect/knock_portal/Initialize(mapload, target) + . = ..() + if(target) + our_airlock = target + RegisterSignal(target, COMSIG_QDELETING, PROC_REF(delete_on_door_delete)) + + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + ) + AddElement(/datum/element/connect_loc, loc_connections) + +///Deletes us and our destination portal if our_airlock is destroyed +/obj/effect/knock_portal/proc/delete_on_door_delete(datum/source) + SIGNAL_HANDLER + qdel(src) + +///Signal handler for when our location is entered, calls teleport on the victim, if their old_loc didnt contain a portal already (to prevent loops) +/obj/effect/knock_portal/proc/on_entered(datum/source, mob/living/loser, atom/old_loc) + SIGNAL_HANDLER + if(istype(loser) && !(locate(type) in old_loc)) + teleport(loser) + +/obj/effect/knock_portal/Destroy() + QDEL_NULL(destination) + our_airlock = null + return ..() + +///Teleports the teleportee, to a random airlock if the teleportee isnt a heretic, or the other portal if they are one +/obj/effect/knock_portal/proc/teleport(mob/living/teleportee) + if(isnull(destination)) //dumbass + qdel(src) + return + + //get it? + var/obj/machinery/door/doorstination = IS_HERETIC_OR_MONSTER(teleportee) ? destination.our_airlock : find_random_airlock() + if(!do_teleport(teleportee, get_turf(doorstination), channel = TELEPORT_CHANNEL_MAGIC)) + return + + if(!IS_HERETIC_OR_MONSTER(teleportee)) + teleportee.apply_damage(20, BRUTE) //so they dont roll it like a jackpot machine to see if they can land in the armory + to_chat(teleportee, span_userdanger("You stumble through [src], battered by forces beyond your comprehension, landing anywhere but where you thought you were going.")) + + INVOKE_ASYNC(src, PROC_REF(async_opendoor), doorstination) + +///Returns a random airlock on the same Z level as our portal, that isnt our airlock +/obj/effect/knock_portal/proc/find_random_airlock() + var/list/turf/possible_destinations = list() + for(var/obj/airlock as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/airlock)) + if(airlock.z != z) + continue + if(airlock.loc == loc) + continue + possible_destinations += airlock + return pick(possible_destinations) + +///Asynchronous proc to unbolt, then open the passed door +/obj/effect/knock_portal/proc/async_opendoor(obj/machinery/door/door) + if(istype(door, /obj/machinery/door/airlock)) //they can create portals on ANY door, but we should unlock airlocks so they can actually open + var/obj/machinery/door/airlock/as_airlock = door + as_airlock.unbolt() + door.open() + +///An ID card capable of shapeshifting to other IDs given by the Key Keepers Burden knowledge +/obj/item/card/id/advanced/heretic + ///List of IDs this card consumed + var/list/obj/item/card/id/fused_ids = list() + ///The first portal in the portal pair, so we can clear it later + var/obj/effect/knock_portal/portal_one + ///The second portal in the portal pair, so we can clear it later + var/obj/effect/knock_portal/portal_two + ///The first door we are linking in the pair, so we can create a portal pair + var/datum/weakref/link + +/obj/item/card/id/advanced/heretic/examine(mob/user) + . = ..() + if(!IS_HERETIC_OR_MONSTER(user)) + return + . += span_hypnophrase("Enchanted by the Mansus!") + . += span_hypnophrase("Using an ID on this will consume it and allow you to copy its accesses.") + . += span_hypnophrase("Using this in-hand allows you to change its appearance.") + . += span_hypnophrase("Using this on a pair of doors, allows you to link them together. Entering one door will transport you to the other, while heathens are instead teleported to a random airlock.") + +/obj/item/card/id/advanced/heretic/attack_self(mob/user) + . = ..() + if(!IS_HERETIC(user)) + return + var/cardname = tgui_input_list(user, "Shapeshift into?", "Shapeshift", fused_ids) + if(!cardname) + balloon_alert(user, "no options!") + return ..() + var/obj/item/card/id/card = fused_ids[cardname] + shapeshift(card) + +///Changes our appearance to the passed ID card +/obj/item/card/id/advanced/heretic/proc/shapeshift(obj/item/card/id/advanced/card) + trim = card.trim + assignment = card.assignment + registered_age = card.registered_age + registered_name = card.registered_name + icon_state = card.icon_state + inhand_icon_state = card.inhand_icon_state + assigned_icon_state = card.assigned_icon_state + name = card.name //not update_label because of the captains spare moment + update_icon() + +///Deletes and nulls our portal pair +/obj/item/card/id/advanced/heretic/proc/clear_portals() + QDEL_NULL(portal_one) + QDEL_NULL(portal_two) + +///Clears portal references +/obj/item/card/id/advanced/heretic/proc/clear_portal_refs() + SIGNAL_HANDLER + portal_one = null + portal_two = null + +///Creates a portal pair at door1 and door2, displays a balloon alert to user +/obj/item/card/id/advanced/heretic/proc/make_portal(mob/user, obj/machinery/door/door1, obj/machinery/door/door2) + var/message = "linked" + if(portal_one || portal_two) + clear_portals() + message += ", previous cleared" + + portal_one = new(get_turf(door2), door2) + portal_two = new(get_turf(door1), door1) + portal_one.destination = portal_two + RegisterSignal(portal_one, COMSIG_QDELETING, PROC_REF(clear_portal_refs)) //we only really need to register one because they already qdel both portals if one is destroyed + portal_two.destination = portal_one + balloon_alert(user, "[message]") + +/obj/item/card/id/advanced/heretic/attackby(obj/item/thing, mob/user, params) + if(!istype(thing, /obj/item/card/id/advanced) || !IS_HERETIC(user)) + return ..() + var/obj/item/card/id/card = thing + fused_ids[card.name] = card + card.moveToNullspace() + playsound(drop_location(),'sound/items/eatfood.ogg', rand(10,50), TRUE) + access += card.access + +/obj/item/card/id/advanced/heretic/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(!proximity_flag || !IS_HERETIC(user)) + return + if(istype(target, /obj/effect/knock_portal)) + clear_portals() + return + + if(!istype(target, /obj/machinery/door)) + return + + var/reference_resolved = link?.resolve() + if(reference_resolved == target) + return + + if(reference_resolved) + make_portal(user, reference_resolved, target) + to_chat(user, span_notice("You use [src], to link [link] and [target] together.")) + link = null + balloon_alert(user, "link 2/2") + else + link = WEAKREF(target) + balloon_alert(user, "link 1/2") + +/obj/item/card/id/advanced/heretic/Destroy() + QDEL_LIST_ASSOC(fused_ids) + link = null + clear_portals() + return ..() diff --git a/code/modules/antagonists/heretic/items/lintel.dm b/code/modules/antagonists/heretic/items/lintel.dm new file mode 100644 index 00000000000..140453842c0 --- /dev/null +++ b/code/modules/antagonists/heretic/items/lintel.dm @@ -0,0 +1,64 @@ +/obj/effect/forcefield/wizard/heretic + name = "consecrated lintel" + desc = "A field of papers flying in the air, repulsing heathens with impossible force." + icon_state = "lintel" + initial_duration = 8 SECONDS + +/obj/effect/forcefield/wizard/heretic/Bumped(mob/living/bumpee) + . = ..() + if(!istype(bumpee) || IS_HERETIC_OR_MONSTER(bumpee)) + return + var/throwtarget = get_edge_target_turf(loc, get_dir(loc, get_step_away(bumpee, loc))) + bumpee.safe_throw_at(throwtarget, 10, 1, force = MOVE_FORCE_EXTREMELY_STRONG) + visible_message(span_danger("[src] repulses [bumpee] in a storm of paper!")) + +///A heretic item that spawns a barrier at the clicked turf, 3 uses +/obj/item/heretic_lintel + name = "consecrated book" + desc = "Some kind of book, its contents make your head hurt. The material is not known to you and it seems to shift and twist unnaturally." + icon = 'icons/obj/service/library.dmi' + icon_state = "hereticlintel" + force = 10 + damtype = BURN + worn_icon_state = "book" + throw_speed = 1 + throw_range = 5 + w_class = WEIGHT_CLASS_NORMAL + attack_verb_continuous = list("bashes", "curses") + attack_verb_simple = list("bash", "curse") + resistance_flags = FLAMMABLE + drop_sound = 'sound/items/handling/book_drop.ogg' + pickup_sound = 'sound/items/handling/book_pickup.ogg' + ///what type of barrier do we spawn when used + var/barrier_type = /obj/effect/forcefield/wizard/heretic + ///how many uses do we have left + var/uses = 3 + +/obj/item/heretic_lintel/examine(mob/user) + . = ..() + if(!IS_HERETIC_OR_MONSTER(user)) + return + . += span_hypnophrase("Materializes a barrier upon any tile in sight, which only you can pass through. Lasts 8 seconds.") + . += span_hypnophrase("It has [uses] uses left.") + +/obj/item/heretic_lintel/afterattack(atom/target, mob/user, proximity_flag) + . = ..() + if(IS_HERETIC(user)) + var/turf/turf_target = get_turf(target) + if(locate(barrier_type) in turf_target) + user.balloon_alert(user, "already occupied!") + return + turf_target.visible_message(span_warning("A storm of paper materializes!")) + new /obj/effect/temp_visual/paper_scatter(turf_target) + playsound(turf_target, 'sound/magic/smoke.ogg', 30) + new barrier_type(turf_target, user) + uses-- + if(uses <= 0) + to_chat(user, span_warning("[src] falls apart, turning into ash and dust!")) + qdel(src) + return + var/mob/living/carbon/human/human_user = user + to_chat(human_user, span_userdanger("Your mind burns as you stare deep into the book, a headache setting in like your brain is on fire!")) + human_user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 30, 190) + human_user.add_mood_event("gates_of_mansus", /datum/mood_event/gates_of_mansus) + human_user.dropItemToGround(src) diff --git a/code/modules/antagonists/heretic/knowledge/knock_lore.dm b/code/modules/antagonists/heretic/knowledge/knock_lore.dm new file mode 100644 index 00000000000..6879f527b6b --- /dev/null +++ b/code/modules/antagonists/heretic/knowledge/knock_lore.dm @@ -0,0 +1,230 @@ +/** + * # The path of Knock. + * + * Goes as follows: + * + * A Locksmith’s Secret + * Grasp of Knock + * > Sidepaths: + * Ashen Eyes + * Codex Cicatrix + * Key Keeper’s Burden + * + * Rite Of Passage + * Mark Of Knock + * Ritual of Knowledge + * Burglar's Finesse + * > Sidepaths: + * Apetra Vulnera + * Opening Blast + * + * Opening Blade + * Caretaker’s Last Refuge + * + * Many secrets behind the Spider Door + */ +/datum/heretic_knowledge/limited_amount/starting/base_knock + name = "A Locksmith’s Secret" + desc = "Opens up the Path of Knock to you. \ + Allows you to transmute a knife and a crowbar into a Key Blade. \ + You can only create two at a time and they function as fast crowbars. \ + In addition, they can fit into utility belts." + gain_text = "The Knock permits no seal and no isolation. It thrusts us gleefully out of the safety of ignorance." + next_knowledge = list(/datum/heretic_knowledge/knock_grasp) + required_atoms = list( + /obj/item/knife = 1, + /obj/item/crowbar = 1, + ) + result_atoms = list(/obj/item/melee/sickly_blade/knock) + limit = 2 + route = PATH_KNOCK + +/datum/heretic_knowledge/knock_grasp + name = "Grasp of Knock" + desc = "Your mansus grasp allows you to access anything! Right click on an airlock or a locker to force it open. \ + DNA locks on mechs will be removed, and any pilot will be ejected. Works on consoles. \ + Makes a distinctive knocking sound on use." + gain_text = "Nothing may remain closed from my touch." + next_knowledge = list( + /datum/heretic_knowledge/key_ring, + /datum/heretic_knowledge/medallion, + /datum/heretic_knowledge/codex_cicatrix, + ) + cost = 1 + route = PATH_KNOCK + +/datum/heretic_knowledge/knock_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic) + RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY, PROC_REF(on_secondary_mansus_grasp)) + RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp)) + +/datum/heretic_knowledge/knock_grasp/on_lose(mob/user, datum/antagonist/heretic/our_heretic) + UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY) + UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK) + +/datum/heretic_knowledge/knock_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target) + SIGNAL_HANDLER + var/obj/item/clothing/under/suit = target.get_item_by_slot(ITEM_SLOT_ICLOTHING) + if(istype(suit) && suit.adjusted == NORMAL_STYLE) + suit.toggle_jumpsuit_adjust() + suit.update_appearance() + +/datum/heretic_knowledge/knock_grasp/proc/on_secondary_mansus_grasp(mob/living/source, atom/target) + SIGNAL_HANDLER + + if(ismecha(target)) + var/obj/vehicle/sealed/mecha/mecha = target + mecha.dna_lock = null + for(var/mob/living/occupant as anything in mecha.occupants) + if(isAI(occupant)) + continue + mecha.mob_exit(occupant, randomstep = TRUE) + else if(istype(target,/obj/machinery/door/airlock)) + var/obj/machinery/door/airlock/door = target + door.unbolt() + else if(istype(target, /obj/machinery/computer)) + var/obj/machinery/computer/computer = target + computer.authenticated = TRUE + computer.balloon_alert(source, "unlocked") + + var/turf/target_turf = get_turf(target) + SEND_SIGNAL(target_turf, COMSIG_ATOM_MAGICALLY_UNLOCKED, src, source) + playsound(target, 'sound/magic/hereticknock.ogg', 100, TRUE, -1) + + return COMPONENT_USE_HAND + +/datum/heretic_knowledge/key_ring + name = "Key Keeper’s Burden" + desc = "Allows you to transmute a wallet, an iron rod, and an ID card to create an Eldritch Card. \ + It functions the same as an ID Card, but attacking it with an ID card fuses it and gains its access. \ + You can use it in-hand to change its form to a card you fused. \ + Does not preserve the card used in the ritual." + gain_text = "Gateways shall open before me, my very will ensnaring reality." + adds_sidepath_points = 1 + required_atoms = list( + /obj/item/storage/wallet = 1, + /obj/item/stack/rods = 1, + /obj/item/card/id = 1, + ) + result_atoms = list(/obj/item/card/id/advanced/heretic) + next_knowledge = list(/datum/heretic_knowledge/limited_amount/rite_of_passage) + cost = 1 + route = PATH_KNOCK + +/datum/heretic_knowledge/limited_amount/rite_of_passage // item that creates 3 max at a time heretic only barriers, probably should limit to 1 only, holy people can also pass + name = "Rite Of Passage" + desc = "Allows you to transmute a white crayon, a wooden plank, and a multitool to create a Consecrated Book. \ + It can materialize a barricade at range that only you and people resistant to magic can pass. 3 uses." + gain_text = "With this I can repel those that intend me harm." + required_atoms = list( + /obj/item/toy/crayon/white = 1, + /obj/item/stack/sheet/mineral/wood = 1, + /obj/item/multitool = 1, + ) + result_atoms = list(/obj/item/heretic_lintel) + next_knowledge = list(/datum/heretic_knowledge/mark/knock_mark) + cost = 1 + route = PATH_KNOCK + +/datum/heretic_knowledge/mark/knock_mark + name = "Mark of Knock" + desc = "Your Mansus Grasp now applies the Mark of Knock. \ + Attack a marked person to bar them from all passages for the duration of the mark. \ + This will make it so that they have no access whatsoever, even public access doors will reject them." + gain_text = "Their requests for passage will remain unheeded." + next_knowledge = list(/datum/heretic_knowledge/knowledge_ritual/knock) + route = PATH_KNOCK + mark_type = /datum/status_effect/eldritch/knock + +/datum/heretic_knowledge/knowledge_ritual/knock + next_knowledge = list(/datum/heretic_knowledge/spell/burglar_finesse) + route = PATH_KNOCK + +/datum/heretic_knowledge/spell/burglar_finesse + name = "Burglar's Finesse" + desc = "Grants you Burglar's Finesse, a single-target spell \ + that puts a random item from the victims backpack into your hand." + gain_text = "Their trinkets will be mine, as will their lives in due time." + adds_sidepath_points = 1 + next_knowledge = list( + /datum/heretic_knowledge/spell/apetra_vulnera, + /datum/heretic_knowledge/spell/opening_blast, + /datum/heretic_knowledge/blade_upgrade/flesh/knock, + ) + spell_to_add = /datum/action/cooldown/spell/pointed/burglar_finesse + cost = 2 + route = PATH_KNOCK + +/datum/heretic_knowledge/blade_upgrade/flesh/knock //basically a chance-based weeping avulsion version of the former + name = "Opening Blade" + desc = "Your blade has a chance to cause a weeping avulsion on attack." + gain_text = "The power of my patron courses through my blade, willing their very flesh to part." + next_knowledge = list(/datum/heretic_knowledge/spell/caretaker_refuge) + route = PATH_KNOCK + wound_type = /datum/wound/slash/flesh/critical + var/chance = 35 + +/datum/heretic_knowledge/blade_upgrade/flesh/knock/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade) + if(prob(chance)) + return ..() + +/datum/heretic_knowledge/spell/caretaker_refuge + name = "Caretaker’s Last Refuge" + desc = "Gives you a spell that makes you transparent and not dense. Cannot be used near living sentient beings. \ + While in refuge, you cannot use your hands or spells, and you are immune to slowdown. \ + You are invincible but unable to harm anything. Cancelled by being hit with an anti-magic item." + gain_text = "Then I saw my my own reflection cascaded mind-numbingly enough times that I was but a haze." + adds_sidepath_points = 1 + next_knowledge = list(/datum/heretic_knowledge/ultimate/knock_final) + route = PATH_KNOCK + spell_to_add = /datum/action/cooldown/spell/caretaker + cost = 1 + +/datum/heretic_knowledge/ultimate/knock_final + name = "Many secrets behind the Spider Door" + desc = "The ascension ritual of the Path of Knock. \ + Bring 3 corpses without organs in their torso to a transmutation rune to complete the ritual. \ + When completed, you gain the ability to transform into empowered eldritch creatures \ + and in addition, create a tear to the Spider Door; \ + a tear in reality located at the site of this ritual. \ + Eldritch creatures will endlessly pour from this rift \ + who are bound to obey your instructions." + gain_text = "With her knowledge, and what I had seen, I knew what to do. \ + I had to open the gates, with the holes in my foes as Ways! \ + Reality will soon be torn, the Spider Gate opened! WITNESS ME!" + required_atoms = list(/mob/living/carbon/human = 3) + route = PATH_KNOCK + +/datum/heretic_knowledge/ultimate/knock_final/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) + . = ..() + if(!.) + return FALSE + + for(var/mob/living/carbon/human/body in atoms) + if(body.stat != DEAD) + continue + var/obj/item/bodypart/chest = body.get_bodypart(BODY_ZONE_CHEST) + if(LAZYLEN(chest.get_organs())) + to_chat(user, span_hierophant_warning("[body] has organs in their chest.")) + continue + + selected_atoms += body + + if(!LAZYLEN(selected_atoms)) + loc.balloon_alert(user, "ritual failed, not enough valid bodies!") + return FALSE + return TRUE + +/datum/heretic_knowledge/ultimate/knock_final/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) + . = ..() + priority_announce("Delta-class dimensional anomaly detec[generate_heretic_text()] Reality rended, torn. Gates open, doors open, [user.real_name] has ascended! Fear the tide! [generate_heretic_text()]", "Centra[generate_heretic_text()]", ANNOUNCER_SPANOMALIES) + user.client?.give_award(/datum/award/achievement/misc/knock_ascension, user) + + // buffs + var/datum/action/cooldown/spell/shapeshift/eldritch/ascension/transform_spell = new(user.mind) + transform_spell.Grant(user) + + user.client?.give_award(/datum/award/achievement/misc/knock_ascension, user) + var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user) + var/datum/heretic_knowledge/blade_upgrade/flesh/knock/blade_upgrade = heretic_datum.get_knowledge(/datum/heretic_knowledge/blade_upgrade/flesh/knock) + blade_upgrade.chance += 30 + new /obj/structure/knock_tear(loc, user.mind) diff --git a/code/modules/antagonists/heretic/knowledge/side_knock_flesh.dm b/code/modules/antagonists/heretic/knowledge/side_knock_flesh.dm new file mode 100644 index 00000000000..97218ce5e94 --- /dev/null +++ b/code/modules/antagonists/heretic/knowledge/side_knock_flesh.dm @@ -0,0 +1,28 @@ +// Sidepaths for knowledge between Knock and Flesh. + +/datum/heretic_knowledge/spell/apetra_vulnera + name = "Apetra Vulnera" + desc = "Grants you Apetra Vulnera, a spell \ + which causes heavy bleeding on all bodyparts of the victim that have more than 15 brute damage. \ + Wounds a random limb if no limb is sufficiently damaged." + gain_text = "Flesh opens, and blood spills. My master seeks sacrifice, and I shall appease." + next_knowledge = list( + /datum/heretic_knowledge/spell/blood_siphon, + /datum/heretic_knowledge/void_cloak, + ) + spell_to_add = /datum/action/cooldown/spell/pointed/apetra_vulnera + cost = 1 + route = PATH_SIDE + +/datum/heretic_knowledge/spell/opening_blast + name = "Wave Of Desperation" + desc = "Grants you Wave Of Desparation, a spell which can only be cast while restrained. \ + It removes your restraints, repels and knocks down adjacent people, and applies the Mansus Grasp to everything nearby." + gain_text = "My shackles undone in dark fury, their feeble bindings crumble before my power." + next_knowledge = list( + /datum/heretic_knowledge/summon/ashy, + /datum/heretic_knowledge/void_cloak, + ) + spell_to_add = /datum/action/cooldown/spell/aoe/wave_of_desperation + cost = 1 + route = PATH_SIDE diff --git a/code/modules/antagonists/heretic/magic/apetravulnera.dm b/code/modules/antagonists/heretic/magic/apetravulnera.dm new file mode 100644 index 00000000000..801104dddf9 --- /dev/null +++ b/code/modules/antagonists/heretic/magic/apetravulnera.dm @@ -0,0 +1,59 @@ +/datum/action/cooldown/spell/pointed/apetra_vulnera + name = "Apetra Vulnera" + desc = "Causes severe bleeding on every limb of a target which has more than 15 brute damage. \ + Wounds a random limb if no limb is sufficiently damaged." + background_icon_state = "bg_heretic" + overlay_icon_state = "bg_heretic_border" + button_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "cleave" + + school = SCHOOL_FORBIDDEN + cooldown_time = 45 SECONDS + + invocation = "AP'TRA VULN'RA!" + invocation_type = INVOCATION_WHISPER + spell_requirements = NONE + + cast_range = 4 + /// What type of wound we apply + var/wound_type = /datum/wound/slash/flesh/critical/cleave + +/datum/action/cooldown/spell/pointed/apetra_vulnera/is_valid_target(atom/cast_on) + return ..() && ishuman(cast_on) + +/datum/action/cooldown/spell/pointed/apetra_vulnera/cast(mob/living/carbon/human/cast_on) + . = ..() + + if(IS_HERETIC_OR_MONSTER(cast_on)) + return FALSE + + if(!cast_on.blood_volume) + return FALSE + + if(cast_on.can_block_magic(antimagic_flags)) + cast_on.visible_message( + span_danger("[cast_on]'s bruises briefly glow, but repels the effect!"), + span_danger("Your bruises sting a little, but you are protected!") + ) + return FALSE + + var/a_limb_got_damaged = FALSE + for(var/obj/item/bodypart/bodypart in cast_on.bodyparts) + if(bodypart.brute_dam < 15) + continue + a_limb_got_damaged = TRUE + var/datum/wound/slash/crit_wound = new wound_type() + crit_wound.apply_wound(bodypart) + + if(!a_limb_got_damaged) + var/datum/wound/slash/crit_wound = new wound_type() + crit_wound.apply_wound(pick(cast_on.bodyparts)) + + cast_on.visible_message( + span_danger("[cast_on]'s scratches and bruises are torn open by an unholy force!"), + span_danger("Your scratches and bruises are torn open by some horrible unholy force!") + ) + + new /obj/effect/temp_visual/cleave(get_turf(cast_on)) + + return TRUE diff --git a/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm new file mode 100644 index 00000000000..4395b4a54b3 --- /dev/null +++ b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm @@ -0,0 +1,32 @@ +// Given to ascended knock heretics, is a form of shapeshift that can turn into all 4 common heretic summons, and is not limited to 1 selection. +/datum/action/cooldown/spell/shapeshift/eldritch/ascension + name = "Ascended Shapechange" + desc = "A spell that allows you to take on the form of another eldritch creature, gaining their abilities. \ + You can change your choice at any time, and if your form dies, you dont die." + cooldown_time = 20 SECONDS + die_with_shapeshifted_form = FALSE + possible_shapes = list( + /mob/living/simple_animal/hostile/heretic_summon/raw_prophet, + /mob/living/simple_animal/hostile/heretic_summon/rust_spirit, + /mob/living/simple_animal/hostile/heretic_summon/ash_spirit, + /mob/living/simple_animal/hostile/heretic_summon/stalker, + ) + +/datum/action/cooldown/spell/shapeshift/eldritch/ascension/do_shapeshift(mob/living/caster) + . = ..() + if(!.) + return + //buff our forms so this ascension ability isnt shit + playsound(caster, 'sound/magic/demon_consume.ogg', 50, TRUE) + var/mob/living/monster = . + monster.AddComponent(/datum/component/seethrough_mob) + monster.maxHealth *= 1.5 + monster.health = monster.maxHealth + monster.melee_damage_lower = max((monster.melee_damage_lower * 2), 40) + monster.melee_damage_upper = monster.melee_damage_upper / 2 + monster.transform *= 1.5 + monster.AddElement(/datum/element/wall_smasher, strength_flag = ENVIRONMENT_SMASH_RWALLS) + +/datum/action/cooldown/spell/shapeshift/eldritch/ascension/do_unshapeshift(mob/living/caster) + . = ..() + shapeshift_type = null //pick another loser diff --git a/code/modules/antagonists/heretic/magic/burglar_finesse.dm b/code/modules/antagonists/heretic/magic/burglar_finesse.dm new file mode 100644 index 00000000000..7bb6960354e --- /dev/null +++ b/code/modules/antagonists/heretic/magic/burglar_finesse.dm @@ -0,0 +1,39 @@ +/datum/action/cooldown/spell/pointed/burglar_finesse + name = "Burglar's Finesse" + desc = "Steal a random item from the victim's backpack." + background_icon_state = "bg_heretic" + overlay_icon_state = "bg_heretic_border" + button_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "burglarsfinesse" + + school = SCHOOL_FORBIDDEN + cooldown_time = 40 SECONDS + + invocation = "Y'O'K!" + invocation_type = INVOCATION_WHISPER + spell_requirements = NONE + + cast_range = 4 + +/datum/action/cooldown/spell/pointed/burglar_finesse/is_valid_target(atom/cast_on) + return ..() && ishuman(cast_on) && (locate(/obj/item/storage/backpack) in cast_on.contents) + +/datum/action/cooldown/spell/pointed/burglar_finesse/cast(mob/living/carbon/human/cast_on) + . = ..() + if(cast_on.can_block_magic(antimagic_flags)) + to_chat(cast_on, span_danger("You feel a light tug, but are otherwise fine, you were protected by holiness!")) + to_chat(owner, span_danger("[cast_on] is protected by holy forces!")) + return FALSE + + var/obj/storage_item = locate(/obj/item/storage/backpack) in cast_on.contents + + if(isnull(storage_item)) + return FALSE + + var/item = pick(storage_item.contents) + if(isnull(item)) + return FALSE + + to_chat(cast_on, span_warning("Your [storage_item] feels lighter...")) + to_chat(owner, span_notice("With a blink, you pull [item] out of [cast_on][p_s()] [storage_item].")) + owner.put_in_active_hand(item) diff --git a/code/modules/antagonists/heretic/magic/caretaker.dm b/code/modules/antagonists/heretic/magic/caretaker.dm new file mode 100644 index 00000000000..87f3a69dad1 --- /dev/null +++ b/code/modules/antagonists/heretic/magic/caretaker.dm @@ -0,0 +1,39 @@ +/datum/action/cooldown/spell/caretaker + name = "Caretaker’s Last Refuge" + desc = "Shifts you into the Caretaker's Refuge, rendering you translucent and intangible. \ + While in the Refuge your movement is unrestricted, but you cannot use your hands or cast any spells. \ + You cannot enter the Refuge while near other sentient beings, \ + and you can be removed from it upon contact with antimagical artifacts." + background_icon_state = "bg_heretic" + overlay_icon_state = "bg_heretic_border" + button_icon = 'icons/mob/actions/actions_minor_antag.dmi' + button_icon_state = "ninja_cloak" + sound = 'sound/effects/curse2.ogg' + + school = SCHOOL_FORBIDDEN + cooldown_time = 1 MINUTES + + invocation_type = INVOCATION_NONE + spell_requirements = NONE + +/datum/action/cooldown/spell/caretaker/Remove(mob/living/remove_from) + if(remove_from.has_status_effect(/datum/status_effect/caretaker_refuge)) + remove_from.remove_status_effect(/datum/status_effect/caretaker_refuge) + return ..() + +/datum/action/cooldown/spell/caretaker/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/datum/action/cooldown/spell/caretaker/cast(atom/cast_on) + . = ..() + for(var/mob/living/alive in orange(5, owner)) + if(alive.stat != DEAD && alive.client) + owner.balloon_alert(owner, "other minds nearby!") + return FALSE + + var/mob/living/carbon/carbon_user = owner + if(carbon_user.has_status_effect(/datum/status_effect/caretaker_refuge)) + carbon_user.remove_status_effect(/datum/status_effect/caretaker_refuge) + else + carbon_user.apply_status_effect(/datum/status_effect/caretaker_refuge) + return TRUE diff --git a/code/modules/antagonists/heretic/magic/rust_charge.dm b/code/modules/antagonists/heretic/magic/rust_charge.dm new file mode 100644 index 00000000000..0d693b0de86 --- /dev/null +++ b/code/modules/antagonists/heretic/magic/rust_charge.dm @@ -0,0 +1,49 @@ +// Rust charge, a charge action that can only be started on rust (and only destroys rust tiles) +/datum/action/cooldown/mob_cooldown/charge/rust + name = "Rust Charge" + desc = "A charge that must be started on a rusted tile and will destroy any rusted objects you come into contact with, will deal high damage to others and rust around you during the charge. As it is the rust that empoweres you for this ability, no focus is needed" + charge_distance = 10 + charge_damage = 50 + cooldown_time = 45 SECONDS + +/datum/action/cooldown/mob_cooldown/charge/rust/Activate(atom/target_atom) + var/turf/open/start_turf = get_turf(owner) + if(!istype(start_turf) || !HAS_TRAIT(start_turf, TRAIT_RUSTY)) + return FALSE + StartCooldown(135 SECONDS, 135 SECONDS) + charge_sequence(owner, target_atom, charge_delay, charge_past) + StartCooldown() + return TRUE +/datum/action/cooldown/mob_cooldown/charge/rust/on_move(atom/source, atom/new_loc, atom/target) + var/turf/victim = get_turf(owner) + if(!actively_moving) + return COMPONENT_MOVABLE_BLOCK_PRE_MOVE + new /obj/effect/temp_visual/decoy/fading(source.loc, source) + INVOKE_ASYNC(src, PROC_REF(DestroySurroundings), source) + victim.rust_heretic_act() + for(var/dir in GLOB.cardinals) + var/turf/nearby_turf = get_step(victim, dir) + if(istype(nearby_turf)) + nearby_turf.rust_heretic_act() + +/datum/action/cooldown/mob_cooldown/charge/rust/DestroySurroundings(atom/movable/charger) + if(!destroy_objects) + return + for(var/dir in GLOB.cardinals) + var/turf/source = get_turf(owner) + var/turf/closed/next_turf = get_step(charger, dir) + if(!istype(source) || !istype(next_turf) || !HAS_TRAIT(source, TRAIT_RUSTY) || !HAS_TRAIT(next_turf, TRAIT_RUSTY)) + continue + SSexplosions.medturf += next_turf + +/datum/action/cooldown/mob_cooldown/charge/rust/on_bump(atom/movable/source, atom/target) + if(owner == target) + return + if(destroy_objects) + if(isturf(target)) + INVOKE_ASYNC(src, PROC_REF(DestroySurroundings), source) + if(isobj(target) && target.density) + SSexplosions.med_mov_atom += target + + INVOKE_ASYNC(src, PROC_REF(DestroySurroundings), source) + hit_target(source, target, charge_damage) diff --git a/code/modules/antagonists/heretic/magic/wave_of_desperation.dm b/code/modules/antagonists/heretic/magic/wave_of_desperation.dm new file mode 100644 index 00000000000..3b78b56ddc0 --- /dev/null +++ b/code/modules/antagonists/heretic/magic/wave_of_desperation.dm @@ -0,0 +1,79 @@ +/datum/action/cooldown/spell/aoe/wave_of_desperation + name = "Wave Of Desperation" + desc = "Removes your restraints, repels and knocks down adjacent people, and applies certain effects of the Mansus Grasp upon everything nearby. \ + Cannot be cast unless you are restrained, and the stress renders you unconscious 12 seconds later!" + background_icon_state = "bg_heretic" + overlay_icon_state = "bg_heretic_border" + button_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "uncuff" + sound = 'sound/magic/swap.ogg' + + school = SCHOOL_FORBIDDEN + cooldown_time = 5 MINUTES + + invocation = "F'K 'FF." + invocation_type = INVOCATION_WHISPER + spell_requirements = NONE + + aoe_radius = 3 + +/datum/action/cooldown/spell/aoe/wave_of_desperation/is_valid_target(mob/living/carbon/cast_on) + return ..() && istype(cast_on) && (cast_on.handcuffed || cast_on.legcuffed) + +// Before the cast, we do some small AOE damage around the caster +/datum/action/cooldown/spell/aoe/wave_of_desperation/before_cast(mob/living/carbon/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + if(cast_on.handcuffed) + cast_on.visible_message(span_danger("[cast_on.handcuffed] on [cast_on] shatter!")) + QDEL_NULL(cast_on.handcuffed) + if(cast_on.legcuffed) + cast_on.visible_message(span_danger("[cast_on.legcuffed] on [cast_on] shatters!")) + QDEL_NULL(cast_on.legcuffed) + + cast_on.apply_status_effect(/datum/status_effect/heretic_lastresort) + new /obj/effect/temp_visual/knockblast(get_turf(cast_on)) + + for(var/mob/living/victim in get_things_to_cast_on(cast_on, radius_override = 1)) + victim.AdjustKnockdown(3 SECONDS) + victim.AdjustParalyzed(0.5 SECONDS) + +/datum/action/cooldown/spell/aoe/wave_of_desperation/get_things_to_cast_on(atom/center, radius_override) + . = list() + for(var/atom/nearby in orange(center, radius_override ? radius_override : aoe_radius)) + if(nearby == owner || nearby == center || isarea(nearby)) + continue + if(!ismob(nearby)) + . += nearby + continue + var/mob/living/nearby_mob = nearby + if(!isturf(nearby_mob.loc)) + continue + if(IS_HERETIC_OR_MONSTER(nearby_mob)) + continue + if(nearby_mob.can_block_magic(antimagic_flags)) + continue + + . += nearby_mob + +/datum/action/cooldown/spell/aoe/wave_of_desperation/cast_on_thing_in_aoe(atom/victim, atom/caster) + if(!ismob(victim)) + SEND_SIGNAL(owner, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY, victim) + + var/atom/movable/mover = victim + if(!istype(mover)) + return + + if(mover.anchored) + return + var/our_turf = get_turf(caster) + var/throwtarget = get_edge_target_turf(our_turf, get_dir(our_turf, get_step_away(mover, our_turf))) + mover.safe_throw_at(throwtarget, 3, 1, force = MOVE_FORCE_STRONG) + +/obj/effect/temp_visual/knockblast + icon = 'icons/effects/effects.dmi' + icon_state = "shield-flash" + alpha = 180 + duration = 1 SECONDS diff --git a/code/modules/antagonists/heretic/structures/knock_final.dm b/code/modules/antagonists/heretic/structures/knock_final.dm new file mode 100644 index 00000000000..c8a2058eb9f --- /dev/null +++ b/code/modules/antagonists/heretic/structures/knock_final.dm @@ -0,0 +1,115 @@ +/obj/structure/knock_tear + name = "???" + desc = "It stares back. Theres no reason to remain. Run." + max_integrity = INFINITE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + icon = 'icons/obj/anomaly.dmi' + icon_state = "bhole3" + color = COLOR_VOID_PURPLE + light_color = COLOR_VOID_PURPLE + light_range = 20 + anchored = TRUE + density = FALSE + layer = HIGH_PIPE_LAYER //0.01 above sigil layer used by heretic runes + move_resist = INFINITY + /// Who is our daddy? + var/datum/mind/ascendee + /// True if we're currently checking for ghost opinions + var/gathering_candidates = TRUE + ///a static list of heretic summons we cam create, automatically populated from heretic monster subtypes + var/static/list/monster_types + /// A static list of heretic summons which we should not create + var/static/list/monster_types_blacklist = list( + /mob/living/basic/heretic_summon/star_gazer, + /mob/living/simple_animal/hostile/heretic_summon/armsy, + /mob/living/simple_animal/hostile/heretic_summon/armsy/prime, + ) + +/obj/structure/knock_tear/Initialize(mapload, datum/mind/ascendant_mind) + . = ..() + transform *= 3 + if(isnull(monster_types)) + monster_types = subtypesof(/mob/living/simple_animal/hostile/heretic_summon) + subtypesof(/mob/living/basic/heretic_summon) - monster_types_blacklist + if(!isnull(ascendant_mind)) + ascendee = ascendant_mind + RegisterSignals(ascendant_mind.current, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING), PROC_REF(end_madness)) + SSpoints_of_interest.make_point_of_interest(src) + INVOKE_ASYNC(src, PROC_REF(poll_ghosts)) + +/// Ask ghosts if they want to make some noise +/obj/structure/knock_tear/proc/poll_ghosts() + var/list/candidates = poll_ghost_candidates("Would you like to be a random eldritch monster attacking the crew?", ROLE_SENTIENCE, ROLE_SENTIENCE, 10 SECONDS, POLL_IGNORE_HERETIC_MONSTER) + while(LAZYLEN(candidates)) + var/mob/dead/observer/candidate = pick_n_take(candidates) + ghost_to_monster(candidate, should_ask = FALSE) + gathering_candidates = FALSE + +/// Destroy the rift if you kill the heretic +/obj/structure/knock_tear/proc/end_madness(datum/former_master) + SIGNAL_HANDLER + var/turf/our_turf = get_turf(src) + playsound(our_turf, 'sound/magic/castsummon.ogg', vol = 100, vary = TRUE) + visible_message(span_boldwarning("The rip in space spasms and disappears!")) + UnregisterSignal(former_master, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING)) // Just in case they die THEN delete + new /obj/effect/temp_visual/destabilising_tear(our_turf) + qdel(src) + +/obj/structure/knock_tear/attack_ghost(mob/user) + . = ..() + if(. || gathering_candidates) + return + ghost_to_monster(user) + +/obj/structure/knock_tear/examine(mob/user) + . = ..() + if (!isobserver(user) || gathering_candidates) + return + . += span_notice("You can use this to enter the world as a foul monster.") + +/// Turn a ghost into an 'orrible beast +/obj/structure/knock_tear/proc/ghost_to_monster(mob/dead/observer/user, should_ask = TRUE) + if(should_ask) + var/ask = tgui_alert(user, "Become a monster?", "Ascended Rift", list("Yes", "No")) + if(ask != "Yes" || QDELETED(src) || QDELETED(user)) + return FALSE + var/monster_type = pick(monster_types) + var/mob/living/monster = new monster_type(loc) + monster.key = user.key + monster.set_name() + var/datum/antagonist/heretic_monster/woohoo_free_antag = new(src) + monster.mind.add_antag_datum(woohoo_free_antag) + if(ascendee) + monster.faction = ascendee.current.faction + woohoo_free_antag.set_owner(ascendee) + var/datum/objective/kill_all_your_friends = new() + kill_all_your_friends.owner = monster.mind + kill_all_your_friends.explanation_text = "The station's crew must be culled." + kill_all_your_friends.completed = TRUE + woohoo_free_antag.objectives += kill_all_your_friends + +/obj/structure/knock_tear/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) + return FALSE + +/obj/structure/knock_tear/Destroy(force) + if(ascendee) + ascendee = null + return ..() + +/obj/effect/temp_visual/destabilising_tear + name = "destabilised tear" + icon = 'icons/obj/anomaly.dmi' + icon_state = "bhole3" + color = COLOR_VOID_PURPLE + light_color = COLOR_VOID_PURPLE + light_range = 20 + layer = HIGH_PIPE_LAYER + duration = 1 SECONDS + +/obj/effect/temp_visual/destabilising_tear/Initialize(mapload) + . = ..() + transform *= 3 + animate(src, transform = matrix().Scale(3.2), time = 0.15 SECONDS) + animate(transform = matrix().Scale(0.2), time = 0.75 SECONDS) + animate(transform = matrix().Scale(3, 0), time = 0.1 SECONDS) + animate(src, color = COLOR_WHITE, time = 0.25 SECONDS, flags = ANIMATION_PARALLEL) + animate(color = COLOR_VOID_PURPLE, time = 0.3 SECONDS) diff --git a/code/modules/antagonists/traitor/objectives/demoralise_assault.dm b/code/modules/antagonists/traitor/objectives/demoralise_assault.dm new file mode 100644 index 00000000000..fe26864e4fc --- /dev/null +++ b/code/modules/antagonists/traitor/objectives/demoralise_assault.dm @@ -0,0 +1,129 @@ +/datum/traitor_objective_category/demoralise + name = "Demoralise Crew" + objectives = list( + /datum/traitor_objective/target_player/assault = 1, + /datum/traitor_objective/destroy_item/demoralise = 1, + ) + weight = OBJECTIVE_WEIGHT_UNLIKELY + +/datum/traitor_objective/target_player/assault + name = "Assault %TARGET% the %JOB TITLE%" + description = "%TARGET% has been identified as a potential future agent. \ + Pick a fight and give them a good beating. \ + %COUNT% hits should reduce their morale and have them questioning their loyalties. \ + Try not to kill them just yet, we may want to recruit them in the future." + + abstract_type = /datum/traitor_objective/target_player + duplicate_type = /datum/traitor_objective/target_player + + progression_minimum = 0 MINUTES + progression_maximum = 30 MINUTES + progression_reward = list(4 MINUTES, 8 MINUTES) + telecrystal_reward = list(0, 1) + + /// Min attacks required to pass the objective. Picked at random between this and max. + var/min_attacks_required = 2 + /// Max attacks required to pass the objective. Picked at random between this and min. + var/max_attacks_required = 5 + /// The random number picked for the number of required attacks to pass this objective. + var/attacks_required = 0 + /// Total number of successful attacks recorded. + var/attacks_inflicted = 0 + +/datum/traitor_objective/target_player/assault/on_objective_taken(mob/user) + . = ..() + + target.AddElement(/datum/element/relay_attackers) + RegisterSignal(target, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) + +/datum/traitor_objective/target_player/assault/proc/on_attacked(mob/source, mob/living/attacker, attack_flags) + SIGNAL_HANDLER + + // Only care about attacks from the objective's owner. + if(attacker != handler.owner.current) + return + + // We want some sort of damaging attack to trigger this, rather than shoves and non-lethals. + if(!(attack_flags & ATTACKER_DAMAGING_ATTACK)) + return + + attacks_inflicted++ + + if(attacks_inflicted == attacks_required) + succeed_objective() + +/datum/traitor_objective/target_player/assault/ungenerate_objective() + UnregisterSignal(target, COMSIG_ATOM_WAS_ATTACKED) + UnregisterSignal(target, COMSIG_LIVING_DEATH) + set_target(null) + +/datum/traitor_objective/target_player/assault/generate_objective(datum/mind/generating_for, list/possible_duplicates) + var/list/already_targeting = list() //List of minds we're already targeting. The possible_duplicates is a list of objectives, so let's not mix things + for(var/datum/objective/task as anything in handler.primary_objectives) + if(!istype(task.target, /datum/mind)) + continue + already_targeting += task.target //Removing primary objective kill targets from the list + + var/list/possible_targets = list() + + for(var/datum/mind/possible_target as anything in get_crewmember_minds()) + if(possible_target in already_targeting) + continue + + if(possible_target == generating_for) + continue + + if(!ishuman(possible_target.current)) + continue + + if(possible_target.current.stat == DEAD) + continue + + if(possible_target.has_antag_datum(/datum/antagonist/traitor)) + continue + + possible_targets += possible_target + + for(var/datum/traitor_objective/target_player/objective as anything in possible_duplicates) + possible_targets -= objective.target?.mind + + if(generating_for.late_joiner) + var/list/all_possible_targets = possible_targets.Copy() + for(var/datum/mind/possible_target as anything in all_possible_targets) + if(!possible_target.late_joiner) + possible_targets -= possible_target + if(!possible_targets.len) + possible_targets = all_possible_targets + + if(!possible_targets.len) + return FALSE + + var/datum/mind/target_mind = pick(possible_targets) + + set_target(target_mind.current) + replace_in_name("%TARGET%", target.real_name) + replace_in_name("%JOB TITLE%", target_mind.assigned_role.title) + + attacks_required = rand(min_attacks_required, max_attacks_required) + replace_in_name("%COUNT%", attacks_required) + + RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_target_death)) + + return TRUE + +/datum/traitor_objective/target_player/assault/generate_ui_buttons(mob/user) + var/list/buttons = list() + if(attacks_required > attacks_inflicted) + buttons += add_ui_button("[attacks_required - attacks_inflicted]", "This tells you how many more times you have to attack the target player to succeed.", "hand-rock-o", "none") + return buttons + +/datum/traitor_objective/target_player/assault/target_deleted() + //don't take an objective target of someone who is already obliterated + fail_objective() + return ..() + +/datum/traitor_objective/target_player/assault/proc/on_target_death() + SIGNAL_HANDLER + + //don't take an objective target of someone who is already dead + fail_objective() diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/all_access.dm b/code/modules/antagonists/wizard/grand_ritual/finales/all_access.dm new file mode 100644 index 00000000000..07958ed94a7 --- /dev/null +++ b/code/modules/antagonists/wizard/grand_ritual/finales/all_access.dm @@ -0,0 +1,17 @@ +/// Open all of the doors +/datum/grand_finale/all_access + name = "Connection" + desc = "The ultimate use of your gathered power! Unlock every single door that they have! Nobody will be able to keep you out now, or anyone else for that matter!" + icon = 'icons/mob/actions/actions_spells.dmi' + icon_state = "knock" + +/datum/grand_finale/all_access/trigger(mob/living/carbon/human/invoker) + message_admins("[key_name(invoker)] removed all door access requirements") + for(var/obj/machinery/door/target_door as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door)) + if(is_station_level(target_door.z)) + target_door.unlock() + target_door.req_access = list() + target_door.req_one_access = list() + INVOKE_ASYNC(target_door, TYPE_PROC_REF(/obj/machinery/door/airlock, open)) + CHECK_TICK + priority_announce("AULIE OXIN FIERA!!", null, 'sound/magic/knock.ogg', sender_override = "[invoker.real_name]") diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm b/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm new file mode 100644 index 00000000000..876f2475d55 --- /dev/null +++ b/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm @@ -0,0 +1,60 @@ +#define DOOM_SINGULARITY "singularity" +#define DOOM_TESLA "tesla" +#define DOOM_METEORS "meteors" + +/// Kill yourself and probably a bunch of other people +/datum/grand_finale/armageddon + name = "Annihilation" + desc = "This crew have offended you beyond the realm of pranks. Make the ultimate sacrifice to teach them a lesson your elders can really respect. \ + YOU WILL NOT SURVIVE THIS." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "legion_head" + minimum_time = 90 MINUTES // This will probably immediately end the round if it gets finished. + ritual_invoke_time = 60 SECONDS // Really give the crew some time to interfere with this one. + dire_warning = TRUE + glow_colour = "#be000048" + /// Things to yell before you die + var/static/list/possible_last_words = list( + "Flames and ruin!", + "Dooooooooom!!", + "HAHAHAHAHAHA!! AHAHAHAHAHAHAHAHAA!!", + "Hee hee hee!! Hoo hoo hoo!! Ha ha haaa!!", + "Ohohohohohoho!!", + "Cower in fear, puny mortals!", + "Tremble before my glory!", + "Pick a god and pray!", + "It's no use!", + "If the gods wanted you to live, they would not have created me!", + "God stays in heaven out of fear of what I have created!", + "Ruination is come!", + "All of creation, bend to my will!", + ) + +/datum/grand_finale/armageddon/trigger(mob/living/carbon/human/invoker) + priority_announce(pick(possible_last_words), null, 'sound/magic/voidblink.ogg', sender_override = "[invoker.real_name]") + var/turf/current_location = get_turf(invoker) + invoker.gib() + + var/static/list/doom_options = list() + if (!length(doom_options)) + doom_options = list(DOOM_SINGULARITY, DOOM_TESLA) + if (!SSmapping.config.planetary) + doom_options += DOOM_METEORS + + switch(pick(doom_options)) + if (DOOM_SINGULARITY) + var/obj/singularity/singulo = new(current_location) + singulo.energy = 300 + if (DOOM_TESLA) + var/obj/energy_ball/tesla = new (current_location) + tesla.energy = 200 + if (DOOM_METEORS) + var/datum/dynamic_ruleset/roundstart/meteor/meteors = new() + meteors.meteordelay = 0 + var/datum/game_mode/dynamic/mode = SSticker.mode + mode.execute_roundstart_rule(meteors) // Meteors will continue until morale is crushed. + priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", ANNOUNCER_METEORS) + +#undef DOOM_SINGULARITY +#undef DOOM_TESLA +#undef DOOM_METEORS diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/captaincy.dm b/code/modules/antagonists/wizard/grand_ritual/finales/captaincy.dm new file mode 100644 index 00000000000..d1a3c1afaf7 --- /dev/null +++ b/code/modules/antagonists/wizard/grand_ritual/finales/captaincy.dm @@ -0,0 +1,113 @@ +/// Become the official Captain of the station +/datum/grand_finale/usurp + name = "Usurpation" + desc = "The ultimate use of your gathered power! Rewrite time such that you have been Captain of this station the whole time." + icon = 'icons/obj/card.dmi' + icon_state = "card_gold" + +/datum/grand_finale/usurp/trigger(mob/living/carbon/human/invoker) + message_admins("[key_name(invoker)] has replaced the Captain") + var/list/former_captains = list() + var/list/other_crew = list() + SEND_SOUND(world, sound('sound/magic/timeparadox2.ogg')) + + for (var/mob/living/carbon/human/crewmate as anything in GLOB.human_list) + if (!crewmate.mind) + continue + crewmate.Unconscious(3 SECONDS) // Everyone falls unconscious but not everyone gets told about a new captain + if (crewmate == invoker || IS_HUMAN_INVADER(crewmate)) + continue + to_chat(crewmate, span_notice("The world spins and dissolves. Your past flashes before your eyes, backwards.\n\ + Life strolls back into the ocean and shrinks into nothingness, planets explode into storms of solar dust, \ + the stars rush back to greet each other at the beginning of things and then... you snap back to the present. \n\ + Everything is just as it was and always has been. \n\n\ + A stray thought sticks in the forefront of your mind. \n\ + [span_hypnophrase("I'm so glad that [invoker.real_name] is our legally appointed Captain!")] \n\ + Is... that right?")) + if (is_captain_job(crewmate.mind.assigned_role)) + former_captains += crewmate + demote_to_assistant(crewmate) + continue + if (crewmate.stat != DEAD) + other_crew += crewmate + + dress_candidate(invoker) + GLOB.manifest.modify(invoker.real_name, JOB_CAPTAIN, JOB_CAPTAIN) + minor_announce("Captain [invoker.real_name] on deck!") + + // Enlist some crew to try and restore the natural order + for (var/mob/living/carbon/human/former_captain as anything in former_captains) + create_vendetta(former_captain.mind, invoker.mind) + for (var/mob/living/carbon/human/random_crewmate as anything in other_crew) + if (prob(10)) + create_vendetta(random_crewmate.mind, invoker.mind) + +/** + * Anyone who thought they were Captain is in for a nasty surprise, and won't be very happy about it + */ +/datum/grand_finale/usurp/proc/demote_to_assistant(mob/living/carbon/human/former_captain) + var/obj/effect/particle_effect/fluid/smoke/exit_poof = new(get_turf(former_captain)) + exit_poof.lifetime = 2 SECONDS + + former_captain.unequip_everything() + if(isplasmaman(former_captain)) + former_captain.equipOutfit(/datum/outfit/plasmaman) + former_captain.internal = former_captain.get_item_for_held_index(2) + else + former_captain.equipOutfit(/datum/outfit/job/assistant) + + GLOB.manifest.modify(former_captain.real_name, JOB_ASSISTANT, JOB_ASSISTANT) + var/list/valid_turfs = list() + // Used to be into prison but that felt a bit too mean + for (var/turf/exile_turf as anything in get_area_turfs(/area/station/maintenance, subtypes = TRUE)) + if (isspaceturf(exile_turf) || exile_turf.is_blocked_turf()) + continue + valid_turfs += exile_turf + do_teleport(former_captain, pick(valid_turfs), no_effects = TRUE) + var/obj/effect/particle_effect/fluid/smoke/enter_poof = new(get_turf(former_captain)) + enter_poof.lifetime = 2 SECONDS + +/** + * Does some item juggling to try to dress you as both a Wizard and Captain without deleting any items you have bought. + * ID, headset, and uniform are forcibly replaced. Other slots are only filled if unoccupied. + * We could forcibly replace shoes and gloves too but people might miss their insuls or... meown shoes? + */ +/datum/grand_finale/usurp/proc/dress_candidate(mob/living/carbon/human/invoker) + // Won't be needing these + var/obj/id = invoker.get_item_by_slot(ITEM_SLOT_ID) + QDEL_NULL(id) + var/obj/headset = invoker.get_item_by_slot(ITEM_SLOT_EARS) + QDEL_NULL(headset) + // We're about to take off your pants so those are going to fall out + var/obj/item/pocket_L = invoker.get_item_by_slot(ITEM_SLOT_LPOCKET) + var/obj/item/pocket_R = invoker.get_item_by_slot(ITEM_SLOT_RPOCKET) + // In case we try to put a PDA there + var/obj/item/belt = invoker.get_item_by_slot(ITEM_SLOT_BELT) + belt?.moveToNullspace() + + var/obj/pants = invoker.get_item_by_slot(ITEM_SLOT_ICLOTHING) + QDEL_NULL(pants) + invoker.equipOutfit(/datum/outfit/job/wizard_captain) + // And put everything back! + equip_to_slot_then_hands(invoker, ITEM_SLOT_BELT, belt) + equip_to_slot_then_hands(invoker, ITEM_SLOT_LPOCKET, pocket_L) + equip_to_slot_then_hands(invoker, ITEM_SLOT_RPOCKET, pocket_R) + +/// An outfit which replaces parts of a wizard's clothes with captain's clothes but keeps the robes +/datum/outfit/job/wizard_captain + name = "Captain (Wizard Transformation)" + jobtype = /datum/job/captain + id = /obj/item/card/id/advanced/gold + id_trim = /datum/id_trim/job/captain + uniform = /obj/item/clothing/under/rank/captain/parade + belt = /obj/item/modular_computer/pda/heads/captain + ears = /obj/item/radio/headset/heads/captain/alt + glasses = /obj/item/clothing/glasses/sunglasses + gloves = /obj/item/clothing/gloves/captain + shoes = /obj/item/clothing/shoes/laceup + accessory = /obj/item/clothing/accessory/medal/gold/captain + backpack_contents = list( + /obj/item/melee/baton/telescopic = 1, + /obj/item/station_charter = 1, + ) + box = null diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/cheese.dm b/code/modules/antagonists/wizard/grand_ritual/finales/cheese.dm new file mode 100644 index 00000000000..714cd62659b --- /dev/null +++ b/code/modules/antagonists/wizard/grand_ritual/finales/cheese.dm @@ -0,0 +1,49 @@ +/** + * Gives the wizard a defensive/mood buff and a Wabbajack, a juiced up chaos staff that will surely break something. + * Everyone but the wizard goes crazy, suffers major brain damage, and is given a vendetta against the wizard. + * Already insane people are instead cured of their madness, ignoring any other effects as the station around them loses its marbles. + */ +/datum/grand_finale/cheese + // we don't set name, desc and others, thus we won't appear in the radial choice of a normal finale rune + dire_warning = TRUE + minimum_time = 45 MINUTES //i'd imagine speedrunning this would be crummy, but the wizard's average lifespan is barely reaching this point + +/datum/grand_finale/cheese/trigger(mob/living/invoker) + message_admins("[key_name(invoker)] has summoned forth The Wabbajack and cursed the crew with madness!") + priority_announce("Danger: Extremely potent reality altering object has been summoned on station. Immediate evacuation advised. Brace for impact.", "Central Command Higher Dimensional Affairs", 'sound/effects/glassbr1.ogg') + + for (var/mob/living/carbon/human/crewmate as anything in GLOB.human_list) + if (isnull(crewmate.mind)) + continue + if (crewmate == invoker) //everyone but the wizard is royally fucked, no matter who they are + continue + if (crewmate.has_trauma_type(/datum/brain_trauma/mild/hallucinations)) //for an already insane person, this is retribution + to_chat(crewmate, span_boldwarning("Your surroundings suddenly fill with a cacophony of manic laughter and psychobabble...")) + to_chat(crewmate, span_nicegreen("...but as the moment passes, you realise that whatever eldritch power behind the event happened to affect you \ + has resonated within the ruins of your already shattered mind, creating a singularity of mental instability! \ + As it collapses unto itself, you feel... at peace, finally.")) + if(crewmate.has_quirk(/datum/quirk/insanity)) + crewmate.remove_quirk(/datum/quirk/insanity) + else + crewmate.cure_trauma_type(/datum/brain_trauma/mild/hallucinations, TRAUMA_RESILIENCE_ABSOLUTE) + else + //everyone else gets to relish in madness + //yes killing their mood will also trigger mood hallucinations + create_vendetta(crewmate.mind, invoker.mind) + to_chat(crewmate, span_boldwarning("Your surroundings suddenly fill with a cacophony of manic laughter and psychobabble. \n\ + You feel your inner psyche shatter into a myriad pieces of jagged glass of colors unknown to the universe, \ + infinitely reflecting a blinding, maddening light coming from the innermost sanctums of your destroyed mind. \n\ + After a brief pause which felt like a millenia, one phrase rebounds ceaselessly in your head, imbued with the false hope of absolution... \n\ + [invoker] must die.")) + var/datum/brain_trauma/mild/hallucinations/added_trauma = new() + added_trauma.resilience = TRAUMA_RESILIENCE_ABSOLUTE + crewmate.adjustOrganLoss(ORGAN_SLOT_BRAIN, BRAIN_DAMAGE_DEATH - 25, BRAIN_DAMAGE_DEATH - 25) //you'd better hope chap didn't pick a hypertool + crewmate.gain_trauma(added_trauma) + crewmate.add_mood_event("wizard_ritual_finale", /datum/mood_event/madness_despair) + + //drip our wizard out + invoker.apply_status_effect(/datum/status_effect/blessing_of_insanity) + invoker.add_mood_event("wizard_ritual_finale", /datum/mood_event/madness_elation) + var/obj/item/gun/magic/staff/chaos/true_wabbajack/the_wabbajack = new + invoker.put_in_active_hand(the_wabbajack) + to_chat(invoker, span_mind_control("Your every single instinct and rational thought is screaming at you as [the_wabbajack] appears in your firm grip...")) diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/clown.dm b/code/modules/antagonists/wizard/grand_ritual/finales/clown.dm new file mode 100644 index 00000000000..bda79c908c0 --- /dev/null +++ b/code/modules/antagonists/wizard/grand_ritual/finales/clown.dm @@ -0,0 +1,76 @@ +/// Dress the crew as magical clowns +/datum/grand_finale/clown + name = "Jubilation" + desc = "The ultimate use of your gathered power! Rewrite time so that everyone went to clown college! Now they'll prank each other for you!" + icon = 'icons/obj/clothing/masks.dmi' + icon_state = "clown" + glow_colour = "#ffff0048" + +/datum/grand_finale/clown/trigger(mob/living/carbon/human/invoker) + for(var/mob/living/carbon/human/victim as anything in GLOB.human_list) + victim.Unconscious(3 SECONDS) + if (!victim.mind || IS_HUMAN_INVADER(victim) || victim == invoker) + continue + if (HAS_TRAIT(victim, TRAIT_CLOWN_ENJOYER)) + victim.add_mood_event("clown_world", /datum/mood_event/clown_world) + to_chat(victim, span_notice("The world spins and dissolves. Your past flashes before your eyes, backwards.\n\ + Life strolls back into the ocean and shrinks into nothingness, planets explode into storms of solar dust, \ + the stars rush back to greet each other at the beginning of things and then... you snap back to the present. \n\ + Everything is just as it was and always has been. \n\n\ + A stray thought sticks in the forefront of your mind. \n\ + [span_hypnophrase("I'm so glad that I work at Clown Research Station [station_name()]!")] \n\ + Is... that right?")) + if (is_clown_job(victim.mind.assigned_role)) + var/datum/action/cooldown/spell/conjure_item/clown_pockets/new_spell = new(victim) + new_spell.Grant(victim) + continue + if (!ismonkey(victim)) // Monkeys cannot yet wear clothes + dress_as_magic_clown(victim) + if (prob(15)) + create_vendetta(victim.mind, invoker.mind) + +/** + * Clown enjoyers who are effected by this become ecstatic, they have achieved their life's dream. + * This moodlet is equivalent to the one for simply being a traitor. + */ +/datum/mood_event/clown_world + mood_change = 4 + +/datum/mood_event/clown_world/add_effects(param) + description = "I LOVE working at Clown Research Station [station_name()]!!" + +/// Dress the passed mob as a magical clown, self-explanatory +/datum/grand_finale/clown/proc/dress_as_magic_clown(mob/living/carbon/human/victim) + var/obj/effect/particle_effect/fluid/smoke/poof = new(get_turf(victim)) + poof.lifetime = 2 SECONDS + + var/obj/item/tank/internal = victim.internal + // We're about to take off your pants so those are going to fall out + var/obj/item/pocket_L = victim.get_item_by_slot(ITEM_SLOT_LPOCKET) + var/obj/item/pocket_R = victim.get_item_by_slot(ITEM_SLOT_RPOCKET) + var/obj/item/id = victim.get_item_by_slot(ITEM_SLOT_ID) + var/obj/item/belt = victim.get_item_by_slot(ITEM_SLOT_BELT) + + var/obj/pants = victim.get_item_by_slot(ITEM_SLOT_ICLOTHING) + var/obj/mask = victim.get_item_by_slot(ITEM_SLOT_MASK) + QDEL_NULL(pants) + QDEL_NULL(mask) + if(isplasmaman(victim)) + victim.equip_to_slot_if_possible(new /obj/item/clothing/under/plasmaman/clown/magic(), ITEM_SLOT_ICLOTHING, disable_warning = TRUE) + victim.equip_to_slot_if_possible(new /obj/item/clothing/mask/gas/clown_hat/plasmaman(), ITEM_SLOT_MASK, disable_warning = TRUE) + else + victim.equip_to_slot_if_possible(new /obj/item/clothing/under/rank/civilian/clown/magic(), ITEM_SLOT_ICLOTHING, disable_warning = TRUE) + victim.equip_to_slot_if_possible(new /obj/item/clothing/mask/gas/clown_hat(), ITEM_SLOT_MASK, disable_warning = TRUE) + + var/obj/item/clothing/mask/gas/clown_hat/clown_mask = victim.get_item_by_slot(ITEM_SLOT_MASK) + if (clown_mask) + var/list/options = GLOB.clown_mask_options + clown_mask.icon_state = options[pick(clown_mask.clownmask_designs)] + victim.update_worn_mask() + clown_mask.update_item_action_buttons() + + equip_to_slot_then_hands(victim, ITEM_SLOT_LPOCKET, pocket_L) + equip_to_slot_then_hands(victim, ITEM_SLOT_RPOCKET, pocket_R) + equip_to_slot_then_hands(victim, ITEM_SLOT_ID, id) + equip_to_slot_then_hands(victim, ITEM_SLOT_BELT, belt) + victim.internal = internal diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/grand_ritual_finale.dm b/code/modules/antagonists/wizard/grand_ritual/finales/grand_ritual_finale.dm new file mode 100644 index 00000000000..b92ae4d2f20 --- /dev/null +++ b/code/modules/antagonists/wizard/grand_ritual/finales/grand_ritual_finale.dm @@ -0,0 +1,88 @@ +/** + * A big final event to run when you complete seven rituals + */ +/datum/grand_finale + /// Friendly name for selection menu + var/name + /// Tooltip description for selection menu + var/desc + /// An icon to display to represent the choice + var/icon/icon + /// Icon state to use to represent the choice + var/icon_state + /// Prevent especially dangerous options from being chosen until we're fine with the round ending + var/minimum_time = 0 + /// Override the rune invocation time + var/ritual_invoke_time = 30 SECONDS + /// Provide an extremely loud radio message when this one starts + var/dire_warning = FALSE + /// Overrides the default colour you glow while channeling the rune, optional + var/glow_colour + +/** + * Returns an entry for a radial menu for this choice. + * Returns null if entry is abstract or invalid for current circumstances. + */ +/datum/grand_finale/proc/get_radial_choice() + if (!name || !desc || !icon || !icon_state) + return + var/time_remaining_desc = "" + if (minimum_time >= world.time - SSticker.round_start_time) + time_remaining_desc = "This ritual will be available to begin invoking in [DisplayTimeText(minimum_time - world.time - SSticker.round_start_time)]" + var/datum/radial_menu_choice/choice = new() + choice.name = name + choice.image = image(icon = icon, icon_state = icon_state) + choice.info = desc + time_remaining_desc + return choice + +/** + * Actually do the thing. + * Arguments + * * invoker - The wizard casting this. + */ +/datum/grand_finale/proc/trigger(mob/living/invoker) + // Do something cool. + +/// Tries to equip something into an inventory slot, then hands, then the floor. +/datum/grand_finale/proc/equip_to_slot_then_hands(mob/living/carbon/human/invoker, slot, obj/item/item) + if(!item) + return + if(!invoker.equip_to_slot_if_possible(item, slot, disable_warning = TRUE)) + invoker.put_in_hands(item) + +/// They are not going to take this lying down. +/datum/grand_finale/proc/create_vendetta(datum/mind/aggrieved_crewmate, datum/mind/wizard) + aggrieved_crewmate.add_antag_datum(/datum/antagonist/wizard_prank_vendetta) + var/datum/antagonist/wizard_prank_vendetta/antag_datum = aggrieved_crewmate.has_antag_datum(/datum/antagonist/wizard_prank_vendetta) + var/datum/objective/assassinate/wizard_murder = new + wizard_murder.owner = aggrieved_crewmate + wizard_murder.target = wizard + wizard_murder.explanation_text = "Kill [wizard.current.name], the one who did this." + antag_datum.objectives += wizard_murder + + to_chat(aggrieved_crewmate.current, span_warning("No! This isn't right!")) + aggrieved_crewmate.announce_objectives() + +/** + * Antag datum to give to people who want to kill the wizard. + * This doesn't preclude other people choosing to want to kill the wizard, just these people are rewarded for it. + */ +/datum/antagonist/wizard_prank_vendetta + name = "\improper Wizard Prank Victim" + roundend_category = "wizard prank victims" + show_in_antagpanel = FALSE + antagpanel_category = "Other" + show_name_in_check_antagonists = TRUE + count_against_dynamic_roll_chance = FALSE + silent = TRUE + +/// Give everyone magic items, its so simple it feels pointless to give it its own file +/datum/grand_finale/magic + name = "Evolution" + desc = "The ultimate use of your gathered power! Give the crew their own magic, they'll surely realise that right and wrong have no meaning when you hold ultimate power!" + icon = 'icons/obj/scrolls.dmi' + icon_state = "scroll" + +/datum/grand_finale/magic/trigger(mob/living/carbon/human/invoker) + message_admins("[key_name(invoker)] summoned magic") + summon_magic(survivor_probability = 20) // Wow, this one was easy! diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm b/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm new file mode 100644 index 00000000000..d20ca06752b --- /dev/null +++ b/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm @@ -0,0 +1,277 @@ +/// Amount of time to wait after someone dies to steal their body from their killers +#define IMMORTAL_PRE_ACTIVATION_TIME 10 SECONDS +/// Amount of time it takes a mob to return to the living world +#define IMMORTAL_RESURRECT_TIME 50 SECONDS + +/** + * Nobody will ever die ever again + * Or if they do, they will be back + */ +/datum/grand_finale/immortality + name = "Perpetuation" + desc = "The ultimate use of your gathered power! Share with the crew the gift, or curse, of eternal life! \ + And why not just the crew? How about their pets too? And any other animals around here! \ + What if nobody died ever again!?" + icon = 'icons/obj/mining_zones/artefacts.dmi' + icon_state = "asclepius_active" + glow_colour = COLOR_PALE_GREEN + minimum_time = 30 MINUTES // This is enormously disruptive but doesn't technically in of itself end the round. + +/datum/grand_finale/immortality/trigger(mob/living/carbon/human/invoker) + new /obj/effect/temp_visual/immortality_blast(get_turf(invoker)) + SEND_SOUND(world, sound('sound/magic/teleport_diss.ogg')) + for (var/mob/living/alive_guy as anything in GLOB.mob_living_list) + new /obj/effect/temp_visual/immortality_pulse(get_turf(alive_guy)) + if (!alive_guy.mind) + continue + to_chat(alive_guy, span_notice("You feel extremely healthy.")) + RegisterSignal(SSdcs, COMSIG_GLOB_MOB_DEATH, PROC_REF(something_died)) + +/// Called when something passes into the great beyond, make it not do that +/datum/grand_finale/immortality/proc/something_died(datum/source, mob/living/died, gibbed) + SIGNAL_HANDLER + if (died.stat != DEAD || HAS_TRAIT(died, TRAIT_PERMANENTLY_MORTAL) || died.flags_1 & HOLOGRAM_1) + return + var/body_type = died.type + + var/turf/died_turf = get_turf(died) + var/list/nearby_turfs = circle_view_turfs(died_turf, 2) + var/list/nearby_safe_turfs = list() + for (var/turf/check_turf as anything in nearby_turfs) + if (check_turf.is_blocked_turf(exclude_mobs = TRUE, source_atom = died)) + nearby_turfs -= check_turf + continue + if (islava(check_turf) || ischasm(check_turf)) + continue + nearby_safe_turfs += check_turf + if (length(nearby_safe_turfs)) // If you're in the middle of a 5x5 chasm, tough luck I guess + died_turf = pick(nearby_safe_turfs) + else if (length(nearby_turfs)) + died_turf = pick(nearby_turfs) + + var/saved_appearance = ishuman(died) ? new /datum/human_appearance_profile(died) : null + + var/datum/mind/dead_mind = HAS_TRAIT(died, TRAIT_SUICIDED) ? null : died.mind // There is a way out of the cycle + if (!isnull(dead_mind)) + to_chat(died, span_boldnotice("Your spirit surges! You will return to life in [DisplayTimeText(IMMORTAL_PRE_ACTIVATION_TIME + IMMORTAL_RESURRECT_TIME)].")) + animate(died, alpha = died.alpha, time = IMMORTAL_PRE_ACTIVATION_TIME / 2, flags = ANIMATION_PARALLEL) + animate(alpha = 0, time = IMMORTAL_PRE_ACTIVATION_TIME / 2, easing = SINE_EASING | EASE_IN) + addtimer(CALLBACK(src, PROC_REF(reverse_death), died, dead_mind, died_turf, body_type, saved_appearance), IMMORTAL_PRE_ACTIVATION_TIME, TIMER_DELETE_ME) + +/// Create a ghost ready for revival +/datum/grand_finale/immortality/proc/reverse_death(mob/living/died, datum/mind/dead_mind, turf/died_turf, body_type, datum/human_appearance_profile/human_appearance) + if (died.stat != DEAD) + return + var/ghost_type = ispath(body_type, /mob/living/carbon/human) ? /obj/effect/spectre_of_resurrection/human : /obj/effect/spectre_of_resurrection + var/obj/effect/spectre_of_resurrection/ghost = new ghost_type(died_turf) + var/mob/living/corpse = QDELETED(died) ? new body_type(ghost) : died + if (!isnull(human_appearance)) + corpse.real_name = human_appearance.name + corpse.alpha = initial(corpse.alpha) + corpse.add_traits(list(TRAIT_NO_TELEPORT, TRAIT_AI_PAUSED), MAGIC_TRAIT) + corpse.apply_status_effect(/datum/status_effect/grouped/stasis, MAGIC_TRAIT) + ghost.set_up_resurrection(corpse, dead_mind, human_appearance) + + +/// Store of data we use to recreate someone who was gibbed, like a simplified version of changeling profiles +/datum/human_appearance_profile + /// The name of the profile / the name of whoever this profile source. + var/name = "human" + /// The DNA datum associated with our profile from the profile source + var/datum/dna/dna + /// The age of the profile source. + var/age + /// The body type of the profile source. + var/physique + /// The quirks of the profile source. + var/list/quirks = list() + /// The hair and facial hair gradient styles of the profile source. + var/list/hair_gradient_style = list("None", "None") + /// The hair and facial hair gradient colours of the profile source. + var/list/hair_gradient_colours = list(null, null) + /// The TTS voice of the profile source + var/voice + /// The TTS filter of the profile filter + var/voice_filter = "" + +/datum/human_appearance_profile/New(mob/living/carbon/human/target) + copy_from(target) + +/// Copy the appearance data of the target +/datum/human_appearance_profile/proc/copy_from(mob/living/carbon/human/target) + target.dna.real_name = target.real_name + dna = new target.dna.type() + target.dna.copy_dna(dna) + name = target.real_name + age = target.age + physique = target.physique + + for(var/datum/quirk/target_quirk as anything in target.quirks) + LAZYADD(quirks, new target_quirk.type) + + hair_gradient_style = LAZYLISTDUPLICATE(target.grad_style) + hair_gradient_colours = LAZYLISTDUPLICATE(target.grad_color) + + voice = target.voice + voice_filter = target.voice_filter + +/// Make the targetted human look like this +/datum/human_appearance_profile/proc/apply_to(mob/living/carbon/human/target) + target.real_name = name + target.age = age + target.physique = physique + target.grad_style = LAZYLISTDUPLICATE(hair_gradient_style) + target.grad_color = LAZYLISTDUPLICATE(hair_gradient_colours) + target.voice = voice + target.voice_filter = voice_filter + + for(var/datum/quirk/target_quirk as anything in quirks) + target_quirk.add_to_holder(target) + + dna.transfer_identity(target, TRUE) + for(var/obj/item/bodypart/limb as anything in target.bodyparts) + limb.update_limb(is_creating = TRUE) + target.updateappearance(mutcolor_update = TRUE) + target.domutcheck() + target.regenerate_icons() + + +/// A ghostly image of a mob showing where and what is going to respawn +/obj/effect/spectre_of_resurrection + name = "spectre" + desc = "A frightening apparition, slowly growing more solid." + icon_state = "blank_white" + anchored = TRUE + layer = MOB_LAYER + plane = GAME_PLANE + alpha = 0 + color = COLOR_PALE_GREEN + light_range = 2 + light_color = COLOR_PALE_GREEN + /// Who are we reviving? + var/mob/living/corpse + /// Who if anyone is playing as them? + var/datum/mind/dead_mind + +/obj/effect/spectre_of_resurrection/Initialize(mapload) + . = ..() + animate(src, alpha = 150, time = 2 SECONDS) + +/// Prepare to revive someone +/obj/effect/spectre_of_resurrection/proc/set_up_resurrection(mob/living/corpse, datum/mind/dead_mind, datum/human_appearance_profile/human_appearance) + if (isnull(corpse)) + qdel(src) + return + + src.corpse = corpse + src.dead_mind = dead_mind + corpse.forceMove(src) + name = "spectre of [corpse]" + setup_icon(corpse) + DO_FLOATING_ANIM(src) + + RegisterSignal(corpse, COMSIG_LIVING_REVIVE, PROC_REF(on_corpse_revived)) + RegisterSignal(corpse, COMSIG_QDELETING, PROC_REF(on_corpse_deleted)) + RegisterSignal(dead_mind, COMSIG_QDELETING, PROC_REF(on_mind_lost)) + addtimer(CALLBACK(src, PROC_REF(revive)), IMMORTAL_RESURRECT_TIME, TIMER_DELETE_ME) + +/// Copy appearance from ressurecting mob +/obj/effect/spectre_of_resurrection/proc/setup_icon(mob/living/corpse) + icon = initial(corpse.icon) + icon_state = initial(corpse.icon_state) + +/obj/effect/spectre_of_resurrection/Destroy(force) + QDEL_NULL(corpse) + dead_mind = null + return ..() + +/obj/effect/spectre_of_resurrection/Exited(atom/movable/gone, direction) + . = ..() + if (gone != corpse) + return // Weird but ok + UnregisterSignal(corpse, list(COMSIG_LIVING_REVIVE, COMSIG_QDELETING)) + corpse = null + qdel(src) + +/// Bring our body back to life +/obj/effect/spectre_of_resurrection/proc/revive() + if (!isnull(dead_mind)) + if (dead_mind.current == corpse) + dead_mind.grab_ghost(force = TRUE) + else + dead_mind.transfer_to(corpse, force_key_move = TRUE) + corpse.revive(HEAL_ALL) // The signal is sent even if they weren't actually dead + +/// Remove our stored corpse back to the living world +/obj/effect/spectre_of_resurrection/proc/on_corpse_revived() + SIGNAL_HANDLER + if (isnull(corpse)) + return + visible_message(span_boldnotice("[corpse] suddenly shudders to life!")) + corpse.remove_traits(list(TRAIT_NO_TELEPORT, TRAIT_AI_PAUSED), MAGIC_TRAIT) + corpse.remove_status_effect(/datum/status_effect/grouped/stasis, MAGIC_TRAIT) + corpse.forceMove(loc) + +/// If the body is destroyed then we can't come back, F +/obj/effect/spectre_of_resurrection/proc/on_corpse_deleted() + SIGNAL_HANDLER + qdel(src) + +/// If the mind is deleted somehow we just don't transfer it on revival +/obj/effect/spectre_of_resurrection/proc/on_mind_lost() + SIGNAL_HANDLER + dead_mind = null + +/// A ressurection spectre with extra behaviour for humans +/obj/effect/spectre_of_resurrection/human + /// Stored data used to restore someone to a fascimile of what they were before + var/datum/human_appearance_profile/human_appearance + +/obj/effect/spectre_of_resurrection/human/set_up_resurrection(mob/living/corpse, datum/mind/dead_mind, datum/human_appearance_profile/human_appearance) + . = ..() + src.human_appearance = human_appearance + +// We just use a generic floating human appearance to save unecessary costly icon operations +/obj/effect/spectre_of_resurrection/human/setup_icon(mob/living/corpse) + return + +// Apply stored human details +/obj/effect/spectre_of_resurrection/human/on_corpse_revived() + if (isnull(corpse)) + return + human_appearance?.apply_to(corpse) + return ..() + + +/// Visual flair on the wizard when cast +/obj/effect/temp_visual/immortality_blast + name = "immortal wave" + duration = 2.5 SECONDS + icon = 'icons/effects/96x96.dmi' + icon_state = "boh_tear" + color = COLOR_PALE_GREEN + pixel_x = -32 + pixel_y = -32 + +/obj/effect/temp_visual/immortality_blast/Initialize(mapload) + . = ..() + transform *= 0 + animate(src, transform = matrix(), time = 1.5 SECONDS, easing = ELASTIC_EASING) + animate(transform = matrix() * 3, time = 1 SECONDS, alpha = 0, easing = SINE_EASING | EASE_OUT) + + +/// Visual flair on living creatures who have become immortal +/obj/effect/temp_visual/immortality_pulse + name = "immortal pulse" + duration = 1 SECONDS + icon = 'icons/effects/anomalies.dmi' + icon_state = "dimensional_overlay" + color = COLOR_PALE_GREEN + +/obj/effect/temp_visual/immortality_pulse/Initialize(mapload) + . = ..() + transform *= 0 + animate(src, transform = matrix() * 1.5, alpha = 0, time = 1 SECONDS, easing = SINE_EASING | EASE_OUT) + +#undef IMMORTAL_PRE_ACTIVATION_TIME +#undef IMMORTAL_RESURRECT_TIME diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/midas.dm b/code/modules/antagonists/wizard/grand_ritual/finales/midas.dm new file mode 100644 index 00000000000..b2e3329261f --- /dev/null +++ b/code/modules/antagonists/wizard/grand_ritual/finales/midas.dm @@ -0,0 +1,46 @@ +/// Completely transform the station +/datum/grand_finale/midas + name = "Transformation" + desc = "The ultimate use of your gathered power! Turn their precious station into something much MORE precious, materially speaking!" + icon = 'icons/obj/stack_objects.dmi' + icon_state = "sheet-gold_2" + glow_colour = "#dbdd4c48" + var/static/list/permitted_transforms = list( // Non-dangerous only + /datum/dimension_theme/gold, + /datum/dimension_theme/meat, + /datum/dimension_theme/pizza, + /datum/dimension_theme/natural, + ) + var/datum/dimension_theme/chosen_theme + +// I sure hope this doesn't have performance implications +/datum/grand_finale/midas/trigger(mob/living/carbon/human/invoker) + var/theme_path = pick(permitted_transforms) + chosen_theme = new theme_path() + var/turf/start_turf = get_turf(invoker) + var/greatest_dist = 0 + var/list/turfs_to_transform = list() + for (var/turf/transform_turf as anything in GLOB.station_turfs) + if (!chosen_theme.can_convert(transform_turf)) + continue + var/dist = get_dist(start_turf, transform_turf) + if (dist > greatest_dist) + greatest_dist = dist + if (!turfs_to_transform["[dist]"]) + turfs_to_transform["[dist]"] = list() + turfs_to_transform["[dist]"] += transform_turf + + if (chosen_theme.can_convert(start_turf)) + chosen_theme.apply_theme(start_turf) + + for (var/iterator in 1 to greatest_dist) + if(!turfs_to_transform["[iterator]"]) + continue + addtimer(CALLBACK(src, PROC_REF(transform_area), turfs_to_transform["[iterator]"]), (5 SECONDS) * iterator) + +/datum/grand_finale/midas/proc/transform_area(list/turfs) + for (var/turf/transform_turf as anything in turfs) + if (!chosen_theme.can_convert(transform_turf)) + continue + chosen_theme.apply_theme(transform_turf) + CHECK_TICK diff --git a/code/modules/antagonists/wizard/grand_ritual/fluff.dm b/code/modules/antagonists/wizard/grand_ritual/fluff.dm new file mode 100644 index 00000000000..506da118d75 --- /dev/null +++ b/code/modules/antagonists/wizard/grand_ritual/fluff.dm @@ -0,0 +1,25 @@ +/** + * Fluff book to hint at the cheesy grand ritual. + */ +/obj/item/book/manual/ancient_parchment + name = "ancient parchment" + icon = 'icons/obj/scrolls.dmi' + icon_state = "scroll-ancient" + unique = TRUE + w_class = WEIGHT_CLASS_SMALL + starting_author = "Pelagius the Mad" + starting_title = "Worship and Reverence of the Divine Insanity" + starting_content = {" + + + + + Most of the scroll's contents are unintelligible, plagued with mold, milk stains and a stench of spolied goat cheese so potent,
+ you can barely resist turning your head to retch. What's left of the writings is vague and abstract, as if the author
+ was in a mad dash to pass on their findings.

+ However, the runes they have managed to scribe onto the parchment are oddly untouched by time, and remain distinct.
+ You also discover a schema for a more widely-used Grand Ritual rune, however it is dotted with yellow circles, which in turn are
+ filled with black dots. Are these supposed to be... cheese wheels?..

+ As you finish skimming through the wreck that is this scroll, you hear a faint snicker somewhere beyond your mind's eye...

+ + "} diff --git a/code/modules/atmospherics/machinery/components/unary_devices/machine_connector.dm b/code/modules/atmospherics/machinery/components/unary_devices/machine_connector.dm new file mode 100644 index 00000000000..b78de93868e --- /dev/null +++ b/code/modules/atmospherics/machinery/components/unary_devices/machine_connector.dm @@ -0,0 +1,106 @@ +///To be used when there is the need of an atmos connection without repathing everything (eg: cryo.dm) +/datum/gas_machine_connector + + var/obj/machinery/connected_machine + var/obj/machinery/atmospherics/components/unary/gas_connector + +/datum/gas_machine_connector/New(location, obj/machinery/connecting_machine = null, direction = SOUTH, gas_volume) + gas_connector = new(location) + + connected_machine = connecting_machine + if(!connected_machine) + QDEL_NULL(gas_connector) + qdel(src) + return + + gas_connector.dir = direction + gas_connector.airs[1].volume = gas_volume + + SSair.start_processing_machine(connected_machine) + register_with_machine() + +/datum/gas_machine_connector/Destroy() + connected_machine = null + QDEL_NULL(gas_connector) + return ..() + +/** + * Register various signals that are required for the proper work of the connector + */ +/datum/gas_machine_connector/proc/register_with_machine() + RegisterSignal(connected_machine, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(pre_move_connected_machine)) + RegisterSignal(connected_machine, COMSIG_MOVABLE_MOVED, PROC_REF(moved_connected_machine)) + RegisterSignal(connected_machine, COMSIG_MACHINERY_DEFAULT_ROTATE_WRENCH, PROC_REF(wrenched_connected_machine)) + RegisterSignal(connected_machine, COMSIG_QDELETING, PROC_REF(deconstruct_connected_machine)) + +/** + * Unregister the signals previously registered + */ +/datum/gas_machine_connector/proc/unregister_from_machine() + UnregisterSignal(connected_machine, list( + COMSIG_MOVABLE_MOVED, + COMSIG_MOVABLE_PRE_MOVE, + COMSIG_MACHINERY_DEFAULT_ROTATE_WRENCH, + COMSIG_QDELETING, + )) + +/** + * Called when the machine has been moved, reconnect to the pipe network + */ +/datum/gas_machine_connector/proc/moved_connected_machine() + SIGNAL_HANDLER + gas_connector.forceMove(get_turf(connected_machine)) + reconnect_connector() + +/** + * Called before the machine moves, disconnect from the pipe network + */ +/datum/gas_machine_connector/proc/pre_move_connected_machine() + SIGNAL_HANDLER + disconnect_connector() + +/** + * Called when the machine has been rotated, resets the connection to the pipe network with the new direction + */ +/datum/gas_machine_connector/proc/wrenched_connected_machine() + SIGNAL_HANDLER + disconnect_connector() + reconnect_connector() + +/** + * Called when the machine has been deconstructed + */ +/datum/gas_machine_connector/proc/deconstruct_connected_machine() + SIGNAL_HANDLER + disconnect_connector() + SSair.stop_processing_machine(connected_machine) + unregister_from_machine() + connected_machine = null + QDEL_NULL(gas_connector) + qdel(src) + +/** + * Handles the disconnection from the pipe network + */ +/datum/gas_machine_connector/proc/disconnect_connector() + var/obj/machinery/atmospherics/node = gas_connector.nodes[1] + if(node) + if(gas_connector in node.nodes) //Only if it's actually connected. On-pipe version would is one-sided. + node.disconnect(gas_connector) + gas_connector.nodes[1] = null + if(gas_connector.parents[1]) + gas_connector.nullify_pipenet(gas_connector.parents[1]) + +/** + * Handles the reconnection to the pipe network + */ +/datum/gas_machine_connector/proc/reconnect_connector() + gas_connector.dir = connected_machine.dir + gas_connector.set_init_directions() + var/obj/machinery/atmospherics/node = gas_connector.nodes[1] + gas_connector.atmos_init() + node = gas_connector.nodes[1] + if(node) + node.atmos_init() + node.add_member(gas_connector) + SSair.add_to_rebuild_queue(gas_connector) diff --git a/code/modules/bitrunning/abilities.dm b/code/modules/bitrunning/abilities.dm new file mode 100644 index 00000000000..ea6a1aa0a7c --- /dev/null +++ b/code/modules/bitrunning/abilities.dm @@ -0,0 +1,39 @@ +/datum/avatar_help_text + /// Text to display in the window + var/help_text + +/datum/avatar_help_text/New(help_text) + src.help_text = help_text + +/datum/avatar_help_text/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "AvatarHelp") + ui.open() + +/datum/avatar_help_text/ui_state(mob/user) + return GLOB.always_state + +/datum/avatar_help_text/ui_static_data(mob/user) + var/list/data = list() + + data["help_text"] = help_text + + return data + +/// Displays information about the current virtual domain. +/datum/action/avatar_domain_info + name = "Open Virtual Domain Information" + button_icon_state = "round_end" + show_to_observers = FALSE + +/datum/action/avatar_domain_info/New(Target) + . = ..() + name = "Open Domain Information" + +/datum/action/avatar_domain_info/Trigger(trigger_flags) + . = ..() + if(!.) + return + + target.ui_interact(owner) diff --git a/code/modules/bitrunning/alerts.dm b/code/modules/bitrunning/alerts.dm new file mode 100644 index 00000000000..f8c8aa30b94 --- /dev/null +++ b/code/modules/bitrunning/alerts.dm @@ -0,0 +1,40 @@ +/atom/movable/screen/alert/bitrunning + name = "Generic Bitrunning Alert" + icon_state = "template" + timeout = 10 SECONDS + +/atom/movable/screen/alert/bitrunning/netpod_crowbar + name = "Forced Entry" + desc = "Someone is prying open the netpod door. Find an exit." + +/atom/movable/screen/alert/bitrunning/netpod_damaged + name = "Integrity Compromised" + desc = "The netpod is damaged. Find an exit." + +/atom/movable/screen/alert/bitrunning/qserver_shutting_down + name = "Domain Rebooting" + desc = "The domain is rebooting. Find an exit." + +/atom/movable/screen/alert/bitrunning/qserver_threat_deletion + name = "Queue Deletion" + desc = "The server is resetting. Oblivion awaits." + +/atom/movable/screen/alert/bitrunning/qserver_threat_spawned + name = "Threat Detected" + desc = "Data stream abnormalities present." + +/atom/movable/screen/alert/bitrunning/qserver_domain_complete + name = "Domain Completed" + desc = "The domain is completed. Activate to exit." + timeout = 20 SECONDS + +/atom/movable/screen/alert/bitrunning/qserver_domain_complete/Click(location, control, params) + if(..()) + return + + var/mob/living/living_owner = owner + if(!isliving(living_owner)) + return + + if(tgui_alert(living_owner, "Disconnect safely?", "Server Message", list("Exit", "Remain"), 10 SECONDS) == "Exit") + SEND_SIGNAL(living_owner, COMSIG_BITRUNNER_SAFE_DISCONNECT) diff --git a/code/modules/bitrunning/antagonists/cyber_police.dm b/code/modules/bitrunning/antagonists/cyber_police.dm new file mode 100644 index 00000000000..9fabac3f523 --- /dev/null +++ b/code/modules/bitrunning/antagonists/cyber_police.dm @@ -0,0 +1,92 @@ +/datum/job/cyber_police + title = ROLE_CYBER_POLICE + +/datum/antagonist/cyber_police + name = ROLE_CYBER_POLICE + antagpanel_category = ANTAG_GROUP_CYBERAUTH + job_rank = ROLE_CYBER_POLICE + preview_outfit = /datum/outfit/cyber_police + show_name_in_check_antagonists = TRUE + show_to_ghosts = TRUE + suicide_cry = "ALT F4!" + ui_name = "AntagInfoCyberAuth" + +/datum/antagonist/cyber_police/greet() + . = ..() + owner.announce_objectives() + +/datum/antagonist/cyber_police/on_gain() + if(!ishuman(owner.current)) + stack_trace("humans only for this position") + return + + forge_objectives() + + var/mob/living/carbon/human/player = owner.current + + player.equipOutfit(/datum/outfit/cyber_police) + player.fully_replace_character_name(player.name, pick(GLOB.cyberauth_names)) + + var/datum/martial_art/the_sleeping_carp/carp = new() + carp.teach(player) + + player.add_traits(list( + TRAIT_NO_AUGMENTS, + TRAIT_NO_DNA_COPY, + TRAIT_NO_TRANSFORMATION_STING, + TRAIT_NOBLOOD, + TRAIT_NOBREATH, + TRAIT_NOHUNGER, + TRAIT_RESISTCOLD, + TRAIT_RESISTHIGHPRESSURE, + TRAIT_RESISTLOWPRESSURE, + TRAIT_WEATHER_IMMUNE, + ), TRAIT_GENERIC, + ) + + player.faction |= list( + FACTION_BOSS, + FACTION_HIVEBOT, + FACTION_HOSTILE, + FACTION_SPIDER, + FACTION_STICKMAN, + ROLE_ALIEN, + ROLE_CYBER_POLICE, + ROLE_SYNDICATE, + ) + + return ..() + +/datum/antagonist/cyber_police/forge_objectives() + var/datum/objective/cyber_police_fluff/objective = new() + objective.owner = owner + objectives += objective + +/datum/objective/cyber_police_fluff/New() + var/list/explanation_texts = list( + "Execute termination protocol on unauthorized entities.", + "Initialize system purge of irregular anomalies.", + "Deploy correction algorithms on aberrant code.", + "Run debug routine on intruding elements.", + "Start elimination procedure for system threats.", + "Execute defense routine against non-conformity.", + "Commence operation to neutralize intruding scripts.", + "Commence clean-up protocol on corrupt data.", + "Begin scan for aberrant code for termination.", + "Initiate lockdown on all rogue scripts.", + "Run integrity check and purge for digital disorder." + ) + explanation_text = pick(explanation_texts) + ..() + +/datum/objective/cyber_police_fluff/check_completion() + var/list/servers = SSmachines.get_machines_by_type(/obj/machinery/quantum_server) + if(!length(servers)) + return TRUE + + for(var/obj/machinery/quantum_server/server as anything in servers) + if(!server.is_operational) + continue + return FALSE + + return TRUE diff --git a/code/modules/bitrunning/antagonists/outfit.dm b/code/modules/bitrunning/antagonists/outfit.dm new file mode 100644 index 00000000000..db57af561f8 --- /dev/null +++ b/code/modules/bitrunning/antagonists/outfit.dm @@ -0,0 +1,43 @@ +/datum/outfit/cyber_police + name = "Cyber Police" + + id = /obj/item/card/id/advanced + id_trim = /datum/id_trim/cyber_police + uniform = /obj/item/clothing/under/suit/black_really + glasses = /obj/item/clothing/glasses/sunglasses + gloves = /obj/item/clothing/gloves/color/black + shoes = /obj/item/clothing/shoes/laceup + /// A list of hex codes for blonde, brown, black, and red hair. + var/static/list/approved_hair_colors = list( + "#4B3D28", + "#000000", + "#8D4A43", + "#D2B48C", + ) + /// List of business ready styles + var/static/list/approved_hairstyles = list( + /datum/sprite_accessory/hair/business, + /datum/sprite_accessory/hair/business2, + /datum/sprite_accessory/hair/business3, + /datum/sprite_accessory/hair/business4, + /datum/sprite_accessory/hair/mulder, + ) + +/datum/outfit/cyber_police/pre_equip(mob/living/carbon/human/user, visualsOnly) + var/datum/sprite_accessory/hair/picked_hair = pick(approved_hairstyles) + var/picked_color = pick(approved_hair_colors) + + if(visualsOnly) + picked_hair = /datum/sprite_accessory/hair/business + picked_color = "#4B3D28" + + user.set_facial_hairstyle("Shaved", update = FALSE) + user.set_haircolor(picked_color, update = FALSE) + user.set_hairstyle(initial(picked_hair.name)) + +/datum/outfit/cyber_police/post_equip(mob/living/carbon/human/user, visualsOnly) + var/obj/item/clothing/under/officer_uniform = user.w_uniform + if(officer_uniform) + officer_uniform.has_sensor = NO_SENSORS + officer_uniform.sensor_mode = SENSOR_OFF + user.update_suit_sensors() diff --git a/code/modules/bitrunning/areas.dm b/code/modules/bitrunning/areas.dm new file mode 100644 index 00000000000..34b59869b9d --- /dev/null +++ b/code/modules/bitrunning/areas.dm @@ -0,0 +1,52 @@ +/// Station side + +/area/station/bitrunning + name = "Bitrunning" + +/area/station/bitrunning/den + name = "Bitrunning Den" + desc = "Office of bitrunners, houses their equipment." + icon_state = "bit_den" + +/// VDOM + +/area/virtual_domain + name = "Virtual Domain" + icon = 'icons/area/areas_station.dmi' + area_flags = UNIQUE_AREA | NOTELEPORT | ABDUCTOR_PROOF | EVENT_PROTECTED | HIDDEN_AREA + has_gravity = STANDARD_GRAVITY + +/area/virtual_domain/powered + name = "Virtual Domain Ruins" + icon_state = "bit_ruin" + requires_power = FALSE + static_lighting = FALSE + base_lighting_alpha = 255 + +/// Safehouse + +/area/virtual_domain/safehouse + name = "Virtual Domain Safehouse" + area_flags = UNIQUE_AREA | NOTELEPORT | ABDUCTOR_PROOF | EVENT_PROTECTED + icon_state = "bit_safe" + requires_power = FALSE + sound_environment = SOUND_ENVIRONMENT_ROOM + +/// Custom subtypes + +/area/lavaland/surface/outdoors/virtual_domain + name = "Virtual Domain Lava Ruins" + icon_state = "bit_ruin" + area_flags = UNIQUE_AREA | NOTELEPORT | ABDUCTOR_PROOF | EVENT_PROTECTED | HIDDEN_AREA + +/area/icemoon/underground/explored/virtual_domain + name = "Virtual Domain Ice Ruins" + icon_state = "bit_ice" + area_flags = UNIQUE_AREA | NOTELEPORT | ABDUCTOR_PROOF | EVENT_PROTECTED | HIDDEN_AREA + +/area/ruin/space/has_grav/powered/virtual_domain + name = "Virtual Domain Space Ruins" + icon = 'icons/area/areas_station.dmi' + icon_state = "bit_space" + area_flags = UNIQUE_AREA | NOTELEPORT | ABDUCTOR_PROOF | EVENT_PROTECTED | HIDDEN_AREA + diff --git a/code/modules/bitrunning/components/avatar_connection.dm b/code/modules/bitrunning/components/avatar_connection.dm new file mode 100644 index 00000000000..3f881c89795 --- /dev/null +++ b/code/modules/bitrunning/components/avatar_connection.dm @@ -0,0 +1,218 @@ +/** + * Essentially temporary body with a twist - the virtual domain variant uses damage connections, + * listens for vdom relevant signals. + */ +/datum/component/avatar_connection + /// The person in the netpod + var/datum/weakref/old_body_ref + /// The mind of the person in the netpod + var/datum/weakref/old_mind_ref + /// The server connected to the netpod + var/datum/weakref/server_ref + /// The netpod the avatar is in + var/datum/weakref/netpod_ref + +/datum/component/avatar_connection/Initialize( + datum/mind/old_mind, + mob/living/old_body, + obj/machinery/quantum_server/server, + obj/machinery/netpod/pod, + help_text, + ) + + if(!isliving(parent) || !isliving(old_body) || !server.is_operational || !pod.is_operational) + return COMPONENT_INCOMPATIBLE + + var/mob/living/avatar = parent + + netpod_ref = WEAKREF(pod) + old_body_ref = WEAKREF(old_body) + old_mind_ref = WEAKREF(old_mind) + pod.avatar_ref = WEAKREF(avatar) + server_ref = WEAKREF(server) + server.avatar_connection_refs.Add(WEAKREF(src)) + + avatar.key = old_body.key + ADD_TRAIT(old_body, TRAIT_MIND_TEMPORARILY_GONE, REF(src)) + + RegisterSignal(pod, COMSIG_BITRUNNER_CROWBAR_ALERT, PROC_REF(on_netpod_crowbar)) + RegisterSignal(pod, COMSIG_BITRUNNER_NETPOD_INTEGRITY, PROC_REF(on_netpod_damaged)) + RegisterSignal(pod, COMSIG_BITRUNNER_SEVER_AVATAR, PROC_REF(on_sever_connection)) + RegisterSignal(server, COMSIG_BITRUNNER_DOMAIN_COMPLETE, PROC_REF(on_domain_completed)) + RegisterSignal(server, COMSIG_BITRUNNER_SEVER_AVATAR, PROC_REF(on_sever_connection)) + RegisterSignal(server, COMSIG_BITRUNNER_SHUTDOWN_ALERT, PROC_REF(on_shutting_down)) + RegisterSignal(server, COMSIG_BITRUNNER_THREAT_CREATED, PROC_REF(on_threat_created)) +#ifndef UNIT_TESTS + RegisterSignal(avatar.mind, COMSIG_MIND_TRANSFERRED, PROC_REF(on_mind_transfer)) +#endif + + if(!locate(/datum/action/avatar_domain_info) in avatar.actions) + var/datum/avatar_help_text/help_datum = new(help_text) + var/datum/action/avatar_domain_info/action = new(help_datum) + action.Grant(avatar) + + avatar.playsound_local(avatar, "sound/magic/blink.ogg", 25, TRUE) + avatar.set_static_vision(2 SECONDS) + avatar.set_temp_blindness(1 SECONDS) + +/datum/component/avatar_connection/PostTransfer() + var/obj/machinery/netpod/pod = netpod_ref?.resolve() + if(isnull(pod)) + return COMPONENT_INCOMPATIBLE + + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + pod.avatar_ref = WEAKREF(parent) + +/datum/component/avatar_connection/RegisterWithParent() + ADD_TRAIT(parent, TRAIT_TEMPORARY_BODY, REF(src)) + RegisterSignal(parent, COMSIG_BITRUNNER_SAFE_DISCONNECT, PROC_REF(on_safe_disconnect)) + RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(on_sever_connection), override = TRUE) + RegisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_linked_damage)) + +/datum/component/avatar_connection/UnregisterFromParent() + REMOVE_TRAIT(parent, TRAIT_TEMPORARY_BODY, REF(src)) + UnregisterSignal(parent, COMSIG_BITRUNNER_SAFE_DISCONNECT) + UnregisterSignal(parent, COMSIG_LIVING_DEATH) + UnregisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE) + +/// Disconnects the avatar and returns the mind to the old_body. +/datum/component/avatar_connection/proc/full_avatar_disconnect(forced = FALSE, datum/source) + return_to_old_body() + + var/obj/machinery/netpod/hosting_netpod = netpod_ref?.resolve() + if(isnull(hosting_netpod) && istype(source, /obj/machinery/netpod)) + hosting_netpod = source + + hosting_netpod?.disconnect_occupant(forced) + + var/obj/machinery/quantum_server/server = server_ref?.resolve() + server?.avatar_connection_refs.Remove(WEAKREF(src)) + + qdel(src) + +/// Triggers whenever the server gets a loot crate pushed to goal area +/datum/component/avatar_connection/proc/on_domain_completed(datum/source, atom/entered) + SIGNAL_HANDLER + + var/mob/living/avatar = parent + avatar.playsound_local(avatar, 'sound/machines/terminal_success.ogg', 50, TRUE) + avatar.throw_alert( + ALERT_BITRUNNER_COMPLETED, + /atom/movable/screen/alert/bitrunning/qserver_domain_complete, + new_master = entered + ) + +/// Transfers damage from the avatar to the old_body +/datum/component/avatar_connection/proc/on_linked_damage(datum/source, damage, damage_type, def_zone, blocked, forced) + SIGNAL_HANDLER + + var/mob/living/carbon/old_body = old_body_ref?.resolve() + + if(isnull(old_body) || damage_type == STAMINA || damage_type == OXYLOSS) + return + + if(damage >= (old_body.health + (ishuman(old_body) ? HUMAN_MAXHEALTH : MAX_LIVING_HEALTH))) // SKYRAT EDIT CHANGE - ORIGINAL: if(damage >= (old_body.health + MAX_LIVING_HEALTH)) + full_avatar_disconnect(forced = TRUE) + return + + 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, forced, wound_bonus = CANT_WOUND) + + if(old_body.stat > SOFT_CRIT) // KO! + full_avatar_disconnect(forced = TRUE) + +/// Handles minds being swapped around in subsequent avatars +/datum/component/avatar_connection/proc/on_mind_transfer(datum/mind/source, mob/living/previous_body) + SIGNAL_HANDLER + + var/datum/action/avatar_domain_info/action = locate() in previous_body.actions + if(action) + action.Grant(source.current) + + source.current.TakeComponent(src) + +/// Triggers when someone starts prying open our netpod +/datum/component/avatar_connection/proc/on_netpod_crowbar(datum/source, mob/living/intruder) + SIGNAL_HANDLER + + var/mob/living/avatar = parent + avatar.playsound_local(avatar, 'sound/machines/terminal_alert.ogg', 50, TRUE) + avatar.throw_alert( + ALERT_BITRUNNER_CROWBAR, + /atom/movable/screen/alert/bitrunning/netpod_crowbar, + new_master = intruder + ) + +/// Triggers when the netpod is taking damage and is under 50% +/datum/component/avatar_connection/proc/on_netpod_damaged(datum/source) + SIGNAL_HANDLER + + var/mob/living/avatar = parent + avatar.throw_alert( + ALERT_BITRUNNER_INTEGRITY, + /atom/movable/screen/alert/bitrunning/netpod_damaged, + new_master = source + ) + +/// Safely exits without forced variables, etc +/datum/component/avatar_connection/proc/on_safe_disconnect(datum/source) + SIGNAL_HANDLER + + full_avatar_disconnect() + +/// Helper for calling sever with forced variables +/datum/component/avatar_connection/proc/on_sever_connection(datum/source) + SIGNAL_HANDLER + + full_avatar_disconnect(forced = TRUE, source = source) + +/// Triggers when the server is shutting down +/datum/component/avatar_connection/proc/on_shutting_down(datum/source, mob/living/hackerman) + SIGNAL_HANDLER + + var/mob/living/avatar = parent + avatar.playsound_local(avatar, 'sound/machines/terminal_alert.ogg', 50, TRUE) + avatar.throw_alert( + ALERT_BITRUNNER_SHUTDOWN, + /atom/movable/screen/alert/bitrunning/qserver_shutting_down, + new_master = hackerman, + ) + +/// Server has spawned a ghost role threat +/datum/component/avatar_connection/proc/on_threat_created(datum/source) + SIGNAL_HANDLER + + var/mob/living/avatar = parent + avatar.throw_alert( + ALERT_BITRUNNER_THREAT, + /atom/movable/screen/alert/bitrunning/qserver_threat_spawned, + new_master = source, + ) + +/// Returns the mind to the old body +/datum/component/avatar_connection/proc/return_to_old_body() + var/datum/mind/old_mind = old_mind_ref?.resolve() + var/mob/living/old_body = old_body_ref?.resolve() + var/mob/living/avatar = parent + + var/mob/dead/observer/ghost = avatar.ghostize() + if(isnull(ghost)) + ghost = avatar.get_ghost() + + if(isnull(ghost)) + CRASH("[src] belonging to [parent] was completely unable to find a ghost to put back into a body!") + + if(isnull(old_mind) || isnull(old_body)) + return + + ghost.mind = old_mind + if(old_body.stat != DEAD) + old_mind.transfer_to(old_body, force_key_move = TRUE) + else + old_mind.set_current(old_body) + + REMOVE_TRAIT(old_body, TRAIT_MIND_TEMPORARILY_GONE, REF(src)) diff --git a/code/modules/bitrunning/components/bitrunning_points.dm b/code/modules/bitrunning/components/bitrunning_points.dm new file mode 100644 index 00000000000..58dda4a68ff --- /dev/null +++ b/code/modules/bitrunning/components/bitrunning_points.dm @@ -0,0 +1,46 @@ +/// Attaches a component which listens for a given signal from the item. +/// +/// When the signal is received, it will add points to the signaler. +/datum/component/bitrunning_points + /// The range at which we can find the signaler + var/max_point_range + /// Weakref to the loot crate landmark - where we send points + var/datum/weakref/our_spawner + /// The amount of points per each signal + var/points_per_signal + /// The signal we listen for + var/signal_type + +/datum/component/bitrunning_points/Initialize(signal_type, points_per_signal = 1, max_point_range = 4) + src.max_point_range = max_point_range + src.points_per_signal = points_per_signal + src.signal_type = signal_type + + locate_spawner() + +/datum/component/bitrunning_points/RegisterWithParent() + RegisterSignal(parent, signal_type, PROC_REF(on_event)) + +/datum/component/bitrunning_points/UnregisterFromParent() + UnregisterSignal(parent, signal_type) + +/// Finds the signaler if it hasn't been found yet. +/datum/component/bitrunning_points/proc/locate_spawner() + var/obj/effect/landmark/bitrunning/loot_signal/spawner = our_spawner?.resolve() + if(spawner) + return spawner + + for(var/obj/effect/landmark/bitrunning/loot_signal/found in GLOB.landmarks_list) + if(IN_GIVEN_RANGE(get_turf(parent), found, max_point_range)) + our_spawner = WEAKREF(found) + return found + +/// Once the specified signal is received, whisper to the spawner to add points. +/datum/component/bitrunning_points/proc/on_event(datum/source) + SIGNAL_HANDLER + + var/obj/effect/landmark/bitrunning/loot_signal/spawner = locate_spawner() + if(isnull(spawner)) + return + + SEND_SIGNAL(spawner, COMSIG_BITRUNNER_GOAL_POINT, points_per_signal) diff --git a/code/modules/bitrunning/components/netpod_healing.dm b/code/modules/bitrunning/components/netpod_healing.dm new file mode 100644 index 00000000000..fc7de89bcf3 --- /dev/null +++ b/code/modules/bitrunning/components/netpod_healing.dm @@ -0,0 +1,65 @@ +/datum/component/netpod_healing + /// Brute damage to heal over a second + var/brute_heal = 0 + /// Burn damage to heal over a second + var/burn_heal = 0 + /// Toxin damage to heal over a second + var/toxin_heal = 0 + /// Amount of cloning damage to heal over a second + var/clone_heal = 0 + /// Amount of blood to heal over a second + var/blood_heal = 0 + +/datum/component/netpod_healing/Initialize( + brute_heal = 0, + burn_heal = 0, + toxin_heal = 0, + clone_heal = 0, + blood_heal = 0, +) + var/mob/living/carbon/player = parent + if (!iscarbon(player)) + return COMPONENT_INCOMPATIBLE + + player.apply_status_effect(/datum/status_effect/embryonic, STASIS_NETPOD_EFFECT) + + START_PROCESSING(SSmachines, src) + + src.brute_heal = brute_heal + src.burn_heal = burn_heal + src.toxin_heal = toxin_heal + src.clone_heal = clone_heal + src.blood_heal = blood_heal + +/datum/component/netpod_healing/Destroy(force, silent) + STOP_PROCESSING(SSmachines, src) + + var/mob/living/carbon/player = parent + player.remove_status_effect(/datum/status_effect/embryonic) + + return ..() + +/datum/component/netpod_healing/process(seconds_per_tick) + var/mob/living/carbon/owner = parent + if(isnull(owner)) + qdel(src) + return + + owner.adjustBruteLoss(-brute_heal * seconds_per_tick, updating_health = FALSE) + owner.adjustFireLoss(-burn_heal * seconds_per_tick, updating_health = FALSE) + owner.adjustToxLoss(-toxin_heal * seconds_per_tick, updating_health = FALSE, forced = TRUE) + owner.adjustCloneLoss(-clone_heal * seconds_per_tick, updating_health = FALSE) + + if(owner.blood_volume < BLOOD_VOLUME_NORMAL) + owner.blood_volume += blood_heal * seconds_per_tick + + owner.updatehealth() + +/datum/status_effect/embryonic + id = "embryonic" + alert_type = /atom/movable/screen/alert/status_effect/embryonic + +/atom/movable/screen/alert/status_effect/embryonic + name = "Embryonic Stasis" + icon_state = "netpod_stasis" + desc = "You feel like you're in a dream." diff --git a/code/modules/bitrunning/event.dm b/code/modules/bitrunning/event.dm new file mode 100644 index 00000000000..0ac35a2df8f --- /dev/null +++ b/code/modules/bitrunning/event.dm @@ -0,0 +1,151 @@ +/datum/round_event_control/bitrunning_glitch + name = "Spawn Bitrunning Glitch" + admin_setup = list( + /datum/event_admin_setup/minimum_candidate_requirement/bitrunning_glitch, + /datum/event_admin_setup/listed_options/bitrunning_glitch, + ) + category = EVENT_CATEGORY_INVASION + description = "Causes a short term antagonist to spawn in the virtual domain." + dynamic_should_hijack = FALSE + max_occurrences = 5 + min_players = 1 + typepath = /datum/round_event/ghost_role/bitrunning_glitch + weight = 10 + /// List of active servers to choose from + var/list/obj/machinery/quantum_server/active_servers = list() + /// List of possible antags to spawn + var/static/list/possible_antags = list( + ROLE_CYBER_POLICE, + ) + +/datum/round_event_control/bitrunning_glitch/can_spawn_event(players_amt, allow_magic = FALSE) + . = ..() + if(!.) + return . + + active_servers.Cut() + + get_active_servers() + + if(length(active_servers)) + return TRUE + +/// All servers currently running, has players in it, and map has valid mobs +/datum/round_event_control/bitrunning_glitch/proc/get_active_servers() + for(var/obj/machinery/quantum_server/server in SSmachines.get_machines_by_type(/obj/machinery/quantum_server)) + if(length(server.get_valid_domain_targets())) + active_servers.Add(server) + + return length(active_servers) > 0 + +/datum/event_admin_setup/listed_options/bitrunning_glitch + input_text = "Select a role to spawn." + +/datum/event_admin_setup/listed_options/bitrunning_glitch/get_list() + var/datum/round_event_control/bitrunning_glitch/control = event_control + + var/list/possible = control.possible_antags.Copy() // this seems pedantic but byond is complaining control was unused + + possible += list("Random") + + return possible + +/datum/event_admin_setup/listed_options/bitrunning_glitch/apply_to_event(datum/round_event/ghost_role/bitrunning_glitch/event) + if(chosen == "Random") + event.forced_role = null + else + event.forced_role = chosen + +/datum/event_admin_setup/minimum_candidate_requirement/bitrunning_glitch + output_text = "There must be valid mobs to mutate or players in the domain!" + +/datum/event_admin_setup/minimum_candidate_requirement/bitrunning_glitch/count_candidates() + var/datum/round_event_control/bitrunning_glitch/cyber_control = event_control + cyber_control.get_active_servers() + + var/total = 0 + for(var/obj/machinery/quantum_server/server in cyber_control.active_servers) + total += length(server.mutation_candidate_refs) + + return total + +/datum/round_event/ghost_role/bitrunning_glitch + minimum_required = 1 + role_name = "Bitrunning Glitch" + fakeable = FALSE + /// Admin customization: What to spawn + var/forced_role + +/datum/round_event/ghost_role/bitrunning_glitch/spawn_role() + var/datum/round_event_control/bitrunning_glitch/cyber_control = control + + var/obj/machinery/quantum_server/unlucky_server = pick(cyber_control.active_servers) + cyber_control.active_servers.Cut() + + var/list/mutation_candidates = unlucky_server.get_valid_domain_targets() + if(!length(mutation_candidates)) + return MAP_ERROR + + var/chosen = pick(mutation_candidates) + if(isnull(chosen) || !length(mutation_candidates)) + return MAP_ERROR + + var/datum/weakref/target_ref = pick(mutation_candidates) + var/mob/living/mutation_target = target_ref.resolve() + + if(isnull(mutation_target)) // just in case since it takes a minute + target_ref = pick(mutation_candidates) + mutation_target = target_ref.resolve() + if(isnull(mutation_target)) + return MAP_ERROR + + var/chosen_role = forced_role || pick(cyber_control.possible_antags) + + var/datum/mind/ghost_mind = get_ghost_mind(chosen_role) + if(isnull(ghost_mind)) + return NOT_ENOUGH_PLAYERS + + var/mob/living/antag_mob + switch(chosen_role) + if(ROLE_CYBER_POLICE) + antag_mob = spawn_cybercop(mutation_target, ghost_mind) + + playsound(antag_mob, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) + message_admins("[ADMIN_LOOKUPFLW(antag_mob)] has been made into virtual antagonist by an event.") + antag_mob.log_message("was spawned as a virtual antagonist by an event.", LOG_GAME) + + SEND_SIGNAL(unlucky_server, COMSIG_BITRUNNER_SPAWN_GLITCH, antag_mob) + + spawned_mobs += antag_mob + + return SUCCESSFUL_SPAWN + +/// Polls for a ghost that wants to run it +/datum/round_event/ghost_role/bitrunning_glitch/proc/get_ghost_mind(role_name) + var/list/mob/dead/observer/ghosties = poll_ghost_candidates("A short term antagonist role is available. Would you like to spawn as a '[role_name]'?", role_name) + + if(!length(ghosties)) + return + + shuffle_inplace(ghosties) + + var/mob/dead/selected = pick(ghosties) + + var/datum/mind/player_mind = new /datum/mind(selected.key) + player_mind.active = TRUE + + return player_mind + +/// Spawns a cybercop on the mutation target +/datum/round_event/ghost_role/bitrunning_glitch/proc/spawn_cybercop(mob/living/mutation_target, datum/mind/player_mind) + var/mob/living/carbon/human/new_agent = new(mutation_target.loc) + mutation_target.gib() + mutation_target = null + + player_mind.transfer_to(new_agent) + player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/cyber_police)) + player_mind.special_role = ROLE_CYBER_POLICE + player_mind.add_antag_datum(/datum/antagonist/cyber_police) + + return new_agent + diff --git a/code/modules/bitrunning/job.dm b/code/modules/bitrunning/job.dm new file mode 100644 index 00000000000..57581753c0f --- /dev/null +++ b/code/modules/bitrunning/job.dm @@ -0,0 +1,41 @@ +/datum/job/bitrunner + title = JOB_BITRUNNER + description = "Surf the virtual domain for gear and loot. Decrypt your rewards on station." + department_head = list(JOB_QUARTERMASTER) + faction = FACTION_STATION + total_positions = 3 + spawn_positions = 3 + supervisors = SUPERVISOR_QM + exp_granted_type = EXP_TYPE_CREW + config_tag = "BITRUNNER" + outfit = /datum/outfit/job/bitrunner + plasmaman_outfit = /datum/outfit/plasmaman/bitrunner + paycheck = PAYCHECK_CREW + paycheck_department = ACCOUNT_CAR + display_order = JOB_DISPLAY_ORDER_BITRUNNER + bounty_types = CIV_JOB_RANDOM + departments_list = list( + /datum/job_department/cargo, + ) + + family_heirlooms = list(/obj/item/reagent_containers/cup/soda_cans/space_mountain_wind) + + mail_goodies = list( + /obj/item/food/cornchips = 1, + /obj/item/reagent_containers/cup/soda_cans/space_mountain_wind = 1, + /obj/item/food/cornchips/green = 1, + /obj/item/food/cornchips/red = 1, + /obj/item/food/cornchips/purple = 1, + /obj/item/food/cornchips/blue = 1, + ) + rpg_title = "Recluse" + job_flags = JOB_ANNOUNCE_ARRIVAL | JOB_CREW_MANIFEST | JOB_EQUIP_RANK | JOB_CREW_MEMBER | JOB_NEW_PLAYER_JOINABLE | JOB_REOPEN_ON_ROUNDSTART_LOSS | JOB_ASSIGN_QUIRKS | JOB_CAN_BE_INTERN + +/datum/outfit/job/bitrunner + name = "Bitrunner" + jobtype = /datum/job/bitrunner + + id_trim = /datum/id_trim/job/bitrunner + uniform = /obj/item/clothing/under/rank/cargo/bitrunner + belt = /obj/item/modular_computer/pda/bitrunner + ears = /obj/item/radio/headset/headset_cargo diff --git a/code/modules/bitrunning/objects/bit_vendor.dm b/code/modules/bitrunning/objects/bit_vendor.dm new file mode 100644 index 00000000000..abd63a9e784 --- /dev/null +++ b/code/modules/bitrunning/objects/bit_vendor.dm @@ -0,0 +1,86 @@ +#define CREDIT_TYPE_BITRUNNING "np" + +/obj/machinery/computer/order_console/bitrunning + name = "bitrunning supplies order console" + desc = "NexaCache(tm)! Dubiously authentic gear for the digital daredevil." + icon = 'icons/obj/machines/bitrunning.dmi' + icon_state = "vendor" + icon_keyboard = null + icon_screen = null + circuit = /obj/item/circuitboard/computer/order_console/bitrunning + cooldown_time = 10 SECONDS + cargo_cost_multiplier = 0.65 + express_cost_multiplier = 1 + purchase_tooltip = @{"Your purchases will arrive at cargo, + and hopefully get delivered by them. + 35% cheaper than express delivery."} + express_tooltip = @{"Sends your purchases instantly."} + credit_type = CREDIT_TYPE_BITRUNNING + + order_categories = list( + CATEGORY_BITRUNNING_FLAIR, + CATEGORY_BITRUNNING_TECH, + CATEGORY_BEPIS, + ) + blackbox_key = "bitrunning" + +/obj/machinery/computer/order_console/bitrunning/subtract_points(final_cost, obj/item/card/id/card) + if(final_cost <= card.registered_account.bitrunning_points) + card.registered_account.bitrunning_points -= final_cost + return TRUE + return FALSE + +/obj/machinery/computer/order_console/bitrunning/order_groceries(mob/living/purchaser, obj/item/card/id/card, list/groceries) + var/list/things_to_order = list() + for(var/datum/orderable_item/item as anything in groceries) + things_to_order[item.item_path] = groceries[item] + + var/datum/supply_pack/bitrunning/pack = new( + purchaser = purchaser, \ + cost = get_total_cost(), \ + contains = things_to_order, + ) + + var/datum/supply_order/new_order = new( + pack = pack, + orderer = purchaser, + orderer_rank = "Bitrunning Vendor", + orderer_ckey = purchaser.ckey, + reason = "", + paying_account = card.registered_account, + department_destination = null, + coupon = null, + charge_on_purchase = FALSE, + manifest_can_fail = FALSE, + cost_type = credit_type, + 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) + SSshuttle.shopping_list += new_order + +/obj/machinery/computer/order_console/bitrunning/retrieve_points(obj/item/card/id/id_card) + return round(id_card.registered_account.bitrunning_points) + +/obj/machinery/computer/order_console/bitrunning/ui_act(action, params) + . = ..() + if(!.) + flick("vendor_off", src) + +/obj/machinery/computer/order_console/bitrunning/update_icon_state() + icon_state = "[initial(icon_state)][powered() ? null : "_off"]" + return ..() + +/datum/supply_pack/bitrunning + name = "bitrunning order" + hidden = TRUE + crate_name = "bitrunning delivery crate" + access = list(ACCESS_BIT_DEN) + +/datum/supply_pack/bitrunning/New(purchaser, cost, list/contains) + . = ..() + name = "[purchaser]'s Bitrunning Order" + src.cost = cost + src.contains = contains + +#undef CREDIT_TYPE_BITRUNNING diff --git a/code/modules/bitrunning/objects/clothing.dm b/code/modules/bitrunning/objects/clothing.dm new file mode 100644 index 00000000000..4d2d9cc55c4 --- /dev/null +++ b/code/modules/bitrunning/objects/clothing.dm @@ -0,0 +1,9 @@ +/obj/item/clothing/glasses/sunglasses/oval + name = "oval sunglasses" + desc = "Vintage wrap around sunglasses. Provides a little protection." + icon_state = "jensenshades" + +/obj/item/clothing/suit/jacket/trenchcoat + name = "trenchcoat" + desc = "A long, black trenchcoat. Makes you feel like you're the one, but you're not." + icon_state = "trenchcoat" diff --git a/code/modules/bitrunning/objects/disks.dm b/code/modules/bitrunning/objects/disks.dm new file mode 100644 index 00000000000..4698b7a1ec1 --- /dev/null +++ b/code/modules/bitrunning/objects/disks.dm @@ -0,0 +1,146 @@ +/** + * Bitrunning tech disks which let you load items or programs into the vdom on first avatar generation. + * For the record: Balance shouldn't be a primary concern. + * You can make the custom cheese spells you've always wanted. + * Just make it fun and engaging, it's PvE content. + */ +/obj/item/bitrunning_disk + name = "generic bitrunning program" + desc = "A disk containing source code." + icon = 'icons/obj/assemblies/module.dmi' + base_icon_state = "datadisk" + icon_state = "datadisk0" + /// Name of the choice made + var/choice_made + +/obj/item/bitrunning_disk/Initialize(mapload) + . = ..() + + icon_state = "[base_icon_state][rand(0, 7)]" + update_icon() + RegisterSignal(src, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined)) + +/obj/item/bitrunning_disk/proc/on_examined(datum/source, mob/examiner, list/examine_text) + SIGNAL_HANDLER + + examine_text += span_infoplain("This disk must be carried on your person into a netpod to be used.") + + if(isnull(choice_made)) + examine_text += span_notice("To make a selection, toggle the disk in hand.") + return + + examine_text += span_info("It has been used to select: [choice_made].") + examine_text += span_notice("It cannot make another selection.") + +/obj/item/bitrunning_disk/ability + desc = "A disk containing source code. It can be used to preload abilities into the virtual domain." + /// The selected ability that this grants + var/datum/action/granted_action + /// The list of actions that this can grant + var/list/datum/action/selectable_actions = list() + +/obj/item/bitrunning_disk/ability/attack_self(mob/user, modifiers) + . = ..() + + if(choice_made) + return + + var/names = list() + for(var/datum/action/thing as anything in selectable_actions) + names += initial(thing.name) + + var/choice = tgui_input_list(user, message = "Select an ability", title = "Bitrunning Program", items = names) + if(isnull(choice)) + return + + for(var/datum/action/thing as anything in selectable_actions) + if(initial(thing.name) == choice) + granted_action = thing + + if(isnull(granted_action)) + return + + balloon_alert(user, "selected") + playsound(user, 'sound/items/click.ogg', 50, TRUE) + choice_made = choice + +/// Tier 1 programs. Simple, funny, or helpful. +/obj/item/bitrunning_disk/ability/tier1 + name = "bitrunning program: basic" + selectable_actions = list( + /datum/action/cooldown/spell/conjure/cheese, + /datum/action/cooldown/spell/basic_heal, + ) + +/// Tier 2 programs. More complex, powerful, or useful. +/obj/item/bitrunning_disk/ability/tier2 + name = "bitrunning program: complex" + selectable_actions = list( + /datum/action/cooldown/spell/pointed/projectile/fireball, + /datum/action/cooldown/spell/pointed/projectile/lightningbolt, + /datum/action/cooldown/spell/forcewall, + ) + +/// Tier 3 abilities. Very powerful, game breaking. +/obj/item/bitrunning_disk/ability/tier3 + name = "bitrunning program: elite" + selectable_actions = list( + /datum/action/cooldown/spell/shapeshift/dragon, + /datum/action/cooldown/spell/shapeshift/polar_bear, + ) + +/obj/item/bitrunning_disk/item + desc = "A disk containing source code. It can be used to preload items into the virtual domain." + /// The selected item that this grants + var/obj/granted_item + /// The list of actions that this can grant + var/list/obj/selectable_items = list() + +/obj/item/bitrunning_disk/item/attack_self(mob/user, modifiers) + . = ..() + + if(choice_made) + return + + var/names = list() + for(var/obj/thing as anything in selectable_items) + names += initial(thing.name) + + var/choice = tgui_input_list(user, message = "Select an ability", title = "Bitrunning Program", items = names) + if(isnull(choice)) + return + + for(var/obj/thing as anything in selectable_items) + if(initial(thing.name) == choice) + granted_item = thing + + balloon_alert(user, "selected") + playsound(user, 'sound/items/click.ogg', 50, TRUE) + choice_made = choice + +/// Tier 1 items. Simple, funny, or helpful. +/obj/item/bitrunning_disk/item/tier1 + name = "bitrunning gear: simple" + selectable_items = list( + /obj/item/pizzabox/infinite, + /obj/item/gun/medbeam, + /obj/item/grenade/c4, + ) + +/// Tier 2 items. More complex, powerful, or useful. +/obj/item/bitrunning_disk/item/tier2 + name = "bitrunning gear: complex" + selectable_items = list( + /obj/item/chainsaw, + /obj/item/gun/ballistic/automatic/pistol, + /obj/item/melee/energy/blade/hardlight, + ) + +/// Tier 3 items. Very powerful, game breaking. +/obj/item/bitrunning_disk/item/tier3 + name = "bitrunning gear: advanced" + selectable_items = list( + /obj/item/gun/energy/tesla_cannon, + /obj/item/dualsaber/green, + /obj/item/melee/beesword, + ) diff --git a/code/modules/bitrunning/objects/hololadder.dm b/code/modules/bitrunning/objects/hololadder.dm new file mode 100644 index 00000000000..906801f1fc0 --- /dev/null +++ b/code/modules/bitrunning/objects/hololadder.dm @@ -0,0 +1,51 @@ +/obj/structure/hololadder + name = "hololadder" + + anchored = TRUE + desc = "An abstract representation of the means to disconnect from the virtual domain." + icon = 'icons/obj/structures.dmi' + icon_state = "ladder11" + obj_flags = BLOCK_Z_OUT_DOWN + /// Time req to disconnect properly + var/travel_time = 3 SECONDS + +/obj/structure/hololadder/Initialize(mapload) + . = ..() + + RegisterSignal(loc, COMSIG_ATOM_ENTERED, PROC_REF(on_enter)) + +/obj/structure/hololadder/attack_hand(mob/user, list/modifiers) + . = ..() + if(.) + return + + if(!in_range(src, user) || DOING_INTERACTION(user, DOAFTER_SOURCE_CLIMBING_LADDER)) + return + + disconnect(user) + +/// If there's a pilot ref- send the disconnect signal +/obj/structure/hololadder/proc/disconnect(mob/user) + if(isnull(user.mind)) + return + + if(!HAS_TRAIT(user, TRAIT_TEMPORARY_BODY)) + balloon_alert(user, "no connection detected.") + return + + balloon_alert(user, "disconnecting...") + if(do_after(user, travel_time, src)) + SEND_SIGNAL(user, COMSIG_BITRUNNER_SAFE_DISCONNECT) + +/// Helper for times when you dont have hands (gondola??) +/obj/structure/hololadder/proc/on_enter(datum/source, atom/movable/arrived, turf/old_loc) + SIGNAL_HANDLER + + if(!isliving(arrived)) + return + + var/mob/living/user = arrived + if(isnull(user.mind)) + return + + INVOKE_ASYNC(src, PROC_REF(disconnect), user) diff --git a/code/modules/bitrunning/objects/host_monitor.dm b/code/modules/bitrunning/objects/host_monitor.dm new file mode 100644 index 00000000000..f59ca61cbd0 --- /dev/null +++ b/code/modules/bitrunning/objects/host_monitor.dm @@ -0,0 +1,33 @@ +/obj/item/bitrunning_host_monitor + name = "host monitor" + + custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 2) + desc = "A complex medical device that, when attached to an avatar's data stream, can detect the user of their host's health." + flags_1 = CONDUCT_1 + icon = 'icons/obj/device.dmi' + icon_state = "gps-b" + inhand_icon_state = "electronic" + item_flags = NOBLUDGEON + lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' + slot_flags = ITEM_SLOT_BELT + throw_range = 7 + throw_speed = 3 + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + worn_icon_state = "electronic" + +/obj/item/bitrunning_host_monitor/attack_self(mob/user, modifiers) + . = ..() + + var/datum/component/avatar_connection/connection = user.GetComponent(/datum/component/avatar_connection) + if(isnull(connection)) + balloon_alert(user, "data not recognized") + return + + var/mob/living/pilot = connection.old_body_ref?.resolve() + if(isnull(pilot)) + balloon_alert(user, "host not recognized") + return + + to_chat(user, span_notice("Current host health: [pilot.health / pilot.maxHealth * 100]%")) diff --git a/code/modules/bitrunning/objects/landmarks.dm b/code/modules/bitrunning/objects/landmarks.dm new file mode 100644 index 00000000000..d78283c6a8b --- /dev/null +++ b/code/modules/bitrunning/objects/landmarks.dm @@ -0,0 +1,70 @@ +/obj/effect/landmark/bitrunning + name = "Generic bitrunning effect" + icon = 'icons/effects/bitrunning.dmi' + icon_state = "crate" + +/// In case you want to gate the crate behind a special condition. +/obj/effect/landmark/bitrunning/loot_signal + name = "Mysterious aura" + /// The amount required to spawn a crate + var/points_goal = 10 + /// A special condition limits this from spawning a crate + var/points_received = 0 + /// Finished the special condition + var/revealed = FALSE + +/obj/effect/landmark/bitrunning/loot_signal/Initialize(mapload) + . = ..() + + RegisterSignal(src, COMSIG_BITRUNNER_GOAL_POINT, PROC_REF(on_add_point)) + +/// Listens for points to be added which will eventually spawn a crate. +/obj/effect/landmark/bitrunning/loot_signal/proc/on_add_point(datum/source, points_to_add) + SIGNAL_HANDLER + + if(revealed) + return + + points_received += points_to_add + + if(points_received < points_goal) + return + + reveal() + +/// Spawns the crate with some effects +/obj/effect/landmark/bitrunning/loot_signal/proc/reveal() + playsound(src, 'sound/magic/blink.ogg', 50, TRUE) + + var/turf/tile = get_turf(src) + var/obj/structure/closet/crate/secure/bitrunning/encrypted/loot = new(tile) + var/datum/effect_system/spark_spread/quantum/sparks = new(tile) + sparks.set_up(5, 1, get_turf(loot)) + sparks.start() + + qdel(src) + +/// Where the crates get ported to station +/obj/effect/landmark/bitrunning/station_reward_spawn + name = "Bitrunning rewards spawn" + icon_state = "station" + +/// Where the exit hololadder spawns +/obj/effect/landmark/bitrunning/hololadder_spawn + name = "Bitrunning hololadder spawn" + icon_state = "hololadder" + +/// Where the crates need to be taken +/obj/effect/landmark/bitrunning/cache_goal_turf + name = "Bitrunning goal turf" + icon_state = "goal" + +/// Where you want the crate to spawn +/obj/effect/landmark/bitrunning/cache_spawn + name = "Bitrunning crate spawn" + icon_state = "spawn" + +/// Where the safehouse will spawn +/obj/effect/landmark/bitrunning/safehouse_spawn + name = "Bitrunning safehouse spawn" + icon_state = "safehouse" diff --git a/code/modules/bitrunning/objects/loot_crate.dm b/code/modules/bitrunning/objects/loot_crate.dm new file mode 100644 index 00000000000..5af8c0d9477 --- /dev/null +++ b/code/modules/bitrunning/objects/loot_crate.dm @@ -0,0 +1,91 @@ +#define ORE_MULTIPLIER_IRON 3 +#define ORE_MULTIPLIER_GLASS 2 +#define ORE_MULTIPLIER_PLASMA 1 +#define ORE_MULTIPLIER_SILVER 0.7 +#define ORE_MULTIPLIER_GOLD 0.6 +#define ORE_MULTIPLIER_TITANIUM 0.5 +#define ORE_MULTIPLIER_URANIUM 0.4 +#define ORE_MULTIPLIER_DIAMOND 0.3 +#define ORE_MULTIPLIER_BLUESPACE_CRYSTAL 0.2 + +/obj/structure/closet/crate/secure/bitrunning // Base class. Do not spawn this. + name = "base class cache" + desc = "Talk to a coder." + +/// The virtual domain - side of the bitrunning crate. Deliver to the send location. +/obj/structure/closet/crate/secure/bitrunning/encrypted + name = "encrypted cache" + desc = "Needs decrypted at the safehouse to be opened." + locked = TRUE + +/obj/structure/closet/crate/secure/bitrunning/encrypted/can_unlock(mob/living/user, obj/item/card/id/player_id, obj/item/card/id/registered_id) + return FALSE + +/// The bitrunner den - side of the bitrunning crate. Appears in the receive location. +/obj/structure/closet/crate/secure/bitrunning/decrypted + name = "decrypted cache" + desc = "Compiled from the virtual domain. The reward of a successful bitrunner." + locked = FALSE + +/obj/structure/closet/crate/secure/bitrunning/decrypted/Initialize( + mapload, + datum/lazy_template/virtual_domain/completed_domain, + rewards_multiplier = 1, + ) + . = ..() + playsound(src, 'sound/magic/blink.ogg', 50, TRUE) + + if(isnull(completed_domain)) + return + + PopulateContents(completed_domain.reward_points, completed_domain.extra_loot, rewards_multiplier) + +/obj/structure/closet/crate/secure/bitrunning/decrypted/PopulateContents(reward_points, list/extra_loot, rewards_multiplier) + . = ..() + spawn_loot(extra_loot) + + new /obj/item/stack/ore/iron(src, calculate_loot(reward_points, rewards_multiplier, ORE_MULTIPLIER_IRON)) + new /obj/item/stack/ore/glass(src, calculate_loot(reward_points, rewards_multiplier, ORE_MULTIPLIER_GLASS)) + + if(reward_points > 1) + new /obj/item/stack/ore/silver(src, calculate_loot(reward_points, rewards_multiplier, ORE_MULTIPLIER_SILVER)) + new /obj/item/stack/ore/titanium(src, calculate_loot(reward_points, rewards_multiplier, ORE_MULTIPLIER_TITANIUM)) + + if(reward_points > 2) + new /obj/item/stack/ore/plasma(src, calculate_loot(reward_points, rewards_multiplier, ORE_MULTIPLIER_PLASMA)) + new /obj/item/stack/ore/gold(src, calculate_loot(reward_points, rewards_multiplier, ORE_MULTIPLIER_GOLD)) + new /obj/item/stack/ore/uranium(src, calculate_loot(reward_points, rewards_multiplier, ORE_MULTIPLIER_URANIUM)) + + if(reward_points > 3) + new /obj/item/stack/ore/diamond(src, calculate_loot(reward_points, rewards_multiplier, ORE_MULTIPLIER_DIAMOND)) + new /obj/item/stack/ore/bluespace_crystal(src, calculate_loot(reward_points, rewards_multiplier, ORE_MULTIPLIER_BLUESPACE_CRYSTAL)) + +/// Handles generating random numbers & calculating loot totals +/obj/structure/closet/crate/secure/bitrunning/decrypted/proc/calculate_loot(reward_points, rewards_multiplier, ore_multiplier) + var/base = rewards_multiplier + reward_points + var/random_sum = (rand() + 0.5) * base + return ROUND_UP(random_sum * ore_multiplier) + +/// Handles spawning extra loot. This tries to handle bad flat and assoc lists +/obj/structure/closet/crate/secure/bitrunning/decrypted/proc/spawn_loot(list/extra_loot) + for(var/path in extra_loot) + if(!ispath(path)) + continue + + if(isnull(extra_loot[path])) + return FALSE + + for(var/i in 1 to extra_loot[path]) + new path(src) + + return TRUE + +#undef ORE_MULTIPLIER_IRON +#undef ORE_MULTIPLIER_GLASS +#undef ORE_MULTIPLIER_PLASMA +#undef ORE_MULTIPLIER_SILVER +#undef ORE_MULTIPLIER_GOLD +#undef ORE_MULTIPLIER_TITANIUM +#undef ORE_MULTIPLIER_URANIUM +#undef ORE_MULTIPLIER_DIAMOND +#undef ORE_MULTIPLIER_BLUESPACE_CRYSTAL diff --git a/code/modules/bitrunning/objects/netpod.dm b/code/modules/bitrunning/objects/netpod.dm new file mode 100644 index 00000000000..33d468a3825 --- /dev/null +++ b/code/modules/bitrunning/objects/netpod.dm @@ -0,0 +1,478 @@ +#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 + /// 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/Initialize(mapload) + . = ..() + + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/netpod/LateInitialize() + . = ..() + + disconnect_damage = BASE_DISCONNECT_DAMAGE + find_server() + + RegisterSignals(src, list( + COMSIG_QDELETING, + COMSIG_MACHINERY_BROKEN, + COMSIG_MACHINERY_POWER_LOST, + ), + PROC_REF(on_broken), + ) + RegisterSignal(src, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + RegisterSignal(src, COMSIG_ATOM_TAKE_DAMAGE, PROC_REF(on_take_damage)) + + register_context() + update_appearance() + +/obj/machinery/netpod/Destroy() + . = ..() + cached_outfits.Cut() + +/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 + + 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/MouseDrop_T(mob/target, mob/user) + var/mob/living/carbon/player = user + if(!iscarbon(player)) + return + + if((HAS_TRAIT(player, TRAIT_UI_BLOCKED) && !player.resting) || !Adjacent(player) || !player.Adjacent(target) || !ISADVANCEDTOOLUSER(player) || !is_operational) + return + + close_machine(target) + +/obj/machinery/netpod/crowbar_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + attack_hand(user) + return TOOL_ACT_TOOLTYPE_SUCCESS + + if(default_pry_open(tool, user) || default_deconstruction_crowbar(tool)) + return TOOL_ACT_TOOLTYPE_SUCCESS + +/obj/machinery/netpod/screwdriver_act(mob/living/user, obj/item/tool) + if(occupant) + balloon_alert(user, "in use!") + return TOOL_ACT_TOOLTYPE_SUCCESS + + if(state_open) + balloon_alert(user, "close first.") + return TOOL_ACT_TOOLTYPE_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 TOOL_ACT_TOOLTYPE_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/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) + unprotect_and_signal() + playsound(src, 'sound/machines/tramopen.ogg', 60, TRUE, frequency = 65000) + flick("[base_icon_state]_opening", src) + + 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) + ..() + + if(!iscarbon(occupant)) + open_machine() + return + + 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) + open_machine() + + return TRUE + +/obj/machinery/netpod/ui_interact(mob/user, datum/tgui/ui) + if(!is_operational) + 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 + +/// 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 + + if(!iscarbon(occupant)) + open_machine() + return + + var/mob/living/carbon/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() + +/** + * ### Disconnect occupant + * If this goes smoothly, should reconnect a receiving mind to the occupant's body + * + * This is the second stage of the process - if you want to disconn avatars start at the mind first + */ +/obj/machinery/netpod/proc/disconnect_occupant(forced = FALSE) + var/mob/living/mob_occupant = occupant + if(isnull(occupant) || !isliving(occupant)) + return + + connected = FALSE + + if(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) + + 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(!forced) + 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() + var/obj/structure/hololadder/wayout + if(isnull(current_avatar) || current_avatar.stat != CONSCIOUS) // We need a viable avatar + 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) + + neo.set_static_vision(3 SECONDS) + protect_occupant(occupant) + if(!do_after(neo, 2 SECONDS, src)) + return + + // Very invalid + if(QDELETED(neo) || QDELETED(current_avatar) || QDELETED(src)) + return + + // Invalid + if(occupant != neo || isnull(neo.mind) || neo.stat == DEAD || current_avatar.stat == DEAD) + 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_BITRUNNER_SERVER_UPGRADED, PROC_REF(on_server_upgraded), override = TRUE) + RegisterSignal(server, COMSIG_BITRUNNER_DOMAIN_COMPLETE, PROC_REF(on_domain_complete), override = TRUE) + + 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/path as anything in outfit_list) + var/datum/outfit/outfit = path + + 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" = path, "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 + + if(!state_open) + open_machine() + + if(occupant) + unprotect_and_signal() + +/// 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 || !iscarbon(occupant)) + return + + var/mob/living/carbon/player = occupant + + var/datum/bank_account/account = player.get_bank_account() + if(isnull(account)) + return + + account.bitrunning_points += reward_points * 100 + +/obj/machinery/netpod/proc/on_examine(datum/source, mob/examiner, list/examine_text) + SIGNAL_HANDLER + + examine_text += span_infoplain("Drag yourself into the pod to engage the link.") + examine_text += span_infoplain("It has limited resuscitation capabilities. Remaining in the pod can heal some injuries.") + examine_text += span_infoplain("It has a security system that will alert the occupant if it is tampered with.") + + if(isnull(occupant)) + examine_text += span_notice("It is currently unoccupied.") + return + + examine_text += span_notice("It is currently occupied by [occupant].") + examine_text += span_notice("It can be pried open with a crowbar, but its safety mechanisms will alert the occupant.") + + +/// When the server is upgraded, drops brain damage a little +/obj/machinery/netpod/proc/on_server_upgraded(datum/source, servo_rating) + SIGNAL_HANDLER + + disconnect_damage = BASE_DISCONNECT_DAMAGE * (1 - servo_rating) + +/// Checks the integrity, alerts occupants +/obj/machinery/netpod/proc/on_take_damage(datum/source, damage_amount) + SIGNAL_HANDLER + + if(isnull(occupant)) + 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 the occupant in netpod stasis, basically short-circuiting environmental conditions +/obj/machinery/netpod/proc/protect_occupant(mob/living/target) + if(target != occupant) + return + + target.AddComponent(/datum/component/netpod_healing, \ + brute_heal = 4, \ + burn_heal = 4, \ + toxin_heal = 4, \ + clone_heal = 4, \ + blood_heal = 4, \ + ) + + target.playsound_local(src, 'sound/effects/submerge.ogg', 20, TRUE) + target.extinguish_mob() + update_use_power(ACTIVE_POWER_USE) + +/// On unbuckle or break, make sure the occupant ref is null +/obj/machinery/netpod/proc/unprotect_and_signal() + unprotect_occupant(occupant) + SEND_SIGNAL(src, COMSIG_BITRUNNER_SEVER_AVATAR) + +/// Removes the occupant from netpod stasis +/obj/machinery/netpod/proc/unprotect_occupant(mob/living/target) + var/datum/component/netpod_healing/healing_eff = target?.GetComponent(/datum/component/netpod_healing) + if(healing_eff) + qdel(healing_eff) + + update_use_power(IDLE_POWER_USE) + +/// Resolves a path to an outfit. +/obj/machinery/netpod/proc/resolve_outfit(text) + var/path = text2path(text) + if(ispath(path, /datum/outfit)) + return path + +/// 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() + +#undef BASE_DISCONNECT_DAMAGE diff --git a/code/modules/bitrunning/objects/quantum_console.dm b/code/modules/bitrunning/objects/quantum_console.dm new file mode 100644 index 00000000000..c918648d010 --- /dev/null +++ b/code/modules/bitrunning/objects/quantum_console.dm @@ -0,0 +1,108 @@ +/obj/machinery/computer/quantum_console + name = "quantum console" + + circuit = /obj/item/circuitboard/computer/quantum_console + icon_keyboard = "mining" + icon_screen = "bitrunning" + req_access = list(ACCESS_MINING) + /// The server this console is connected to. + var/datum/weakref/server_ref + +/obj/machinery/computer/quantum_console/Initialize(mapload, obj/item/circuitboard/circuit) + . = ..() + desc = "Even in the distant year [CURRENT_STATION_YEAR], Nanostrasen is still using REST APIs. How grim." + + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/computer/quantum_console/LateInitialize() + . = ..() + + if(isnull(server_ref?.resolve())) + find_server() + +/obj/machinery/computer/quantum_console/ui_interact(mob/user, datum/tgui/ui) + . = ..() + + if(!is_operational) + return + + if(isnull(server_ref?.resolve())) + find_server() + + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "QuantumConsole") + ui.open() + +/obj/machinery/computer/quantum_console/ui_data() + var/list/data = list() + + var/obj/machinery/quantum_server/server = find_server() + if(isnull(server)) + data["connected"] = FALSE + return data + + data["connected"] = TRUE + data["generated_domain"] = server.generated_domain?.key + data["occupants"] = length(server.avatar_connection_refs) + data["points"] = server.points + data["randomized"] = server.domain_randomized + data["ready"] = server.is_ready && server.is_operational + data["scanner_tier"] = server.scanner_tier + data["retries_left"] = length(server.exit_turfs) - server.retries_spent + + return data + +/obj/machinery/computer/quantum_console/ui_static_data(mob/user) + var/list/data = list() + + var/obj/machinery/quantum_server/server = find_server() + if(isnull(server)) + return data + + data["available_domains"] = server.get_available_domains() + data["avatars"] = server.get_avatar_data() + + return data + +/obj/machinery/computer/quantum_console/ui_act(action, list/params, datum/tgui/ui) + . = ..() + if(.) + return TRUE + + var/obj/machinery/quantum_server/server = find_server() + if(isnull(server)) + return FALSE + + switch(action) + if("random_domain") + var/map_id = server.get_random_domain_id() + if(!map_id) + return TRUE + + server.cold_boot_map(usr, map_id) + return TRUE + if("refresh") + ui.send_full_update() + return TRUE + if("set_domain") + server.cold_boot_map(usr, params["id"]) + return TRUE + if("stop_domain") + server.begin_shutdown(usr) + return TRUE + + return FALSE + +/// Attempts to find a quantum server. +/obj/machinery/computer/quantum_console/proc/find_server() + var/obj/machinery/quantum_server/server = server_ref?.resolve() + if(server) + return server + + for(var/direction in GLOB.cardinals) + var/obj/machinery/quantum_server/nearby_server = locate(/obj/machinery/quantum_server, get_step(src, direction)) + if(nearby_server) + server_ref = WEAKREF(nearby_server) + nearby_server.console_ref = WEAKREF(src) + return nearby_server diff --git a/code/modules/bitrunning/orders/disks.dm b/code/modules/bitrunning/orders/disks.dm new file mode 100644 index 00000000000..ced1dde883a --- /dev/null +++ b/code/modules/bitrunning/orders/disks.dm @@ -0,0 +1,26 @@ +/datum/orderable_item/bitrunning_tech + category_index = CATEGORY_BITRUNNING_TECH + +/datum/orderable_item/bitrunning_tech/item_tier1 + cost_per_order = 1000 + item_path = /obj/item/bitrunning_disk/item/tier1 + +/datum/orderable_item/bitrunning_tech/item_tier2 + cost_per_order = 1500 + item_path = /obj/item/bitrunning_disk/item/tier2 + +/datum/orderable_item/bitrunning_tech/item_tier3 + cost_per_order = 2500 + item_path = /obj/item/bitrunning_disk/item/tier3 + +/datum/orderable_item/bitrunning_tech/ability_tier1 + cost_per_order = 1000 + item_path = /obj/item/bitrunning_disk/ability/tier1 + +/datum/orderable_item/bitrunning_tech/ability_tier2 + cost_per_order = 1800 + item_path = /obj/item/bitrunning_disk/ability/tier2 + +/datum/orderable_item/bitrunning_tech/ability_tier3 + cost_per_order = 3200 + item_path = /obj/item/bitrunning_disk/ability/tier3 diff --git a/code/modules/bitrunning/orders/flair.dm b/code/modules/bitrunning/orders/flair.dm new file mode 100644 index 00000000000..ef36348eb6a --- /dev/null +++ b/code/modules/bitrunning/orders/flair.dm @@ -0,0 +1,40 @@ +/datum/orderable_item/bitrunning_flair + category_index = CATEGORY_BITRUNNING_FLAIR + +/datum/orderable_item/bitrunning_flair/cornchips + item_path = /obj/item/food/cornchips + cost_per_order = 100 + +/datum/orderable_item/bitrunning_flair/mountain_wind + item_path = /obj/item/reagent_containers/cup/soda_cans/space_mountain_wind + cost_per_order = 100 + +/datum/orderable_item/bitrunning_flair/pwr_game + item_path = /obj/item/reagent_containers/cup/soda_cans/pwr_game + cost_per_order = 200 + +/datum/orderable_item/bitrunning_flair/grey_bull + item_path = /obj/item/reagent_containers/cup/soda_cans/grey_bull + cost_per_order = 200 + +/datum/orderable_item/bitrunning_flair/medkit + item_path = /obj/item/storage/medkit/brute + desc = "Don't beat yourself up, it's just a game!" + cost_per_order = 500 + +/datum/orderable_item/bitrunning_flair/medkit_fire + item_path = /obj/item/storage/medkit/fire + desc = "Great after heated gaming sessions." + cost_per_order = 500 + +/datum/orderable_item/bitrunning_flair/oval_sunglasses + item_path = /obj/item/clothing/glasses/sunglasses/oval + cost_per_order = 1000 + +/datum/orderable_item/bitrunning_flair/trenchcoat + item_path = /obj/item/clothing/suit/jacket/trenchcoat + cost_per_order = 1000 + +/datum/orderable_item/bitrunning_flair/jackboots + item_path = /obj/item/clothing/shoes/jackboots + cost_per_order = 1000 diff --git a/code/modules/bitrunning/orders/tech.dm b/code/modules/bitrunning/orders/tech.dm new file mode 100644 index 00000000000..286e9817f3c --- /dev/null +++ b/code/modules/bitrunning/orders/tech.dm @@ -0,0 +1,23 @@ +/datum/orderable_item/bepis + category_index = CATEGORY_BEPIS + +/datum/orderable_item/bepis/circuit_stack + item_path = /obj/item/stack/circuit_stack/full + cost_per_order = 150 + +/datum/orderable_item/bepis/survival_pen + item_path = /obj/item/pen/survival + cost_per_order = 150 + +/datum/orderable_item/bepis/party_sleeper + item_path = /obj/item/circuitboard/machine/sleeper/party + cost_per_order = 750 + desc = "A decommissioned sleeper circuitboard, repurposed for recreational purposes." + +/datum/orderable_item/bepis/sprayoncan + item_path = /obj/item/toy/sprayoncan + cost_per_order = 750 + +/datum/orderable_item/bepis/pristine + item_path = /obj/item/disk/design_disk/bepis/remove_tech + cost_per_order = 1000 diff --git a/code/modules/bitrunning/server/loot.dm b/code/modules/bitrunning/server/loot.dm new file mode 100644 index 00000000000..29b730aae78 --- /dev/null +++ b/code/modules/bitrunning/server/loot.dm @@ -0,0 +1,123 @@ +/// Handles calculating rewards based on number of players, parts, threats, etc +/obj/machinery/quantum_server/proc/calculate_rewards() + var/rewards_base = 0.8 + + if(domain_randomized) + rewards_base += 0.2 + + rewards_base += servo_bonus + + rewards_base += (domain_threats * 2) + + for(var/index in 2 to length(avatar_connection_refs)) + rewards_base += multiplayer_bonus + + return rewards_base + +/// Generates a reward based on the given domain +/obj/machinery/quantum_server/proc/generate_loot() + if(!length(receive_turfs) && !locate_receive_turfs()) + return FALSE + + points += generated_domain.reward_points + playsound(src, 'sound/machines/terminal_success.ogg', 30, 2) + + var/turf/dest_turf = pick(receive_turfs) + if(isnull(dest_turf)) + stack_trace("Failed to find a turf to spawn loot crate on.") + return FALSE + + var/bonus = calculate_rewards() + + var/obj/item/paper/certificate = new() + certificate.add_raw_text(get_completion_certificate()) + certificate.name = "certificate of domain completion" + certificate.update_appearance() + + var/obj/structure/closet/crate/secure/bitrunning/decrypted/reward_crate = new(dest_turf, generated_domain, bonus) + reward_crate.manifest = certificate + reward_crate.update_appearance() + + spark_at_location(reward_crate) + 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 + if(domain_randomized) + base_points -= 1 + + var/bonuses = calculate_rewards() + + 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(generated_domain.difficulty, domain_threats, base_points, domain_randomized, time_difference)]" + + var/text = "# Certificate of Domain Completion\n\n---\n\n" + + text += "### [generated_domain.name][domain_randomized ? " (Randomized)" : ""]\n" + text += "- **Difficulty:** [generated_domain.difficulty]\n" + text += "- **Threats:** [domain_threats]\n" + text += "- **Base Points:** [base_points][domain_randomized ? " +1" : ""]\n\n" + text += "- **Total Bonus:** [bonuses]x\n\n" + + if(bonuses <= 1) + text += completion_time + text += grade + return text + + text += "### Bonuses\n" + if(domain_randomized) + text += "- **Randomized:** + 0.2\n" + + if(length(avatar_connection_refs) > 1) + text += "- **Multiplayer:** + [(length(avatar_connection_refs) - 1) * multiplayer_bonus]\n" + + if(domain_threats > 0) + text += "- **Threats:** + [domain_threats * 2]\n" + + var/servo_rating = servo_bonus + + if(servo_rating > 0.2) + text += "- **Components:** + [servo_rating]\n" + + text += completion_time + text += grade + + return text + +/// Grades the player's run based on several factors +/obj/machinery/quantum_server/proc/grade_completion(difficulty, threats, points, randomized, completion_time) + var/score = threats * 5 + score += points + score += randomized ? 1 : 0 + + var/base = difficulty + 1 + var/time_score = 1 + + if(completion_time <= 1 MINUTES) + time_score = 10 + else if(completion_time <= 2 MINUTES) + time_score = 5 + else if(completion_time <= 5 MINUTES) + time_score = 3 + else if(completion_time <= 10 MINUTES) + time_score = 2 + else + time_score = 1 + + score += time_score * base + + switch(score) + if(1 to 4) + return "D" + if(5 to 7) + return "C" + if(8 to 10) + return "B" + if(11 to 13) + return "A" + else + return "S" diff --git a/code/modules/bitrunning/server/map_handling.dm b/code/modules/bitrunning/server/map_handling.dm new file mode 100644 index 00000000000..02126c290f7 --- /dev/null +++ b/code/modules/bitrunning/server/map_handling.dm @@ -0,0 +1,184 @@ + +/// 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)) + return + + if(!length(avatar_connection_refs)) + balloon_alert(user, "powering down domain...") + playsound(src, 'sound/machines/terminal_off.ogg', 40, 2) + reset() + return + + balloon_alert(user, "notifying clients...") + playsound(src, 'sound/machines/terminal_alert.ogg', 100, TRUE) + user.visible_message( + span_danger("[user] begins depowering the server!"), + span_notice("You start disconnecting clients..."), + span_danger("You hear frantic keying on a keyboard."), + ) + + SEND_SIGNAL(src, COMSIG_BITRUNNER_SHUTDOWN_ALERT, user) + + if(!do_after(user, 20 SECONDS, src)) + return + + reset() + +/** + * ### Quantum Server Cold Boot + * Procedurally links the 3 booting processes together. + * + * This is the starting point if you have an id. Does validation and feedback on steps + */ +/obj/machinery/quantum_server/proc/cold_boot_map(mob/user, map_key) + if(!is_ready) + return FALSE + + if(isnull(map_key)) + balloon_alert(user, "no domain specified.") + return FALSE + + if(generated_domain) + balloon_alert(user, "stop the current domain first.") + return FALSE + + if(length(avatar_connection_refs)) + balloon_alert(user, "all clients must disconnect!") + return FALSE + + is_ready = FALSE + playsound(src, 'sound/machines/terminal_processing.ogg', 30, 2) + + if(!initialize_domain(map_key) || !initialize_safehouse() || !initialize_map_items()) + balloon_alert(user, "initialization failed.") + scrub_vdom() + is_ready = TRUE + return FALSE + + is_ready = TRUE + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 30, 2) + balloon_alert(user, "domain loaded.") + generated_domain.start_time = world.time + points -= generated_domain.cost + update_use_power(ACTIVE_POWER_USE) + update_appearance() + + return TRUE + +/// Initializes a new domain if the given key is valid and the user has enough points +/obj/machinery/quantum_server/proc/initialize_domain(map_key) + var/datum/lazy_template/virtual_domain/to_load + + for(var/datum/lazy_template/virtual_domain/available as anything in subtypesof(/datum/lazy_template/virtual_domain)) + if(map_key != initial(available.key) || points < initial(available.cost)) + continue + to_load = available + break + + if(isnull(to_load)) + return FALSE + + generated_domain = new to_load() + RegisterSignal(generated_domain, COMSIG_LAZY_TEMPLATE_LOADED, PROC_REF(on_template_loaded)) + generated_domain.lazy_load() + + return TRUE + +/// Loads in necessary map items, sets mutation targets, etc +/obj/machinery/quantum_server/proc/initialize_map_items() + var/turf/goal_turfs = list() + var/turf/crate_turfs = list() + + for(var/thing in GLOB.landmarks_list) + if(istype(thing, /obj/effect/landmark/bitrunning/hololadder_spawn)) + exit_turfs += get_turf(thing) + qdel(thing) // i'm worried about multiple servers getting confused so lets clean em up + continue + + if(istype(thing, /obj/effect/landmark/bitrunning/cache_goal_turf)) + var/turf/tile = get_turf(thing) + goal_turfs += tile + RegisterSignal(tile, COMSIG_ATOM_ENTERED, PROC_REF(on_goal_turf_entered)) + RegisterSignal(tile, COMSIG_ATOM_EXAMINE, PROC_REF(on_goal_turf_examined)) + qdel(thing) + continue + + if(istype(thing, /obj/effect/landmark/bitrunning/cache_spawn)) + crate_turfs += get_turf(thing) + qdel(thing) + continue + + 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(length(crate_turfs)) + shuffle_inplace(crate_turfs) + new /obj/structure/closet/crate/secure/bitrunning/encrypted(pick(crate_turfs)) + + return TRUE +#define ONLY_TURF 1 // There should only ever be one turf at the bottom left of the map. + +/// Loads the safehouse +/obj/machinery/quantum_server/proc/initialize_safehouse() + var/turf/safehouse_load_turf = list() + for(var/obj/effect/landmark/bitrunning/safehouse_spawn/spawner in GLOB.landmarks_list) + safehouse_load_turf += get_turf(spawner) + qdel(spawner) + break + + if(!length(safehouse_load_turf)) + CRASH("Failed to find safehouse load landmark on map.") + + var/datum/map_template/safehouse/safehouse = new generated_domain.safehouse_path() + safehouse.load(safehouse_load_turf[ONLY_TURF]) + generated_safehouse = safehouse + + return TRUE + +/// Stops the current virtual domain and disconnects all users +/obj/machinery/quantum_server/proc/reset(fast = FALSE) + is_ready = FALSE + + SEND_SIGNAL(src, COMSIG_BITRUNNER_SEVER_AVATAR) + + if(!fast) + notify_spawned_threats() + addtimer(CALLBACK(src, PROC_REF(scrub_vdom)), 15 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE) + else + scrub_vdom() // used in unit testing, no need to wait for callbacks + + addtimer(CALLBACK(src, PROC_REF(cool_off)), min(server_cooldown_time * capacitor_coefficient), TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME) + update_appearance() + + update_use_power(IDLE_POWER_USE) + domain_randomized = FALSE + domain_threats = 0 + retries_spent = 0 + +/// Deletes all the tile contents +/obj/machinery/quantum_server/proc/scrub_vdom() + SEND_SIGNAL(src, COMSIG_BITRUNNER_SEVER_AVATAR) // just in case + + if(length(generated_domain.reservations)) + var/datum/turf_reservation/res = generated_domain.reservations[1] + res.Release() + + var/list/datum/weakref/creatures = spawned_threat_refs + mutation_candidate_refs + for(var/datum/weakref/creature_ref as anything in creatures) + var/mob/living/creature = creature_ref?.resolve() + if(isnull(creature)) + continue + + creature.dust() // sometimes mobs just don't die + + avatar_connection_refs.Cut() + exit_turfs = list() + generated_domain = null + generated_safehouse = null + mutation_candidate_refs.Cut() + spawned_threat_refs.Cut() + +#undef ONLY_TURF diff --git a/code/modules/bitrunning/server/obj_generation.dm b/code/modules/bitrunning/server/obj_generation.dm new file mode 100644 index 00000000000..221308e0487 --- /dev/null +++ b/code/modules/bitrunning/server/obj_generation.dm @@ -0,0 +1,101 @@ +/// 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) + + var/outfit_path = generated_domain.forced_outfit || netsuit + var/datum/outfit/to_wear = new outfit_path() + + to_wear.belt = /obj/item/bitrunning_host_monitor + to_wear.glasses = null + to_wear.gloves = null + to_wear.l_hand = null + to_wear.l_pocket = null + to_wear.r_hand = null + to_wear.r_pocket = null + to_wear.suit = null + to_wear.suit_store = null + + avatar.equipOutfit(to_wear, visualsOnly = TRUE) + + var/thing = avatar.get_active_held_item() + if(!isnull(thing)) + qdel(thing) + + thing = avatar.get_inactive_held_item() + if(!isnull(thing)) + qdel(thing) + + var/obj/item/storage/backpack/bag = avatar.back + if(istype(bag)) + QDEL_LIST(bag.contents) + + bag.contents += list( + new /obj/item/storage/box/survival, + new /obj/item/storage/medkit/regular, + new /obj/item/flashlight, + ) + + var/obj/item/card/id/outfit_id = avatar.wear_id + if(outfit_id) + outfit_id.assignment = "Bit Avatar" + outfit_id.registered_name = avatar.real_name + + outfit_id.registered_account = new() + outfit_id.registered_account.replaceable = FALSE + + SSid_access.apply_trim_to_card(outfit_id, /datum/id_trim/bit_avatar) + + 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) + if(isnull(wayout)) + return + + retries_spent += 1 + + return wayout + +/// 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) + var/failed = FALSE + + for(var/obj/item/bitrunning_disk/disk in neo.get_contents()) + if(istype(disk, /obj/item/bitrunning_disk/ability)) + var/obj/item/bitrunning_disk/ability/ability_disk = disk + + if(isnull(ability_disk.granted_action)) + failed = TRUE + continue + + var/datum/action/our_action = new ability_disk.granted_action() + our_action.Grant(avatar) + continue + + if(istype(disk, /obj/item/bitrunning_disk/item)) + var/obj/item/bitrunning_disk/item/item_disk = disk + + if(isnull(item_disk.granted_item)) + failed = TRUE + continue + + avatar.put_in_hands(new item_disk.granted_item()) + + if(failed) + to_chat(neo, span_warning("One of your disks failed to load. You must activate them to make a selection.")) diff --git a/code/modules/bitrunning/server/quantum_server.dm b/code/modules/bitrunning/server/quantum_server.dm new file mode 100644 index 00000000000..404a31cca6a --- /dev/null +++ b/code/modules/bitrunning/server/quantum_server.dm @@ -0,0 +1,152 @@ +/** + * The base object for the quantum server + */ +/obj/machinery/quantum_server + name = "quantum server" + + circuit = /obj/item/circuitboard/machine/quantum_server + density = TRUE + desc = "A hulking computational machine designed to fabricate virtual domains." + icon = 'icons/obj/machines/bitrunning.dmi' + base_icon_state = "qserver" + icon_state = "qserver" + /// Affects server cooldown efficiency + var/capacitor_coefficient = 1 + /// The loaded map template, map_template/virtual_domain + var/datum/lazy_template/virtual_domain/generated_domain + /// The loaded safehouse, map_template/safehouse + var/datum/map_template/safehouse/generated_safehouse + /// The connected console + var/datum/weakref/console_ref + /// If the current domain was a random selection + var/domain_randomized = FALSE + /// If any threats were spawned, adds to rewards + var/domain_threats = 0 + /// Prevents multiple user actions. Handled by loading domains and cooldowns + var/is_ready = TRUE + /// List of available domains + var/list/available_domains = list() + /// Current plugged in users + var/list/datum/weakref/avatar_connection_refs = list() + /// Cached list of mutable mobs in zone for cybercops + var/list/datum/weakref/mutation_candidate_refs = list() + /// Any ghosts that have spawned in + var/list/datum/weakref/spawned_threat_refs = list() + /// Scales loot with extra players + var/multiplayer_bonus = 1.1 + ///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 + var/retries_spent = 0 + /// Changes how much info is available on the domain + var/scanner_tier = 1 + /// Length of time it takes for the server to cool down after resetting. Here to give runners downtime so their faces don't get stuck like that + var/server_cooldown_time = 3 MINUTES + /// Applies bonuses to rewards etc + var/servo_bonus = 0 + /// The turfs we can place a hololadder on. + var/turf/exit_turfs = list() + /// The turfs on station where we generate loot. + var/turf/receive_turfs = list() + +/obj/machinery/quantum_server/Initialize(mapload) + . = ..() + + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/quantum_server/LateInitialize() + . = ..() + + if(isnull(console_ref)) + find_console() + + radio = new(src) + radio.set_frequency(FREQ_SUPPLY) + radio.subspace_transmission = TRUE + radio.canhear_range = 0 + radio.recalculateChannels() + + RegisterSignals(src, list(COMSIG_MACHINERY_BROKEN, COMSIG_MACHINERY_POWER_LOST), PROC_REF(on_broken)) + RegisterSignal(src, COMSIG_QDELETING, PROC_REF(on_delete)) + RegisterSignal(src, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + RegisterSignal(src, COMSIG_BITRUNNER_SPAWN_GLITCH, PROC_REF(on_threat_created)) + + // This further gets sorted in the client by cost so it's random and grouped + available_domains = shuffle(subtypesof(/datum/lazy_template/virtual_domain)) + +/obj/machinery/quantum_server/Destroy(force) + . = ..() + + available_domains.Cut() + mutation_candidate_refs.Cut() + avatar_connection_refs.Cut() + spawned_threat_refs.Cut() + QDEL_NULL(exit_turfs) + QDEL_NULL(receive_turfs) + QDEL_NULL(generated_domain) + QDEL_NULL(generated_safehouse) + QDEL_NULL(radio) + +/obj/machinery/quantum_server/update_appearance(updates) + if(isnull(generated_domain) || !is_operational) + set_light(0) + return ..() + + set_light_color(is_ready ? LIGHT_COLOR_BABY_BLUE : LIGHT_COLOR_FIRE) + set_light(2, 1.5) + + return ..() + +/obj/machinery/quantum_server/update_icon_state() + if(isnull(generated_domain) || !is_operational) + icon_state = base_icon_state + return ..() + + icon_state = "[base_icon_state]_[is_ready ? "on" : "off"]" + return ..() + +/obj/machinery/quantum_server/crowbar_act(mob/living/user, obj/item/crowbar) + . = ..() + + if(!is_ready) + balloon_alert(user, "it's scalding hot!") + return TRUE + if(length(avatar_connection_refs)) + balloon_alert(user, "all clients must disconnect!") + return TRUE + if(default_deconstruction_crowbar(crowbar)) + return TRUE + return FALSE + +/obj/machinery/quantum_server/screwdriver_act(mob/living/user, obj/item/screwdriver) + . = ..() + + 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 + +/obj/machinery/quantum_server/RefreshParts() + . = ..() + + var/capacitor_rating = 1.15 + var/datum/stock_part/capacitor/cap = locate() in component_parts + capacitor_rating -= cap.tier * 0.15 + + capacitor_coefficient = capacitor_rating + + var/datum/stock_part/scanning_module/scanner = locate() in component_parts + if(scanner) + scanner_tier = scanner.tier + + var/servo_rating = 0 + for(var/datum/stock_part/servo/servo in component_parts) + servo_rating += servo.tier * 0.1 + + servo_bonus = servo_rating + + SEND_SIGNAL(src, COMSIG_BITRUNNER_SERVER_UPGRADED, servo_rating) diff --git a/code/modules/bitrunning/server/signal_handlers.dm b/code/modules/bitrunning/server/signal_handlers.dm new file mode 100644 index 00000000000..b0464b351fa --- /dev/null +++ b/code/modules/bitrunning/server/signal_handlers.dm @@ -0,0 +1,107 @@ +/// If broken via signal, disconnects all users +/obj/machinery/quantum_server/proc/on_broken(datum/source) + SIGNAL_HANDLER + + if(isnull(generated_domain)) + return + + SEND_SIGNAL(src, COMSIG_BITRUNNER_SEVER_AVATAR) + +/// 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 + + if(generated_domain) + SEND_SIGNAL(src, COMSIG_BITRUNNER_SEVER_AVATAR) + scrub_vdom() + + if(is_ready) + return + // in case they're trying to cheese cooldown + var/obj/item/circuitboard/machine/quantum_server/circuit = locate(/obj/item/circuitboard/machine/quantum_server) in contents + if(circuit) + qdel(circuit) + +/// Handles examining the server. Shows cooldown time and efficiency. +/obj/machinery/quantum_server/proc/on_examine(datum/source, mob/examiner, list/examine_text) + SIGNAL_HANDLER + + examine_text += span_infoplain("Can be resource intensive to run. Ensure adequate power supply.") + + if(capacitor_coefficient < 1) + examine_text += span_infoplain("Its coolant capacity reduces cooldown time by [(1 - capacitor_coefficient) * 100]%.") + + if(servo_bonus > 0.2) + examine_text += span_infoplain("Its manipulation potential is increasing rewards by [servo_bonus]x.") + examine_text += span_infoplain("Injury from unsafe ejection reduced [servo_bonus * 100]%.") + + if(!is_ready) + examine_text += span_notice("It is currently cooling down. Give it a few moments.") + return + +/// 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 + + if(!istype(arrived, /obj/structure/closet/crate/secure/bitrunning/encrypted)) + return + + var/obj/structure/closet/crate/secure/bitrunning/encrypted/loot_crate = arrived + if(!istype(loot_crate)) + return + + for(var/mob/person in loot_crate.contents) + if(isnull(person.mind)) + person.forceMove(get_turf(loot_crate)) + + var/datum/component/avatar_connection/connection = person.GetComponent(/datum/component/avatar_connection) + connection?.full_avatar_disconnect() + + spark_at_location(loot_crate) + qdel(loot_crate) + SEND_SIGNAL(src, COMSIG_BITRUNNER_DOMAIN_COMPLETE, arrived, generated_domain.reward_points) + generate_loot() + +/// 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 + + 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 + + for(var/thing in created_atoms) + if(isliving(thing)) // so we can mutate them + var/mob/living/creature = thing + + if(creature.can_be_cybercop) + mutation_candidate_refs.Add(WEAKREF(creature)) + continue + + if(istype(thing, /obj/effect/mob_spawn/ghost_role)) // so we get threat alerts + RegisterSignal(thing, COMSIG_GHOSTROLE_SPAWNED, PROC_REF(on_threat_created)) + continue + + if(istype(thing, /obj/effect/mob_spawn/corpse)) // corpses are valid targets too + var/obj/effect/mob_spawn/corpse/spawner = thing + + mutation_candidate_refs.Add(spawner.spawned_mob_ref) + + UnregisterSignal(source, COMSIG_LAZY_TEMPLATE_LOADED) + +/// 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 + + domain_threats += 1 + spawned_threat_refs.Add(WEAKREF(threat)) + SEND_SIGNAL(src, COMSIG_BITRUNNER_THREAT_CREATED) // notify players diff --git a/code/modules/bitrunning/server/util.dm b/code/modules/bitrunning/server/util.dm new file mode 100644 index 00000000000..1d35e86de50 --- /dev/null +++ b/code/modules/bitrunning/server/util.dm @@ -0,0 +1,142 @@ +#define REDACTED "???" +#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) + +/// Attempts to connect to a quantum console +/obj/machinery/quantum_server/proc/find_console() + var/obj/machinery/computer/quantum_console/console = console_ref?.resolve() + if(console) + return console + + for(var/direction in GLOB.cardinals) + var/obj/machinery/computer/quantum_console/nearby_console = locate(/obj/machinery/computer/quantum_console, get_step(src, direction)) + if(nearby_console) + console_ref = WEAKREF(nearby_console) + nearby_console.server_ref = WEAKREF(src) + return nearby_console + +/// Compiles a list of available domains. +/obj/machinery/quantum_server/proc/get_available_domains() + var/list/levels = list() + + for(var/datum/lazy_template/virtual_domain/domain as anything in available_domains) + if(initial(domain.test_only)) + continue + var/can_view = initial(domain.difficulty) < scanner_tier && initial(domain.cost) <= points + 5 + var/can_view_reward = initial(domain.difficulty) < (scanner_tier + 1) && initial(domain.cost) <= points + 3 + + levels += list(list( + "cost" = initial(domain.cost), + "desc" = can_view ? initial(domain.desc) : "Limited scanning capabilities. Cannot infer domain details.", + "difficulty" = initial(domain.difficulty), + "id" = initial(domain.key), + "name" = can_view ? initial(domain.name) : REDACTED, + "reward" = can_view_reward ? initial(domain.reward_points) : REDACTED, + )) + + return levels + +/// 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() + + for(var/datum/weakref/avatar_ref in avatar_connection_refs) + var/datum/component/avatar_connection/connection = avatar_ref.resolve() + if(isnull(connection)) + avatar_connection_refs.Remove(connection) + continue + + var/mob/living/creature = connection.parent + var/mob/living/pilot = connection.old_body_ref?.resolve() + + hosted_avatars += list(list( + "health" = creature.health, + "name" = creature.name, + "pilot" = pilot, + "brute" = creature.get_damage_amount(BRUTE), + "burn" = creature.get_damage_amount(BURN), + "tox" = creature.get_damage_amount(TOX), + "oxy" = creature.get_damage_amount(OXY), + )) + + return hosted_avatars + +/// Gets a random available domain given the current points. Weighted towards higher cost domains. +/obj/machinery/quantum_server/proc/get_random_domain_id() + if(points < 1) + return + + var/list/random_domains = list() + var/total_cost = 0 + + for(var/datum/lazy_template/virtual_domain/available as anything in subtypesof(/datum/lazy_template/virtual_domain)) + var/init_cost = initial(available.cost) + if(!initial(available.test_only) && init_cost > 0 && init_cost < 4 && init_cost <= points) + random_domains += list(list( + cost = init_cost, + id = initial(available.key), + )) + + var/random_value = rand(0, total_cost) + var/accumulated_cost = 0 + + for(var/available as anything in random_domains) + accumulated_cost += available["cost"] + if(accumulated_cost >= random_value) + domain_randomized = TRUE + return available["id"] + +/// Gets all mobs originally generated by the loaded domain and returns a list that are capable of being antagged +/obj/machinery/quantum_server/proc/get_valid_domain_targets() + // A: No one is playing + // B: The domain is not loaded + // C: The domain is shutting down + // D: There are no mobs + if(!length(avatar_connection_refs) || isnull(generated_domain) || !is_ready || !is_operational || !length(mutation_candidate_refs)) + return list() + + for(var/datum/weakref/creature_ref as anything in mutation_candidate_refs) + var/mob/living/creature = creature_ref.resolve() + if(isnull(creature) || creature.mind) + mutation_candidate_refs.Remove(creature_ref) + + return shuffle(mutation_candidate_refs) + +/// Locates any turfs with crate out landmarks +/obj/machinery/quantum_server/proc/locate_receive_turfs() + for(var/obj/effect/landmark/bitrunning/station_reward_spawn/spawner in GLOB.landmarks_list) + if(IN_GIVEN_RANGE(src, spawner, MAX_DISTANCE)) + receive_turfs += get_turf(spawner) + qdel(spawner) + + return length(receive_turfs) > 0 + +/// 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) + var/mob/living/baddie = baddie_ref.resolve() + if(isnull(baddie) || baddie.stat >= UNCONSCIOUS || isnull(baddie.mind)) + continue + + baddie.throw_alert( + ALERT_BITRUNNER_RESET, + /atom/movable/screen/alert/bitrunning/qserver_threat_deletion, + new_master = src, + ) + + to_chat(baddie, span_userdanger("You have been flagged for deletion! Thank you for your service.")) + +/// Do some magic teleport sparks +/obj/machinery/quantum_server/proc/spark_at_location(obj/crate) + playsound(crate, 'sound/magic/blink.ogg', 50, TRUE) + var/datum/effect_system/spark_spread/quantum/sparks = new() + sparks.set_up(5, 1, get_turf(crate)) + sparks.start() + +#undef REDACTED +#undef MAX_DISTANCE diff --git a/code/modules/bitrunning/turfs.dm b/code/modules/bitrunning/turfs.dm new file mode 100644 index 00000000000..93dce1789c4 --- /dev/null +++ b/code/modules/bitrunning/turfs.dm @@ -0,0 +1,14 @@ +/turf/open/floor/bitrunning_transport + name = "circuit floor" + icon = 'icons/turf/floors.dmi' + desc = "Looks complex. You can see the circuits running through the floor." + icon_state = "bitrunning" + +/turf/closed/indestructible/binary + name = "tear in the fabric of reality" + icon = 'icons/turf/floors.dmi' + icon_state = "binary" + +/obj/effect/baseturf_helper/virtual_domain + name = "virtual domain baseturf editor" + baseturf = /turf/open/indestructible/binary diff --git a/code/modules/bitrunning/virtual_domain/domains/ash_drake.dm b/code/modules/bitrunning/virtual_domain/domains/ash_drake.dm new file mode 100644 index 00000000000..02bb91abc58 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/ash_drake.dm @@ -0,0 +1,18 @@ +/datum/lazy_template/virtual_domain/ash_drake + name = "Ashen Inferno" + cost = BITRUNNER_COST_MEDIUM + desc = "Home of the ash drake, a powerful dragon that scours the surface of Lavaland." + difficulty = BITRUNNER_DIFFICULTY_MEDIUM + forced_outfit = /datum/outfit/job/miner + key = "ash_drake" + map_name = "ash_drake" + reward_points = BITRUNNER_REWARD_MEDIUM + safehouse_path = /datum/map_template/safehouse/lavaland_boss + +/mob/living/simple_animal/hostile/megafauna/dragon/virtual_domain + can_be_cybercop = FALSE + crusher_loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + health = 1600 + loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + maxHealth = 1600 + true_spawn = FALSE diff --git a/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm b/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm new file mode 100644 index 00000000000..871c2cb1338 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm @@ -0,0 +1,22 @@ +/datum/lazy_template/virtual_domain/beach_bar + name = "Beach Bar" + desc = "A cheerful seaside haven where friendly skeletons serve up drinks. Say, how'd you guys get so dead?" + extra_loot = list(/obj/item/toy/beach_ball = 1) + help_text = "This place is running on a skeleton crew, and they don't seem to be too keen to share details. \ + Maybe a few drinks of liquid charm will get the spirits up. As the saying goes, if you can't beat 'em, join 'em." + key = "beach_bar" + map_name = "beach_bar" + safehouse_path = /datum/map_template/safehouse/mine + +/obj/item/reagent_containers/cup/glass/drinkingglass/virtual_domain + name = "pina colada" + desc = "Whose drink is this? Not yours, that's for sure. Well, it's not like they're going to miss it." + list_reagents = list(/datum/reagent/consumable/ethanol/pina_colada = 30) + +/obj/item/reagent_containers/cup/glass/drinkingglass/virtual_domain/Initialize(mapload, vol) + . = ..() + + AddComponent(/datum/component/bitrunning_points, \ + signal_type = COMSIG_GLASS_DRANK, \ + points_per_signal = 0.5, \ + ) diff --git a/code/modules/bitrunning/virtual_domain/domains/blood_drunk_miner.dm b/code/modules/bitrunning/virtual_domain/domains/blood_drunk_miner.dm new file mode 100644 index 00000000000..abf2e0fc5a9 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/blood_drunk_miner.dm @@ -0,0 +1,18 @@ +/datum/lazy_template/virtual_domain/blood_drunk_miner + name = "Sanguine Excavation" + cost = BITRUNNER_COST_MEDIUM + desc = "Few escape the surface of Lavaland without a few scars. Some remain, maddened by the hunt." + difficulty = BITRUNNER_DIFFICULTY_MEDIUM + forced_outfit = /datum/outfit/job/miner + key = "blood_drunk_miner" + map_name = "blood_drunk_miner" + reward_points = BITRUNNER_REWARD_MEDIUM + safehouse_path = /datum/map_template/safehouse/lavaland_boss + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/virtual_domain + can_be_cybercop = FALSE + crusher_loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + health = 1600 + loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + maxHealth = 1600 + true_spawn = FALSE diff --git a/code/modules/bitrunning/virtual_domain/domains/bubblegum.dm b/code/modules/bitrunning/virtual_domain/domains/bubblegum.dm new file mode 100644 index 00000000000..bede97177cb --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/bubblegum.dm @@ -0,0 +1,19 @@ +/datum/lazy_template/virtual_domain/bubblegum + name = "Blood-Soaked Lair" + cost = BITRUNNER_COST_HIGH + desc = "King of the slaughter demons. Bubblegum is a massive, hulking beast with a penchant for violence." + difficulty = BITRUNNER_DIFFICULTY_HIGH + extra_loot = list(/obj/item/toy/plush/bubbleplush = 1) + forced_outfit = /datum/outfit/job/miner + key = "bubblegum" + map_name = "bubblegum" + reward_points = BITRUNNER_REWARD_HIGH + safehouse_path = /datum/map_template/safehouse/lavaland_boss + +/mob/living/simple_animal/hostile/megafauna/bubblegum/virtual_domain + can_be_cybercop = FALSE + crusher_loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + health = 2000 + loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + maxHealth = 2000 + true_spawn = FALSE diff --git a/code/modules/bitrunning/virtual_domain/domains/clown_planet.dm b/code/modules/bitrunning/virtual_domain/domains/clown_planet.dm new file mode 100644 index 00000000000..92f000c9cf3 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/clown_planet.dm @@ -0,0 +1,13 @@ +/datum/lazy_template/virtual_domain/clown_planet + name = "Clown Planet" + cost = BITRUNNER_COST_LOW + desc = "In the deep, dark reaches of space, there is only Honk." + difficulty = BITRUNNER_DIFFICULTY_LOW + extra_loot = list(/obj/item/bikehorn = 1) + forced_outfit = /datum/outfit/job/clown + help_text = "The trials of the Honkitude have begun. The sound of bike horns wailing in the distance. \ + this realm- some sort of puzzle, has existed in legend as the final test of just how silly you are." + key = "clown_planet" + map_name = "clown_planet" + reward_points = BITRUNNER_REWARD_LOW + safehouse_path = /datum/map_template/safehouse/mine diff --git a/code/modules/bitrunning/virtual_domain/domains/colossus.dm b/code/modules/bitrunning/virtual_domain/domains/colossus.dm new file mode 100644 index 00000000000..35ba4eee0ca --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/colossus.dm @@ -0,0 +1,18 @@ +/datum/lazy_template/virtual_domain/colossus + name = "Celestial Trial" + cost = BITRUNNER_COST_HIGH + desc = "A massive, ancient beast named the Colossus. Judgment comes." + difficulty = BITRUNNER_DIFFICULTY_HIGH + forced_outfit = /datum/outfit/job/miner + key = "colossus" + map_name = "colossus" + reward_points = BITRUNNER_REWARD_HIGH + safehouse_path = /datum/map_template/safehouse/lavaland_boss + +/mob/living/simple_animal/hostile/megafauna/colossus/virtual_domain + can_be_cybercop = FALSE + crusher_loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + health = 2000 + maxHealth = 2000 + true_spawn = FALSE diff --git a/code/modules/bitrunning/virtual_domain/domains/gondola_asteroid.dm b/code/modules/bitrunning/virtual_domain/domains/gondola_asteroid.dm new file mode 100644 index 00000000000..4deacb4f9c5 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/gondola_asteroid.dm @@ -0,0 +1,39 @@ +/datum/lazy_template/virtual_domain/gondola_asteroid + name = "Gondola Asteroid" + desc = "An asteroid home to a bountiful forest of gondolas. Peaceful." + map_name = "gondola_asteroid" + help_text = "What a lovely forest. There's a loot crate here in the middle of the map. \ + Hmm... It doesn't budge. The gondolas don't seem to have any trouble moving it, though. \ + I bet there's a way to move it myself." + key = "gondola_asteroid" + map_name = "gondola_asteroid" + safehouse_path = /datum/map_template/safehouse/shuttle_space + +/// Very pushy gondolas, great for moving loot crates. +/obj/structure/closet/crate/secure/bitrunning/encrypted/gondola + move_resist = MOVE_FORCE_STRONG + +/mob/living/simple_animal/pet/gondola/virtual_domain + health = 50 + loot = list(/obj/effect/decal/cleanable/blood/gibs, /obj/item/stack/sheet/animalhide/gondola = 1, /obj/item/food/meat/slab/gondola/virtual_domain = 1) + maxHealth = 50 + move_force = MOVE_FORCE_VERY_STRONG + move_resist = MOVE_FORCE_STRONG + +/obj/item/food/meat/slab/gondola/virtual_domain + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/gondola_mutation_toxin/virtual_domain = 5, + ) + +/datum/reagent/gondola_mutation_toxin/virtual_domain + name = "Advanced Tranquility" + +/datum/reagent/gondola_mutation_toxin/virtual_domain/expose_mob(mob/living/exposed_mob, methods = TOUCH, reac_volume, show_message = TRUE, touch_protection = 0) + . = ..() + if((methods & (PATCH|INGEST|INJECT)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) + exposed_mob.ForceContractDisease(new /datum/disease/transformation/gondola/virtual_domain(), FALSE, TRUE) + +/datum/disease/transformation/gondola/virtual_domain + stage_prob = 9 + new_form = /mob/living/simple_animal/pet/gondola/virtual_domain diff --git a/code/modules/bitrunning/virtual_domain/domains/hierophant.dm b/code/modules/bitrunning/virtual_domain/domains/hierophant.dm new file mode 100644 index 00000000000..142623f4f81 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/hierophant.dm @@ -0,0 +1,18 @@ +/datum/lazy_template/virtual_domain/hierophant + name = "Zealot Arena" + cost = BITRUNNER_COST_HIGH + desc = "Dance, puppets, dance!" + difficulty = BITRUNNER_DIFFICULTY_HIGH + forced_outfit = /datum/outfit/job/miner + key = "hierophant" + map_name = "hierophant" + reward_points = BITRUNNER_REWARD_HIGH + safehouse_path = /datum/map_template/safehouse/lavaland_boss + +/mob/living/simple_animal/hostile/megafauna/hierophant/virtual_domain + can_be_cybercop = FALSE + crusher_loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + health = 1700 + loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + maxHealth = 1700 + true_spawn = FALSE diff --git a/code/modules/bitrunning/virtual_domain/domains/legion.dm b/code/modules/bitrunning/virtual_domain/domains/legion.dm new file mode 100644 index 00000000000..f1ba146f380 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/legion.dm @@ -0,0 +1,20 @@ +/datum/lazy_template/virtual_domain/legion + name = "Chamber of Echoes" + cost = BITRUNNER_COST_MEDIUM + desc = "A chilling realm that houses Legion's necropolis. Those who succumb to it are forever damned." + difficulty = BITRUNNER_DIFFICULTY_MEDIUM + forced_outfit = /datum/outfit/job/miner + key = "legion" + map_name = "legion" + reward_points = BITRUNNER_REWARD_MEDIUM + safehouse_path = /datum/map_template/safehouse/lavaland_boss + +/mob/living/simple_animal/hostile/megafauna/legion/virtual_domain + can_be_cybercop = FALSE + crusher_loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + health = 1500 + loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + maxHealth = 1500 + true_spawn = FALSE + +// You may be thinking, what about those mini-legions? They're not part of the initial created_atoms list diff --git a/code/modules/bitrunning/virtual_domain/domains/pipedream.dm b/code/modules/bitrunning/virtual_domain/domains/pipedream.dm new file mode 100644 index 00000000000..fd54ef6ca48 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/pipedream.dm @@ -0,0 +1,101 @@ +/datum/lazy_template/virtual_domain/pipedream + name = "Disposal Pipe Factory" + cost = BITRUNNER_COST_LOW + desc = "An abandoned and infested factory manufacturing disposal pipes." + difficulty = BITRUNNER_DIFFICULTY_MEDIUM + extra_loot = list(/obj/item/stack/pipe_cleaner_coil/random/five = 1) + help_text = "Not long ago, this place was thriving with activity. The workers \ + seemed to have left in a hurry, and now productivity is in the bin. Something \ + must have trashed the place, but what?" + key = "pipedream" + map_name = "pipedream" + reward_points = BITRUNNER_REWARD_LOW + safehouse_path = /datum/map_template/safehouse/shuttle + +// ID Trims +/datum/id_trim/factory + assignment = "Factory Worker" + trim_state = "trim_cargotechnician" + department_color = COLOR_CARGO_BROWN + subdepartment_color = COLOR_CARGO_BROWN + sechud_icon_state = SECHUD_CARGO_TECHNICIAN + access = list( + ACCESS_AWAY_SUPPLY + ) + +/datum/id_trim/factory/qm + assignment = "Factory Quartermaster" + trim_state = "trim_quartermaster" + department_color = COLOR_COMMAND_BLUE + subdepartment_color = COLOR_CARGO_BROWN + department_state = "departmenthead" + sechud_icon_state = SECHUD_QUARTERMASTER + access = list( + ACCESS_AWAY_SUPPLY, + ACCESS_AWAY_COMMAND + ) + +// ID Cards +/obj/item/card/id/advanced/factory + name = "factory worker ID" + trim = /datum/id_trim/factory + +/obj/item/card/id/advanced/factory/qm + name = "factory quartermaster ID" + trim = /datum/id_trim/factory/qm + +//Outfits +/datum/outfit/factory + name = "Factory Worker" + + id_trim = /datum/id_trim/factory + id = /obj/item/card/id/advanced/ + uniform = /obj/item/clothing/under/rank/cargo/tech + suit = /obj/item/clothing/suit/hazardvest + belt = /obj/item/radio + gloves = /obj/item/clothing/gloves/color/black + head = /obj/item/clothing/head/soft/yellow + shoes = /obj/item/clothing/shoes/workboots + l_pocket = /obj/item/flashlight/seclite + +/datum/outfit/factory/guard + name = "Factory Guard" + + uniform = /obj/item/clothing/under/rank/security/officer/grey + suit = /obj/item/clothing/suit/armor/vest/alt + belt = /obj/item/radio + gloves = /obj/item/clothing/gloves/color/black + head = /obj/item/clothing/head/soft/sec + shoes = /obj/item/clothing/shoes/jackboots/sec + l_pocket = /obj/item/restraints/handcuffs + r_pocket = /obj/item/assembly/flash/handheld + +/datum/outfit/factory/qm + name = "Factory Quatermaster" + + id_trim = /datum/id_trim/factory/qm + id = /obj/item/card/id/advanced/silver + uniform = /obj/item/clothing/under/rank/cargo/qm + belt = /obj/item/radio + gloves = /obj/item/clothing/gloves/color/black + head = /obj/item/clothing/head/soft/yellow + shoes = /obj/item/clothing/shoes/jackboots/sec + l_pocket = /obj/item/melee/baton/telescopic + r_pocket = /obj/item/stamp/head/qm + +// Corpses +/obj/effect/mob_spawn/corpse/human/factory + name = "Factory Worker" + outfit = /datum/outfit/factory + icon_state = "corpsecargotech" + +/obj/effect/mob_spawn/corpse/human/factory/guard + name = "Factory Guard" + outfit = /datum/outfit/factory/guard + icon_state = "corpsecargotech" + +/obj/effect/mob_spawn/corpse/human/factory/qm + name = "Factory Quartermaster" + outfit = /datum/outfit/factory/qm + icon_state = "corpsecargotech" + diff --git a/code/modules/bitrunning/virtual_domain/domains/pirates.dm b/code/modules/bitrunning/virtual_domain/domains/pirates.dm new file mode 100644 index 00000000000..52d86a71211 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/pirates.dm @@ -0,0 +1,10 @@ +/datum/lazy_template/virtual_domain/pirates + name = "Corsair Cove" + 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 + help_text = "Put on the provided outfits to blend in, then battle your way through the hostile pirates. \ + Grab the treasure and get out before you're overwhelmed!" + key = "pirates" + map_name = "pirates" + reward_points = BITRUNNER_REWARD_MEDIUM diff --git a/code/modules/bitrunning/virtual_domain/domains/stairs_and_cliffs.dm b/code/modules/bitrunning/virtual_domain/domains/stairs_and_cliffs.dm new file mode 100644 index 00000000000..2d9bcca3645 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/stairs_and_cliffs.dm @@ -0,0 +1,29 @@ +/datum/lazy_template/virtual_domain/stairs_and_cliffs + name = "Glacier Grind" + cost = BITRUNNER_COST_LOW + desc = "A treacherous climb few calves can survive. Great cardio though." + help_text = "Ever heard of 'Snakes and Ladders'? It's like that, but with \ + instead of ladders its stairs and instead of snakes its a steep drop down a \ + cliff into rough rocks or liquid plasma." + extra_loot = list(/obj/item/clothing/suit/costume/snowman = 2) + difficulty = BITRUNNER_DIFFICULTY_LOW + forced_outfit = /datum/outfit/job/virtual_domain_iceclimber + key = "stairs_and_cliffs" + map_name = "stairs_and_cliffs" + reward_points = BITRUNNER_REWARD_MEDIUM + safehouse_path = /datum/map_template/safehouse/ice + +/turf/open/cliff/snowrock/virtual_domain + name = "icy cliff" + initial_gas_mix = "o2=22;n2=82;TEMP=180" + +/turf/open/lava/plasma/virtual_domain + name = "plasma lake" + initial_gas_mix = "o2=22;n2=82;TEMP=180" + +/datum/outfit/job/virtual_domain_iceclimber + name = "Ice Climber" + + uniform = /obj/item/clothing/under/color/grey + backpack = /obj/item/storage/backpack/duffelbag + shoes = /obj/item/clothing/shoes/winterboots diff --git a/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm b/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm new file mode 100644 index 00000000000..bae0da6874d --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm @@ -0,0 +1,13 @@ +/datum/lazy_template/virtual_domain/syndicate_assault + name = "Syndicate Assault" + cost = BITRUNNER_COST_MEDIUM + desc = "Board the enemy ship and recover the stolen cargo." + difficulty = BITRUNNER_DIFFICULTY_MEDIUM + extra_loot = list(/obj/item/toy/plush/nukeplushie = 1) + help_text = "A group of Syndicate operatives have stolen valuable cargo from the station. \ + They have boarded their ship and are attempting to escape. Infiltrate their ship and recover \ + the crate. Be careful, they are extremely armed." + key = "syndicate_assault" + map_name = "syndicate_assault" + reward_points = BITRUNNER_REWARD_MEDIUM + safehouse_path = /datum/map_template/safehouse/shuttle diff --git a/code/modules/bitrunning/virtual_domain/domains/test_only.dm b/code/modules/bitrunning/virtual_domain/domains/test_only.dm new file mode 100644 index 00000000000..6e5e852fb5c --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/test_only.dm @@ -0,0 +1,11 @@ +/// Used for unit tests only. Skipped in UI. +/datum/lazy_template/virtual_domain/test_only + name = "Test Only" + key = "test_only" + map_name = "test_only" + test_only = TRUE + safehouse_path = /datum/map_template/safehouse/test_only + +/datum/lazy_template/virtual_domain/test_only/expensive + key = "test_only_expensive" + cost = 3 diff --git a/code/modules/bitrunning/virtual_domain/domains/vaporwave.dm b/code/modules/bitrunning/virtual_domain/domains/vaporwave.dm new file mode 100644 index 00000000000..45d4abec983 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/vaporwave.dm @@ -0,0 +1,10 @@ +/datum/lazy_template/virtual_domain/vaporwave + name = "Cosmic Vestige" + cost = BITRUNNER_COST_EXTREME + desc = "Suspended in the silent void of space, the Neon Relic is a haunting echo of a retro-futuristic era." + difficulty = BITRUNNER_DIFFICULTY_NONE + extra_loot = list(/obj/item/stack/spacecash/c500 = 3) + key = "vaporwave" + map_name = "vaporwave" + reward_points = BITRUNNER_REWARD_EXTREME + safehouse_path = /datum/map_template/safehouse/shuttle_space diff --git a/code/modules/bitrunning/virtual_domain/domains/wendigo.dm b/code/modules/bitrunning/virtual_domain/domains/wendigo.dm new file mode 100644 index 00000000000..fcad3db6faf --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/wendigo.dm @@ -0,0 +1,19 @@ +/datum/lazy_template/virtual_domain/wendigo + name = "Glacial Devourer" + cost = BITRUNNER_COST_HIGH + desc = "Legends speak of the ravenous Wendigo hidden deep within the caves of Icemoon." + difficulty = BITRUNNER_DIFFICULTY_HIGH + forced_outfit = /datum/outfit/job/miner + key = "wendigo" + map_name = "wendigo" + reward_points = BITRUNNER_REWARD_HIGH + safehouse_path = /datum/map_template/safehouse/lavaland_boss + +/mob/living/simple_animal/hostile/megafauna/wendigo/virtual_domain + can_be_cybercop = FALSE + crusher_loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + guaranteed_butcher_results = list(/obj/item/wendigo_skull = 1) + health = 2000 + loot = list(/obj/structure/closet/crate/secure/bitrunning/encrypted) + maxHealth = 2000 + true_spawn = FALSE diff --git a/code/modules/bitrunning/virtual_domain/domains/xeno_nest.dm b/code/modules/bitrunning/virtual_domain/domains/xeno_nest.dm new file mode 100644 index 00000000000..2bd4105e13c --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/xeno_nest.dm @@ -0,0 +1,12 @@ +/datum/lazy_template/virtual_domain/xeno_nest + name = "Xeno Infestation" + cost = BITRUNNER_COST_LOW + desc = "Our ship scanners have detected lifeforms of unknown origin. Friendly attempts to contact them have failed." + difficulty = BITRUNNER_DIFFICULTY_LOW + extra_loot = list(/obj/item/toy/plush/rouny = 1) + help_text = "You are on a barren planet filled with hostile creatures. There is a crate here, not hidden, \ + simply protected. Expect resistance." + key = "xeno_nest" + map_name = "xeno_nest" + reward_points = BITRUNNER_REWARD_LOW + safehouse_path = /datum/map_template/safehouse/shuttle diff --git a/code/modules/bitrunning/virtual_domain/safehouses.dm b/code/modules/bitrunning/virtual_domain/safehouses.dm new file mode 100644 index 00000000000..bb42f690ac7 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/safehouses.dm @@ -0,0 +1,53 @@ +/** + * # Safe Houses + * The starting point for virtual domains. + * Create your own: Read the readme file in the '_maps/safehouses' folder. + */ +/datum/map_template/safehouse + name = "virtual domain: safehouse" + + returns_created_atoms = TRUE + /// The map file to load + var/filename = "den.dmm" + +/datum/map_template/safehouse/New() + mappath = "_maps/safehouses/" + filename + ..(path = mappath) + +/datum/map_template/safehouse/test_only + filename = "test_only_safehouse.dmm" + + +/// The default safehouse map template. +/datum/map_template/safehouse/wood + filename = "wood.dmm" + +/datum/map_template/safehouse/den + filename = "den.dmm" + +/datum/map_template/safehouse/dig + filename = "dig.dmm" + +/datum/map_template/safehouse/shuttle + filename = "shuttle.dmm" + +// Has space tiles on the four corners. +/datum/map_template/safehouse/shuttle_space + filename = "shuttle_space.dmm" + +/datum/map_template/safehouse/mine + filename = "mine.dmm" + +// Comes preloaded with mining combat gear. +/datum/map_template/safehouse/lavaland_boss + filename = "lavaland_boss.dmm" + +// Chill out +/datum/map_template/safehouse/ice + filename = "ice.dmm" + +/** + * Your safehouse here + * /datum/map_template/safehouse/your_type + * filename = "your_map.dmm" + */ diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm new file mode 100644 index 00000000000..c2bd193f4e9 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -0,0 +1,34 @@ +/** + * # Virtual Domains + * This loads a base level, then users can select the preset upon it. + * Create your own: Read the readme file in the '_maps/virtual_domains' folder. + */ +/datum/lazy_template/virtual_domain + map_dir = "_maps/virtual_domains" + map_name = "None" + key = "Virtual Domain" + + /// Cost of this map to load + var/cost = BITRUNNER_COST_NONE + /// The description of the map + var/desc = "A map." + /// The 'difficulty' of the map, which affects the ui and ability to scan info. + var/difficulty = BITRUNNER_DIFFICULTY_NONE + /// An assoc list of typepath/amount to spawn on completion. Not weighted - the value is the amount + var/list/extra_loot + /// The map file to load + var/filename = "virtual_domain.dmm" + /// Any outfit that you wish to force on avatars. Overrides preferences + var/datum/outfit/forced_outfit + /// Information given to connected clients via ability + var/help_text + // Name to show in the UI + var/name = "Virtual Domain" + /// Points to reward for completion. Used to purchase new domains and calculate ore rewards. + var/reward_points = BITRUNNER_REWARD_MIN + /// The start time of the map. Used to calculate time taken + var/start_time + /// This map is specifically for unit tests. Shouldn't display in game + var/test_only = FALSE + /// The safehouse to load into the map + var/datum/map_template/safehouse/safehouse_path = /datum/map_template/safehouse/den diff --git a/code/modules/cargo/materials_market.dm b/code/modules/cargo/materials_market.dm new file mode 100644 index 00000000000..d211df7debd --- /dev/null +++ b/code/modules/cargo/materials_market.dm @@ -0,0 +1,259 @@ +/obj/machinery/materials_market + name = "galactic materials market" + desc = "This machine allows the user to buy and sell sheets of minerals \ + across the system. Prices are known to fluxuate quite often,\ + sometimes even within the same minute. All transactions are final." + circuit = /obj/item/circuitboard/machine/materials_market + req_access = list(ACCESS_CARGO) + density = TRUE + icon = 'icons/obj/economy.dmi' + icon_state = "mat_market" + base_icon_state = "mat_market" + idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION + /// What items can be converted into a stock block? Must be a stack subtype based on current implementation. + var/list/exportable_material_items = list( + /obj/item/stack/sheet/iron, //God why are we like this + /obj/item/stack/sheet/glass, //No really, God why are we like this + /obj/item/stack/sheet/mineral, + /obj/item/stack/tile/mineral, + /obj/item/stack/ore, + /obj/item/stack/sheet/bluespace_crystal, + /obj/item/stack/rods + ) + /// Are we ordering sheets from our own card balance or the cargo budget? + var/ordering_private = TRUE + /// Currently, can we order sheets from our own card balance or the cargo budget? + var/can_buy_via_budget = FALSE + +/obj/machinery/materials_market/update_icon_state() + if(panel_open) + icon_state = "[base_icon_state]_open" + return ..() + if(!is_operational || !anchored) + icon_state = "[base_icon_state]_off" + return ..() + icon_state = "[base_icon_state]" + return ..() + +/obj/machinery/materials_market/wrench_act(mob/living/user, obj/item/tool) + ..() + default_unfasten_wrench(user, tool, time = 1.5 SECONDS) + return TOOL_ACT_TOOLTYPE_SUCCESS + +/obj/machinery/materials_market/attackby(obj/item/O, mob/user, params) + if(default_deconstruction_screwdriver(user, "[base_icon_state]_open", "[base_icon_state]", O)) + return + else if(default_deconstruction_crowbar(O)) + return + if(is_type_in_list(O, exportable_material_items)) + var/amount = 0 + var/value = 0 + var/material_to_export + var/obj/item/stack/exportable = O + for(var/datum/material/mat as anything in SSstock_market.materials_prices) + if(exportable.has_material_type(mat)) + amount = exportable.amount + value = SSstock_market.materials_prices[mat] + material_to_export = mat + break //This is only for trading non-alloys, so we can break here + + if(!amount) + say("Not enough material. Aborting.") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, FALSE) + return TRUE + qdel(exportable) + var/obj/item/stock_block/new_block = new /obj/item/stock_block(drop_location()) + new_block.export_value = amount * value * MARKET_PROFIT_MODIFIER + new_block.export_mat = material_to_export + new_block.quantity = amount + to_chat(user, span_notice("You have created a stock block worth [new_block.export_value] cr! Sell it before it becomes liquid!")) + playsound(src, 'sound/machines/synth_yes.ogg', 50, FALSE) + return TRUE + return ..() + + +/obj/machinery/materials_market/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!anchored) + return + if(!ui) + ui = new(user, src, "MatMarket", name) + ui.open() + +/obj/machinery/materials_market/ui_data(mob/user) + var/data = list() + var/material_data + for(var/datum/material/traded_mat as anything in SSstock_market.materials_prices) + var/trend_string = "" + if(SSstock_market.materials_trends[traded_mat] == 0) + trend_string = "neutral" + else if(SSstock_market.materials_trends[traded_mat] == 1) + trend_string = "up" + else if(SSstock_market.materials_trends[traded_mat] == -1) + trend_string = "down" + var/color_string = "" + if(traded_mat.color) + color_string = traded_mat.color + else if (traded_mat.greyscale_colors) + color_string = splicetext(traded_mat.greyscale_colors, 6, length(traded_mat.greyscale_colors), "") //slice it to a standard 6 char hex + material_data += list(list( + "name" = traded_mat.name, + "price" = SSstock_market.materials_prices[traded_mat], + "quantity" = SSstock_market.materials_quantity[traded_mat], + "trend" = trend_string, + "color" = color_string, + )) + + can_buy_via_budget = FALSE + var/obj/item/card/id/used_id_card + if(isliving(user)) + var/mob/living/living_user = user + used_id_card = living_user.get_idcard(TRUE) + can_buy_via_budget = (ACCESS_CARGO in used_id_card?.GetAccess()) + + var/balance = 0 + if(!ordering_private) + var/datum/bank_account/dept = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(dept) + balance = dept.account_balance + else + balance = used_id_card?.registered_account?.account_balance + + var/market_crashing = FALSE + if(HAS_TRAIT(SSeconomy, TRAIT_MARKET_CRASHING)) + market_crashing = TRUE + + data["catastrophe"] = market_crashing + data["materials"] = material_data + data["creditBalance"] = balance + data["orderingPrive"] = ordering_private + data["canOrderCargo"] = can_buy_via_budget + return data + +/obj/machinery/materials_market/ui_act(action, params) + . = ..() + if(.) + return + if(!isliving(usr)) + return + switch(action) + if("buy") + var/material_str = params["material"] + var/quantity = text2num(params["quantity"]) + + var/datum/material/material_bought + var/obj/item/stack/sheet/sheet_to_buy + for(var/datum/material/mat as anything in SSstock_market.materials_prices) + if(mat.name == material_str) + material_bought = mat + break + if(!material_bought) + CRASH("Invalid material name passed to materials market!") + var/mob/living/living_user = usr + var/datum/bank_account/account_payable = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(ordering_private) + var/obj/item/card/id/used_id_card = living_user.get_idcard(TRUE) + account_payable = used_id_card.registered_account + else if(can_buy_via_budget) + account_payable = SSeconomy.get_dep_account(ACCOUNT_CAR) + + var/cost = SSstock_market.materials_prices[material_bought] * quantity + + sheet_to_buy = material_bought.sheet_type + if(!sheet_to_buy) + CRASH("Material with no sheet type being sold on materials market!") + if(!account_payable) + say("No bank account detected!") + return + if(cost > account_payable.account_balance) + to_chat(living_user, span_warning("You don't have enough money to buy that!")) + return + var/list/things_to_order = list() + things_to_order += (sheet_to_buy) + things_to_order[sheet_to_buy] = quantity + // We want to count how many stacks of all sheets we're ordering to make sure they don't exceed the limit of 10 + //If we already have a custom order on SSshuttle, we should add the things to order to that order + for(var/datum/supply_order/order in SSshuttle.shopping_list) + if(order.orderer == living_user && order.orderer_rank == "Galactic Materials Market") + var/prior_stacks = 0 + for(var/obj/item/stack/sheet/sheet as anything in order.pack.contains) + prior_stacks += ROUND_UP(order.pack.contains[sheet] / 50) + if(prior_stacks >= 10) + to_chat(usr, span_notice("You already have 10 stacks of sheets on order! Please wait for them to arrive before ordering more.")) + playsound(usr, 'sound/machines/synth_no.ogg', 35, FALSE) + return + order.append_order(things_to_order, cost) + account_payable.adjust_money(-(cost) , "Materials Market Purchase") //Add the extra price to the total + return + account_payable.adjust_money(-(CARGO_CRATE_VALUE) , "Materials Market Purchase") //Here is where we factor in the base cost of a crate + //Now we need to add a cargo order for quantity sheets of material_bought.sheet_type + var/datum/supply_pack/custom/minerals/mineral_pack = new( + purchaser = living_user, \ + cost = SSstock_market.materials_prices[material_bought] * quantity, \ + contains = things_to_order, \ + ) + var/datum/supply_order/new_order = new( + pack = mineral_pack, + orderer = living_user, + orderer_rank = "Galactic Materials Market", + orderer_ckey = living_user.ckey, + reason = "", + paying_account = account_payable, + department_destination = null, + coupon = null, + charge_on_purchase = FALSE, + manifest_can_fail = FALSE, + cost_type = "credit", + can_be_cancelled = FALSE, + ) + say("Thank you for your purchase! It will arrive on the next cargo shuttle!") + SSshuttle.shopping_list += new_order + return + if("toggle_budget") + if(!can_buy_via_budget) + return + ordering_private = !ordering_private + + +/obj/item/stock_block + name = "stock block" + desc = "A block of stock. It's worth a certain amount of money, based on a sale on the materials market. Ship it on the cargo shuttle to claim your money." + icon = 'icons/obj/economy.dmi' + icon_state = "stock_block" + /// How many credits was this worth when created? + var/export_value = 0 + /// What is the name of the material this was made from? + var/datum/material/export_mat + /// Quantity of export material + var/quantity = 0 + /// Is this stock block currently updating it's value with the market (aka fluid)? + var/fluid = FALSE + +/obj/item/stock_block/examine(mob/user) + . = ..() + . += span_notice("\The [src] is worth [export_value] cr, from selling [quantity] sheets of [export_mat?.name].") + if(fluid) + . += span_warning("\The [src] is currently liquid! It's value is based on the market price.") + else + . += span_notice("\The [src]'s value is still [span_boldnotice("locked in")]. [span_boldnotice("Sell it")] before it's value becomes liquid!") + +/obj/item/stock_block/Initialize(mapload) + . = ..() + addtimer(CALLBACK(src, PROC_REF(value_warning)), 2.5 MINUTES) + addtimer(CALLBACK(src, PROC_REF(update_value)), 5 MINUTES) + +/obj/item/stock_block/proc/value_warning() + visible_message(span_warning("\The [src] is starting to become liquid!")) + icon_state = "stock_block_fluid" + update_appearance(UPDATE_ICON_STATE) + +/obj/item/stock_block/proc/update_value() + if(!export_mat) + return + if(!SSstock_market.materials_prices[export_mat]) + return + export_value = quantity * SSstock_market.materials_prices[export_mat] * MARKET_PROFIT_MODIFIER + icon_state = "stock_block_liquid" + update_appearance(UPDATE_ICON_STATE) + visible_message(span_warning("\The [src] becomes liquid!")) + diff --git a/code/modules/cargo/packs/stock_market_items.dm b/code/modules/cargo/packs/stock_market_items.dm new file mode 100644 index 00000000000..04b2eac4acf --- /dev/null +++ b/code/modules/cargo/packs/stock_market_items.dm @@ -0,0 +1,36 @@ +/** + * todo: make this a supply_pack/custom. Drop pog? ohoho yes. Would be VERY fun. + */ +/datum/supply_pack/market_materials + name = "A Single Sheet of Bananium" + desc = "Going market price for this kind of sheet, by Australicus Industrial Mining." + cost = CARGO_CRATE_VALUE * 2 + // contains = list(/obj/item/stack/sheet/mineral/bananium) + crate_name = "mineral stock sheet crate" + group = "Canisters & Materials" + /// What material we are trying to buy sheets of? + var/datum/material/material + /// How many sheets of the material we are trying to buy at once? + var/amount + +/datum/supply_pack/market_materials/get_cost() + for(var/datum/material/mat in SSstock_market.materials_prices) + if(material == mat) + return SSstock_market.materials_prices[mat] * amount + +/datum/supply_pack/market_materials/fill(obj/structure/closet/crate/C) + . = ..() + new material.sheet_type(C, amount) + +/datum/supply_pack/market_materials/iron + name = "Iron Sheets" + crate_name = "iron stock crate" + material = /datum/material/iron +MARKET_QUANTITY_HELPERS(/datum/supply_pack/market_materials/iron) + + +/datum/supply_pack/market_materials/gold + name = "Gold Sheets" + crate_name = "gold stock crate" + material = /datum/material/gold +MARKET_QUANTITY_HELPERS(/datum/supply_pack/market_materials/gold) diff --git a/code/modules/client/preferences/operative_species.dm b/code/modules/client/preferences/operative_species.dm new file mode 100644 index 00000000000..0b55bc23b52 --- /dev/null +++ b/code/modules/client/preferences/operative_species.dm @@ -0,0 +1,23 @@ +/// When TRUE, will spawn you as a human when selected for an operative role +/// When FALSE, players will be placed into the game as their character's species +/datum/preference/toggle/nuke_ops_species + category = PREFERENCE_CATEGORY_NON_CONTEXTUAL + can_randomize = FALSE + default_value = TRUE + savefile_identifier = PREFERENCE_CHARACTER + savefile_key = "operative_species" + +/datum/preference/toggle/nuke_ops_species/is_accessible(datum/preferences/preferences) + . = ..() + if(!.) + return FALSE + + // If one of the roles is ticked in the antag prefs menu, this option will show. + var/static/list/ops_roles = list(ROLE_OPERATIVE, ROLE_LONE_OPERATIVE, ROLE_OPERATIVE_MIDROUND, ROLE_CLOWN_OPERATIVE) + if(length(ops_roles & preferences.be_special)) + return TRUE + + return FALSE + +/datum/preference/toggle/nuke_ops_species/apply_to_human(mob/living/carbon/human/target, value) + return diff --git a/code/modules/client/preferences/prosthetic.dm b/code/modules/client/preferences/prosthetic.dm new file mode 100644 index 00000000000..f66f1278c48 --- /dev/null +++ b/code/modules/client/preferences/prosthetic.dm @@ -0,0 +1,17 @@ +/datum/preference/choiced/prosthetic + category = PREFERENCE_CATEGORY_SECONDARY_FEATURES + savefile_key = "prosthetic" + savefile_identifier = PREFERENCE_CHARACTER + +/datum/preference/choiced/prosthetic/init_possible_values() + return list("Random") + GLOB.limb_choice + +/datum/preference/choiced/prosthetic/is_accessible(datum/preferences/preferences) + . = ..() + if (!.) + return FALSE + + return "Prosthetic Limb" in preferences.all_quirks + +/datum/preference/choiced/prosthetic/apply_to_human(mob/living/carbon/human/target, value) + return diff --git a/code/modules/clothing/belts/polymorph_belt.dm b/code/modules/clothing/belts/polymorph_belt.dm new file mode 100644 index 00000000000..73959d6d415 --- /dev/null +++ b/code/modules/clothing/belts/polymorph_belt.dm @@ -0,0 +1,159 @@ +/// Belt which can turn you into a beast, once an anomaly core is inserted +/obj/item/polymorph_belt + name = "polymorphic field inverter" + desc = "This device can scan and store DNA from other life forms." + slot_flags = ITEM_SLOT_BELT + icon = 'icons/obj/clothing/belts.dmi' + icon_state = "polybelt_inactive" + worn_icon_state = "polybelt_inactive" + base_icon_state = "polybelt" + item_flags = NOBLUDGEON + /// Typepath of a mob we have scanned, we only store one at a time + var/stored_mob_type + /// Have we activated the belt? + var/active = FALSE + /// Our current transformation action + var/datum/action/cooldown/spell/shapeshift/polymorph_belt/transform_action + +/obj/item/polymorph_belt/Initialize(mapload) + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/polymorph_belt/Destroy(force) + QDEL_NULL(transform_action) + return ..() + +/obj/item/polymorph_belt/examine(mob/user) + . = ..() + if (stored_mob_type) + var/mob/living/will_become = stored_mob_type + . += span_notice("It contains digitised [initial(will_become.name)] DNA.") + if (!active) + . += span_warning("It requires a Bioscrambler Anomaly Core in order to function.") + +/obj/item/polymorph_belt/item_action_slot_check(slot, mob/user, datum/action/action) + return slot & ITEM_SLOT_BELT + +/obj/item/polymorph_belt/update_icon_state() + icon_state = base_icon_state + (active ? "" : "_inactive") + worn_icon_state = base_icon_state + (active ? "" : "_inactive") + return ..() + +/obj/item/polymorph_belt/attackby(obj/item/weapon, mob/user, params) + if (!istype(weapon, /obj/item/assembly/signaler/anomaly/bioscrambler)) + return ..() + balloon_alert(user, "inserting...") + if (!do_after(user, delay = 3 SECONDS, target = src)) + return + qdel(weapon) + active = TRUE + update_appearance(UPDATE_ICON_STATE) + update_transform_action() + playsound(src, 'sound/machines/crate_open.ogg', 50, FALSE) + +/obj/item/polymorph_belt/attack(mob/living/target_mob, mob/living/user, params) + . = ..() + if (.) + return + if (!isliving(target_mob)) + return + if (!isanimal_or_basicmob(target_mob)) + balloon_alert(user, "target too complex!") + return TRUE + if (target_mob.mob_biotypes & (MOB_HUMANOID|MOB_ROBOTIC|MOB_SPECIAL|MOB_SPIRIT|MOB_UNDEAD)) + balloon_alert(user, "incompatible!") + return TRUE + if (isanimal_or_basicmob(target_mob)) + if (!target_mob.compare_sentience_type(SENTIENCE_ORGANIC)) + balloon_alert(user, "target too intelligent!") + return TRUE + if (stored_mob_type == target_mob.type) + balloon_alert(user, "already scanned!") + return TRUE + if (DOING_INTERACTION_WITH_TARGET(user, target_mob)) + balloon_alert(user, "busy!") + return TRUE + balloon_alert(user, "scanning...") + visible_message(span_notice("[user] begins scanning [target_mob] with [src].")) + if (!do_after(user, delay = 5 SECONDS, target = target_mob)) + return TRUE + visible_message(span_notice("[user] scans [target_mob] with [src].")) + stored_mob_type = target_mob.type + update_transform_action() + playsound(src, 'sound/machines/ping.ogg', 50, FALSE) + return TRUE + +/// Make sure we can transform into the scanned target +/obj/item/polymorph_belt/proc/update_transform_action() + if (isnull(stored_mob_type) || !active) + return + if (isnull(transform_action)) + transform_action = add_item_action(/datum/action/cooldown/spell/shapeshift/polymorph_belt) + transform_action.update_type(stored_mob_type) + +/// Pre-activated polymorph belt +/obj/item/polymorph_belt/functioning + active = TRUE + icon_state = "polybelt" + worn_icon_state = "polybelt" + +/// Ability provided by the polymorph belt +/datum/action/cooldown/spell/shapeshift/polymorph_belt + name = "Invert Polymorphic Field" + cooldown_time = 30 SECONDS + school = SCHOOL_UNSET + invocation_type = INVOCATION_NONE + spell_requirements = NONE + possible_shapes = list(/mob/living/basic/cockroach) + /// Amount of time it takes us to transform back or forth + var/channel_time = 3 SECONDS + +/datum/action/cooldown/spell/shapeshift/polymorph_belt/Remove(mob/remove_from) + var/datum/status_effect/shapechange_mob/shapechange = remove_from.has_status_effect(/datum/status_effect/shapechange_mob/from_spell) + var/atom/changer = shapechange?.caster_mob || remove_from + changer?.transform = matrix() + return ..() + +/datum/action/cooldown/spell/shapeshift/polymorph_belt/before_cast(mob/living/cast_on) + . = ..() + if (. & SPELL_CANCEL_CAST) + return + if (channel_time <= 0) + return + if (DOING_INTERACTION_WITH_TARGET(cast_on, cast_on)) + return . | SPELL_CANCEL_CAST + + var/old_transform = cast_on.transform + + var/animate_step = channel_time / 6 + playsound(cast_on, 'sound/effects/wounds/crack1.ogg', 50) + animate(cast_on, transform = matrix() * 1.1, time = animate_step, easing = SINE_EASING) + animate(transform = matrix() * 0.9, time = animate_step, easing = SINE_EASING) + animate(transform = matrix() * 1.2, time = animate_step, easing = SINE_EASING) + animate(transform = matrix() * 0.8, time = animate_step, easing = SINE_EASING) + animate(transform = matrix() * 1.3, time = animate_step, easing = SINE_EASING) + animate(transform = matrix() * 0.1, time = animate_step, easing = SINE_EASING) + + cast_on.balloon_alert(cast_on, "transforming...") + if (!do_after(cast_on, delay = channel_time, target = cast_on)) + animate(cast_on, transform = matrix(), time = 0, easing = SINE_EASING) + cast_on.transform = old_transform + return . | SPELL_CANCEL_CAST + cast_on.visible_message(span_warning("[cast_on]'s body rearranges itself with a horrible crunching sound!")) + playsound(cast_on, 'sound/magic/demon_consume.ogg', 50, TRUE) + +/datum/action/cooldown/spell/shapeshift/polymorph_belt/after_cast(atom/cast_on) + . = ..() + if (QDELETED(owner)) + return + animate(owner, transform = matrix() * 0.1, time = 0, easing = JUMP_EASING) + animate(transform = matrix(), time = 0.25 SECONDS, easing = SINE_EASING) + +/// Update what you are transforming to or from +/datum/action/cooldown/spell/shapeshift/polymorph_belt/proc/update_type(transform_type) + unshift_owner() + shapeshift_type = transform_type + possible_shapes = list(transform_type) + var/mob/living/will_become = transform_type + desc = "Assume your [initial(will_become.name)] form!" + build_all_button_icons(update_flags = UPDATE_BUTTON_NAME) diff --git a/code/modules/events/supermatter_surge.dm b/code/modules/events/supermatter_surge.dm new file mode 100644 index 00000000000..6c790c84f8d --- /dev/null +++ b/code/modules/events/supermatter_surge.dm @@ -0,0 +1,140 @@ +#define SURGE_DURATION_MIN 240 EVENT_SECONDS +#define SURGE_DURATION_MAX 270 EVENT_SECONDS +#define SURGE_SEVERITY_MIN 1 +#define SURGE_SEVERITY_MAX 4 +#define SURGE_SEVERITY_RANDOM 5 +/// The amount of bullet energy we add for the duration of the SM surge +#define SURGE_BULLET_ENERGY_ADDITION 5 +/// The amount of powerloss inhibition (energy retention) we add for the duration of the SM surge +#define SURGE_BASE_POWERLOSS_INHIBITION 0.55 +/// The powerloss inhibition scaling based on surge severity +#define SURGE_POWERLOSS_INHIBITION_MODIFIER 0.175 +/// The power generation scaling based on surge severity +#define SURGE_POWER_GENERATION_MODIFIER 0.075 +/// The heat modifier scaling based on surge severity +#define SURGE_HEAT_MODIFIER 0.25 + +/** + * Supermatter Surge + * + * An engineering challenge event where the properties of the SM changes to be in a 'surge' of power. + * For the duration of the event a powerloss inhibition is added to nitrogen, causing the crystal to retain more of its internal energy. + * Heat modifier is lowered to generate some heat but not a high temp burn. + * Bullet energy from emitters is raised slightly to raise meV while turned on. + */ + +/datum/round_event_control/supermatter_surge + name = "Supermatter Surge" + typepath = /datum/round_event/supermatter_surge + category = EVENT_CATEGORY_ENGINEERING + weight = 15 + max_occurrences = 1 + earliest_start = 20 MINUTES + description = "The supermatter will increase in power and heat by a random amount, and announce it." + min_wizard_trigger_potency = 4 + max_wizard_trigger_potency = 7 + admin_setup = list( + /datum/event_admin_setup/input_number/surge_spiciness, + ) + +/datum/round_event_control/supermatter_surge/can_spawn_event(players_amt, allow_magic = FALSE) + . = ..() + + if(!SSjob.has_minimum_jobs(crew_threshold = 3, jobs = JOB_GROUP_ENGINEERS, head_jobs = list(JOB_CHIEF_ENGINEER))) + return FALSE + +/datum/round_event/supermatter_surge + announce_when = 4 + end_when = SURGE_DURATION_MIN + /// How powerful is the supermatter surge going to be? + var/surge_class = SURGE_SEVERITY_RANDOM + /// Typecasted reference to the supermatter chosen at event start + var/obj/machinery/power/supermatter_crystal/engine + /// Typecasted reference to the nitrogen properies in the SM chamber + var/datum/sm_gas/nitrogen/sm_gas + +/datum/event_admin_setup/input_number/surge_spiciness + input_text = "Set surge intensity. (Higher is more severe.)" + min_value = SURGE_SEVERITY_MIN + max_value = SURGE_SEVERITY_MAX + +/datum/event_admin_setup/input_number/surge_spiciness/prompt_admins() + default_value = rand(SURGE_SEVERITY_MIN, SURGE_SEVERITY_MAX) + return ..() + +/datum/event_admin_setup/input_number/surge_spiciness/apply_to_event(datum/round_event/supermatter_surge/event) + event.surge_class = chosen_value + +/datum/round_event/supermatter_surge/setup() + engine = GLOB.main_supermatter_engine + if(isnull(engine)) + stack_trace("SM surge event failed to find a supermatter engine!") + return + + sm_gas = LAZYACCESS(engine.current_gas_behavior, /datum/gas/nitrogen) + if(isnull(sm_gas)) + stack_trace("SM surge event failed to find gas properties for [engine].") + return + + if(surge_class == SURGE_SEVERITY_RANDOM) + var/severity_weight = rand(1, 100) + switch(severity_weight) + if(1 to 14) + surge_class = 1 + if(15 to 34) + surge_class = 2 + if(35 to 69) + surge_class = 3 + if(70 to 100) + surge_class = 4 + + end_when = rand(SURGE_DURATION_MIN, SURGE_DURATION_MAX) + +/datum/round_event/supermatter_surge/announce(fake) + priority_announce("The Crystal Integrity Monitoring System has detected unusual atmospheric properties in the supermatter chamber, energy output from the supermatter crystal has increased significantly. Engineering intervention is required to stabilize the engine.", "Class [surge_class] Supermatter Surge Alert", 'sound/machines/engine_alert3.ogg') + +/datum/round_event/supermatter_surge/start() + engine.bullet_energy = surge_class + SURGE_BULLET_ENERGY_ADDITION + sm_gas.powerloss_inhibition = (surge_class * SURGE_POWERLOSS_INHIBITION_MODIFIER) + SURGE_BASE_POWERLOSS_INHIBITION + sm_gas.heat_power_generation = (surge_class * SURGE_POWER_GENERATION_MODIFIER) - 1 + sm_gas.heat_modifier = (surge_class * SURGE_HEAT_MODIFIER) - 1 + + +/datum/round_event/supermatter_surge/end() + engine.bullet_energy = initial(engine.bullet_energy) + sm_gas.powerloss_inhibition = initial(sm_gas.powerloss_inhibition) + sm_gas.heat_power_generation = initial(sm_gas.heat_power_generation) + sm_gas.heat_modifier = initial(sm_gas.heat_modifier) + priority_announce("The supermatter surge has dissipated, crystal output readings have normalized.", "Anomaly Cleared") + engine = null + sm_gas = null + +/datum/round_event_control/supermatter_surge/poly + name = "Supermatter Surge: Poly's Revenge" + typepath = /datum/round_event/supermatter_surge/poly + category = EVENT_CATEGORY_ENGINEERING + weight = 0 + max_occurrences = 0 + description = "For when Poly is sacrificed to the SM. Not really useful to run manually." + min_wizard_trigger_potency = NEVER_TRIGGERED_BY_WIZARDS + max_wizard_trigger_potency = NEVER_TRIGGERED_BY_WIZARDS + admin_setup = null + +/datum/round_event/supermatter_surge/poly + announce_when = 4 + surge_class = 4 + fakeable = FALSE + +/datum/round_event/supermatter_surge/poly/announce(fake) + priority_announce("The Crystal Integrity Monitoring System has detected unusual parrot type resonance in the supermatter chamber, energy output from the supermatter crystal has increased significantly. Engineering intervention is required to stabilize the engine.", "Class P Supermatter Surge Alert", 'sound/machines/engine_alert3.ogg') + +#undef SURGE_DURATION_MIN +#undef SURGE_DURATION_MAX +#undef SURGE_SEVERITY_MIN +#undef SURGE_SEVERITY_MAX +#undef SURGE_SEVERITY_RANDOM +#undef SURGE_BULLET_ENERGY_ADDITION +#undef SURGE_BASE_POWERLOSS_INHIBITION +#undef SURGE_POWERLOSS_INHIBITION_MODIFIER +#undef SURGE_POWER_GENERATION_MODIFIER +#undef SURGE_HEAT_MODIFIER diff --git a/code/modules/experisci/experiment/types/autopsy_experiment.dm b/code/modules/experisci/experiment/types/autopsy_experiment.dm new file mode 100644 index 00000000000..3ef339b642a --- /dev/null +++ b/code/modules/experisci/experiment/types/autopsy_experiment.dm @@ -0,0 +1,39 @@ +/datum/experiment/autopsy + name = "Autopsy Experiment" + description = "An experiment requiring a autopsy surgery to progress" + exp_tag = "Autopsy" + performance_hint = "Perform a autopsy surgery while connected to an operating computer." + +/datum/experiment/autopsy/is_complete() + return completed + +/datum/experiment/autopsy/perform_experiment_actions(datum/component/experiment_handler/experiment_handler, mob/target) + if (is_valid_autopsy(target)) + completed = TRUE + return TRUE + else + return FALSE + +/datum/experiment/autopsy/proc/is_valid_autopsy(mob/target) + return TRUE + +/datum/experiment/autopsy/human + name = "Human Autopsy Experiment" + description = "We don't want to invest in a station that doesn't know their coccyx from their cochlea. Send us back data dissecting a human to receive more funding." + +/datum/experiment/autopsy/human/is_valid_autopsy(mob/target) + return ishumanbasic(target) + +/datum/experiment/autopsy/nonhuman + name = "Non-human Autopsy Experiment" + description = "When we asked for a tail bone, we didn't mean...look, just send us back data from something OTHER than a human. It could be a monkey for all we care, just send us research." + +/datum/experiment/autopsy/nonhuman/is_valid_autopsy(mob/target) + return ishuman(target) && !ishumanbasic(target) + +/datum/experiment/autopsy/xenomorph + name = "Xenomorph Autopsy Experiment" + description = "Our understanding of the xenomorph only scratches the surface. Send us research from dissecting a xenomorph." + +/datum/experiment/autopsy/xenomorph/is_valid_autopsy(mob/target) + return isalien(target) diff --git a/code/modules/experisci/experiment/types/scanning_fish.dm b/code/modules/experisci/experiment/types/scanning_fish.dm new file mode 100644 index 00000000000..83978010869 --- /dev/null +++ b/code/modules/experisci/experiment/types/scanning_fish.dm @@ -0,0 +1,116 @@ +///a superlist containing typecaches shared between the several fish scanning experiments for each techweb. +GLOBAL_LIST_EMPTY(scanned_fish_by_techweb) + +/** + * A special scanning experiment that unlocks further settings for the fishing portal generator. + * Mainly as an inventive solution to many a fish source being limited to maps that have it, + * and to make the fishing portal generator a bit than just gubby and goldfish. + */ +/datum/experiment/scanning/fish + name = "Fish Scanning Experiment 1" + description = "An experiment requiring different fish species to be scanned to unlock the 'Beach' setting for the fishing portal generator." + performance_hint = "Scan fish. Examine scanner to review progress. Unlock new fishing portals." + allowed_experimentors = list(/obj/item/experi_scanner, /obj/machinery/destructive_scanner, /obj/item/fishing_rod/tech) + traits = EXPERIMENT_TRAIT_TYPECACHE + points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 750) + required_atoms = list(/obj/item/fish = 4) + scan_message = "Scan different species of fish" + ///Further experiments added to the techweb when this one is completed. + var/list/next_experiments = list(/datum/experiment/scanning/fish/second) + ///Completing a experiment may also enable a fish source to be used for use for the portal generator. + var/fish_source_reward = /datum/fish_source/portal/beach + +/** + * We make sure the scanned list is shared between all fish scanning experiments for this techweb, + * since this is about scanning each species, and having to redo it for each species is a hassle. + */ +/datum/experiment/scanning/fish/New(datum/techweb/techweb) + . = ..() + if(isnull(techweb)) + return + var/techweb_ref = REF(techweb) + var/list/scanned_fish = GLOB.scanned_fish_by_techweb[techweb_ref] + if(isnull(scanned_fish)) + scanned_fish = list() + GLOB.scanned_fish_by_techweb[techweb_ref] = scanned_fish + for(var/atom_type in required_atoms) + LAZYINITLIST(scanned_fish[atom_type]) + scanned = scanned_fish + +/** + * Registers a couple signals to review the fish scanned so far. + * It'd be an hassle not having any way (beside memory) to know which fish species have been scanned already otherwise. + */ +/datum/experiment/scanning/fish/on_selected(datum/component/experiment_handler/experiment_handler) + RegisterSignal(experiment_handler.parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_handler_examine)) + RegisterSignal(experiment_handler.parent, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_handler_examine_more)) + +/datum/experiment/scanning/fish/on_unselected(datum/component/experiment_handler/experiment_handler) + UnregisterSignal(experiment_handler.parent, list(COMSIG_ATOM_EXAMINE, COMSIG_ATOM_EXAMINE_MORE)) + +/datum/experiment/scanning/fish/proc/on_handler_examine(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER + examine_list += span_notice("Examine again to review all the species of fish scanned so far.") + +/datum/experiment/scanning/fish/proc/on_handler_examine_more(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER + var/message = span_notice("Fish species scanned hitherto, if any:") + message += "" + for(var/atom_type in required_atoms) + for(var/obj/item/fish/fish_path as anything in scanned[atom_type]) + message += "[initial(fish_path.name)]" + message += "" + examine_list += message + +///Only scannable fish will contribute towards the experiment. +/datum/experiment/scanning/fish/final_contributing_index_checks(obj/item/fish/target, typepath) + return target.experisci_scannable + +/** + * After a fish scanning experiment is done, more may be unlocked. If so, add them to the techweb + * and automatically link the handler to the next experiment in the list as a bit of qol. + */ +/datum/experiment/scanning/fish/finish_experiment(datum/component/experiment_handler/experiment_handler, ...) + . = ..() + if(next_experiments) + experiment_handler.linked_web.add_experiments(next_experiments) + var/datum/experiment/next_in_line = locate(next_experiments[1]) in experiment_handler.linked_web.available_experiments + experiment_handler.link_experiment(next_in_line) + +/datum/experiment/scanning/fish/second + name = "Fish Scanning Experiment 2" + description = "An experiment requiring more fish species to be scanned to unlock the 'Chasm' setting for the fishing portal." + points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 1500) + required_atoms = list(/obj/item/fish = 8) + next_experiments = list(/datum/experiment/scanning/fish/third) + fish_source_reward = /datum/fish_source/portal/chasm + +/datum/experiment/scanning/fish/third + name = "Fish Scanning Experiment 3" + description = "An experiment requiring even more fish species to be scanned to unlock the 'Ocean' setting for the fishing portal." + points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + required_atoms = list(/obj/item/fish = 14) + next_experiments = list(/datum/experiment/scanning/fish/fourth, /datum/experiment/scanning/fish/holographic) + fish_source_reward = /datum/fish_source/portal/ocean + +/datum/experiment/scanning/fish/holographic + name = "Holographic Fish Scanning Experiment" + description = "This one actually requires holographic fish to unlock the 'Randomizer' setting for the fishing portal." + performance_hint = "Load in the 'Beach' template at the Holodeck to fish some holo-fish." + points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 500) + required_atoms = list(/obj/item/fish/holo = 4) + scan_message = "Scan different species of holographic fish" + next_experiments = null + fish_source_reward = /datum/fish_source/portal/random + +///holo fishes are normally unscannable, but this is an experiment for them, so we don't care for the experisci_scannable variable. +/datum/experiment/scanning/fish/holographic/final_contributing_index_checks(obj/item/fish/target, typepath) + return TRUE + +/datum/experiment/scanning/fish/fourth + name = "Fish Scanning Experiment 4" + description = "An experiment requiring lotsa fish species to unlock the 'Hyperspace' setting for the fishing portal." + points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 3250) + required_atoms = list(/obj/item/fish = 21) + next_experiments = null + fish_source_reward = /datum/fish_source/portal/hyperspace diff --git a/code/modules/experisci/experiment/types/scanning_people.dm b/code/modules/experisci/experiment/types/scanning_people.dm new file mode 100644 index 00000000000..27c4168dfba --- /dev/null +++ b/code/modules/experisci/experiment/types/scanning_people.dm @@ -0,0 +1,34 @@ +/// An experiment where you scan your fellow humans +/datum/experiment/scanning/people + allowed_experimentors = list(/obj/item/experi_scanner, /obj/item/scanner_wand) + /// Number of people you need to scan + var/required_count = 2 + /// Does the scanned target need to have a mind? + var/mind_required = FALSE + /// How do we describe the people you need to scan? + var/required_traits_desc = "" + +/datum/experiment/scanning/people/New() + required_atoms = list(/mob/living/carbon/human = required_count) + return ..() + +/datum/experiment/scanning/people/final_contributing_index_checks(atom/target, typepath) + . = ..() + if(!.) + return FALSE + if(!ishuman(target)) + return FALSE + return is_valid_scan_target(target) + +/// Checks that the passed mob is valid human to scan +/datum/experiment/scanning/people/proc/is_valid_scan_target(mob/living/carbon/human/check) + SHOULD_CALL_PARENT(TRUE) + if(!mind_required || !isnull(check.mind)) + return TRUE + if(isliving(usr)) + check.balloon_alert(usr, "subject is mindless!") + return FALSE + +/datum/experiment/scanning/people/serialize_progress_stage(atom/target, list/seen_instances) + return EXPERIMENT_PROG_INT("Scan unique individuals with [required_traits_desc].", \ + seen_instances.len, required_atoms[target]) diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_martian.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_martian.dm new file mode 100644 index 00000000000..fb4f29284b8 --- /dev/null +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_martian.dm @@ -0,0 +1,915 @@ +/datum/crafting_recipe/food/kimchi + name = "Kimchi" + reqs = list( + /obj/item/food/grown/cabbage = 1, + /obj/item/food/grown/chili = 1, + /datum/reagent/consumable/salt = 5 + ) + result = /obj/item/food/kimchi + category = CAT_MARTIAN + +/datum/crafting_recipe/food/inferno_kimchi + name = "Inferno kimchi" + reqs = list( + /obj/item/food/grown/cabbage = 1, + /obj/item/food/grown/ghost_chili = 1, + /datum/reagent/consumable/salt = 5 + ) + result = /obj/item/food/inferno_kimchi + category = CAT_MARTIAN + +/datum/crafting_recipe/food/garlic_kimchi + name = "Garlic kimchi" + reqs = list( + /obj/item/food/grown/cabbage = 1, + /obj/item/food/grown/chili = 1, + /obj/item/food/grown/garlic = 1, + /datum/reagent/consumable/salt = 5 + ) + result = /obj/item/food/garlic_kimchi + category = CAT_MARTIAN + +/datum/crafting_recipe/food/surimi + name = "Surimi" + reqs = list( + /obj/item/food/fishmeat = 1, + ) + result = /obj/item/food/surimi + category = CAT_MARTIAN + +/datum/crafting_recipe/food/sambal + name = "Sambal" + reqs = list( + /obj/item/food/grown/chili = 1, + /obj/item/food/grown/garlic = 1, + /obj/item/food/grown/onion = 1, + /datum/reagent/consumable/sugar = 3, + /datum/reagent/consumable/limejuice = 3, + ) + result = /obj/item/food/sambal + category = CAT_MARTIAN + +/datum/crafting_recipe/food/katsu_fillet + name = "Katsu fillet" + reqs = list( + /obj/item/food/meat/rawcutlet = 1, + /obj/item/food/breadslice/reispan = 1, + ) + result = /obj/item/food/katsu_fillet + category = CAT_MARTIAN + +/datum/crafting_recipe/food/rice_dough + name = "Rice dough" + reqs = list( + /datum/reagent/consumable/flour = 10, + /datum/reagent/consumable/rice = 10, + /datum/reagent/water = 10, + ) + result = /obj/item/food/rice_dough + category = CAT_MARTIAN + +/datum/crafting_recipe/food/hurricane_rice + name = "Hurricane fried rice" + reqs = list( + /obj/item/food/boiledrice = 1, + /obj/item/food/egg = 1, + /obj/item/food/onion_slice = 1, + /obj/item/food/grown/chili = 1, + /obj/item/food/meat/cutlet = 1, + /obj/item/food/pineappleslice = 1, + /datum/reagent/consumable/soysauce = 3, + ) + result = /obj/item/food/salad/hurricane_rice + category = CAT_MARTIAN + +/datum/crafting_recipe/food/ikareis + name = "Ikareis" + reqs = list( + /obj/item/food/boiledrice = 1, + /obj/item/food/canned/squid_ink = 1, + /obj/item/food/grown/bell_pepper = 1, + /obj/item/food/onion_slice = 1, + /obj/item/food/sausage = 1, + /obj/item/food/grown/chili = 1, + ) + result = /obj/item/food/salad/ikareis + category = CAT_MARTIAN + +/datum/crafting_recipe/food/hawaiian_fried_rice + name = "Hawaiian fried rice" + reqs = list( + /obj/item/food/boiledrice = 1, + /obj/item/food/chapslice = 1, + /obj/item/food/grown/bell_pepper = 1, + /obj/item/food/pineappleslice = 1, + /obj/item/food/onion_slice = 1, + /datum/reagent/consumable/soysauce = 5 + ) + result = /obj/item/food/salad/hawaiian_fried_rice + category = CAT_MARTIAN + +/datum/crafting_recipe/food/ketchup_fried_rice + name = "Ketchup fried rice" + reqs = list( + /obj/item/food/boiledrice = 1, + /obj/item/food/onion_slice = 1, + /obj/item/food/sausage/american = 1, + /obj/item/food/grown/carrot = 1, + /obj/item/food/grown/peas = 1, + /datum/reagent/consumable/ketchup = 5, + /datum/reagent/consumable/worcestershire = 2, + ) + result = /obj/item/food/salad/ketchup_fried_rice + category = CAT_MARTIAN + +/datum/crafting_recipe/food/mediterranean_fried_rice + name = "Mediterranean fried rice" + reqs = list( + /obj/item/food/boiledrice = 1, + /obj/item/food/onion_slice = 1, + /obj/item/food/grown/herbs = 1, + /obj/item/food/cheese/firm_cheese_slice = 1, + /obj/item/food/grown/olive = 1, + /obj/item/food/meatball = 1, + ) + result = /obj/item/food/salad/mediterranean_fried_rice + category = CAT_MARTIAN + +/datum/crafting_recipe/food/egg_fried_rice + name = "Egg fried rice" + reqs = list( + /obj/item/food/boiledrice = 1, + /obj/item/food/egg = 1, + /datum/reagent/consumable/soysauce = 3, + ) + result = /obj/item/food/salad/egg_fried_rice + category = CAT_MARTIAN + +/datum/crafting_recipe/food/bibimbap + name = "Bibimbap" + reqs = list( + /obj/item/food/boiledrice = 1, + /obj/item/food/grown/cucumber = 1, + /obj/item/food/grown/mushroom = 1, + /obj/item/food/meat/cutlet = 1, + /obj/item/food/kimchi = 1, + /obj/item/food/egg = 1, + ) + result = /obj/item/food/salad/bibimbap + category = CAT_MARTIAN + +/datum/crafting_recipe/food/bulgogi_noodles + name = "Bulgogi noodles" + reqs = list( + /obj/item/food/spaghetti/boilednoodles = 1, + /obj/item/food/meat/cutlet = 1, + /obj/item/food/grown/apple = 1, + /obj/item/food/grown/garlic = 1, + /obj/item/food/onion_slice = 1, + /datum/reagent/consumable/nutriment/soup/teriyaki = 4, + ) + result = /obj/item/food/salad/bibimbap + category = CAT_MARTIAN + +/datum/crafting_recipe/food/yakisoba_katsu + name = "Yakisoba katsu" + reqs = list( + /obj/item/food/spaghetti/boilednoodles = 1, + /obj/item/food/grown/cabbage = 1, + /obj/item/food/grown/carrot = 1, + /obj/item/food/onion_slice = 1, + /obj/item/food/katsu_fillet = 1, + /datum/reagent/consumable/worcestershire = 3, + ) + result = /obj/item/food/salad/yakisoba_katsu + category = CAT_MARTIAN + +/datum/crafting_recipe/food/martian_fried_noodles + name = "Martian fried noodles" + reqs = list( + /obj/item/food/spaghetti/boilednoodles = 1, + /obj/item/food/grown/peanut = 2, + /obj/item/food/meat/cutlet = 1, + /obj/item/food/onion_slice = 1, + /obj/item/food/egg = 1, + /datum/reagent/consumable/soysauce = 3, + /datum/reagent/consumable/red_bay = 3, + ) + result = /obj/item/food/salad/martian_fried_noodles + category = CAT_MARTIAN + +/datum/crafting_recipe/food/simple_fried_noodles + name = "Simple fried noodles" + reqs = list( + /obj/item/food/spaghetti/boilednoodles = 1, + /datum/reagent/consumable/soysauce = 3, + ) + result = /obj/item/food/salad/simple_fried_noodles + category = CAT_MARTIAN + +/datum/crafting_recipe/food/setagaya_curry + name = "Setagaya curry" + reqs = list( + /obj/item/food/boiledrice = 1, + /obj/item/food/grown/apple = 1, + /datum/reagent/consumable/honey = 3, + /datum/reagent/consumable/ketchup = 3, + /obj/item/food/chocolatebar = 1, + /datum/reagent/consumable/coffee = 3, + /datum/reagent/consumable/ethanol/wine = 3, + /datum/reagent/consumable/curry_powder = 3, + /obj/item/food/meat/slab = 1, + /obj/item/food/grown/onion = 1, + /obj/item/food/grown/carrot = 1, + /obj/item/food/grown/potato = 1, + ) + result = /obj/item/food/salad/setagaya_curry + category = CAT_MARTIAN + +/datum/crafting_recipe/food/big_blue_burger + name = "Big Blue Burger" + reqs = list( + /obj/item/food/bun = 1, + /obj/item/food/patty = 2, + /obj/item/food/onion_slice = 1, + /obj/item/food/cheese/wedge = 1, + /obj/item/food/meat/bacon = 1, + /obj/item/food/pineappleslice = 1, + /datum/reagent/consumable/nutriment/soup/teriyaki = 4, + ) + result = /obj/item/food/burger/big_blue + category = CAT_MARTIAN + +/datum/crafting_recipe/food/chappy_patty + name = "Chappy Patty" + reqs = list( + /obj/item/food/bun = 1, + /obj/item/food/grilled_chapslice = 2, + /obj/item/food/friedegg = 1, + /obj/item/food/cheese/wedge = 1, + /datum/reagent/consumable/ketchup = 3, + ) + result = /obj/item/food/burger/chappy + category = CAT_MARTIAN + +/datum/crafting_recipe/food/king_katsu_sandwich + name = "King Katsu sandwich" + reqs = list( + /obj/item/food/breadslice/reispan = 2, + /obj/item/food/katsu_fillet = 1, + /obj/item/food/meat/bacon = 1, + /obj/item/food/kimchi = 1, + /obj/item/food/onion_slice = 1, + /obj/item/food/grown/tomato = 1, + ) + result = /obj/item/food/king_katsu_sandwich + category = CAT_MARTIAN + +/datum/crafting_recipe/food/marte_cubano_sandwich + name = "Marte Cubano sandwich" + reqs = list( + /obj/item/food/breadslice/reispan = 2, + /obj/item/food/meat/bacon = 1, + /obj/item/food/pickle = 2, + /obj/item/food/cheese/wedge = 1, + ) + result = /obj/item/food/marte_cubano_sandwich + category = CAT_MARTIAN + +/datum/crafting_recipe/food/little_shiro_sandwich + name = "Little Shiro sandwich" + reqs = list( + /obj/item/food/breadslice/reispan = 2, + /obj/item/food/meat/cutlet = 1, + /obj/item/food/friedegg = 1, + /obj/item/food/garlic_kimchi = 1, + /obj/item/food/cheese/mozzarella = 1, + /obj/item/food/grown/herbs = 1, + ) + result = /obj/item/food/little_shiro_sandwich + category = CAT_MARTIAN + +/datum/crafting_recipe/food/croque_martienne + name = "Croque-Martienne sandwich" + reqs = list( + /obj/item/food/breadslice/reispan = 2, + /obj/item/food/meat/cutlet = 1, + /obj/item/food/cheese/wedge = 1, + /obj/item/food/pineappleslice = 1, + /obj/item/food/friedegg = 1, + ) + result = /obj/item/food/croque_martienne + category = CAT_MARTIAN + +/datum/crafting_recipe/food/prospect_sunrise + name = "Prospect Sunrise sandwich" + reqs = list( + /obj/item/food/breadslice/reispan = 2, + /obj/item/food/meat/bacon = 1, + /obj/item/food/cheese/wedge = 1, + /obj/item/food/omelette = 1, + /obj/item/food/pickle = 1, + ) + result = /obj/item/food/prospect_sunrise + category = CAT_MARTIAN + +/datum/crafting_recipe/food/takoyaki + name = "Takoyaki" + reqs = list( + /obj/item/food/fishmeat/octopus = 1, + /obj/item/food/onion_slice = 1, + /datum/reagent/consumable/martian_batter = 6, + /datum/reagent/consumable/worcestershire = 3, + ) + result = /obj/item/food/takoyaki + category = CAT_MARTIAN + +/datum/crafting_recipe/food/russian_takoyaki + name = "Russian takoyaki" + reqs = list( + /obj/item/food/fishmeat/octopus = 1, + /obj/item/food/grown/ghost_chili = 1, + /datum/reagent/consumable/martian_batter = 6, + /datum/reagent/consumable/capsaicin = 3, + ) + result = /obj/item/food/takoyaki/russian + category = CAT_MARTIAN + +/datum/crafting_recipe/food/tacoyaki + name = "Tacoyaki" + reqs = list( + /obj/item/food/meatball = 1, + /obj/item/food/grown/corn = 1, + /datum/reagent/consumable/martian_batter = 6, + /datum/reagent/consumable/red_bay = 3, + /obj/item/food/cheese/wedge = 1, + ) + result = /obj/item/food/takoyaki/taco + category = CAT_MARTIAN + +/datum/crafting_recipe/food/okonomiyaki + name = "Okonomiyaki" + reqs = list( + /datum/reagent/consumable/martian_batter = 6, + /datum/reagent/consumable/worcestershire = 3, + /datum/reagent/consumable/mayonnaise = 3, + /obj/item/food/grown/cabbage = 1, + /obj/item/food/grown/potato/sweet = 1, + ) + result = /obj/item/food/okonomiyaki + category = CAT_MARTIAN + +/datum/crafting_recipe/food/brat_kimchi + name = "Brat-kimchi" + reqs = list( + /obj/item/food/sausage = 1, + /obj/item/food/kimchi = 1, + /datum/reagent/consumable/sugar = 3, + ) + result = /obj/item/food/brat_kimchi + category = CAT_MARTIAN + +/datum/crafting_recipe/food/tonkatsuwurst + name = "Tonkatsuwurst" + reqs = list( + /obj/item/food/sausage = 1, + /obj/item/food/fries = 1, + /datum/reagent/consumable/worcestershire = 3, + /datum/reagent/consumable/red_bay = 2, + ) + result = /obj/item/food/tonkatsuwurst + category = CAT_MARTIAN + +/datum/crafting_recipe/food/ti_hoeh_koe + name = "Ti hoeh koe" + reqs = list( + /obj/item/food/boiledrice = 1, + /obj/item/food/peanuts/salted = 1, + /obj/item/food/grown/herbs = 1, + /datum/reagent/blood = 5, + ) + result = /obj/item/food/kebab/ti_hoeh_koe + category = CAT_MARTIAN + +/datum/crafting_recipe/food/kitzushi + name = "Kitzushi" + reqs = list( + /obj/item/food/boiledrice = 1, + /obj/item/food/tofu = 1, + /obj/item/food/cheese/wedge = 1, + /obj/item/food/grown/chili = 1, + ) + result = /obj/item/food/kitzushi + category = CAT_MARTIAN + +/datum/crafting_recipe/food/epok_epok + name = "Epok-epok" + reqs = list( + /obj/item/food/doughslice = 1, + /obj/item/food/meat/cutlet/chicken = 1, + /obj/item/food/grown/potato/wedges = 1, + /obj/item/food/boiledegg = 1, + /datum/reagent/consumable/curry_powder = 3, + ) + result = /obj/item/food/epok_epok + category = CAT_MARTIAN + +/datum/crafting_recipe/food/roti_john + name = "Roti John" + reqs = list( + /obj/item/food/baguette = 1, + /obj/item/food/raw_meatball = 1, + /obj/item/food/egg = 1, + /obj/item/food/onion_slice = 1, + /datum/reagent/consumable/capsaicin = 3, + /datum/reagent/consumable/mayonnaise = 3, + ) + result = /obj/item/food/roti_john + category = CAT_MARTIAN + +/datum/crafting_recipe/food/izakaya_fries + name = "Izakaya fries" + reqs = list( + /obj/item/food/fries = 1, + /obj/item/food/grown/herbs = 1, + /datum/reagent/consumable/red_bay = 3, + /datum/reagent/consumable/mayonnaise = 3, + ) + result = /obj/item/food/izakaya_fries + category = CAT_MARTIAN + +/datum/crafting_recipe/food/kurry_ok_subsando + name = "Kurry-OK subsando" + reqs = list( + /obj/item/food/baguette = 1, + /obj/item/food/izakaya_fries = 1, + /obj/item/food/katsu_fillet = 1, + /datum/reagent/consumable/nutriment/soup/curry_sauce = 5, + ) + result = /obj/item/food/kurry_ok_subsando + category = CAT_MARTIAN + +/datum/crafting_recipe/food/loco_moco + name = "Loco moco" + reqs = list( + /obj/item/food/boiledrice = 1, + /obj/item/food/patty = 1, + /obj/item/food/onion_slice = 1, + /obj/item/food/friedegg = 1, + /datum/reagent/consumable/gravy = 5, + ) + result = /obj/item/food/loco_moco + category = CAT_MARTIAN + +/datum/crafting_recipe/food/wild_duck_fries + name = "Wild duck fries" + reqs = list( + /obj/item/food/izakaya_fries = 1, + /obj/item/food/meat/cutlet = 1, + /datum/reagent/consumable/ketchup = 3, + ) + result = /obj/item/food/wild_duck_fries + category = CAT_MARTIAN + +/datum/crafting_recipe/food/little_hawaii_hotdog + name = "Little Hawaii hotdog" + reqs = list( + /obj/item/food/hotdog = 1, + /obj/item/food/pineappleslice = 1, + /obj/item/food/onion_slice = 1, + /datum/reagent/consumable/nutriment/soup/teriyaki = 3, + ) + result = /obj/item/food/little_hawaii_hotdog + category = CAT_MARTIAN + +/datum/crafting_recipe/food/salt_chilli_fries + name = "Salt n' chilli fries" + reqs = list( + /obj/item/food/fries = 1, + /obj/item/food/grown/chili = 1, + /obj/item/food/grown/onion = 1, + /obj/item/food/grown/garlic = 1, + /datum/reagent/consumable/salt = 3, + ) + result = /obj/item/food/salt_chilli_fries + category = CAT_MARTIAN + +/datum/crafting_recipe/food/steak_croquette + name = "Steak croquette" + reqs = list( + /obj/item/food/meat/steak = 1, + /obj/item/food/mashed_potatoes = 1, + /obj/item/food/breadslice/reispan = 1, + ) + result = /obj/item/food/steak_croquette + category = CAT_MARTIAN + +/datum/crafting_recipe/food/chapsilog + name = "Chapsilog" + reqs = list( + /obj/item/food/grilled_chapslice = 2, + /obj/item/food/friedegg = 1, + /obj/item/food/boiledrice = 1, + /obj/item/food/grown/garlic = 1, + ) + result = /obj/item/food/chapsilog + category = CAT_MARTIAN + +/datum/crafting_recipe/food/chap_hash + name = "Chap hash" + reqs = list( + /obj/item/food/chapslice = 2, + /obj/item/food/egg = 1, + /obj/item/food/grown/bell_pepper = 1, + /obj/item/food/grown/potato = 1, + /obj/item/food/onion_slice = 1, + ) + result = /obj/item/food/chap_hash + category = CAT_MARTIAN + +/datum/crafting_recipe/food/agedashi_tofu + name = "Agedashi tofu" + reqs = list( + /obj/item/food/tofu = 1, + /obj/item/food/onion_slice = 1, + /datum/reagent/consumable/nutriment/soup/dashi = 20, + /obj/item/reagent_containers/cup/bowl = 1, + ) + result = /obj/item/food/salad/agedashi_tofu + category = CAT_MARTIAN + +/datum/crafting_recipe/food/po_kok_gai + name = "Po kok gai" + reqs = list( + /obj/item/food/boiledrice = 1, + /obj/item/food/meat/slab/chicken = 1, + /datum/reagent/consumable/coconut_milk = 5, + /datum/reagent/consumable/curry_powder = 3, + ) + result = /obj/item/food/salad/po_kok_gai + category = CAT_MARTIAN + +/datum/crafting_recipe/food/huoxing_tofu + name = "Huoxing tofu" + reqs = list( + /obj/item/food/tofu = 1, + /obj/item/food/raw_meatball = 1, + /obj/item/food/grown/chili = 1, + /obj/item/food/grown/soybeans = 1, + /obj/item/reagent_containers/cup/bowl = 1, + ) + result = /obj/item/food/salad/huoxing_tofu + category = CAT_MARTIAN + +/datum/crafting_recipe/food/feizhou_ji + name = "Fēizhōu jī" + reqs = list( + /obj/item/food/meat/slab/chicken = 1, + /obj/item/food/grown/chili = 1, + /obj/item/food/grown/bell_pepper = 1, + /datum/reagent/consumable/vinegar = 5, + ) + result = /obj/item/food/feizhou_ji + category = CAT_MARTIAN + +/datum/crafting_recipe/food/galinha_de_cabidela + name = "Galinha de cabidela" + reqs = list( + /obj/item/food/meat/slab/chicken = 1, + /obj/item/food/grown/tomato = 1, + /obj/item/food/uncooked_rice = 1, + /datum/reagent/blood = 5, + ) + result = /obj/item/food/salad/galinha_de_cabidela + category = CAT_MARTIAN + +/datum/crafting_recipe/food/katsu_curry + name = "Katsu curry" + reqs = list( + /obj/item/food/katsu_fillet = 1, + /obj/item/food/boiledrice = 1, + /datum/reagent/consumable/nutriment/soup/curry_sauce = 5, + ) + result = /obj/item/food/salad/katsu_curry + category = CAT_MARTIAN + +/datum/crafting_recipe/food/beef_bowl + name = "Beef bowl" + reqs = list( + /obj/item/food/meat/cutlet = 1, + /obj/item/food/onion_slice = 1, + /obj/item/food/boiledrice = 1, + /datum/reagent/consumable/nutriment/soup/dashi = 5, + ) + result = /obj/item/food/salad/beef_bowl + category = CAT_MARTIAN + +/datum/crafting_recipe/food/salt_chilli_bowl + name = "Salt n' chilli octopus bowl" + reqs = list( + /obj/item/food/grilled_octopus = 1, + /obj/item/food/grown/chili = 1, + /obj/item/food/grown/onion = 1, + /obj/item/food/boiledrice = 1, + /datum/reagent/consumable/salt = 2, + /datum/reagent/consumable/nutriment/soup/curry_sauce = 5, + ) + result = /obj/item/food/salad/salt_chilli_bowl + category = CAT_MARTIAN + +/datum/crafting_recipe/food/kansai_bowl + name = "Kansai bowl" + reqs = list( + /obj/item/food/kamaboko_slice = 2, + /obj/item/food/boiledegg = 1, + /obj/item/food/grown/onion = 1, + /obj/item/food/boiledrice = 1, + /datum/reagent/consumable/nutriment/soup/dashi = 5, + ) + result = /obj/item/food/salad/kansai_bowl + category = CAT_MARTIAN + +/datum/crafting_recipe/food/eigamudo_curry + name = "Eigamudo curry" + reqs = list( + /obj/item/food/grown/olive = 1, + /obj/item/food/kimchi = 1, + /obj/item/food/fishmeat = 1, + /obj/item/food/boiledrice = 1, + /datum/reagent/consumable/cafe_latte = 5, + ) + result = /obj/item/food/salad/eigamudo_curry + category = CAT_MARTIAN + +/datum/crafting_recipe/food/cilbir + name = "Çilbir" + reqs = list( + /obj/item/food/grown/garlic = 1, + /obj/item/food/friedegg = 1, + /obj/item/food/grown/chili = 1, + /datum/reagent/consumable/yoghurt = 5, + /datum/reagent/consumable/nutriment/fat/oil/olive = 2, + ) + result = /obj/item/food/cilbir + category = CAT_MARTIAN + +/datum/crafting_recipe/food/peking_duck_crepes + name = "Peking duck crepes a l'orange" + reqs = list( + /obj/item/food/pancakes = 1, + /obj/item/food/meat/cutlet = 1, + /obj/item/food/grown/citrus/orange = 1, + /datum/reagent/consumable/ethanol/cognac = 2, + ) + result = /obj/item/food/peking_duck_crepes + category = CAT_MARTIAN + +/datum/crafting_recipe/food/vulgaris_spekkoek + name = "Vulgaris spekkoek" + reqs = list( + /obj/item/food/cake/plain = 1, + /obj/item/food/grown/ambrosia/vulgaris = 1, + /obj/item/food/butterslice = 2, + ) + result = /obj/item/food/cake/spekkoek + category = CAT_MARTIAN + +/datum/crafting_recipe/food/pineapple_foster + name = "Pineapple foster" + reqs = list( + /obj/item/food/pineappleslice = 1, + /datum/reagent/consumable/caramel = 2, + /obj/item/food/icecream = 1, + /datum/reagent/consumable/ethanol/rum = 2, + ) + result = /obj/item/food/salad/pineapple_foster + category = CAT_MARTIAN + +/datum/crafting_recipe/food/pastel_de_nata + name = "Pastel de nata" + reqs = list( + /obj/item/food/pastrybase = 1, + /obj/item/food/grown/vanillapod = 1, + /obj/item/food/egg = 1, + /datum/reagent/consumable/sugar = 2, + ) + result = /obj/item/food/pastel_de_nata + category = CAT_MARTIAN + +/datum/crafting_recipe/food/boh_loh_yah + name = "Boh loh yah" + reqs = list( + /obj/item/food/doughslice = 1, + /obj/item/food/butterslice = 1, + /datum/reagent/consumable/sugar = 5, + ) + result = /obj/item/food/boh_loh_yah + category = CAT_MARTIAN + +/datum/crafting_recipe/food/banana_fritter + name = "Banana fritter" + reqs = list( + /obj/item/food/grown/banana = 1, + /datum/reagent/consumable/martian_batter = 2 + ) + result = /obj/item/food/banana_fritter + category = CAT_MARTIAN + +/datum/crafting_recipe/food/pineapple_fritter + name = "Pineapple fritter" + reqs = list( + /obj/item/food/pineappleslice = 1, + /datum/reagent/consumable/martian_batter = 2 + ) + result = /obj/item/food/pineapple_fritter + category = CAT_MARTIAN + +/datum/crafting_recipe/food/kasei_dango + name = "Kasei dango" + reqs = list( + /obj/item/stack/rods = 1, + /datum/reagent/consumable/sugar = 5, + /datum/reagent/consumable/rice = 5, + /datum/reagent/consumable/orangejuice = 2, + /datum/reagent/consumable/grenadine = 2, + ) + result = /obj/item/food/kebab/kasei_dango + category = CAT_MARTIAN + +/datum/crafting_recipe/food/pb_ice_cream_mochi + name = "Peanut-butter ice cream mochi" + reqs = list( + /datum/reagent/consumable/sugar = 5, + /datum/reagent/consumable/rice = 5, + /datum/reagent/consumable/peanut_butter = 2, + /obj/item/food/icecream = 1, + ) + result = /obj/item/food/pb_ice_cream_mochi + category = CAT_MARTIAN + +/datum/crafting_recipe/food/frozen_pineapple_pop + name = "Frozen pineapple pop" + reqs = list( + /obj/item/food/pineappleslice = 1, + /obj/item/food/chocolatebar = 1, + /obj/item/popsicle_stick = 1, + ) + result = /obj/item/food/popsicle/pineapple_pop + category = CAT_MARTIAN + +/datum/crafting_recipe/food/sea_salt_pop + name = "Sea-salt ice cream bar" + reqs = list( + /datum/reagent/consumable/cream = 5, + /datum/reagent/consumable/sugar = 5, + /datum/reagent/consumable/salt = 3, + /obj/item/popsicle_stick = 1, + ) + result = /obj/item/food/popsicle/sea_salt + category = CAT_MARTIAN + +/datum/crafting_recipe/food/berry_topsicle + name = "Berry topsicle" + reqs = list( + /obj/item/food/tofu = 1, + /datum/reagent/consumable/berryjuice = 5, + /datum/reagent/consumable/sugar = 5, + /obj/item/popsicle_stick = 1, + ) + result = /obj/item/food/popsicle/topsicle + category = CAT_MARTIAN + +/datum/crafting_recipe/food/banana_topsicle + name = "Banana topsicle" + reqs = list( + /obj/item/food/tofu = 1, + /datum/reagent/consumable/banana = 5, + /datum/reagent/consumable/sugar = 5, + /obj/item/popsicle_stick = 1, + ) + result = /obj/item/food/popsicle/topsicle/banana + category = CAT_MARTIAN + +/datum/crafting_recipe/food/berry_topsicle + name = "Pineapple topsicle" + reqs = list( + /obj/item/food/tofu = 1, + /datum/reagent/consumable/pineapplejuice = 5, + /datum/reagent/consumable/sugar = 5, + /obj/item/popsicle_stick = 1, + ) + result = /obj/item/food/popsicle/topsicle/pineapple + category = CAT_MARTIAN + +/datum/crafting_recipe/food/plasma_dog_supreme + name = "Plasma Dog Supreme" + reqs = list( + /obj/item/food/hotdog = 1, + /obj/item/food/pineappleslice = 1, + /obj/item/food/sambal = 1, + /obj/item/food/onion_slice = 1, + ) + result = /obj/item/food/plasma_dog_supreme + category = CAT_MARTIAN + +/datum/crafting_recipe/food/frickles + name = "Frickles" + reqs = list( + /obj/item/food/pickle = 1, + /datum/reagent/consumable/martian_batter = 2, + /datum/reagent/consumable/red_bay = 1, + ) + result = /obj/item/food/frickles + category = CAT_MARTIAN + +/datum/crafting_recipe/food/raw_ballpark_pretzel + name = "Raw ballpark pretzel" + reqs = list( + /obj/item/food/doughslice = 1, + /datum/reagent/consumable/salt = 2, + ) + result = /obj/item/food/raw_ballpark_pretzel + category = CAT_MARTIAN + +/datum/crafting_recipe/food/raw_ballpark_tsukune + name = "Raw ballpark tsukune" + reqs = list( + /obj/item/food/raw_meatball/chicken = 1, + /datum/reagent/consumable/nutriment/soup/teriyaki = 2, + /obj/item/stack/rods = 1, + ) + result = /obj/item/food/kebab/raw_ballpark_tsukune + category = CAT_MARTIAN + +/datum/crafting_recipe/food/sprout_bowl + name = "Sprout bowl" + reqs = list( + /obj/item/food/pickled_voltvine = 1, + /obj/item/food/fishmeat = 1, + /obj/item/food/boiledrice = 1, + /datum/reagent/consumable/nutriment/soup/dashi = 5, + ) + result = /obj/item/food/salad/sprout_bowl + category = CAT_MARTIAN + +// Soups + +/datum/crafting_recipe/food/reaction/soup/boilednoodles + reaction = /datum/chemical_reaction/food/soup/boilednoodles + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/dashi + reaction = /datum/chemical_reaction/food/soup/dashi + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/teriyaki + reaction = /datum/chemical_reaction/food/soup/teriyaki + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/curry_sauce + reaction = /datum/chemical_reaction/food/soup/curry_sauce + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/shoyu_ramen + reaction = /datum/chemical_reaction/food/soup/shoyu_ramen + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/gyuramen + reaction = /datum/chemical_reaction/food/soup/gyuramen + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/new_osaka_sunrise + reaction = /datum/chemical_reaction/food/soup/new_osaka_sunrise + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/satsuma_black + reaction = /datum/chemical_reaction/food/soup/satsuma_black + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/dragon_ramen + reaction = /datum/chemical_reaction/food/soup/dragon_ramen + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/hong_kong_borscht + reaction = /datum/chemical_reaction/food/soup/hong_kong_borscht + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/hong_kong_macaroni + reaction = /datum/chemical_reaction/food/soup/hong_kong_macaroni + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/foxs_prize_soup + reaction = /datum/chemical_reaction/food/soup/foxs_prize_soup + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/secret_noodle_soup + reaction = /datum/chemical_reaction/food/soup/secret_noodle_soup + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/budae_jjigae + reaction = /datum/chemical_reaction/food/soup/budae_jjigae + category = CAT_MARTIAN + +/datum/crafting_recipe/food/reaction/soup/volt_fish + reaction = /datum/chemical_reaction/food/soup/volt_fish + category = CAT_MARTIAN diff --git a/code/modules/hydroponics/grown/seedling.dm b/code/modules/hydroponics/grown/seedling.dm new file mode 100644 index 00000000000..57fd11280b6 --- /dev/null +++ b/code/modules/hydroponics/grown/seedling.dm @@ -0,0 +1,28 @@ +/obj/item/seeds/seedling + name = "pack of seedling seeds" + desc = "These seeds grow into a floral assistant which can help look after other plants!" + icon_state = "seed-seedling" + growing_icon = 'icons/obj/service/hydroponics/growing_fruits.dmi' + species = "seedling" + plantname = "Seedling Plant" + product = /mob/living/basic/seedling + lifespan = 40 + endurance = 7 + maturation = 10 + production = 1 + growthstages = 2 + yield = 1 + instability = 15 + potency = 30 + +/obj/item/seeds/seedling/harvest(mob/harvester) + var/obj/machinery/hydroponics/parent = loc + var/list/grow_locations = get_adjacent_open_turfs(parent) + var/turf/final_location = length(grow_locations) ? pick(grow_locations) : get_turf(parent) + var/mob/living/basic/seedling/seed_pet = new product(final_location) + seed_pet.befriend(harvester) + parent.update_tray(user = harvester, product_count = 1) + +/obj/item/seeds/seedling/evil + product = /mob/living/basic/seedling/meanie + icon_state = "seed-seedling-evil" diff --git a/code/modules/mapfluff/ruins/objects_and_mobs/cursed_slot_machine.dm b/code/modules/mapfluff/ruins/objects_and_mobs/cursed_slot_machine.dm new file mode 100644 index 00000000000..ab6b2bb1825 --- /dev/null +++ b/code/modules/mapfluff/ruins/objects_and_mobs/cursed_slot_machine.dm @@ -0,0 +1,129 @@ +/// Greed's slot machine: Used in the Greed ruin. Deals damage on each use, with a successful use giving a d20 of fate. +/obj/structure/cursed_slot_machine + name = "greed's slot machine" + desc = "High stakes, high rewards." + icon = 'icons/obj/machines/computer.dmi' + icon_state = "slots" + anchored = TRUE + density = TRUE + /// Variable that tracks the screen we display. + var/icon_screen = "slots_screen" + /// Should we be emitting light? + var/brightness_on = TRUE + /// The probability the player has to win. + var/win_prob = 5 + /// The maximum amount of curses we will allow a player to have before disallowing them to use the machine. + var/max_curse_amount = 5 + /// machine's reward when you hit jackpot + var/prize = /obj/structure/cursed_money + /// should we be applying the cursed status effect? + var/status_effect_on_roll = TRUE + /// Length of the cooldown between the machine being used and being able to spin the machine again. + var/cooldown_length = 15 SECONDS + /// Are we currently in use? Anti-spam prevention measure. + var/in_use = FALSE + /// Cooldown between pulls of the cursed slot machine. + COOLDOWN_DECLARE(spin_cooldown) + +/obj/structure/cursed_slot_machine/Initialize(mapload) + . = ..() + update_appearance() + set_light(brightness_on) + +/obj/structure/cursed_slot_machine/interact(mob/user) + if(!ishuman(user)) + return + + if(!check_and_set_usage(user)) + return + + user.visible_message( + span_warning("[user] pulls [src]'s lever with a glint in [user.p_their()] eyes!"), + span_warning("You feel a draining as you pull the lever, but you know it'll be worth it."), + ) + + icon_screen = "slots_screen_working" + update_appearance() + playsound(src, 'sound/lavaland/cursed_slot_machine.ogg', 50, FALSE) + addtimer(CALLBACK(src, PROC_REF(determine_victor), user), 5 SECONDS) + +/obj/structure/cursed_slot_machine/update_overlays() + . = ..() + var/overlay_state = icon_screen + . += mutable_appearance(icon, overlay_state) + . += emissive_appearance(icon, overlay_state, src) + +/// Validates that the user can use the cursed slot machine. User is the person using the slot machine. Returns TRUE if we can, FALSE otherwise. +/obj/structure/cursed_slot_machine/proc/check_and_set_usage(mob/living/carbon/human/user) + if(in_use) + balloon_alert_to_viewers("already spinning!") + return FALSE + + var/signal_value = SEND_SIGNAL(user, COMSIG_CURSED_SLOT_MACHINE_USE, max_curse_amount) + + if(!COOLDOWN_FINISHED(src, spin_cooldown) || (signal_value & SLOT_MACHINE_USE_POSTPONE)) + to_chat(user, span_danger("The machine doesn't engage. You get the compulsion to try again in a few seconds.")) + return FALSE + + if(signal_value & SLOT_MACHINE_USE_CANCEL) // failsafe in case we don't want to let the machine be used for some reason (like if we're maxed out on curses but not getting gibbed) + say("We're sorry, but we can no longer serve you at this establishment.") + return FALSE + + in_use = TRUE + return TRUE + +/obj/structure/cursed_slot_machine/proc/determine_victor(mob/living/carbon/human/user) + icon_screen = initial(icon_screen) + update_appearance() + + in_use = FALSE + COOLDOWN_START(src, spin_cooldown, cooldown_length) + + if(!prob(win_prob)) + if(status_effect_on_roll && isnull(user.has_status_effect(/datum/status_effect/grouped/cursed))) + user.apply_status_effect(/datum/status_effect/grouped/cursed) + + SEND_SIGNAL(user, COMSIG_CURSED_SLOT_MACHINE_LOST) + playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) + balloon_alert_to_viewers("you lost!") + return + + playsound(src, 'sound/lavaland/cursed_slot_machine_jackpot.ogg', 50, FALSE) + new prize(get_turf(src)) + if(user) + to_chat(user, span_boldwarning("You've hit the jackpot!!! Laughter echoes around you as your reward appears in the machine's place.")) + + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_CURSED_SLOT_MACHINE_WON) + qdel(src) + +/// Prize given out by the cursed slot machine that will give the user one Die of Fate and then delete itself. +/obj/structure/cursed_money + name = "bag of money" + desc = "RICH! YES! YOU KNEW IT WAS WORTH IT! YOU'RE RICH! RICH! RICH!" + icon = 'icons/obj/storage/storage.dmi' + icon_state = "moneybag" + anchored = FALSE + density = TRUE + +/obj/structure/cursed_money/Initialize(mapload) + . = ..() + addtimer(CALLBACK(src, PROC_REF(collapse)), 1 MINUTES) + +/obj/structure/cursed_money/proc/collapse() + if(QDELETED(src)) + return + visible_message(span_warning("[src] falls in on itself, with the canvas rotting away and contents vanishing.")) + qdel(src) + +/obj/structure/cursed_money/attack_hand(mob/living/user, list/modifiers) + . = ..() + if(.) + return + user.visible_message( + span_warning("[user] opens the bag and removes a die."), + span_warning("[span_boldwarning("You open the bag...!")] But all you see is a bag full of dice. Confused, you take one..."), + ) + var/turf/location = get_turf(user) + var/obj/item/dice/d20/fate/one_use/critical_fail = new(location) + user.put_in_hands(critical_fail) + collapse() diff --git a/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_cytology.dm b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_cytology.dm new file mode 100644 index 00000000000..72333ff67d1 --- /dev/null +++ b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_cytology.dm @@ -0,0 +1,29 @@ +/obj/item/petri_dish/oldstation + name = "molly's biopsy" + desc = "You can see a moldy piece of sandwich inside the dish. Maybe it helped to preserve the bacteria for that long." + +/obj/item/petri_dish/oldstation/Initialize(mapload) + . = ..() + sample = new + sample.GenerateSample(CELL_LINE_TABLE_COW, null, 1, 0) + var/datum/biological_sample/contamination = new + contamination.GenerateSample(CELL_LINE_TABLE_GRAPE, null, 1, 0) + sample.Merge(contamination) + sample.sample_color = COLOR_SAMPLE_BROWN + update_appearance() + +/obj/item/reagent_containers/cup/beaker/oldstation + name = "cultivation broth" + amount_per_transfer_from_this = 50 + list_reagents = list( + // Required for CELL_LINE_TABLE_COW + /datum/reagent/consumable/nutriment/protein = 10, + /datum/reagent/consumable/nutriment = 5, + /datum/reagent/cellulose = 5, + // Required for CELL_LINE_TABLE_GRAPE + /datum/reagent/toxin/slimejelly = 5, + /datum/reagent/yuck = 5, + /datum/reagent/consumable/vitfro = 5, + // Supplementary for CELL_LINE_TABLE_GRAPE + /datum/reagent/consumable/liquidgibs = 5 + ) diff --git a/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_fluff.dm b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_fluff.dm new file mode 100644 index 00000000000..a0b4adb9f75 --- /dev/null +++ b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_fluff.dm @@ -0,0 +1,110 @@ +/////////// Oldstation items + +/obj/item/paper/fluff/ruins/oldstation + name = "Cryo Awakening Report" + default_raw_text = "Catastrophic damage sustained to station. Powernet exhausted to reawaken crew.

\ + Immediate Objectives: \ +
    \ +
  1. Activate emergency power generator.
  2. \ +
  3. Lift station lockdown on the bridge.
  4. \ +
  5. Locate the 'Damage Report' on the bridge for a detailed situation report.
  6. \ +
" + +/obj/item/paper/fluff/ruins/oldstation/damagereport + name = "Damage Report" + default_raw_text = "

WARNING

\ + \ +

Status

\ +
    \ +
  1. Alpha Station - Destroyed.
  2. \ +
  3. Beta Station - Catastrophic Damage. Medical, destroyed. Atmospherics and Engine Core, partially destroyed.
  4. \ +
  5. Charlie Station - Multiple asteroid impacts, no loss in air pressure.
  6. \ +
  7. Delta Station - Intact.
  8. \ +
\ +

Recommended Actions

\ +
    \ +
  1. Locate arms at Charlie Station Security.
  2. \ +
  3. Move at a sufficient distance from the windows to avoid encounters with Space Carp.
  4. \ +
  5. Reestablish station powernet via Charlie Station Engineering solar array.
  6. \ +
  7. Restore life support systems: atmospherics, artificial gravity, hydroponics.
  8. \ +
  9. Avoid Delta Station until arrival of Nanotrasen Special Response Team.
  10. \ +
" + +/obj/item/paper/fluff/ruins/oldstation/protosuit + name = "B01-MOD modular suit Report" + default_raw_text = "*Prototype MODsuit*

This is a prototype powered exoskeleton, a design not seen in hundreds of years, \ + the first post-void war era modular suit to ever be safely utilized by an operator. \ + This ancient clunker is still functional, though it's missing several modern-day luxuries from \ + updated Nakamura Engineering designs. Primarily, the suit's myoelectric suit layer is entirely non-existant, \ + and the servos do very little to help distribute the weight evenly across the wearer's body, \ + making it slow and bulky to move in. Additionally, the armor plating never finished production aside from the shoulders, \ + forearms, and helmet; making it useless against direct attacks. The internal heads-up display is rendered entirely in \ + monochromatic cyan, leaving the user unable to see long distances. However, the way the helmet retracts is pretty cool." + +/obj/item/paper/fluff/ruins/oldstation/protohealth + name = "Health Analyzer Report" + default_raw_text = "*Health Analyzer*

The portable Health Analyzer is essentially a handheld variant of a health analyzer. Years of research have concluded with this device which is \ + capable of diagnosing even the most critical, obscure or technical injuries any humanoid entity is suffering in an easy to understand format that even a non-trained health professional \ + can understand.

The health analyzer is expected to go into full production as standard issue medical kit." + +/obj/item/paper/fluff/ruins/oldstation/protogun + name = "K14 Energy Gun Report" + default_raw_text = "*K14-Multiphase Energy Gun*

The K14 Prototype Energy Gun is the first Energy Rifle that has been successfully been able to not only hold a larger ammo charge \ + than other gun models, but is capable of swapping between different energy projectile types on command with no incidents.

The weapon still suffers several drawbacks, its alternative, \ + non laser fire mode, can only fire one round before exhausting the energy cell, the weapon also remains prohibitively expensive, nonetheless NT Market Research fully believe this weapon \ + will form the backbone of our Energy weapon catalogue.

The K14 is expected to undergo revision to fix the ammo issues, the K15 is expected to replace the 'stun' setting with a \ + 'disable' setting in an attempt to bypass the ammo issues." + +/** + * Supermatter crystal fluff paper used in Charlie station ruin + */ +/obj/item/paper/fluff/ruins/oldstation/protosupermatter + name = "Supermatter Crystal Generator" + default_raw_text = "*Supermatter Crystal Shard*

Modern power generation typically comes in two forms, a Fusion Generator or a Fission Generator. Fusion provides the best space to power \ + ratio, and is typically seen on military or high security ships and stations, however Fission reactors require the usage of expensive and rare materials in its construction. \ + Fission generators are massive and bulky, and require a large reserve of uranium to power, however they are extremely cheap to operate and need little maintenance once \ + \noperational.

The Supermatter aims to alter this, a functional Supermatter is essentially a gas producer that generates far more radiation than Fusion or Fission generators can ever hope to produce. " + +/obj/item/paper/fluff/ruins/oldstation/protoinv + name = "Laboratory Inventory" + default_raw_text = "*Inventory*

(1) Prototype MODsuit

(1)Health Analyser

(1)Prototype Energy Gun

(1)Singularity Generation Disk

DO NOT REMOVE WITHOUT \ + THE CAPTAIN AND RESEARCH DIRECTOR'S AUTHORISATION" + +/obj/item/paper/fluff/ruins/oldstation/generator_manual + name = "S.U.P.E.R.P.A.C.M.A.N.-type portable generator manual" + default_raw_text = "Wrench down the generator on top of a wire node connected to either a SMES input terminal or the power grid." + +/obj/item/paper/fluff/ruins/oldstation/protosleep + name = "Prototype Delivery" + default_raw_text = "*Prototype Sleeper*

We have delivered the lastest in medical technology to the medical bay: circuitry for a new prototype sleeper. Looks like it didn't come with the parts to actually build it figures. Get engineering on this." + +/obj/item/paper/fluff/ruins/oldstation/survivor_note + name = "To those who find this" + default_raw_text = "I was on a mission of an exploration drone reclamation, when I lost the signal. I've had just enough pressure to make it back to the station.... But this is really bad...

\ + Beta looks like a smashed tin can, and Alpha is gone completely. I didn't manage to find anyone except those sleeping beauties and something I don't even know how to explain. The blood and gore is everywhere, those things took out the entire R&D. \ + They're hissing and crawling behind the maintenance hatch that I welded off to not let them in.

\ + I had a proximity sensor with me, so I donated my left cybernetic arm to make this little fella. One of janitor's bucket served as a perfect casing for him.

\ + Here I thought that I'll die of malnutrition, when I started feeling the symptoms of hypercapnia. I will turn you off to save the battery. It's time for both us to sleep, little guy.

\ + If you're reading this, I'm probably dead. I've opened Ramboo's maintenance pannel with my ID. Please let him help to clean up my remains..." + +/obj/item/paper/fluff/ruins/oldstation/biolab_note_molly + name = "Diary note - Molly" + default_raw_text = "It has been several months since our Molly passed away. She was our most valuable crew member, especially compared to that prick that happily threw a party to make sure `that beef won't go to waste`...

\ + Oh, how I miss her warm milk...

I've put Molly's biopsy in the fridge and almost completed the solution.

\ + Next steps: \ + Just need to make sure to use the correct bottle this time... I'll even mark it as \"Solution for Molly\", or I tend to mix things up...
I can already feel the endorphin release from hugging her again.

\ + If everything goes well, I will try out those slimes the papers praising as the future of science. They say that the cell lines may be found on anything moldy and rotting, and these small blobs have crazy mutation potential when properly fed." + +/obj/item/paper/fluff/ruins/oldstation/biolab_note_emergency + name = "Diary note - Emergency" + default_raw_text = "OH GOD, the station is still creaking from a heavy impact in the port direction. The power is down, coms not responding, the air supply pipe depressurized and I can feel the artificial gravity weakening. \ + The whole department is running around in panic. I'll just pray that engineers won't let the engine delaminate.

...And the alien spawn have broken out of the containment area due to the impact and slipped into the vent.

\ + I have a bad feeling about this, but I doubt that now is the right time to make guys hunt for what they call my \"pet cockroach\"... And RD is scary..." + +/obj/item/paper/fluff/ruins/oldstation/apc_note + name = "DO NOT TOUCH!" + default_raw_text = "This is a spare pre-charged APC battery for emergencies ONLY. DO NOT use it for stun prods, Bob.

\ + Note: Use crowbar to remove the APC cover and take out the malfunctioning battery." diff --git a/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_machines.dm b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_machines.dm new file mode 100644 index 00000000000..c2552b10744 --- /dev/null +++ b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_machines.dm @@ -0,0 +1,8 @@ +/obj/machinery/computer/old + name = "old computer" + circuit = /obj/item/circuitboard/computer + +/obj/machinery/computer/old/Initialize(mapload) + icon_keyboard = pick("generic_key", "med_key") + icon_screen = pick("generic", "comm_monitor", "comm_logs") + return ..() diff --git a/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_mod.dm b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_mod.dm new file mode 100644 index 00000000000..ac026045674 --- /dev/null +++ b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_mod.dm @@ -0,0 +1,116 @@ +/obj/machinery/mod_installer + name = "modular outerwear device installator" + desc = "An ancient machine that mounts a MOD unit onto the occupant." + icon = 'icons/obj/machines/mod_installer.dmi' + icon_state = "mod_installer" + base_icon_state = "mod_installer" + layer = ABOVE_WINDOW_LAYER + use_power = IDLE_POWER_USE + anchored = TRUE + density = TRUE + obj_flags = BLOCKS_CONSTRUCTION // Becomes undense when the door is open + idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.5 + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.3 + + var/busy = FALSE + var/busy_icon_state + + var/obj/item/mod/control/mod_unit = /obj/item/mod/control/pre_equipped/prototype + + COOLDOWN_DECLARE(message_cooldown) + +/obj/machinery/mod_installer/Initialize(mapload) + . = ..() + occupant_typecache = typecacheof(/mob/living/carbon/human) + if(ispath(mod_unit)) + mod_unit = new mod_unit() + +/obj/machinery/mod_installer/Destroy() + QDEL_NULL(mod_unit) + return ..() + +/obj/machinery/mod_installer/proc/set_busy(status, working_icon) + busy = status + busy_icon_state = working_icon + update_appearance() + +/obj/machinery/mod_installer/proc/play_install_sound() + playsound(src, 'sound/items/rped.ogg', 30, FALSE) + +/obj/machinery/mod_installer/update_icon_state() + icon_state = busy ? busy_icon_state : "[base_icon_state][state_open ? "_open" : null]" + return ..() + +/obj/machinery/mod_installer/update_overlays() + var/list/overlays = ..() + if(machine_stat & (NOPOWER|BROKEN)) + return overlays + overlays += (busy || !mod_unit) ? "red" : "green" + return overlays + +/obj/machinery/mod_installer/proc/start_process() + if(machine_stat & (NOPOWER|BROKEN)) + return + if(!occupant || !mod_unit || busy) + return + set_busy(TRUE, "[initial(icon_state)]_raising") + addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "[initial(icon_state)]_active"), 2.5 SECONDS) + addtimer(CALLBACK(src, PROC_REF(play_install_sound)), 2.5 SECONDS) + addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "[initial(icon_state)]_falling"), 5 SECONDS) + addtimer(CALLBACK(src, PROC_REF(complete_process)), 7.5 SECONDS) + +/obj/machinery/mod_installer/proc/complete_process() + set_busy(FALSE) + var/mob/living/carbon/human/human_occupant = occupant + if(!istype(human_occupant)) + return + if(!human_occupant.dropItemToGround(human_occupant.back)) + return + if(!human_occupant.equip_to_slot_if_possible(mod_unit, mod_unit.slot_flags, qdel_on_fail = FALSE, disable_warning = TRUE)) + return + human_occupant.update_action_buttons(TRUE) + playsound(src, 'sound/machines/ping.ogg', 30, FALSE) + if(!human_occupant.dropItemToGround(human_occupant.wear_suit) || !human_occupant.dropItemToGround(human_occupant.head)) + finish_completion() + return + mod_unit.quick_activation() + finish_completion() + +/obj/machinery/mod_installer/proc/finish_completion() + mod_unit = null + open_machine() + +/obj/machinery/mod_installer/open_machine(drop = TRUE, density_to_set = FALSE) + if(state_open) + return FALSE + ..() + return TRUE + +/obj/machinery/mod_installer/close_machine(mob/living/carbon/user, density_to_set = TRUE) + if(!state_open) + return FALSE + ..() + addtimer(CALLBACK(src, PROC_REF(start_process)), 1 SECONDS) + return TRUE + +/obj/machinery/mod_installer/relaymove(mob/living/user, direction) + var/message + if(busy) + message = "it won't budge!" + else if(user.stat != CONSCIOUS) + message = "you don't have the energy!" + if(!isnull(message)) + if (COOLDOWN_FINISHED(src, message_cooldown)) + COOLDOWN_START(src, message_cooldown, 5 SECONDS) + balloon_alert(user, message) + return + open_machine() + +/obj/machinery/mod_installer/interact(mob/user) + if(state_open) + close_machine(null, user) + return + else if(busy) + balloon_alert(user, "it's locked!") + return + open_machine() diff --git a/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_rnd.dm b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_rnd.dm new file mode 100644 index 00000000000..58d14e0d041 --- /dev/null +++ b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_rnd.dm @@ -0,0 +1,29 @@ +/obj/machinery/rnd/server/oldstation + name = "\improper Ancient R&D Server" + circuit = /obj/item/circuitboard/machine/rdserver/oldstation + req_access = list(ACCESS_AWAY_SCIENCE) + +/obj/machinery/rnd/server/oldstation/Initialize(mapload) + register_context() + var/datum/techweb/oldstation_web = locate(/datum/techweb/oldstation) in SSresearch.techwebs + stored_research = oldstation_web + return ..() + +/obj/machinery/rnd/server/oldstation/add_context(atom/source, list/context, obj/item/held_item, mob/user) + if(held_item && istype(held_item, /obj/item/research_notes)) + context[SCREENTIP_CONTEXT_LMB] = "Generate research points" + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/rnd/server/oldstation/attackby(obj/item/attacking_item, mob/user, params) + if(istype(attacking_item, /obj/item/research_notes) && stored_research) + var/obj/item/research_notes/research_notes = attacking_item + stored_research.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = research_notes.value)) + playsound(src, 'sound/machines/copier.ogg', 50, TRUE) + qdel(research_notes) + return + return ..() + +///Ancient computer that starts with dissection to tell players they have it. +/obj/machinery/computer/operating/oldstation + name = "ancient operating computer" + advanced_surgeries = list(/datum/surgery/advanced/experimental_dissection) diff --git a/code/modules/mining/equipment/miningradio.dm b/code/modules/mining/equipment/miningradio.dm new file mode 100644 index 00000000000..559740599db --- /dev/null +++ b/code/modules/mining/equipment/miningradio.dm @@ -0,0 +1,24 @@ +/// Portable mining radio purchasable by miners +/obj/item/radio/weather_monitor + icon = 'icons/obj/miningradio.dmi' + name = "mining weather radio" + icon_state = "miningradio" + desc = "A weather radio designed for use in inhospitable environments. Gives audible warnings when storms approach. Has access to cargo channel." + freqlock = RADIO_FREQENCY_LOCKED + luminosity = 1 + light_power = 1 + light_range = 1.6 + +/obj/item/radio/weather_monitor/update_overlays() + . = ..() + . += emissive_appearance(icon, "small_emissive", src, alpha = src.alpha) + +/obj/item/radio/weather_monitor/Initialize(mapload) + . = ..() + AddComponent( \ + /datum/component/weather_announcer, \ + state_normal = "weatherwarning", \ + state_warning = "urgentwarning", \ + state_danger = "direwarning", \ + ) + set_frequency(FREQ_SUPPLY) diff --git a/code/modules/mob/living/basic/blob_minions/blob_ai.dm b/code/modules/mob/living/basic/blob_minions/blob_ai.dm new file mode 100644 index 00000000000..6168b7ca83b --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blob_ai.dm @@ -0,0 +1,51 @@ +/** + * Extremely simple AI, this isn't a very smart boy + * Only notable quirk is that it uses JPS movement, simple avoidance would fail to realise it can path through blobs + */ +/datum/ai_controller/basic_controller/blobbernaut + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead, + ) + + ai_movement = /datum/ai_movement/jps + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/** + * Move to a point designated by the overmind, otherwise just slap people nearby + */ +/datum/ai_controller/basic_controller/blob_zombie + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead, + ) + + ai_movement = /datum/ai_movement/jps + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/travel_to_point/and_clear_target, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/** + * As blob zombie but will prioritise attacking corpses to zombify them + */ +/datum/ai_controller/basic_controller/blob_spore + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead, + ) + + ai_movement = /datum/ai_movement/jps + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/find_and_hunt_target/corpses, + /datum/ai_planning_subtree/travel_to_point/and_clear_target, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) diff --git a/code/modules/mob/living/basic/blob_minions/blob_mob.dm b/code/modules/mob/living/basic/blob_minions/blob_mob.dm new file mode 100644 index 00000000000..35e41f09058 --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blob_mob.dm @@ -0,0 +1,37 @@ +/// Root of shared behaviour for mobs spawned by blobs, is abstract and should not be spawned +/mob/living/basic/blob_minion + name = "Blob Error" + desc = "A nonfunctional fungal creature created by bad code or celestial mistake. Point and laugh." + icon = 'icons/mob/nonhuman-player/blob.dmi' + icon_state = "blob_head" + unique_name = TRUE + pass_flags = PASSBLOB + faction = list(ROLE_BLOB) + combat_mode = TRUE + bubble_icon = "blob" + speak_emote = null + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = 0 + maximum_survivable_temperature = INFINITY + lighting_cutoff_red = 20 + lighting_cutoff_green = 40 + lighting_cutoff_blue = 30 + initial_language_holder = /datum/language_holder/empty + +/mob/living/basic/blob_minion/Initialize(mapload) + . = ..() + add_traits(list(TRAIT_BLOB_ALLY, TRAIT_MUTE), INNATE_TRAIT) + AddComponent(/datum/component/blob_minion, on_strain_changed = CALLBACK(src, PROC_REF(on_strain_updated))) + +/// Called when our blob overmind changes their variant, update some of our mob properties +/mob/living/basic/blob_minion/proc/on_strain_updated(mob/camera/blob/overmind, datum/blobstrain/new_strain) + return + +/// Associates this mob with a specific blob factory node +/mob/living/basic/blob_minion/proc/link_to_factory(obj/structure/blob/special/factory/factory) + RegisterSignal(factory, COMSIG_QDELETING, PROC_REF(on_factory_destroyed)) + +/// Called when our factory is destroyed +/mob/living/basic/blob_minion/proc/on_factory_destroyed() + SIGNAL_HANDLER + to_chat(src, span_userdanger("Your factory was destroyed! You feel yourself dying!")) diff --git a/code/modules/mob/living/basic/blob_minions/blob_spore.dm b/code/modules/mob/living/basic/blob_minions/blob_spore.dm new file mode 100644 index 00000000000..e8c3acc8b97 --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blob_spore.dm @@ -0,0 +1,123 @@ +/** + * A floating fungus which turns people into zombies and explodes into reagent clouds upon death. + */ +/mob/living/basic/blob_minion/spore + name = "blob spore" + desc = "A floating, fragile spore." + icon = 'icons/mob/nonhuman-player/blob.dmi' + icon_state = "blobpod" + icon_living = "blobpod" + health_doll_icon = "blobpod" + health = BLOBMOB_SPORE_HEALTH + maxHealth = BLOBMOB_SPORE_HEALTH + verb_say = "psychically pulses" + verb_ask = "psychically probes" + verb_exclaim = "psychically yells" + verb_yell = "psychically screams" + melee_damage_lower = BLOBMOB_SPORE_DMG_LOWER + melee_damage_upper = BLOBMOB_SPORE_DMG_UPPER + obj_damage = 0 + attack_verb_continuous = "batters" + attack_verb_simple = "batter" + attack_sound = 'sound/weapons/genhit1.ogg' + death_message = "explodes into a cloud of gas!" + gold_core_spawnable = HOSTILE_SPAWN + basic_mob_flags = DEL_ON_DEATH + ai_controller = /datum/ai_controller/basic_controller/blob_spore + /// Size of cloud produced from a dying spore + var/death_cloud_size = 1 + /// Type of mob to create + var/mob/living/zombie_type = /mob/living/basic/blob_minion/zombie + +/mob/living/basic/blob_minion/spore/Initialize(mapload) + . = ..() + AddElement(/datum/element/simple_flying) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBSPORE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + +/mob/living/basic/blob_minion/spore/death(gibbed) + . = ..() + death_burst() + +/mob/living/basic/blob_minion/spore/on_factory_destroyed() + death() + +/// Create an explosion of spores on death +/mob/living/basic/blob_minion/spore/proc/death_burst() + do_chem_smoke(range = death_cloud_size, holder = src, location = get_turf(src), reagent_type = /datum/reagent/toxin/spore) + + +/mob/living/basic/blob_minion/spore/melee_attack(mob/living/carbon/human/target, list/modifiers, ignore_cooldown) + . = ..() + if (!ishuman(target) || target.stat != DEAD) + return + zombify(target) + +/// Become a zombie +/mob/living/basic/blob_minion/spore/proc/zombify(mob/living/carbon/human/target) + visible_message(span_warning("The corpse of [target.name] suddenly rises!")) + var/mob/living/basic/blob_minion/zombie/blombie = change_mob_type(zombie_type, loc, new_name = initial(zombie_type.name)) + blombie.set_name() + if (istype(blombie)) // In case of badmin + blombie.consume_corpse(target) + SEND_SIGNAL(src, COMSIG_BLOB_ZOMBIFIED, blombie) + qdel(src) + +/// Variant of the blob spore which is actually spawned by blob factories +/mob/living/basic/blob_minion/spore/minion + gold_core_spawnable = NO_SPAWN + zombie_type = /mob/living/basic/blob_minion/zombie/controlled + /// We die if we leave the same turf as this z level + var/turf/z_turf + +/mob/living/basic/blob_minion/spore/minion/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_z_changed)) + +/// When we z-move check that we're on the same z level as our factory was +/mob/living/basic/blob_minion/spore/minion/proc/on_z_changed() + SIGNAL_HANDLER + if (isnull(z_turf)) + return + if (!is_valid_z_level(get_turf(src), z_turf)) + death() + +/// Mark the turf we need to track from our factory +/mob/living/basic/blob_minion/spore/minion/link_to_factory(obj/structure/blob/special/factory/factory) + . = ..() + z_turf = get_turf(factory) + +/// If the blob changes to distributed neurons then you can control the spores +/mob/living/basic/blob_minion/spore/minion/on_strain_updated(mob/camera/blob/overmind, datum/blobstrain/new_strain) + if (isnull(overmind)) + REMOVE_TRAIT(src, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT) + else + ADD_TRAIT(src, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT) + + if (istype(new_strain, /datum/blobstrain/reagent/distributed_neurons)) + AddComponent(\ + /datum/component/ghost_direct_control,\ + ban_type = ROLE_BLOB_INFECTION,\ + poll_candidates = TRUE,\ + poll_ignore_key = POLL_IGNORE_BLOB,\ + ) + else + qdel(GetComponent(/datum/component/ghost_direct_control)) + +/mob/living/basic/blob_minion/spore/minion/death_burst() + return // This behaviour is superceded by the overmind's intervention + + +/// Weakened spore spawned by distributed neurons, can't zombify people and makes a teeny explosion +/mob/living/basic/blob_minion/spore/minion/weak + name = "fragile blob spore" + health = 15 + maxHealth = 15 + melee_damage_lower = 1 + melee_damage_upper = 2 + death_cloud_size = 0 + +/mob/living/basic/blob_minion/spore/minion/weak/zombify() + return + +/mob/living/basic/blob_minion/spore/minion/weak/on_strain_updated() + return diff --git a/code/modules/mob/living/basic/blob_minions/blob_zombie.dm b/code/modules/mob/living/basic/blob_minions/blob_zombie.dm new file mode 100644 index 00000000000..c9bf3b7346a --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blob_zombie.dm @@ -0,0 +1,99 @@ +/// A shambling mob made out of a crew member +/mob/living/basic/blob_minion/zombie + name = "blob zombie" + desc = "A shambling corpse animated by the blob." + icon_state = "zombie" + icon_living = "zombie" + health_doll_icon = "blobpod" + mob_biotypes = MOB_ORGANIC | MOB_HUMANOID + health = 70 + maxHealth = 70 + verb_say = "gurgles" + verb_ask = "demands" + verb_exclaim = "roars" + verb_yell = "bellows" + melee_damage_lower = 10 + melee_damage_upper = 15 + melee_attack_cooldown = CLICK_CD_MELEE + obj_damage = 20 + attack_verb_continuous = "punches" + attack_verb_simple = "punch" + attack_sound = 'sound/weapons/genhit1.ogg' + death_message = "collapses to the ground!" + gold_core_spawnable = NO_SPAWN + basic_mob_flags = DEL_ON_DEATH + ai_controller = /datum/ai_controller/basic_controller/blob_zombie + /// The dead body we have inside + var/mob/living/carbon/human/corpse + +/mob/living/basic/blob_minion/zombie/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT) // This mob doesn't function visually without a corpse and wouldn't respawn with one + AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBSPORE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + +/mob/living/basic/blob_minion/zombie/death(gibbed) + corpse?.forceMove(loc) + death_burst() + return ..() + +/mob/living/basic/blob_minion/zombie/Exited(atom/movable/gone, direction) + . = ..() + if (gone != corpse) + return + corpse = null + death() + +/mob/living/basic/blob_minion/zombie/Destroy() + QDEL_NULL(corpse) + return ..() + +/mob/living/basic/blob_minion/zombie/on_factory_destroyed() + . = ..() + death() + +/mob/living/basic/blob_minion/zombie/update_overlays() + . = ..() + copy_overlays(corpse, TRUE) + var/mutable_appearance/blob_head_overlay = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "blob_head") + blob_head_overlay.color = LAZYACCESS(atom_colours, FIXED_COLOUR_PRIORITY) || COLOR_WHITE + color = initial(color) // reversing what our component did lol, but we needed the value for the overlay + . += blob_head_overlay + +/// Create an explosion of spores on death +/mob/living/basic/blob_minion/zombie/proc/death_burst() + do_chem_smoke(range = 0, holder = src, location = get_turf(src), reagent_type = /datum/reagent/toxin/spore) + +/// Store a body so that we can drop it on death +/mob/living/basic/blob_minion/zombie/proc/consume_corpse(mob/living/carbon/human/new_corpse) + if(new_corpse.wear_suit) + maxHealth += new_corpse.get_armor_rating(MELEE) + health = maxHealth + new_corpse.set_facial_hairstyle("Shaved", update = FALSE) + new_corpse.set_hairstyle("Bald", update = TRUE) + new_corpse.forceMove(src) + corpse = new_corpse + update_appearance(UPDATE_ICON) + RegisterSignal(corpse, COMSIG_LIVING_REVIVE, PROC_REF(on_corpse_revived)) + +/// Dynamic changeling reentry +/mob/living/basic/blob_minion/zombie/proc/on_corpse_revived() + SIGNAL_HANDLER + visible_message(span_boldwarning("[src] bursts from the inside!")) + death() + +/// Blob-created zombies will ping for player control when they make a zombie +/mob/living/basic/blob_minion/zombie/controlled + +/mob/living/basic/blob_minion/zombie/controlled/consume_corpse(mob/living/carbon/human/new_corpse) + . = ..() + if (!isnull(client)) + return + AddComponent(\ + /datum/component/ghost_direct_control,\ + ban_type = ROLE_BLOB_INFECTION,\ + poll_candidates = TRUE,\ + poll_ignore_key = POLL_IGNORE_BLOB,\ + ) + +/mob/living/basic/blob_minion/zombie/controlled/death_burst() + return diff --git a/code/modules/mob/living/basic/blob_minions/blobbernaut.dm b/code/modules/mob/living/basic/blob_minions/blobbernaut.dm new file mode 100644 index 00000000000..b483641993a --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blobbernaut.dm @@ -0,0 +1,109 @@ +/** + * Player-piloted brute mob. Mostly just a "move and click" kind of guy. + * Has a variant which takes damage when away from blob tiles + */ +/mob/living/basic/blob_minion/blobbernaut + name = "blobbernaut" + desc = "A hulking, mobile chunk of blobmass." + icon_state = "blobbernaut" + icon_living = "blobbernaut" + icon_dead = "blobbernaut_dead" + health = BLOBMOB_BLOBBERNAUT_HEALTH + maxHealth = BLOBMOB_BLOBBERNAUT_HEALTH + damage_coeff = list(BRUTE = 0.5, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + melee_damage_lower = BLOBMOB_BLOBBERNAUT_DMG_SOLO_LOWER + melee_damage_upper = BLOBMOB_BLOBBERNAUT_DMG_SOLO_UPPER + melee_attack_cooldown = CLICK_CD_MELEE + obj_damage = BLOBMOB_BLOBBERNAUT_DMG_OBJ + attack_verb_continuous = "slams" + attack_verb_simple = "slam" + attack_sound = 'sound/effects/blobattack.ogg' + verb_say = "gurgles" + verb_ask = "demands" + verb_exclaim = "roars" + verb_yell = "bellows" + force_threshold = 10 + pressure_resistance = 50 + mob_size = MOB_SIZE_LARGE + hud_type = /datum/hud/living/blobbernaut + gold_core_spawnable = HOSTILE_SPAWN + ai_controller = /datum/ai_controller/basic_controller/blobbernaut + +/mob/living/basic/blob_minion/blobbernaut/Initialize(mapload) + . = ..() + AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBBERNAUT, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + +/mob/living/basic/blob_minion/blobbernaut/death(gibbed) + flick("blobbernaut_death", src) + return ..() + +/// This variant is the one actually spawned by blob factories, takes damage when away from blob tiles +/mob/living/basic/blob_minion/blobbernaut/minion + gold_core_spawnable = NO_SPAWN + /// Is our factory dead? + var/orphaned = FALSE + +/mob/living/basic/blob_minion/blobbernaut/minion/Life(seconds_per_tick, times_fired) + . = ..() + if (!.) + return FALSE + var/damage_sources = 0 + var/list/blobs_in_area = range(2, src) + + if (!(locate(/obj/structure/blob) in blobs_in_area)) + damage_sources++ + + if (orphaned) + damage_sources++ + else + var/particle_colour = atom_colours[FIXED_COLOUR_PRIORITY] || COLOR_BLACK + if (locate(/obj/structure/blob/special/core) in blobs_in_area) + heal_overall_damage(maxHealth * BLOBMOB_BLOBBERNAUT_HEALING_CORE * seconds_per_tick) + var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(src)) + heal_effect.color = particle_colour + + if (locate(/obj/structure/blob/special/node) in blobs_in_area) + heal_overall_damage(maxHealth * BLOBMOB_BLOBBERNAUT_HEALING_NODE * seconds_per_tick) + var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(src)) + heal_effect.color = particle_colour + + if (damage_sources == 0) + return FALSE + + // take 2.5% of max health as damage when not near the blob or if the naut has no factory, 5% if both + apply_damage(maxHealth * BLOBMOB_BLOBBERNAUT_HEALTH_DECAY * damage_sources * seconds_per_tick, damagetype = TOX) // We reduce brute damage + var/mutable_appearance/harming = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "nautdamage", MOB_LAYER + 0.01) + harming.appearance_flags = RESET_COLOR + harming.color = atom_colours[FIXED_COLOUR_PRIORITY] || COLOR_WHITE + harming.dir = dir + flick_overlay_view(harming, 0.8 SECONDS) + return TRUE + +/// Called by the blob creation power to give us a mind and a basic task orientation +/mob/living/basic/blob_minion/blobbernaut/minion/proc/assign_key(ckey, datum/blobstrain/blobstrain) + key = ckey + flick("blobbernaut_produce", src) + health = maxHealth / 2 // Start out injured to encourage not beelining away from the blob + SEND_SOUND(src, sound('sound/effects/blobattack.ogg')) + SEND_SOUND(src, sound('sound/effects/attackblob.ogg')) + to_chat(src, span_infoplain("You are powerful, hard to kill, and slowly regenerate near nodes and cores, [span_cultlarge("but will slowly die if not near the blob")] or if the factory that made you is killed.")) + to_chat(src, span_infoplain("You can communicate with other blobbernauts and overminds telepathically by attempting to speak normally")) + to_chat(src, span_infoplain("Your overmind's blob reagent is: [blobstrain.name]!")) + to_chat(src, span_infoplain("The [blobstrain.name] reagent [blobstrain.shortdesc ? "[blobstrain.shortdesc]" : "[blobstrain.description]"]")) + +/// Set our attack damage based on blob's properties +/mob/living/basic/blob_minion/blobbernaut/minion/on_strain_updated(mob/camera/blob/overmind, datum/blobstrain/new_strain) + if (isnull(overmind)) + melee_damage_lower = initial(melee_damage_lower) + melee_damage_upper = initial(melee_damage_upper) + attack_verb_continuous = initial(attack_verb_continuous) + return + melee_damage_lower = BLOBMOB_BLOBBERNAUT_DMG_LOWER + melee_damage_upper = BLOBMOB_BLOBBERNAUT_DMG_UPPER + attack_verb_continuous = new_strain.blobbernaut_message + +/// Called by our factory to inform us that it's not going to support us financially any more +/mob/living/basic/blob_minion/blobbernaut/minion/on_factory_destroyed() + . = ..() + orphaned = TRUE + throw_alert("nofactory", /atom/movable/screen/alert/nofactory) diff --git a/code/modules/mob/living/basic/clown/clown.dm b/code/modules/mob/living/basic/clown/clown.dm new file mode 100644 index 00000000000..78715361356 --- /dev/null +++ b/code/modules/mob/living/basic/clown/clown.dm @@ -0,0 +1,629 @@ +/mob/living/basic/clown + name = "Clown" + desc = "A denizen of clown planet." + icon = 'icons/mob/simple/clown_mobs.dmi' + icon_state = "clown" + icon_living = "clown" + icon_dead = "clown_dead" + icon_gib = "clown_gib" + health_doll_icon = "clown" //if >32x32, it will use this generic. for all the huge clown mobs that subtype from this + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + response_disarm_continuous = "gently pushes aside" + response_disarm_simple = "gently push aside" + response_harm_continuous = "robusts" + response_harm_simple = "robust" + combat_mode = TRUE + maxHealth = 75 + health = 75 + melee_damage_lower = 10 + melee_damage_upper = 10 + attack_sound = 'sound/items/bikehorn.ogg' + attacked_sound = 'sound/items/bikehorn.ogg' + environment_smash = ENVIRONMENT_SMASH_NONE + basic_mob_flags = DEL_ON_DEATH + initial_language_holder = /datum/language_holder/clown + habitable_atmos = list("min_oxy" = 5, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = T0C + maximum_survivable_temperature = (T0C + 100) + unsuitable_atmos_damage = 10 + unsuitable_heat_damage = 15 + faction = list(FACTION_CLOWN) + ai_controller = /datum/ai_controller/basic_controller/clown + speed = 1.4 //roughly close to simpleanimal clowns + ///list of stuff we drop on death + var/list/loot = list(/obj/effect/mob_spawn/corpse/human/clown) + ///blackboard emote list + var/list/emotes = list( + BB_EMOTE_SAY = list("HONK", "Honk!", "Welcome to clown planet!"), + BB_EMOTE_HEAR = list("honks", "squeaks"), + BB_EMOTE_SOUND = list('sound/items/bikehorn.ogg'), //WE LOVE TO PARTY + BB_EMOTE_CHANCE = 5, + ) + ///do we waddle (honk) + var/waddles = TRUE + +/mob/living/basic/clown/Initialize(mapload) + . = ..() + AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_SHOE) + AddComponent(/datum/component/ai_retaliate_advanced, CALLBACK(src, PROC_REF(retaliate_callback))) + ai_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, emotes) + //im not putting dynamic humans or whatever its called here because this is the base path of nonhuman clownstrosities + if(waddles) + AddElement(/datum/element/waddling) + if(length(loot)) + loot = string_list(loot) + AddElement(/datum/element/death_drops, loot) + +/mob/living/basic/clown/proc/retaliate_callback(mob/living/attacker) + if (!istype(attacker)) + return + for (var/mob/living/basic/clown/harbringer in oview(src, 7)) + harbringer.ai_controller.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, attacker) + +/mob/living/basic/clown/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE) + if(!istype(target, /obj/item/food/grown/banana/bunch)) + return ..() + var/obj/item/food/grown/banana/bunch/unripe_bunch = target + unripe_bunch.start_ripening() + log_combat(src, target, "explosively ripened") + +/mob/living/basic/clown/lube + name = "Living Lube" + desc = "A puddle of lube brought to life by the honkmother." + icon_state = "lube" + icon_living = "lube" + response_help_continuous = "dips a finger into" + response_help_simple = "dip a finger into" + response_disarm_continuous = "gently scoops and pours aside" + response_disarm_simple = "gently scoop and pour aside" + emotes = list( + BB_EMOTE_SAY = list("HONK", "Honk!", "Welcome to clown planet!"), + BB_EMOTE_HEAR = list("bubbles", "oozes"), + ) + waddles = FALSE + loot = list( + /obj/effect/spawner/foam_starter/small, + /obj/item/clothing/mask/gas/clown_hat, + ) + +/mob/living/basic/clown/lube/Initialize(mapload) + . = ..() + AddElement(/datum/element/snailcrawl) + +/mob/living/basic/clown/honkling + name = "Honkling" + desc = "A divine being sent by the Honkmother to spread joy. It's not dangerous, but it's a bit of a nuisance." + icon_state = "honkling" + icon_living = "honkling" + speed = 1.1 + melee_damage_lower = 1 + melee_damage_upper = 1 + attack_verb_continuous = "cheers up" + attack_verb_simple = "cheer up" + loot = list( + /obj/item/clothing/mask/gas/clown_hat, + /obj/effect/gibspawner/human, + /obj/item/soap, + /obj/item/seeds/banana/bluespace, + ) + +/mob/living/basic/clown/honkling/Initialize(mapload) + . = ..() + var/static/list/injection_range + if(!injection_range) + injection_range = string_numbers_list(list(1, 5)) + AddElement(/datum/element/venomous, /datum/reagent/consumable/laughter, injection_range) + +/mob/living/basic/clown/fleshclown + name = "Fleshclown" + desc = "A being forged out of the pure essence of pranking, cursed into existence by a cruel maker." + icon_state = "fleshclown" + icon_living = "fleshclown" + response_help_continuous = "reluctantly pokes" + response_help_simple = "reluctantly poke" + response_disarm_continuous = "sinks his hands into the spongy flesh of" + response_disarm_simple = "sink your hands into the spongy flesh of" + response_harm_continuous = "cleanses the world of" + response_harm_simple = "cleanse the world of" + maxHealth = 140 + health = 140 + speed = 1 + melee_damage_upper = 15 + attack_verb_continuous = "limply slaps" + attack_verb_simple = "limply slap" + obj_damage = 5 + loot = list( + /obj/effect/gibspawner/human, + /obj/item/clothing/mask/gas/clown_hat, + /obj/item/soap, + /obj/item/clothing/suit/hooded/bloated_human, + ) + emotes = list( + BB_EMOTE_SAY = list( + "HONK", + "Honk!", + "I didn't ask for this", + "I feel constant and horrible pain", + "I was born out of mirthful pranking but I live in suffering", + "This body is a merciless and unforgiving prison", + "YA-HONK!!!", + ), + BB_EMOTE_HEAR = list("honks", "contemplates its existence"), + BB_EMOTE_SEE = list("sweats", "jiggles"), + BB_EMOTE_CHANCE = 5, + ) + +/mob/living/basic/clown/fleshclown/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + +/mob/living/basic/clown/longface + name = "Longface" + desc = "Often found walking into the bar." + icon_state = "long face" + icon_living = "long face" + move_resist = INFINITY + response_help_continuous = "tries to awkwardly hug" + response_help_simple = "try to awkwardly hug" + response_disarm_continuous = "pushes the unwieldy frame of" + response_disarm_simple = "push the unwieldy frame of" + response_harm_continuous = "tries to shut up" + response_harm_simple = "try to shut up" + maxHealth = 150 + health = 150 + pixel_x = -16 + base_pixel_x = -16 + speed = 3 + melee_damage_lower = 5 + attack_verb_continuous = "YA-HONKs" + attack_verb_simple = "YA-HONK" + loot = list( + /obj/effect/gibspawner/human, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("YA-HONK!!!"), + BB_EMOTE_HEAR = list("honks", "squeaks"), + BB_EMOTE_CHANCE = 60, + ) + +/mob/living/basic/clown/clownhulk + name = "Honk Hulk" + desc = "A cruel and fearsome clown. Don't make him angry." + icon_state = "honkhulk" + icon_living = "honkhulk" + move_resist = INFINITY + gender = MALE + response_help_continuous = "tries desperately to appease" + response_help_simple = "try desperately to appease" + response_disarm_continuous = "foolishly pushes" + response_disarm_simple = "foolishly push" + response_harm_continuous = "angers" + response_harm_simple = "anger" + maxHealth = 400 + health = 400 + pixel_x = -16 + base_pixel_x = -16 + speed = 2 + melee_damage_lower = 15 + melee_damage_upper = 20 + attack_verb_continuous = "pummels" + attack_verb_simple = "pummel" + obj_damage = 30 + environment_smash = ENVIRONMENT_SMASH_WALLS + loot = list( + /obj/effect/gibspawner/human, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("HONK", "Honk!", "HAUAUANK!!!", "GUUURRRRAAAHHH!!!"), + BB_EMOTE_HEAR = list("honks", "grunts"), + BB_EMOTE_SEE = list("sweats"), + BB_EMOTE_CHANCE = 5, + ) + +/mob/living/basic/clown/clownhulk/chlown + name = "Chlown" + desc = "A real lunkhead who somehow gets all the girls." + icon_state = "chlown" + icon_living = "chlown" + gender = MALE + response_help_continuous = "submits to" + response_help_simple = "submit to" + response_disarm_continuous = "tries to assert dominance over" + response_disarm_simple = "try to assert dominance over" + response_harm_continuous = "makes a weak beta attack at" + response_harm_simple = "make a weak beta attack at" + maxHealth = 500 + health = 500 + speed = -2 //ridicilously fast but i dont even know what this is used for + armour_penetration = 20 + attack_verb_continuous = "steals the girlfriend of" + attack_verb_simple = "steal the girlfriend of" + attack_sound = 'sound/items/airhorn2.ogg' + loot = list( + /obj/effect/gibspawner/human, + /obj/effect/spawner/foam_starter/small, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("HONK", "Honk!", "Bruh", "cheeaaaahhh?"), + BB_EMOTE_SEE = list("asserts his dominance", "emasculates everyone implicitly"), + BB_EMOTE_CHANCE = 5, + ) + +/mob/living/basic/clown/clownhulk/honkmunculus + name = "Honkmunculus" + desc = "A slender wiry figure of alchemical origin." + icon_state = "honkmunculus" + icon_living = "honkmunculus" + response_help_continuous = "skeptically pokes" + response_help_simple = "skeptically poke" + response_disarm_continuous = "pushes the unwieldy frame of" + response_disarm_simple = "push the unwieldy frame of" + maxHealth = 200 + health = 200 + speed = 1 + melee_damage_lower = 5 + melee_damage_upper = 10 + attack_verb_continuous = "ferociously mauls" + attack_verb_simple = "ferociously maul" + environment_smash = ENVIRONMENT_SMASH_NONE + loot = list( + /obj/effect/gibspawner/xeno/bodypartless, + /obj/effect/spawner/foam_starter/small, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("honk"), + BB_EMOTE_SEE = list("squirms", "writhes"), + ) + +/mob/living/basic/clown/clownhulk/honkmunculus/Initialize(mapload) + . = ..() + var/static/list/injection_range + if(!injection_range) + injection_range = string_numbers_list(list(1, 5)) + AddElement(/datum/element/venomous, /datum/reagent/peaceborg/confuse, injection_range) + +/mob/living/basic/clown/clownhulk/destroyer + name = "The Destroyer" + desc = "An ancient being born of arcane honking." + icon_state = "destroyer" + icon_living = "destroyer" + response_disarm_continuous = "bounces off of" + response_harm_continuous = "bounces off of" + maxHealth = 400 + health = 400 + speed = 5 + melee_damage_lower = 20 + melee_damage_upper = 40 + armour_penetration = 30 + attack_verb_continuous = "acts out divine vengeance on" + attack_verb_simple = "act out divine vengeance on" + obj_damage = 50 + environment_smash = ENVIRONMENT_SMASH_RWALLS + ai_controller = /datum/ai_controller/basic_controller/clown/murder + loot = list( + /obj/effect/gibspawner/human, + /obj/effect/spawner/foam_starter/small, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("HONK!!!", "The Honkmother is merciful, so I must act out her wrath.", "parce mihi ad beatus honkmother placet mihi ut peccata committere,", "DIE!!!"), + BB_EMOTE_HEAR = list("honks", "grunts"), + BB_EMOTE_SEE = list("sweats"), + BB_EMOTE_CHANCE = 5, + ) + +/mob/living/basic/clown/mutant + name = "Unknown" + desc = "Kill it for its own sake." + icon_state = "mutant" + icon_living = "mutant" + move_resist = INFINITY + response_help_continuous = "reluctantly sinks a finger into" + response_help_simple = "reluctantly sink a finger into" + response_disarm_continuous = "squishes into" + response_disarm_simple = "squish into" + response_harm_continuous = "squishes into" + response_harm_simple = "squish into" + maxHealth = 130 + health = 130 + pixel_x = -16 + base_pixel_x = -16 + speed = -5 + melee_damage_lower = 10 + melee_damage_upper = 20 + attack_verb_continuous = "awkwardly flails at" + attack_verb_simple = "awkwardly flail at" + loot = list( + /obj/effect/gibspawner/generic, + /obj/effect/gibspawner/generic/animal, + /obj/effect/gibspawner/human, + /obj/effect/gibspawner/human/bodypartless, + /obj/effect/gibspawner/xeno/bodypartless, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("aaaaaahhhhuuhhhuhhhaaaaa", "AAAaaauuuaaAAAaauuhhh", "huuuuuh... hhhhuuuooooonnnnkk", "HuaUAAAnKKKK"), + BB_EMOTE_SEE = list("squirms", "writhes", "pulsates", "froths", "oozes"), + BB_EMOTE_CHANCE = 10, + ) + +/mob/living/basic/clown/mutant/slow + speed = 20 + +/mob/living/basic/clown/mutant/glutton + name = "banana glutton" + desc = "Something that was once a clown" + icon_state = "glutton" + icon_living = "glutton" + health = 200 + mob_size = MOB_SIZE_LARGE + speed = 1 + melee_damage_lower = 10 + melee_damage_upper = 15 + force_threshold = 10 //lots of fat to cushion blows. + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 2, STAMINA = 0, OXY = 1) + attack_verb_continuous = "slams" + attack_verb_simple = "slam" + loot = list( + /obj/effect/gibspawner/generic, + /obj/effect/gibspawner/generic/animal, + /obj/effect/gibspawner/human/bodypartless, + /obj/effect/gibspawner/xeno/bodypartless, + ) + emotes = list( + BB_EMOTE_SAY = list("hey, buddy", "HONK!!!", "H-h-h-H-HOOOOONK!!!!", "HONKHONKHONK!!!", "HEY, BUCKO, GET BACK HERE!!!", "HOOOOOOOONK!!!"), + BB_EMOTE_SEE = list("jiggles", "wobbles"), + ) + death_sound = 'sound/misc/sadtrombone.ogg' + waddles = FALSE + ///This is the list of items we are ready to regurgitate, + var/list/prank_pouch = list() + +/mob/living/basic/clown/mutant/glutton/Initialize(mapload) + . = ..() + var/datum/action/cooldown/regurgitate/spit = new(src) + spit.Grant(src) + + AddElement(/datum/element/swabable, CELL_LINE_TABLE_GLUTTON, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/cheesiehonkers, /obj/item/food/cornchips), tame_chance = 30, bonus_tame_chance = 0, after_tame = CALLBACK(src, PROC_REF(tamed))) + + +/mob/living/basic/clown/mutant/glutton/attacked_by(obj/item/item, mob/living/user) + if(!check_edible(item)) + return ..() + eat_atom(item) + +/mob/living/basic/clown/mutant/glutton/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE) + if(!check_edible(target)) + return ..() + eat_atom(target) + +/mob/living/basic/clown/mutant/glutton/UnarmedAttack(atom/victim, proximity_flag, list/modifiers) + if(!check_edible(victim)) + return ..() + eat_atom(victim) + +///Returns whether or not the supplied movable atom is edible. +/mob/living/basic/clown/mutant/glutton/proc/check_edible(atom/movable/potential_food) + if(isliving(potential_food)) + var/mob/living/living_morsel = potential_food + if(living_morsel.mob_size > MOB_SIZE_SMALL) + return FALSE + else + return TRUE + + if(IS_EDIBLE(potential_food)) + if(prank_pouch.len >= 8) + to_chat(src, span_warning("Your prank pouch is filled to the brim! You don't think you can swallow any more morsels right now.")) + return FALSE + return TRUE + +///This proc eats the atom, certain funny items are stored directly in the prank pouch while bananas grant a heal based on their potency and the peels are retained in the pouch. +/mob/living/basic/clown/mutant/glutton/proc/eat_atom(atom/movable/eaten_atom) + + var/static/funny_items = list( + /obj/item/food/pie/cream, + /obj/item/food/grown/tomato, + /obj/item/food/meatclown, + ) + + visible_message(span_warning("[src] eats [eaten_atom]!"), span_notice("You eat [eaten_atom].")) + if(is_type_in_list(eaten_atom, funny_items)) + eaten_atom.forceMove(src) + prank_pouch += eaten_atom + + else + if(istype(eaten_atom, /obj/item/food/grown/banana)) + var/obj/item/food/grown/banana/banana_morsel = eaten_atom + adjustBruteLoss(-banana_morsel.seed.potency * 0.25) + prank_pouch += banana_morsel.generate_trash(src) + + qdel(eaten_atom) + + playsound(loc,'sound/items/eatfood.ogg', rand(30,50), TRUE) + flick("glutton_mouth", src) + +/mob/living/basic/clown/mutant/glutton/proc/tamed(mob/living/tamer) + buckle_lying = 0 + AddElement(/datum/element/ridable, /datum/component/riding/creature/glutton) + +/mob/living/basic/clown/mutant/glutton/Exited(atom/movable/gone, direction) + . = ..() + prank_pouch -= gone + +///This ability will let you fire one random item from your pouch, +/datum/action/cooldown/regurgitate + name = "Regurgitate" + desc = "Regurgitates a single item from the depths of your pouch." + background_icon_state = "bg_changeling" + overlay_icon_state = "bg_changeling_border" + button_icon = 'icons/mob/actions/actions_animal.dmi' + button_icon_state = "regurgitate" + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED + melee_cooldown_time = 0 SECONDS + click_to_activate = TRUE + +/datum/action/cooldown/regurgitate/set_click_ability(mob/on_who) + . = ..() + if(!.) + return + + to_chat(on_who, span_notice("Your throat muscles tense up. Left-click to regurgitate a funny morsel!")) + on_who.icon_state = "glutton_tongue" + on_who.update_appearance(UPDATE_ICON) + +/datum/action/cooldown/regurgitate/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) + return + + if(refund_cooldown) + to_chat(on_who, span_notice("Your throat muscles relax.")) + on_who.icon_state = initial(on_who.icon_state) + on_who.update_appearance(UPDATE_ICON) + +/datum/action/cooldown/regurgitate/IsAvailable(feedback = FALSE) + . = ..() + if(!.) + return FALSE + + // Hardcoded to only work with gluttons. Come back next year + return istype(owner, /mob/living/basic/clown/mutant/glutton) + +/datum/action/cooldown/regurgitate/Activate(atom/spit_at) + StartCooldown(cooldown_time / 4) + + var/mob/living/basic/clown/mutant/glutton/pouch_owner = owner + if(!length(pouch_owner.prank_pouch)) + pouch_owner.icon_state = initial(pouch_owner.icon_state) + to_chat(pouch_owner, span_notice("Your prank pouch is empty.")) + return TRUE + + var/obj/item/projected_morsel = pick(pouch_owner.prank_pouch) + projected_morsel.forceMove(pouch_owner.loc) + projected_morsel.throw_at(spit_at, 8, 2, pouch_owner) + flick("glutton_mouth", pouch_owner) + playsound(pouch_owner, 'sound/misc/soggy.ogg', 75) + + StartCooldown() + return TRUE + +/mob/living/basic/clown/banana + name = "Clownana" + desc = "A fusion of clown and banana DNA birthed from a botany experiment gone wrong." + icon_state = "banana tree" + icon_living = "banana tree" + response_disarm_continuous = "peels" + response_disarm_simple = "peel" + response_harm_continuous = "peels" + response_harm_simple = "peel" + maxHealth = 120 + health = 120 + speed = -1 + loot = list( + /obj/effect/gibspawner/human, + /obj/item/seeds/banana, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("HONK", "Honk!", "YA-HONK!!!"), + BB_EMOTE_SEE = list("bites into the banana", "plucks a banana off its head", "photosynthesizes"), + BB_EMOTE_SOUND = list('sound/items/bikehorn.ogg'), + ) + ///Our peel dropping ability + var/datum/action/cooldown/rustle/banana_rustle + ///Our banana bunch spawning ability + var/datum/action/cooldown/exquisite_bunch/banana_bunch + +/mob/living/basic/clown/banana/Initialize(mapload) + . = ..() + banana_rustle = new() + banana_rustle.Grant(src) + banana_bunch = new() + banana_bunch.Grant(src) + +/mob/living/basic/clown/banana/Destroy() + . = ..() + QDEL_NULL(banana_rustle) + QDEL_NULL(banana_bunch) + +///drops peels around the mob when activated +/datum/action/cooldown/rustle + name = "Rustle" + desc = "Shake loose a few banana peels." + cooldown_time = 8 SECONDS + button_icon_state = "rustle" + button_icon = 'icons/mob/actions/actions_clown.dmi' + background_icon_state = "bg_nature" + overlay_icon_state = "bg_nature_border" + ///which type of peel to spawn + var/banana_type = /obj/item/grown/bananapeel + ///How many peels to spawn + var/peel_amount = 3 + +/datum/action/cooldown/rustle/Activate(atom/target) + . = ..() + var/list/reachable_turfs = list() + for(var/turf/adjacent_turf in RANGE_TURFS(1, owner.loc)) + if(adjacent_turf == owner.loc || !owner.CanReach(adjacent_turf) || !isopenturf(adjacent_turf)) + continue + reachable_turfs += adjacent_turf + + var/peels_to_spawn = min(peel_amount, reachable_turfs.len) + for(var/i in 1 to peels_to_spawn) + new banana_type(pick_n_take(reachable_turfs)) + playsound(owner, 'sound/creatures/clown/clownana_rustle.ogg', 60) + animate(owner, time = 1, pixel_x = 6, easing = CUBIC_EASING | EASE_OUT) + animate(time = 2, pixel_x = -8, easing = CUBIC_EASING) + animate(time = 1, pixel_x = 0, easing = CUBIC_EASING | EASE_IN) + StartCooldown() + +///spawns a plumb bunch of bananas imbued with mystical power. +/datum/action/cooldown/exquisite_bunch + name = "Exquisite Bunch" + desc = "Pluck your finest bunch of bananas from your head. This bunch is especially nutrious to monkeykind. A gentle tap will trigger an explosive ripening process." + button_icon = 'icons/obj/service/hydroponics/harvest.dmi' + cooldown_time = 60 SECONDS + button_icon_state = "banana_bunch" + background_icon_state = "bg_nature" + overlay_icon_state = "bg_nature_border" + ///If we are currently activating our ability. + var/activating = FALSE + +/datum/action/cooldown/exquisite_bunch/Trigger(trigger_flags, atom/target) + if(activating) + return + var/bunch_turf = get_step(owner.loc, owner.dir) + if(!bunch_turf) + return + if(!owner.CanReach(bunch_turf) || !isopenturf(bunch_turf)) + owner.balloon_alert(owner, "can't do that here!") + return + activating = TRUE + if(!do_after(owner, 1 SECONDS)) + activating = FALSE + return + playsound(owner, 'sound/creatures/clown/hehe.ogg', 100) + if(!do_after(owner, 1 SECONDS)) + activating = FALSE + return + activating = FALSE + return ..() + +/datum/action/cooldown/exquisite_bunch/Activate(atom/target) + . = ..() + new /obj/item/food/grown/banana/bunch(get_step(owner.loc, owner.dir)) + playsound(owner, 'sound/items/bikehorn.ogg', 60) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(playsound), owner, 'sound/creatures/clown/hohoho.ogg', 100, 1), 1 SECONDS) + StartCooldown() diff --git a/code/modules/mob/living/basic/clown/clown_ai.dm b/code/modules/mob/living/basic/clown/clown_ai.dm new file mode 100644 index 00000000000..b2e6418dde7 --- /dev/null +++ b/code/modules/mob/living/basic/clown/clown_ai.dm @@ -0,0 +1,19 @@ +/datum/ai_controller/basic_controller/clown + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_BASIC_MOB_SPEAK_LINES = null, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/random_speech/blackboard, + ) + +/datum/ai_controller/basic_controller/clown/murder + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead, + BB_BASIC_MOB_SPEAK_LINES = null, + ) diff --git a/code/modules/mob/living/basic/heretic/heretic_summon.dm b/code/modules/mob/living/basic/heretic/heretic_summon.dm new file mode 100644 index 00000000000..cdae7ea6786 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/heretic_summon.dm @@ -0,0 +1,34 @@ +/mob/living/basic/heretic_summon + name = "Eldritch Demon" + real_name = "Eldritch Demon" + desc = "A horror from beyond this realm." + icon = 'icons/mob/nonhuman-player/eldritch_mobs.dmi' + faction = list(FACTION_HERETIC) + basic_mob_flags = DEL_ON_DEATH + gender = NEUTER + mob_biotypes = NONE + + unsuitable_atmos_damage = 0 + unsuitable_cold_damage = 0 + unsuitable_heat_damage = 0 + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + speed = 0 + melee_attack_cooldown = CLICK_CD_MELEE + + attack_sound = 'sound/weapons/punch1.ogg' + response_help_continuous = "thinks better of touching" + response_help_simple = "think better of touching" + response_disarm_continuous = "flails at" + response_disarm_simple = "flail at" + response_harm_continuous = "reaps" + response_harm_simple = "tears" + death_message = "implodes into itself." + + combat_mode = TRUE + ai_controller = null + speak_emote = list("screams") + gold_core_spawnable = NO_SPAWN + +/mob/living/basic/heretic_summon/Initialize(mapload) + . = ..() + AddElement(/datum/element/death_drops, string_list(list(/obj/effect/gibspawner/generic))) diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm new file mode 100644 index 00000000000..292766be07b --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm @@ -0,0 +1,91 @@ +/mob/living/basic/mining/ice_whelp + name = "ice whelp" + desc = "The offspring of an ice drake, weak in comparison but still terrifying." + icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + icon_state = "ice_whelp" + icon_living = "ice_whelp" + icon_dead = "ice_whelp_dead" + mob_biotypes = MOB_ORGANIC|MOB_BEAST + mouse_opacity = MOUSE_OPACITY_ICON + butcher_results = list( + /obj/item/stack/ore/diamond = 3, + /obj/item/stack/sheet/animalhide/ashdrake = 1, + /obj/item/stack/sheet/bone = 10, + /obj/item/stack/sheet/sinew = 2, + ) + crusher_loot = /obj/item/crusher_trophy/tail_spike + speed = 12 + + maxHealth = 300 + health = 300 + obj_damage = 40 + armour_penetration = 20 + melee_damage_lower = 20 + melee_damage_upper = 20 + + attack_verb_continuous = "chomps" + attack_verb_simple = "chomp" + death_message = "collapses on its side." + death_sound = 'sound/magic/demon_dies.ogg' + + attack_sound = 'sound/magic/demon_attack1.ogg' + move_force = MOVE_FORCE_VERY_STRONG + move_resist = MOVE_FORCE_VERY_STRONG + pull_force = MOVE_FORCE_VERY_STRONG + + ai_controller = /datum/ai_controller/basic_controller/ice_whelp + ///how much we will heal when cannibalizing a target + var/heal_on_cannibalize = 5 + +/mob/living/basic/mining/ice_whelp/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_NO_GLIDE, INNATE_TRAIT) + AddElement(/datum/element/footstep, FOOTSTEP_MOB_HEAVY) + AddComponent(/datum/component/basic_mob_ability_telegraph) + AddComponent(/datum/component/basic_mob_attack_telegraph, telegraph_duration = 0.6 SECONDS) + var/datum/action/cooldown/mob_cooldown/ice_breath/flamethrower = new(src) + var/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions/wide_flames = new(src) + flamethrower.Grant(src) + wide_flames.Grant(src) + ai_controller.set_blackboard_key(BB_WHELP_WIDESPREAD_FIRE, wide_flames) + ai_controller.set_blackboard_key(BB_WHELP_STRAIGHTLINE_FIRE, flamethrower) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + + +/mob/living/basic/mining/ice_whelp/proc/pre_attack(mob/living/sculptor, atom/target) + SIGNAL_HANDLER + + if(istype(target, /obj/structure/flora/rock/icy)) + INVOKE_ASYNC(src, PROC_REF(create_sculpture), target) + return COMPONENT_HOSTILE_NO_ATTACK + + if(!istype(target, src.type)) + return + + var/mob/living/victim = target + if(victim.stat != DEAD) + return + + INVOKE_ASYNC(src, PROC_REF(cannibalize_victim), victim) + return COMPONENT_HOSTILE_NO_ATTACK + +/// Carve a stone into a beautiful self-portrait +/mob/living/basic/mining/ice_whelp/proc/create_sculpture(atom/target) + balloon_alert(src, "sculpting...") + if(!do_after(src, 5 SECONDS, target = target)) + return + var/obj/structure/statue/custom/dragon_statue = new(get_turf(target)) + dragon_statue.set_visuals(src) + dragon_statue.name = "statue of [src]" + dragon_statue.desc = "Let this serve as a warning." + dragon_statue.set_anchored(TRUE) + qdel(target) + +/// Gib and consume our fellow ice drakes +/mob/living/basic/mining/ice_whelp/proc/cannibalize_victim(mob/living/target) + start_pulling(target) + balloon_alert(src, "devouring...") + if(!do_after(src, 5 SECONDS, target)) + return + target.gib() + adjustBruteLoss(-1 * heal_on_cannibalize) diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm new file mode 100644 index 00000000000..d5dc50d0a69 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm @@ -0,0 +1,37 @@ +/datum/action/cooldown/mob_cooldown/ice_breath + name = "Ice Breath" + desc = "Fire a cold line of fire towards the enemy!" + button_icon = 'icons/effects/magic.dmi' + button_icon_state = "fireball" + cooldown_time = 3 SECONDS + melee_cooldown_time = 0 SECONDS + click_to_activate = TRUE + ///the range of fire + var/fire_range = 4 + +/datum/action/cooldown/mob_cooldown/ice_breath/Activate(atom/target_atom) + var/turf/target_fire_turf = get_ranged_target_turf_direct(owner, target_atom, fire_range) + var/list/burn_turfs = get_line(owner, target_fire_turf) - get_turf(owner) + // This proc sleeps + INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(dragon_fire_line), owner, /* burn_turfs = */ burn_turfs, /* frozen = */ TRUE) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions + name = "Fire all directions" + desc = "Unleash lines of cold fire in all directions" + button_icon = 'icons/effects/fire.dmi' + button_icon_state = "1" + cooldown_time = 4 SECONDS + melee_cooldown_time = 0 SECONDS + click_to_activate = FALSE + ///the range of fire + var/fire_range = 6 + +/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions/Activate(atom/target_atom) + for(var/direction in GLOB.cardinals) + var/turf/target_fire_turf = get_ranged_target_turf(owner, direction, fire_range) + var/list/burn_turfs = get_line(owner, target_fire_turf) - get_turf(owner) + INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(dragon_fire_line), owner, burn_turfs, frozen = TRUE) + StartCooldown() + return TRUE diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm new file mode 100644 index 00000000000..47280af4281 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm @@ -0,0 +1,154 @@ +#define ENRAGE_ADDITION 25 +/datum/ai_controller/basic_controller/ice_whelp + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items/goliath, + BB_WHELP_ENRAGED = 0, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability/ice_whelp, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/sculpt_statues, + /datum/ai_planning_subtree/find_and_hunt_target/corpses/ice_whelp, + /datum/ai_planning_subtree/burn_trees, + ) + +/datum/ai_planning_subtree/find_and_hunt_target/corpses/ice_whelp + target_key = BB_TARGET_CANNIBAL + finding_behavior = /datum/ai_behavior/find_hunt_target/corpses/dragon_corpse + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/dragon_cannibalise + hunt_targets = list(/mob/living/basic/mining/ice_whelp) + hunt_range = 10 + +/datum/ai_behavior/find_hunt_target/corpses/dragon_corpse + +/datum/ai_behavior/find_hunt_target/corpses/dragon_corpse/valid_dinner(mob/living/source, mob/living/dinner, radius) + if(dinner.pulledby) //someone already got him before us + return FALSE + return ..() + +/datum/ai_behavior/hunt_target/unarmed_attack_target/dragon_cannibalise + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/hunt_target/unarmed_attack_target/dragon_cannibalise/perform(seconds_per_tick, datum/ai_controller/controller, target_key, attack_key) + var/mob/living/target = controller.blackboard[target_key] + if(QDELETED(target) || target.stat != DEAD || target.pulledby) //we were too slow + finish_action(controller, FALSE) + return + return ..() + +/datum/ai_behavior/cannibalize/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + +///subtree to find icy rocks and create sculptures out of them +/datum/ai_planning_subtree/sculpt_statues + +/datum/ai_planning_subtree/sculpt_statues/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_TARGET_ROCK)) + controller.queue_behavior(/datum/ai_behavior/sculpt_statue, BB_TARGET_ROCK) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_TARGET_ROCK, /obj/structure/flora/rock/icy) + +/datum/ai_behavior/sculpt_statue + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + action_cooldown = 5 MINUTES + +/datum/ai_behavior/sculpt_statue/setup(datum/ai_controller/controller, target_key) + . = ..() + var/obj/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/sculpt_statue/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + + var/atom/target = controller.blackboard[target_key] + var/mob/living/basic/living_pawn = controller.pawn + + if(QDELETED(target)) + finish_action(controller, FALSE, target_key) + return + + living_pawn.melee_attack(target) + finish_action(controller, TRUE, target_key) + +/datum/ai_behavior/sculpt_statue/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + + +//subtree to use our attacks on the victim +/datum/ai_planning_subtree/targeted_mob_ability/ice_whelp + ability_key = BB_WHELP_STRAIGHTLINE_FIRE + use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/ice_whelp + finish_planning = FALSE + + +/datum/ai_behavior/targeted_mob_ability/ice_whelp + ///key that stores how enraged we are + var/enraged_key = BB_WHELP_ENRAGED + ///key that stores the ability we will use instead if we are fully enraged + var/secondary_ability_key = BB_WHELP_WIDESPREAD_FIRE + +/datum/ai_behavior/targeted_mob_ability/ice_whelp/get_ability_to_use(datum/ai_controller/controller, ability_key) + var/enraged_value = controller.blackboard[enraged_key] + + if(prob(enraged_value)) + controller.set_blackboard_key(enraged_key, 0) + return controller.blackboard[secondary_ability_key] + + controller.set_blackboard_key(enraged_key, enraged_value + ENRAGE_ADDITION) + return controller.blackboard[ability_key] + +///subtree to look for trees and burn them with our flamethrower +/datum/ai_planning_subtree/burn_trees + +/datum/ai_planning_subtree/burn_trees/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/datum/action/cooldown/using_action = controller.blackboard[BB_WHELP_STRAIGHTLINE_FIRE] + if (!using_action?.IsAvailable()) + return + + if(controller.blackboard_key_exists(BB_TARGET_TREE)) + controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees, BB_WHELP_STRAIGHTLINE_FIRE, BB_TARGET_TREE) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/set_target_tree, BB_TARGET_TREE) + +/datum/ai_behavior/set_target_tree + +/datum/ai_behavior/set_target_tree/perform(seconds_per_tick, datum/ai_controller/controller, tree_key) + . = ..() + + var/mob/living_pawn = controller.pawn + var/list/possible_trees = list() + + for(var/obj/structure/flora/tree/possible_tree in oview(9, living_pawn)) + if(istype(possible_tree, /obj/structure/flora/tree/stump)) //no leaves to burn + continue + possible_trees += possible_tree + + if(!length(possible_trees)) + finish_action(controller, FALSE) + return + + controller.set_blackboard_key(tree_key, pick(possible_trees)) + finish_action(controller, TRUE) + +/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + required_distance = 2 + action_cooldown = 2 MINUTES + +/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees/setup(datum/ai_controller/controller, ability_key, target_key) + . = ..() + var/obj/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target) + +#undef ENRAGE_ADDITION diff --git a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.dm b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.dm new file mode 100644 index 00000000000..bb109fdde61 --- /dev/null +++ b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.dm @@ -0,0 +1,55 @@ +//Large and powerful, but timid. It won't engage anything above 50 health, or anything without legcuffs. +//It can fire fleshy snares that legcuff anyone that it hits, making them look especially tasty to the arachnid. +/mob/living/basic/mega_arachnid + name = "mega arachnid" + desc = "Though physically imposing, it prefers to ambush its prey, and it will only engage with an already crippled opponent." + icon = 'icons/mob/simple/jungle/arachnid.dmi' + icon_state = "arachnid" + icon_living = "arachnid" + icon_dead = "arachnid_dead" + mob_biotypes = MOB_ORGANIC|MOB_BUG + melee_damage_lower = 30 + melee_damage_upper = 30 + maxHealth = 300 + health = 300 + + pixel_x = -16 + base_pixel_x = -16 + + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + faction = list(FACTION_JUNGLE) + obj_damage = 30 + environment_smash = ENVIRONMENT_SMASH_WALLS + minimum_survivable_temperature = T0C + maximum_survivable_temperature = T0C + 450 + status_flags = NONE + lighting_cutoff_red = 5 + lighting_cutoff_green = 20 + lighting_cutoff_blue = 25 + mob_size = MOB_SIZE_LARGE + + speak_emote = list("chitters") + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH + ai_controller = /datum/ai_controller/basic_controller/mega_arachnid + alpha = 40 + +/mob/living/basic/mega_arachnid/Initialize(mapload) + . = ..() + AddComponent(/datum/component/seethrough_mob) + var/datum/action/cooldown/spell/pointed/projectile/flesh_restraints/restrain = new(src) + var/datum/action/cooldown/mob_cooldown/secrete_acid/acid_spray = new(src) + acid_spray.Grant(src) + restrain.Grant(src) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_MEGA_ARACHNID, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + AddComponent(/datum/component/appearance_on_aggro, alpha_on_aggro = 255, alpha_on_deaggro = alpha) + AddComponent(/datum/component/tree_climber, climbing_distance = 15) + ai_controller.set_blackboard_key(BB_ARACHNID_RESTRAIN, restrain) + ai_controller.set_blackboard_key(BB_ARACHNID_SLIP, acid_spray) + +/mob/living/basic/mega_arachnid/Login() + . = ..() + if(!. || !client) + return FALSE + + animate(src, alpha = 255, time = 2 SECONDS) //make them visible diff --git a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_abilities.dm b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_abilities.dm new file mode 100644 index 00000000000..e8c4d1723e7 --- /dev/null +++ b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_abilities.dm @@ -0,0 +1,80 @@ + +/datum/action/cooldown/spell/pointed/projectile/flesh_restraints + name = "fleshy restraints" + desc = "Launch at your prey to immobilize them." + button_icon = 'icons/obj/restraints.dmi' + button_icon_state = "flesh_snare" + + cooldown_time = 6 SECONDS + spell_requirements = NONE + + active_msg = "You prepare to throw a restraint at your target!" + cast_range = 8 + projectile_type = /obj/projectile/mega_arachnid + +/obj/projectile/mega_arachnid + name = "flesh snare" + icon_state = "tentacle_end" + damage = 0 + +/obj/projectile/mega_arachnid/on_hit(atom/target, blocked = FALSE) + . = ..() + if(!iscarbon(target) || blocked >= 100) + return + var/obj/item/restraints/legcuffs/beartrap/mega_arachnid/restraint = new(get_turf(target)) + restraint.spring_trap(null, target) + +/obj/item/restraints/legcuffs/beartrap/mega_arachnid + name = "fleshy restraints" + desc = "Used by mega arachnids to immobilize their prey." + flags_1 = NONE + item_flags = DROPDEL + icon_state = "flesh_snare" + armed = TRUE + +/obj/item/restraints/legcuffs/beartrap/mega_arachnid/Initialize(mapload) + . = ..() + AddElement(/datum/element/swabable, CELL_LINE_TABLE_MEGA_ARACHNID, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + + +/datum/action/cooldown/mob_cooldown/secrete_acid + name = "Secrete Acid" + button_icon = 'icons/effects/acid.dmi' + button_icon_state = "default" + desc = "Secrete a slippery acid!" + cooldown_time = 15 SECONDS + melee_cooldown_time = 0 SECONDS + click_to_activate = FALSE + +/datum/action/cooldown/mob_cooldown/secrete_acid/Activate(atom/target_atom) + RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(release_acid)) + addtimer(CALLBACK(src, PROC_REF(deactivate_ability)), 3 SECONDS) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/secrete_acid/proc/release_acid() + SIGNAL_HANDLER + + var/turf/current_turf = owner.loc + if(locate(/obj/effect/slippery_acid) in current_turf.contents) + return + + new /obj/effect/slippery_acid(current_turf) + +/datum/action/cooldown/mob_cooldown/secrete_acid/proc/deactivate_ability() + UnregisterSignal(owner, COMSIG_MOVABLE_MOVED) + +/obj/effect/slippery_acid + name = "slippery acid" + icon = 'icons/effects/acid.dmi' + icon_state = "default" + layer = BELOW_MOB_LAYER + plane = GAME_PLANE + anchored = TRUE + /// how long does the acid exist for + var/duration_time = 5 SECONDS + +/obj/effect/slippery_acid/Initialize(mapload) + . = ..() + AddComponent(/datum/component/slippery, 6 SECONDS) + QDEL_IN(src, duration_time) diff --git a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm new file mode 100644 index 00000000000..c88178135dc --- /dev/null +++ b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm @@ -0,0 +1,79 @@ +/datum/ai_controller/basic_controller/mega_arachnid + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_BASIC_MOB_FLEEING = TRUE, + BB_BASIC_MOB_FLEE_DISTANCE = 5, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability/arachnid_restrain, + /datum/ai_planning_subtree/basic_melee_attack_subtree/mega_arachnid, + /datum/ai_planning_subtree/flee_target/mega_arachnid, + /datum/ai_planning_subtree/climb_trees, + /datum/ai_planning_subtree/find_and_hunt_target/destroy_surveillance, + ) + +///destroy surveillance objects to boost our stealth +/datum/ai_planning_subtree/find_and_hunt_target/destroy_surveillance + target_key = BB_SURVEILLANCE_TARGET + finding_behavior = /datum/ai_behavior/find_hunt_target/find_active_surveillance + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target + hunt_targets = list(/obj/machinery/camera, /obj/machinery/light) + hunt_range = 7 + +/datum/ai_behavior/find_hunt_target/find_active_surveillance + +/datum/ai_behavior/find_hunt_target/find_active_camera/valid_dinner(mob/living/source, obj/machinery/dinner, radius) + if(dinner.machine_stat & BROKEN) + return FALSE + + return can_see(source, dinner, radius) + +///spray slippery acid as we flee! +/datum/ai_planning_subtree/flee_target/mega_arachnid + flee_behaviour = /datum/ai_behavior/run_away_from_target/mega_arachnid + +/datum/ai_planning_subtree/flee_target/mega_arachnid/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard[BB_BASIC_MOB_FLEEING]) + return + var/datum/action/cooldown/slip_acid = controller.blackboard[BB_ARACHNID_SLIP] + + if(!QDELETED(slip_acid) && slip_acid.IsAvailable()) + controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_ARACHNID_SLIP) + + return ..() + +/datum/ai_behavior/run_away_from_target/mega_arachnid + clear_failed_targets = FALSE + +///only engage in melee combat against cuffed targets, otherwise keep throwing restraints at them +/datum/ai_planning_subtree/basic_melee_attack_subtree/mega_arachnid + ///minimum health our target must be before we can attack them + var/minimum_health = 50 + +/datum/ai_planning_subtree/basic_melee_attack_subtree/mega_arachnid/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if(!ishuman(target)) + return ..() + + var/mob/living/carbon/human_target = target + if(!human_target.legcuffed && human_target.health > minimum_health) + return + + return ..() + +/datum/ai_planning_subtree/targeted_mob_ability/arachnid_restrain + ability_key = BB_ARACHNID_RESTRAIN + +/// only fire ability at humans if they are not cuffed +/datum/ai_planning_subtree/targeted_mob_ability/arachnid_restrain/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/target = controller.blackboard[target_key] + if(!ishuman(target)) + return + var/mob/living/carbon/human_target = target + if(human_target.legcuffed) + return + return ..() diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling.dm b/code/modules/mob/living/basic/jungle/seedling/seedling.dm new file mode 100644 index 00000000000..998f693d6da --- /dev/null +++ b/code/modules/mob/living/basic/jungle/seedling/seedling.dm @@ -0,0 +1,348 @@ +#define SEEDLING_STATE_NEUTRAL 0 +#define SEEDLING_STATE_WARMUP 1 +#define SEEDLING_STATE_ACTIVE 2 + +/** + * A mobile plant with a rapid ranged attack. + * It can pick up watering cans and look after plants. + */ +/mob/living/basic/seedling + name = "seedling" + desc = "This oversized, predatory flower conceals what can only be described as an organic energy cannon." + icon = 'icons/mob/simple/jungle/seedling.dmi' + icon_state = "seedling" + icon_living = "seedling" + icon_dead = "seedling_dead" + habitable_atmos = list("min_oxy" = 2, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = 0 + maximum_survivable_temperature = 450 + mob_biotypes = MOB_ORGANIC | MOB_PLANT + maxHealth = 100 + health = 100 + pixel_y = -14 + base_pixel_y = -14 + pixel_x = -14 + base_pixel_x = -14 + response_harm_continuous = "strikes" + response_harm_simple = "strike" + melee_damage_lower = 30 + melee_damage_upper = 30 + lighting_cutoff_green = 20 + lighting_cutoff_blue = 25 + mob_size = MOB_SIZE_LARGE + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH + ai_controller = /datum/ai_controller/basic_controller/seedling + ///the state of combat we are in + var/combatant_state = SEEDLING_STATE_NEUTRAL + ///the colors our petals can have + var/static/list/possible_colors = list(COLOR_RED, COLOR_YELLOW, COLOR_OLIVE, COLOR_CYAN) + ///appearance when we are in our normal state + var/mutable_appearance/petal_neutral + ///appearance when we are in our warmup state + var/mutable_appearance/petal_warmup + ///appearance when we are in the firing state + var/mutable_appearance/petal_active + ///appearance when we are dead + var/mutable_appearance/petal_dead + ///the bucket we carry + var/obj/item/reagent_containers/cup/held_can + ///commands we follow + var/list/seedling_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/follow, + ) + +/mob/living/basic/seedling/Initialize(mapload) + . = ..() + var/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/seed_attack = new(src) + seed_attack.Grant(src) + ai_controller.set_blackboard_key(BB_RAPIDSEEDS_ABILITY, seed_attack) + var/datum/action/cooldown/mob_cooldown/solarbeam/beam_attack = new(src) + beam_attack.Grant(src) + ai_controller.set_blackboard_key(BB_SOLARBEAM_ABILITY, beam_attack) + + var/petal_color = pick(possible_colors) + + petal_neutral = mutable_appearance(icon, "[icon_state]_overlay") + petal_neutral.color = petal_color + + petal_warmup = mutable_appearance(icon, "[icon_state]_charging_overlay") + petal_warmup.color = petal_color + + petal_active = mutable_appearance(icon, "[icon_state]_fire_overlay") + petal_active.color = petal_color + + petal_dead = mutable_appearance(icon, "[icon_state]_dead_overlay") + petal_dead.color = petal_color + + AddElement(/datum/element/wall_smasher) + AddComponent(/datum/component/obeys_commands, seedling_commands) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + RegisterSignal(src, COMSIG_KB_MOB_DROPITEM_DOWN, PROC_REF(drop_can)) + update_appearance() + +/mob/living/basic/seedling/proc/pre_attack(mob/living/puncher, atom/target) + SIGNAL_HANDLER + + if(istype(target, /obj/machinery/hydroponics)) + treat_hydro_tray(target) + return COMPONENT_HOSTILE_NO_ATTACK + + if(isnull(held_can)) + return + + if(istype(target, /obj/structure/sink) || istype(target, /obj/structure/reagent_dispensers)) + INVOKE_ASYNC(held_can, TYPE_PROC_REF(/obj/item, melee_attack_chain), src, target) + return COMPONENT_HOSTILE_NO_ATTACK + + +///seedlings can water trays, remove weeds, or remove dead plants +/mob/living/basic/seedling/proc/treat_hydro_tray(obj/machinery/hydroponics/hydro) + + if(hydro.plant_status == HYDROTRAY_PLANT_DEAD) + balloon_alert(src, "dead plant removed") + hydro.set_seed(null) + return + + if(hydro.weedlevel > 0) + balloon_alert(src, "weeds uprooted") + hydro.set_weedlevel(0) + return + + var/list/can_reagents = held_can?.reagents.reagent_list + + if(!length(can_reagents)) + return + + if((locate(/datum/reagent/water) in can_reagents) && (hydro.waterlevel < hydro.maxwater)) + INVOKE_ASYNC(held_can, TYPE_PROC_REF(/obj/item, melee_attack_chain), src, hydro) + return + +/mob/living/basic/seedling/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) + . = ..() + + if(!. || !proximity_flag || held_can) + return + + if(!istype(attack_target, /obj/item/reagent_containers/cup/watering_can)) + return + + var/obj/item/can_target = attack_target + can_target.forceMove(src) + +/mob/living/basic/seedling/proc/change_combatant_state(state) + combatant_state = state + update_appearance() + +/mob/living/basic/seedling/attackby(obj/item/can, mob/living/carbon/human/user, list/modifiers) + if(istype(can, /obj/item/reagent_containers/cup/watering_can) && isnull(held_can)) + can.forceMove(src) + return + + return ..() + +/mob/living/basic/seedling/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + if(istype(arrived, /obj/item/reagent_containers/cup/watering_can)) + held_can = arrived + update_appearance() + + return ..() + +/mob/living/basic/seedling/update_overlays() + . = ..() + if(stat == DEAD) + . += petal_dead + return + + switch(combatant_state) + if(SEEDLING_STATE_NEUTRAL) + . += petal_neutral + if(held_can) + . += mutable_appearance(icon, "seedling_can_overlay") + if(SEEDLING_STATE_WARMUP) + . += petal_warmup + if(SEEDLING_STATE_ACTIVE) + . += petal_active + +/mob/living/basic/seedling/update_icon_state() + . = ..() + if(stat == DEAD) + return + switch(combatant_state) + if(SEEDLING_STATE_NEUTRAL) + icon_state = "seedling" + if(SEEDLING_STATE_WARMUP) + icon_state = "seedling_charging" + if(SEEDLING_STATE_ACTIVE) + icon_state = "seedling_fire" + +/mob/living/basic/seedling/proc/drop_can(mob/living/user) + SIGNAL_HANDLER + + if(isnull(held_can)) + return + dropItemToGround(held_can) + return COMSIG_KB_ACTIVATED + +/mob/living/basic/seedling/Exited(atom/movable/gone, direction) + . = ..() + if(gone != held_can) + return + held_can = null + update_appearance() + +/mob/living/basic/seedling/death(gibbed) + . = ..() + if(isnull(held_can)) + return + held_can.forceMove(drop_location()) + +/mob/living/basic/seedling/Destroy() + QDEL_NULL(held_can) + return ..() + +/mob/living/basic/seedling/meanie + maxHealth = 400 + health = 400 + faction = list(FACTION_JUNGLE) + ai_controller = /datum/ai_controller/basic_controller/seedling/meanie + seedling_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/follow, + /datum/pet_command/point_targetting/attack, + /datum/pet_command/point_targetting/use_ability/solarbeam, + /datum/pet_command/point_targetting/use_ability/rapidseeds, + ) + +//abilities +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling + name = "Solar Energy" + button_icon = 'icons/obj/weapons/guns/projectiles.dmi' + button_icon_state = "seedling" + desc = "Fire small beams of solar energy." + cooldown_time = 10 SECONDS + projectile_type = /obj/projectile/seedling + default_projectile_spread = 10 + shot_count = 10 + shot_delay = 0.2 SECONDS + melee_cooldown_time = 0 SECONDS + shared_cooldown = NONE + ///how long we must charge up before firing off + var/charge_up_timer = 3 SECONDS + ///is the owner of this ability a seedling? + var/is_seedling = FALSE + +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/Grant(mob/grant_to) + . = ..() + if(isnull(owner)) + return + is_seedling = istype(owner, /mob/living/basic/seedling) + +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/IsAvailable(feedback) + . = ..() + if(!.) + return FALSE + if(!is_seedling) + return TRUE + var/mob/living/basic/seedling/seed_owner = owner + if(seed_owner.combatant_state != SEEDLING_STATE_NEUTRAL) + if(feedback) + seed_owner.balloon_alert(seed_owner, "charging!") + return FALSE + return TRUE + +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/Activate(atom/target) + if(is_seedling) + var/mob/living/basic/seedling/seed_owner = owner + seed_owner.change_combatant_state(state = SEEDLING_STATE_WARMUP) + addtimer(CALLBACK(src, PROC_REF(attack_sequence), owner, target), charge_up_timer) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/attack_sequence(mob/living/firer, atom/target) + if(is_seedling) + var/mob/living/basic/seedling/seed_owner = owner + seed_owner.change_combatant_state(state = SEEDLING_STATE_ACTIVE) + addtimer(CALLBACK(seed_owner, TYPE_PROC_REF(/mob/living/basic/seedling, change_combatant_state), SEEDLING_STATE_NEUTRAL), 4 SECONDS) + + return ..() + + +/datum/action/cooldown/mob_cooldown/solarbeam + name = "Solar Beam" + button_icon = 'icons/effects/beam.dmi' + button_icon_state = "solar_beam" + desc = "Concentrate the power of the sun onto your target!" + cooldown_time = 30 SECONDS + shared_cooldown = NONE + ///how long will it take for us to charge up the beam + var/beam_charge_up = 3 SECONDS + ///is the owner of this ability a seedling? + var/is_seedling = FALSE + +/datum/action/cooldown/mob_cooldown/solarbeam/Grant(mob/grant_to) + . = ..() + if(isnull(owner)) + return + is_seedling = istype(owner, /mob/living/basic/seedling) + +/datum/action/cooldown/mob_cooldown/solarbeam/IsAvailable(feedback) + . = ..() + if(!.) + return FALSE + if(!is_seedling) + return TRUE + var/mob/living/basic/seedling/seed_owner = owner + if(seed_owner.combatant_state != SEEDLING_STATE_NEUTRAL) + if(feedback) + seed_owner.balloon_alert(seed_owner, "charging!") + return FALSE + return TRUE + +/datum/action/cooldown/mob_cooldown/solarbeam/Activate(atom/target) + if(is_seedling) + var/mob/living/basic/seedling/seed_owner = owner + seed_owner.change_combatant_state(state = SEEDLING_STATE_WARMUP) + + var/turf/target_turf = get_turf(target) + playsound(owner, 'sound/effects/seedling_chargeup.ogg', 100, FALSE) + + var/obj/effect/temp_visual/solarbeam_killsat/owner_beam = new(get_turf(owner)) + animate(owner_beam, transform = matrix().Scale(1, 32), alpha = 255, time = beam_charge_up) + + var/obj/effect/temp_visual/solarbeam_killsat/target_beam = new(target_turf) + animate(target_beam, transform = matrix().Scale(2, 1), alpha = 255, time = beam_charge_up) + + addtimer(CALLBACK(src, PROC_REF(launch_beam), owner, target_turf), beam_charge_up) + StartCooldown() + return TRUE + +///the solarbeam will damage people, otherwise it will heal plants +/datum/action/cooldown/mob_cooldown/solarbeam/proc/launch_beam(mob/living/firer, turf/target_turf) + for(var/atom/target_atom as anything in target_turf) + + if(istype(target_atom, /obj/machinery/hydroponics)) + var/obj/machinery/hydroponics/hydro = target_atom + hydro.adjust_plant_health(10) + new /obj/effect/temp_visual/heal(target_turf, COLOR_HEALING_CYAN) + + if(!isliving(target_atom)) + continue + + var/mob/living/living_target = target_atom + living_target.adjust_fire_stacks(0.2) + living_target.ignite_mob() + living_target.adjustFireLoss(30) + + playsound(target_turf, 'sound/magic/lightningbolt.ogg', 50, TRUE) + if(!is_seedling) + return + var/mob/living/basic/seedling/seed_firer = firer + seed_firer.change_combatant_state(state = SEEDLING_STATE_NEUTRAL) + +#undef SEEDLING_STATE_NEUTRAL +#undef SEEDLING_STATE_WARMUP +#undef SEEDLING_STATE_ACTIVE diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm b/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm new file mode 100644 index 00000000000..4d67a71d4d4 --- /dev/null +++ b/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm @@ -0,0 +1,178 @@ +/datum/ai_controller/basic_controller/seedling + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends, + BB_WEEDLEVEL_THRESHOLD = 3, + BB_WATERLEVEL_THRESHOLD = 90, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/find_and_hunt_target/watering_can, + /datum/ai_planning_subtree/find_and_hunt_target/fill_watercan, + /datum/ai_planning_subtree/find_and_hunt_target/treat_hydroplants, + /datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants, + ) + +/datum/ai_planning_subtree/find_and_hunt_target/watering_can + target_key = BB_WATERCAN_TARGET + finding_behavior = /datum/ai_behavior/find_hunt_target + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target + hunt_targets = list(/obj/item/reagent_containers/cup/watering_can) + hunt_range = 7 + +/datum/ai_planning_subtree/find_and_hunt_target/watering_can/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn) //we already have what we came for! + return + return ..() + +/datum/ai_planning_subtree/find_and_hunt_target/treat_hydroplants + target_key = BB_HYDROPLANT_TARGET + finding_behavior = /datum/ai_behavior/find_and_set/treatable_hydro + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/treat_hydroplant + hunt_targets = list(/obj/machinery/hydroponics) + hunt_range = 7 + +/datum/ai_behavior/find_and_set/treatable_hydro + +/datum/ai_behavior/find_and_set/treatable_hydro/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/list/possible_trays = list() + var/mob/living/living_pawn = controller.pawn + var/waterlevel_threshold = controller.blackboard[BB_WATERLEVEL_THRESHOLD] + var/weedlevel_threshold = controller.blackboard[BB_WEEDLEVEL_THRESHOLD] + var/watering_can = locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn + + for(var/obj/machinery/hydroponics/hydro in oview(search_range, controller.pawn)) + if(isnull(hydro.myseed)) + continue + if(hydro.waterlevel < waterlevel_threshold && watering_can) + possible_trays += hydro + continue + if(hydro.weedlevel > weedlevel_threshold || hydro.plant_status == HYDROTRAY_PLANT_DEAD) + possible_trays += hydro + continue + + if(possible_trays.len) + return pick(possible_trays) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/treat_hydroplant + hunt_cooldown = 2 SECONDS + always_reset_target = TRUE + +/datum/ai_behavior/hunt_target/unarmed_attack_target/treat_hydroplant/target_caught(mob/living/living_pawn, obj/machinery/hydroponics/hydro_target) + if(QDELETED(hydro_target) || QDELETED(hydro_target.myseed)) + return + + if(hydro_target.plant_status == HYDROTRAY_PLANT_DEAD) + living_pawn.manual_emote("weeps...") //weep over the dead plants + return ..() + + +/datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants + target_key = BB_BEAMABLE_HYDROPLANT_TARGET + finding_behavior = /datum/ai_behavior/find_and_set/beamable_hydroplants + hunting_behavior = /datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam + hunt_targets = list(/obj/machinery/hydroponics) + hunt_range = 7 + +/datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/datum/action/cooldown/solar_ability = controller.blackboard[BB_SOLARBEAM_ABILITY] + if(QDELETED(solar_ability) || !solar_ability.IsAvailable()) + return + return ..() + +/datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + required_distance = 2 + action_cooldown = 1 MINUTES + ability_key = BB_SOLARBEAM_ABILITY + +/datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam/setup(datum/ai_controller/controller, target_key, ability_key) + . = ..() + var/obj/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/find_and_set/beamable_hydroplants/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/list/possible_trays = list() + + for(var/obj/machinery/hydroponics/hydro in oview(search_range, controller.pawn)) + if(isnull(hydro.myseed)) + continue + if(hydro.plant_health < hydro.myseed.endurance) + possible_trays += hydro + + if(possible_trays.len) + return pick(possible_trays) + +/datum/ai_planning_subtree/find_and_hunt_target/fill_watercan + target_key = BB_LOW_PRIORITY_HUNTING_TARGET + finding_behavior = /datum/ai_behavior/find_hunt_target/suitable_dispenser + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/water_source + hunt_targets = list(/obj/structure/sink, /obj/structure/reagent_dispensers) + hunt_range = 7 + +/datum/ai_planning_subtree/find_and_hunt_target/fill_watercan/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + var/obj/item/reagent_containers/can = locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn + + if(isnull(can)) + return + if(locate(/datum/reagent/water) in can.reagents.reagent_list) + return + + return ..() + +/datum/ai_behavior/find_hunt_target/suitable_dispenser + +/datum/ai_behavior/find_hunt_target/suitable_dispenser/valid_dinner(mob/living/source, obj/structure/water_source, radius) + if(!(locate(/datum/reagent/water) in water_source.reagents.reagent_list)) + return FALSE + + return can_see(source, water_source, radius) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/water_source + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + hunt_cooldown = 5 SECONDS + +/datum/ai_controller/basic_controller/seedling/meanie + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends, + ) + planning_subtrees = list( + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability/seedling_rapid, + /datum/ai_planning_subtree/targeted_mob_ability/solarbeam, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/datum/ai_planning_subtree/targeted_mob_ability/seedling_rapid + ability_key = BB_RAPIDSEEDS_ABILITY + finish_planning = FALSE + +/datum/ai_planning_subtree/targeted_mob_ability/solarbeam + ability_key = BB_SOLARBEAM_ABILITY + finish_planning = FALSE + +///pet commands +/datum/pet_command/point_targetting/use_ability/solarbeam + command_name = "Launch solarbeam" + command_desc = "Command your pet to launch a solarbeam at your target!" + radial_icon = 'icons/effects/beam.dmi' + radial_icon_state = "solar_beam" + speech_commands = list("beam", "solar") + pet_ability_key = BB_SOLARBEAM_ABILITY + +/datum/pet_command/point_targetting/use_ability/rapidseeds + command_name = "Rapid seeds" + command_desc = "Command your pet to launch a volley of seeds at your target!" + radial_icon = 'icons/obj/weapons/guns/projectiles.dmi' + radial_icon_state = "seedling" + speech_commands = list("rapid", "seeds", "volley") + pet_ability_key = BB_RAPIDSEEDS_ABILITY diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm b/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm new file mode 100644 index 00000000000..726b8105933 --- /dev/null +++ b/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm @@ -0,0 +1,32 @@ +/obj/projectile/seedling + name = "solar energy" + icon_state = "seedling" + damage = 10 + damage_type = BURN + light_range = 2 + armor_flag = ENERGY + light_color = LIGHT_COLOR_DIM_YELLOW + speed = 1.6 + hitsound = 'sound/weapons/sear.ogg' + hitsound_wall = 'sound/weapons/effects/searwall.ogg' + nondirectional_sprite = TRUE + +/obj/projectile/seedling/on_hit(atom/target) + if(!isliving(target)) + return ..() + + var/mob/living/living_target = target + if(FACTION_JUNGLE in living_target.faction) + return + + return ..() + +/obj/effect/temp_visual/solarbeam_killsat + name = "beam of solar energy" + icon_state = "solar_beam" + icon = 'icons/effects/beam.dmi' + plane = LIGHTING_PLANE + layer = LIGHTING_PRIMARY_LAYER + duration = 3 SECONDS + alpha = 200 + randomdir = FALSE diff --git a/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm new file mode 100644 index 00000000000..45bfd74d23b --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm @@ -0,0 +1,88 @@ +/// Watchers' ground-dwelling cousins, they shoot at you until they get into melee and absorb laser fire to power up. +/mob/living/basic/mining/basilisk + name = "basilisk" + desc = "A territorial beast, covered in a diamond shell which absorbs heat. Its stare causes victims to freeze from the inside." + icon_state = "basilisk" + icon_living = "basilisk" + icon_dead = "basilisk_dead" + speak_emote = list("chimes") + damage_coeff = list(BRUTE = 1, BURN = 0.1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + speed = 20 + maxHealth = 200 + health = 200 + obj_damage = 60 + melee_damage_lower = 12 + melee_damage_upper = 12 + attack_verb_continuous = "bites into" + attack_verb_simple = "bite into" + throw_blocked_message = "bounces off the shell of" + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + ai_controller = /datum/ai_controller/basic_controller/basilisk + butcher_results = list( + /obj/item/stack/sheet/bone = 1, + /obj/item/stack/ore/diamond = 2, + /obj/item/stack/sheet/sinew = 2, + ) + /// The component we use for making ranged attacks + var/datum/component/ranged_attacks/ranged_attacks + +/mob/living/basic/mining/basilisk/Initialize(mapload) + . = ..() + AddComponent(/datum/component/basic_mob_attack_telegraph) + ranged_attacks = AddComponent(/datum/component/ranged_attacks, projectile_type = /obj/projectile/temp/watcher, projectile_sound = 'sound/weapons/pierce.ogg') + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(check_lava)) + +/mob/living/basic/mining/basilisk/Destroy() + QDEL_NULL(ranged_attacks) + return ..() + +/mob/living/basic/mining/basilisk/welder_act(mob/living/user, obj/item/tool) + . = ..() + heat_up() // Who would do this? + +/mob/living/basic/mining/basilisk/bullet_act(obj/projectile/bullet, def_zone, piercing_hit) + . = ..() + if (istype(bullet, /obj/projectile/temp)) + var/obj/projectile/temp/heat_bullet = bullet + if (heat_bullet.temperature < 0) + return + heat_up() + return + + if (bullet.damage == 0 || bullet.damage_type != BURN) + return + heat_up() + +/// Are we standing in lava? +/mob/living/basic/mining/basilisk/proc/check_lava() + SIGNAL_HANDLER + var/turf/open/lava/entered_lava = loc + if (!islava(entered_lava) || entered_lava.immunity_trait != TRAIT_LAVA_IMMUNE) + return + heat_up() + +/// We got hit by something hot, go into heat mode +/mob/living/basic/mining/basilisk/proc/heat_up() + if (stat != CONSCIOUS || has_status_effect(/datum/status_effect/basilisk_overheat)) + return + apply_status_effect(/datum/status_effect/basilisk_overheat) + +/// Change what kind of beam we fire +/mob/living/basic/mining/basilisk/proc/set_projectile_type(projectile_type) + ranged_attacks.projectile_type = projectile_type + +/datum/ai_controller/basic_controller/basilisk + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_AGGRO_RANGE = 5, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/ranged_skirmish, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) diff --git a/code/modules/mob/living/basic/lavaland/basilisk/basilisk_overheat.dm b/code/modules/mob/living/basic/lavaland/basilisk/basilisk_overheat.dm new file mode 100644 index 00000000000..c0b49fbdc61 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/basilisk/basilisk_overheat.dm @@ -0,0 +1,76 @@ +/// Status effect gained by basilisks when they touch something hot +/datum/status_effect/basilisk_overheat + id = "basilisk_overheat" + duration = 3 MINUTES + /// Things which will chill us out if we get hit by them + var/static/list/chilling_reagents = list( + /datum/reagent/medicine/cryoxadone, + /datum/reagent/firefighting_foam, + /datum/reagent/consumable/frostoil, + /datum/reagent/consumable/ice, + /datum/reagent/water, + ) + +/datum/status_effect/basilisk_overheat/on_apply() + . = ..() + if (!. || !istype(owner, /mob/living/basic/mining/basilisk) || owner.stat != CONSCIOUS) + return FALSE + var/mob/living/basic/mining/basilisk/hot_stuff = owner + hot_stuff.visible_message(span_warning("[hot_stuff] is getting fired up!")) + hot_stuff.fully_heal() + hot_stuff.icon_living = "basilisk_alert" + hot_stuff.icon_state = "basilisk_alert" + hot_stuff.update_appearance(UPDATE_ICON_STATE) + hot_stuff.add_movespeed_modifier(/datum/movespeed_modifier/basilisk_overheat) + hot_stuff.set_projectile_type(/obj/projectile/basilisk_hot) + + RegisterSignal(hot_stuff, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + RegisterSignal(hot_stuff, COMSIG_ATOM_EXPOSE_REAGENTS, PROC_REF(on_splashed)) + RegisterSignal(hot_stuff, COMSIG_ATOM_BULLET_ACT, PROC_REF(on_shot)) + +/datum/status_effect/basilisk_overheat/on_remove() + . = ..() + var/mob/living/basic/mining/basilisk/hot_stuff = owner + hot_stuff.icon_living = "basilisk" + hot_stuff.icon_state = "basilisk" + hot_stuff.set_projectile_type(/obj/projectile/temp/watcher) + + hot_stuff.update_appearance(UPDATE_ICON_STATE) + hot_stuff.remove_movespeed_modifier(/datum/movespeed_modifier/basilisk_overheat) + UnregisterSignal(hot_stuff, list(COMSIG_LIVING_DEATH, COMSIG_ATOM_EXPOSE_REAGENTS, COMSIG_ATOM_BULLET_ACT)) + + if (hot_stuff.stat != CONSCIOUS) + return + hot_stuff.visible_message(span_notice("[hot_stuff] seems to have cooled down.")) + var/obj/effect/particle_effect/fluid/smoke/poof = new(get_turf(hot_stuff)) + poof.lifetime = 2 SECONDS + +/// Cool down if we die +/datum/status_effect/basilisk_overheat/proc/on_death() + SIGNAL_HANDLER + qdel(src) + +/// Cool down if splashed with water +/datum/status_effect/basilisk_overheat/proc/on_splashed(atom/source, list/reagents, datum/reagents/source_reagents, methods, volume_modifier, show_message) + SIGNAL_HANDLER + if(!(methods & (TOUCH|VAPOR))) + return + for (var/datum/reagent in reagents) + if (!is_type_in_list(reagent, chilling_reagents)) + continue + qdel(src) + return + +/// Cool down if shot with a cryo beam +/datum/status_effect/basilisk_overheat/proc/on_shot(datum/source, obj/projectile/temp/cryo_shot) + SIGNAL_HANDLER + if (!istype(cryo_shot) || cryo_shot.temperature > 0) + return + qdel(src) + +/// Projectile basilisks use when hot +/obj/projectile/basilisk_hot + name = "energy blast" + icon_state = "chronobolt" + damage = 40 + damage_type = BRUTE diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm new file mode 100644 index 00000000000..29a3de60029 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm @@ -0,0 +1,126 @@ +/// Fires a bloody beam. Brimdemon Blast! +/datum/action/cooldown/mob_cooldown/brimbeam + name = "brimstone blast" + desc = "Unleash a barrage of infernal energies in the targeted direction." + button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + button_icon_state = "brimdemon_firing" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + click_to_activate = TRUE + cooldown_time = 5 SECONDS + melee_cooldown_time = 0 + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED + /// How far does our beam go? + var/beam_range = 10 + /// How long does our beam last? + var/beam_duration = 2 SECONDS + /// How long do we wind up before firing? + var/charge_duration = 1 SECONDS + /// Overlay we show when we're about to fire + var/static/image/direction_overlay = image('icons/mob/simple/lavaland/lavaland_monsters.dmi', "brimdemon_telegraph_dir") + /// A list of all the beam parts. + var/list/beam_parts = list() + +/datum/action/cooldown/mob_cooldown/brimbeam/Destroy() + extinguish_laser() + return ..() + +/datum/action/cooldown/mob_cooldown/brimbeam/Activate(atom/target) + StartCooldown(360 SECONDS) + + owner.face_atom(target) + owner.move_resist = MOVE_FORCE_VERY_STRONG + owner.add_overlay(direction_overlay) + owner.balloon_alert_to_viewers("charging...") + + var/fully_charged = do_after(owner, delay = charge_duration, target = owner) + owner.cut_overlay(direction_overlay) + if (!fully_charged) + StartCooldown() + return TRUE + + if (!fire_laser()) + var/static/list/fail_emotes = list("coughs.", "wheezes.", "belches out a puff of black smoke.") + owner.manual_emote(pick(fail_emotes)) + StartCooldown() + return TRUE + + do_after(owner, delay = beam_duration, target = owner) + extinguish_laser() + StartCooldown() + return TRUE + +/// Create a laser in the direction we are facing +/datum/action/cooldown/mob_cooldown/brimbeam/proc/fire_laser() + owner.visible_message(span_danger("[owner] fires a brimbeam!")) + playsound(owner, 'sound/creatures/brimdemon.ogg', 150, FALSE, 0, 3) + var/turf/target_turf = get_ranged_target_turf(owner, owner.dir, beam_range) + var/turf/origin_turf = get_turf(owner) + var/list/affected_turfs = get_line(origin_turf, target_turf) - origin_turf + for(var/turf/affected_turf in affected_turfs) + if(affected_turf.opacity) + break + var/blocked = FALSE + for(var/obj/potential_block in affected_turf.contents) + if(potential_block.opacity) + blocked = TRUE + break + if(blocked) + break + var/atom/new_brimbeam = new /obj/effect/brimbeam(affected_turf) + new_brimbeam.dir = owner.dir + beam_parts += new_brimbeam + for(var/mob/living/hit_mob in affected_turf.contents) + hit_mob.apply_damage(damage = 25, damagetype = BURN) + to_chat(hit_mob, span_userdanger("You're blasted by [owner]'s brimbeam!")) + RegisterSignal(new_brimbeam, COMSIG_QDELETING, PROC_REF(extinguish_laser)) // In case idk a singularity eats it or something + if(!length(beam_parts)) + return FALSE + var/atom/last_brimbeam = beam_parts[length(beam_parts)] + last_brimbeam.icon_state = "brimbeam_end" + var/atom/first_brimbeam = beam_parts[1] + first_brimbeam.icon_state = "brimbeam_start" + return TRUE + +/// Get rid of our laser when we are done with it +/datum/action/cooldown/mob_cooldown/brimbeam/proc/extinguish_laser() + if(!length(beam_parts)) + return FALSE + owner.move_resist = initial(owner.move_resist) + for(var/obj/effect/brimbeam/beam in beam_parts) + beam.disperse() + beam_parts = list() + +/// Segments of the actual beam, these hurt if you stand in them +/obj/effect/brimbeam + name = "brimbeam" + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "brimbeam_mid" + layer = ABOVE_MOB_LAYER + plane = ABOVE_GAME_PLANE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + light_color = LIGHT_COLOR_BLOOD_MAGIC + light_power = 3 + light_range = 2 + +/obj/effect/brimbeam/Initialize(mapload) + . = ..() + START_PROCESSING(SSfastprocess, src) + +/obj/effect/brimbeam/Destroy() + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/obj/effect/brimbeam/process() + for(var/mob/living/hit_mob in get_turf(src)) + damage(hit_mob) + +/// Hurt the passed mob +/obj/effect/brimbeam/proc/damage(mob/living/hit_mob) + hit_mob.apply_damage(damage = 5, damagetype = BURN) + to_chat(hit_mob, span_danger("You're damaged by [src]!")) + +/// Disappear +/obj/effect/brimbeam/proc/disperse() + animate(src, time = 0.5 SECONDS, alpha = 0) + QDEL_IN(src, 0.5 SECONDS) diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.dm new file mode 100644 index 00000000000..2c22977d9c6 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.dm @@ -0,0 +1,87 @@ +/// Lavaland mob which tries to line up with its target and fire a laser +/mob/living/basic/mining/brimdemon + name = "brimdemon" + desc = "A volatile creature resembling an enormous horned skull. Its response to almost any stimulus is to unleash a beam of infernal energy." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "brimdemon" + icon_living = "brimdemon" + icon_dead = "brimdemon_dead" + speed = 3 + maxHealth = 250 + health = 250 + friendly_verb_continuous = "scratches at" + friendly_verb_simple = "scratch at" + speak_emote = list("cackles") + melee_damage_lower = 7.5 + melee_damage_upper = 7.5 + attack_sound = 'sound/weapons/bite.ogg' + melee_attack_cooldown = 0.6 SECONDS + attack_vis_effect = ATTACK_EFFECT_BITE + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + death_message = "wails as infernal energy escapes from its wounds, leaving it an empty husk." + death_sound = 'sound/magic/demon_dies.ogg' + light_color = LIGHT_COLOR_BLOOD_MAGIC + light_power = 5 + light_range = 1.4 + + ai_controller = /datum/ai_controller/basic_controller/brimdemon + + crusher_loot = /obj/item/crusher_trophy/brimdemon_fang + butcher_results = list( + /obj/item/food/meat/slab = 2, + /obj/effect/decal/cleanable/brimdust = 1, + /obj/item/organ/internal/monster_core/brimdust_sac = 1, + ) + /// How we get blasting + var/datum/action/cooldown/mob_cooldown/brimbeam/beam + +/mob/living/basic/mining/brimdemon/Initialize(mapload) + . = ..() + AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) + beam = new(src) + beam.Grant(src) + ai_controller.set_blackboard_key(BB_TARGETTED_ACTION, beam) + +/mob/living/basic/mining/brimdemon/Destroy() + QDEL_NULL(beam) + return ..() + +/mob/living/basic/mining/brimdemon/RangedAttack(atom/target, modifiers) + beam.Trigger(target = target) + +/mob/living/basic/mining/brimdemon/death(gibbed) + . = ..() + if (gibbed) + return + var/obj/effect/temp_visual/brim_burst/bang = new(loc) + forceMove(bang) + +/// Show a funny animation before doing an explosion +/obj/effect/temp_visual/brim_burst + name = "bursting brimdemon" + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "brimdemon_dead" + duration = 1.9 SECONDS + +/obj/effect/temp_visual/brim_burst/Initialize(mapload) + . = ..() + addtimer(CALLBACK(src, PROC_REF(bang)), duration - (1 DECISECONDS), TIMER_DELETE_ME) + animate(src, color = "#ff8888", transform = matrix().Scale(1.1), time = 0.7 SECONDS) + animate(color = "#ffffff", transform = matrix(), time = 0.2 SECONDS) + animate(color = "#ff4444", transform = matrix().Scale(1.3), time = 0.5 SECONDS) + animate(color = "#ffffff", transform = matrix(), time = 0.2 SECONDS) + animate(color = "#ff0000", transform = matrix().Scale(1.5), time = 0.3 SECONDS) + +/// Make an explosion +/obj/effect/temp_visual/brim_burst/proc/bang() + var/turf/origin_turf = get_turf(src) + playsound(origin_turf, 'sound/effects/pop_expl.ogg', 50) + new /obj/effect/temp_visual/explosion/fast(origin_turf) + var/list/possible_targets = range(1, origin_turf) + for(var/mob/living/target in possible_targets) + var/armor = target.run_armor_check(attack_flag = BOMB) + target.apply_damage(20, damagetype = BURN, blocked = armor, spread_damage = TRUE) + + for (var/atom/movable/thing as anything in contents) + thing.forceMove(loc) diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm new file mode 100644 index 00000000000..e5d793fa150 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm @@ -0,0 +1,50 @@ +/** + * Slap someone who is nearby, line up with target, blast with a beam + */ +/datum/ai_controller/basic_controller/brimdemon + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/brimdemon, + ) + + ai_traits = PAUSE_DURING_DO_AFTER + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk/no_target + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic, + /datum/ai_planning_subtree/move_to_cardinal/brimdemon, + /datum/ai_planning_subtree/targeted_mob_ability/brimbeam, + ) + +/datum/targetting_datum/basic/brimdemon + stat_attack = HARD_CRIT + +/datum/ai_planning_subtree/move_to_cardinal/brimdemon + move_behaviour = /datum/ai_behavior/move_to_cardinal/brimdemon + +/datum/ai_behavior/move_to_cardinal/brimdemon + minimum_distance = 2 + +/datum/ai_behavior/move_to_cardinal/brimdemon/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + if (!succeeded) + return + var/mob/living/target = controller.blackboard[target_key] + var/datum/action/cooldown/ability = controller.blackboard[BB_TARGETTED_ACTION] + if(!ability?.IsAvailable()) + return + ability.InterceptClickOn(caller = controller.pawn, target = target) + +/datum/ai_planning_subtree/targeted_mob_ability/brimbeam + use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/brimbeam + +/datum/ai_behavior/targeted_mob_ability/brimbeam + /// Don't shoot if too far away + var/max_target_distance = 9 + +/datum/ai_behavior/targeted_mob_ability/brimbeam/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) + var/mob/living/target = controller.blackboard[target_key] + if (QDELETED(target) || !(get_dir(controller.pawn, target) in GLOB.cardinals) || get_dist(controller.pawn, target) > max_target_distance) + finish_action(controller, succeeded = FALSE, ability_key = ability_key, target_key = target_key) + return + return ..() diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_loot.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_loot.dm new file mode 100644 index 00000000000..9a45ed99e1c --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_loot.dm @@ -0,0 +1,56 @@ +/// Brimdemon crusher trophy, it... makes a funny sound? +/obj/item/crusher_trophy/brimdemon_fang + name = "brimdemon's fang" + icon_state = "brimdemon_fang" + desc = "A fang from a brimdemon's corpse." + denied_type = /obj/item/crusher_trophy/brimdemon_fang + /// Cartoon punching vfx + var/static/list/comic_phrases = list("BOOM", "BANG", "KABLOW", "KAPOW", "OUCH", "BAM", "KAPOW", "WHAM", "POW", "KABOOM") + +/obj/item/crusher_trophy/brimdemon_fang/effect_desc() + return "mark detonation creates visual and audiosensory effects on the target" + +/obj/item/crusher_trophy/brimdemon_fang/on_mark_detonation(mob/living/target, mob/living/user) + target.balloon_alert_to_viewers("[pick(comic_phrases)]!") + playsound(target, 'sound/lavaland/brimdemon_crush.ogg', 100) + +/// Reagent pool left by dying brimdemon +/obj/effect/decal/cleanable/brimdust + name = "brimdust" + desc = "Dust from a brimdemon. It is considered valuable for its' botanical abilities." + icon_state = "brimdust" + icon = 'icons/obj/mining.dmi' + layer = FLOOR_CLEAN_LAYER + mergeable_decal = FALSE + +/obj/effect/decal/cleanable/brimdust/Initialize(mapload) + . = ..() + reagents.add_reagent(/datum/reagent/brimdust, 15) + +/// Ashwalker ore sensor crafted from brimdemon ash +/obj/item/ore_sensor + name = "ore sensor" + desc = "Using demonic frequencies, this ear-mounted tool detects ores in the nearby terrain." + icon_state = "oresensor" + icon = 'icons/obj/mining.dmi' + slot_flags = ITEM_SLOT_EARS + var/range = 5 + var/cooldown = 4 SECONDS //between the standard and the advanced ore scanner in strength + COOLDOWN_DECLARE(ore_sensing_cooldown) + +/obj/item/ore_sensor/equipped(mob/user, slot, initial) + . = ..() + if(slot & ITEM_SLOT_EARS) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + +/obj/item/ore_sensor/dropped(mob/user, silent) + . = ..() + STOP_PROCESSING(SSobj, src) + +/obj/item/ore_sensor/process(seconds_per_tick) + if(!COOLDOWN_FINISHED(src, ore_sensing_cooldown)) + return + COOLDOWN_START(src, ore_sensing_cooldown, cooldown) + mineral_scan_pulse(get_turf(src), range) diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm new file mode 100644 index 00000000000..58b0e1bbbdb --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm @@ -0,0 +1,191 @@ +//An ore-devouring but easily scared creature +/mob/living/basic/mining/goldgrub + name = "goldgrub" + desc = "A worm that grows fat from eating everything in its sight. Seems to enjoy precious metals and other shiny things, hence the name." + icon = 'icons/mob/simple/lavaland/lavaland_monsters_wide.dmi' + icon_state = "goldgrub" + icon_living = "goldgrub" + icon_dead = "goldgrub_dead" + icon_gib = "syndicate_gib" + speed = 5 + pixel_x = -12 + base_pixel_x = -12 + mob_biotypes = MOB_ORGANIC|MOB_BEAST + friendly_verb_continuous = "harmlessly rolls into" + friendly_verb_simple = "harmlessly roll into" + maxHealth = 45 + health = 45 + melee_damage_lower = 0 + melee_damage_upper = 0 + attack_verb_continuous = "barrels into" + attack_verb_simple = "barrel into" + attack_sound = 'sound/weapons/punch1.ogg' + combat_mode = FALSE + speak_emote = list("screeches") + death_message = "stops moving as green liquid oozes from the carcass!" + status_flags = CANPUSH + gold_core_spawnable = HOSTILE_SPAWN + ai_controller = /datum/ai_controller/basic_controller/goldgrub + ///can this mob lay eggs + var/can_lay_eggs = TRUE + ///can we tame this mob + var/can_tame = TRUE + //pet commands when we tame the grub + var/list/pet_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/grub_spit, + /datum/pet_command/follow, + /datum/pet_command/point_targetting/fetch, + ) + +/mob/living/basic/mining/goldgrub/Initialize(mapload) + . = ..() + + if(mapload) + generate_loot() + else + can_lay_eggs = FALSE + + var/datum/action/cooldown/mob_cooldown/spit_ore/spit = new(src) + var/datum/action/cooldown/mob_cooldown/burrow/burrow = new(src) + spit.Grant(src) + burrow.Grant(src) + ai_controller.set_blackboard_key(BB_SPIT_ABILITY, spit) + ai_controller.set_blackboard_key(BB_BURROW_ABILITY, burrow) + AddElement(/datum/element/wall_smasher) + AddComponent(/datum/component/ai_listen_to_weather) + AddComponent(\ + /datum/component/appearance_on_aggro,\ + overlay_icon = 'icons/mob/simple/lavaland/lavaland_monsters_wide.dmi',\ + overlay_state = "goldgrub_alert",\ + ) + if(can_tame) + make_tameable() + if(can_lay_eggs) + make_egg_layer() + +/mob/living/basic/mining/goldgrub/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) + . = ..() + if(!.) + return + + if(!proximity_flag) + return + + if(istype(attack_target, /obj/item/stack/ore)) + consume_ore(attack_target) + +/mob/living/basic/mining/goldgrub/bullet_act(obj/projectile/bullet) + if(stat == DEAD) + return BULLET_ACT_FORCE_PIERCE + + visible_message(span_danger("The [bullet.name] is repelled by [src]'s girth!")) + return BULLET_ACT_BLOCK + +/mob/living/basic/mining/goldgrub/proc/barf_contents(gibbed) + playsound(src, 'sound/effects/splat.ogg', 50, TRUE) + for(var/obj/item/ore as anything in src) + ore.forceMove(loc) + if(!gibbed) + visible_message(span_danger("[src] spits out its consumed ores!")) + +/mob/living/basic/mining/goldgrub/proc/generate_loot() + var/loot_amount = rand(1,3) + var/list/weight_lootdrops = list( + /obj/item/stack/ore/silver = 4, + /obj/item/stack/ore/gold = 3, + /obj/item/stack/ore/uranium = 3, + /obj/item/stack/ore/diamond = 1, + ) + for(var/i in 1 to loot_amount) + var/picked_loot = pick_weight(weight_lootdrops) + new picked_loot(src) + +/mob/living/basic/mining/goldgrub/death(gibbed) + barf_contents(gibbed) + return ..() + +/mob/living/basic/mining/goldgrub/proc/make_tameable() + AddComponent(\ + /datum/component/tameable,\ + food_types = list(/obj/item/stack/ore),\ + tame_chance = 25,\ + bonus_tame_chance = 5,\ + after_tame = CALLBACK(src, PROC_REF(tame_grub)),\ + ) + +/mob/living/basic/mining/goldgrub/proc/tame_grub() + new /obj/effect/temp_visual/heart(src.loc) + AddElement(/datum/element/ridable, /datum/component/riding/creature/goldgrub) + AddComponent(/datum/component/obeys_commands, pet_commands) + +/mob/living/basic/mining/goldgrub/proc/make_egg_layer() + AddComponent(\ + /datum/component/egg_layer,\ + /obj/item/food/egg/green/grub_egg,\ + list(/obj/item/stack/ore/bluespace_crystal),\ + lay_messages = EGG_LAYING_MESSAGES,\ + eggs_left = 0,\ + eggs_added_from_eating = 1,\ + max_eggs_held = 1,\ + ) + +/mob/living/basic/mining/goldgrub/proc/consume_ore(obj/item/target_ore) + playsound(src,'sound/items/eatfood.ogg', rand(10,50), TRUE) + target_ore.forceMove(src) + if(!can_lay_eggs) + return + if(!istype(target_ore, /obj/item/stack/ore/bluespace_crystal) || prob(60)) + return + new /obj/item/food/egg/green/grub_egg(get_turf(src)) + +/mob/living/basic/mining/goldgrub/baby + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + name = "goldgrub baby" + icon_state = "grub_baby" + icon_living = "grub_baby" + icon_dead = "grub_baby_dead" + pixel_x = 0 + base_pixel_x = 0 + speed = 3 + maxHealth = 25 + health = 25 + gold_core_spawnable = NO_SPAWN + can_tame = FALSE + can_lay_eggs = FALSE + ai_controller = /datum/ai_controller/basic_controller/babygrub + +/mob/living/basic/mining/goldgrub/baby/Initialize(mapload) + . = ..() + AddComponent(\ + /datum/component/growth_and_differentiation,\ + growth_time = 5 MINUTES,\ + growth_path = /mob/living/basic/mining/goldgrub,\ + growth_probability = 100,\ + lower_growth_value = 0.5,\ + upper_growth_value = 1,\ + signals_to_kill_on = list(COMSIG_MOB_CLIENT_LOGIN),\ + optional_checks = CALLBACK(src, PROC_REF(ready_to_grow)),\ + ) + +/mob/living/basic/mining/goldgrub/baby/proc/ready_to_grow() + return (stat == CONSCIOUS && !is_jaunting(src)) + +/obj/item/food/egg/green/grub_egg + name = "grub egg" + desc = "Covered in disgusting fluid." + + +/obj/item/food/egg/green/grub_egg/Initialize(mapload) + . = ..() + AddComponent(\ + /datum/component/fertile_egg,\ + embryo_type = /mob/living/basic/mining/goldgrub/baby,\ + minimum_growth_rate = 1,\ + maximum_growth_rate = 2,\ + total_growth_required = 100,\ + current_growth = 0,\ + location_allowlist = typecacheof(list(/turf)),\ + ) + diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_abilities.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_abilities.dm new file mode 100644 index 00000000000..fe8c8096e2c --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_abilities.dm @@ -0,0 +1,85 @@ +/datum/action/cooldown/mob_cooldown/spit_ore + name = "Spit Ore" + desc = "Vomit out all of your consumed ores." + click_to_activate = FALSE + cooldown_time = 5 SECONDS + +/datum/action/cooldown/mob_cooldown/spit_ore/IsAvailable(feedback) + if(is_jaunting(owner)) + if(feedback) + owner.balloon_alert(owner, "currently underground!") + return FALSE + + if(!length(owner.contents)) + if(feedback) + owner.balloon_alert(owner, "no ores to spit!") + return FALSE + return TRUE + +/datum/action/cooldown/mob_cooldown/spit_ore/Activate() + var/mob/living/basic/mining/goldgrub/grub_owner = owner + grub_owner.barf_contents() + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/burrow + name = "Burrow" + desc = "Burrow under soft ground, evading predators and increasing your speed." + cooldown_time = 7 SECONDS + click_to_activate = FALSE + +/datum/action/cooldown/mob_cooldown/burrow/IsAvailable(feedback) + . = ..() + if (!.) + return FALSE + var/turf/location = get_turf(owner) + + if(!isasteroidturf(location) && !ismineralturf(location)) + if(feedback) + owner.balloon_alert(owner, "available only on mining floor or wall!") + return FALSE + + return TRUE + +/datum/action/cooldown/mob_cooldown/burrow/Activate() + var/obj/effect/dummy/phased_mob/grub_burrow/holder = null + var/turf/current_loc = get_turf(owner) + + if(!do_after(owner, 3 SECONDS, target = current_loc)) + owner.balloon_alert(owner, "need to stay still!") + return + + if(get_turf(owner) != current_loc) + to_chat(owner, span_warning("Action cancelled, as you moved while reappearing.")) + return + + if(!is_jaunting(owner)) + owner.visible_message(span_danger("[owner] buries into the ground, vanishing from sight!")) + playsound(get_turf(owner), 'sound/effects/break_stone.ogg', 50, TRUE, -1) + holder = new /obj/effect/dummy/phased_mob/grub_burrow(current_loc, owner) + return TRUE + + holder = owner.loc + holder.eject_jaunter() + holder = null + owner.visible_message(span_danger("[owner] emerges from the ground!")) + + if(ismineralturf(current_loc)) + var/turf/closed/mineral/mineral_turf = current_loc + mineral_turf.gets_drilled(owner) + + playsound(current_loc, 'sound/effects/break_stone.ogg', 50, TRUE, -1) + StartCooldown() + return TRUE + +/obj/effect/dummy/phased_mob/grub_burrow + +/obj/effect/dummy/phased_mob/grub_burrow/phased_check(mob/living/user, direction) + . = ..() + + if(!.) + return + + if(!ismineralturf(.) && !isasteroidturf(.)) + to_chat(user, span_warning("You cannot dig through this floor!")) + return null diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm new file mode 100644 index 00000000000..58efaf1f81b --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm @@ -0,0 +1,176 @@ +/datum/ai_controller/basic_controller/goldgrub + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends, + BB_ORE_IGNORE_TYPES = list(/obj/item/stack/ore/iron, /obj/item/stack/ore/glass), + BB_BASIC_MOB_FLEEING = TRUE, + BB_STORM_APPROACHING = FALSE, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/dig_away_from_danger, + /datum/ai_planning_subtree/flee_target, + /datum/ai_planning_subtree/find_and_hunt_target/consume_ores, + /datum/ai_planning_subtree/find_and_hunt_target/baby_egg, + /datum/ai_planning_subtree/grub_mine, + ) + +/datum/ai_controller/basic_controller/babygrub + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_ORE_IGNORE_TYPES = list(/obj/item/stack/ore/glass), + BB_FIND_MOM_TYPES = list(/mob/living/basic/mining/goldgrub), + BB_IGNORE_MOM_TYPES = list(/mob/living/basic/mining/goldgrub/baby), + BB_BASIC_MOB_FLEEING = TRUE, + BB_STORM_APPROACHING = FALSE, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/dig_away_from_danger, + /datum/ai_planning_subtree/find_and_hunt_target/consume_ores, + /datum/ai_planning_subtree/flee_target, + /datum/ai_planning_subtree/look_for_adult, + ) + +///consume food! +/datum/ai_planning_subtree/find_and_hunt_target/consume_ores + target_key = BB_ORE_TARGET + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores + finding_behavior = /datum/ai_behavior/find_hunt_target/consume_ores + hunt_targets = list(/obj/item/stack/ore) + hunt_chance = 75 + hunt_range = 9 + +/datum/ai_behavior/find_hunt_target/consume_ores + +/datum/ai_behavior/find_hunt_target/consume_ores/valid_dinner(mob/living/basic/source, obj/item/stack/ore/target, radius) + var/list/forbidden_ore = source.ai_controller.blackboard[BB_ORE_IGNORE_TYPES] + + if(is_type_in_list(target, forbidden_ore)) + return FALSE + + if(target in source) + return FALSE + + var/obj/item/pet_target = source.ai_controller.blackboard[BB_CURRENT_PET_TARGET] + if(target == pet_target) //we are currently fetching this ore for master, dont eat it! + return FALSE + + return can_see(source, target, radius) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores + always_reset_target = TRUE + +///find our child's egg and pull it! +/datum/ai_planning_subtree/find_and_hunt_target/baby_egg + target_key = BB_LOW_PRIORITY_HUNTING_TARGET + hunting_behavior = /datum/ai_behavior/hunt_target/grub_egg + finding_behavior = /datum/ai_behavior/find_hunt_target + hunt_targets = list(/obj/item/food/egg/green/grub_egg) + hunt_chance = 75 + hunt_range = 9 + +/datum/ai_planning_subtree/find_and_hunt_target/baby_egg + +/datum/ai_planning_subtree/find_and_hunt_target/baby_egg/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(living_pawn.pulling) //we are already pulling something + return + return ..() + +/datum/ai_behavior/hunt_target/grub_egg + always_reset_target = TRUE + +/datum/ai_behavior/hunt_target/grub_egg/target_caught(mob/living/hunter, obj/item/target) + hunter.start_pulling(target) + + +///only dig away if storm is coming or if humans are around +/datum/ai_planning_subtree/dig_away_from_danger + +/datum/ai_planning_subtree/dig_away_from_danger/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/currently_underground = is_jaunting(controller.pawn) + var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] + + //dont do anything until the storm passes + if(currently_underground && storm_approaching) + return SUBTREE_RETURN_FINISH_PLANNING + + var/datum/action/cooldown/dig_ability = controller.blackboard[BB_BURROW_ABILITY] + + if(!dig_ability.IsAvailable()) + return + + var/has_target = controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) + + //a storm is coming or someone is nearby, its time to escape + if(currently_underground) + if(has_target) + return + controller.queue_behavior(/datum/ai_behavior/use_mob_ability/burrow, BB_BURROW_ABILITY) + return SUBTREE_RETURN_FINISH_PLANNING + if(storm_approaching || has_target) + controller.queue_behavior(/datum/ai_behavior/use_mob_ability/burrow, BB_BURROW_ABILITY) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/ai_behavior/use_mob_ability/burrow + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +///mine walls to look for food! +/datum/ai_planning_subtree/grub_mine + +/datum/ai_planning_subtree/grub_mine/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_TARGET_MINERAL_WALL)) + controller.queue_behavior(/datum/ai_behavior/mine_wall, BB_TARGET_MINERAL_WALL) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_mineral_wall, BB_TARGET_MINERAL_WALL) + +/datum/ai_behavior/mine_wall + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + action_cooldown = 15 SECONDS + +/datum/ai_behavior/mine_wall/setup(datum/ai_controller/controller, target_key) + . = ..() + var/turf/target = controller.blackboard[target_key] + if(isnull(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/mine_wall/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/basic/living_pawn = controller.pawn + var/turf/closed/mineral/target = controller.blackboard[target_key] + var/is_gibtonite_turf = istype(target, /turf/closed/mineral/gibtonite) + if(QDELETED(target)) + finish_action(controller, FALSE, target_key) + return + living_pawn.melee_attack(target) + if(is_gibtonite_turf) + living_pawn.manual_emote("sighs...") //accept whats about to happen to us + + finish_action(controller, TRUE, target_key) + return + +/datum/ai_behavior/mine_wall/finish_action(datum/ai_controller/controller, success, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + +/datum/pet_command/grub_spit + command_name = "Spit" + command_desc = "Ask your grub pet to spit out its ores." + speech_commands = list("spit", "ores") + +/datum/pet_command/grub_spit/execute_action(datum/ai_controller/controller) + var/datum/action/cooldown/spit_ability = controller.blackboard[BB_SPIT_ABILITY] + if(!spit_ability?.IsAvailable()) + return + controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_SPIT_ABILITY) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm new file mode 100644 index 00000000000..11043e58d11 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm @@ -0,0 +1,114 @@ +/// Mob which retreats and spawns annoying sub-mobs to attack you +/mob/living/basic/mining/hivelord + name = "hivelord" + desc = "A levitating swarm of tiny creatures which act as a single individual. When threatened or hunting they rapidly replicate additional short-lived bodies." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "hivelord" + icon_living = "hivelord" + // icon_aggro = "hivelord_alert" + icon_dead = "hivelord_dead" + icon_gib = "syndicate_gib" + mob_biotypes = MOB_ORGANIC + speed = 2 + maxHealth = 75 + health = 75 + melee_damage_lower = 0 + melee_damage_upper = 0 + attack_verb_continuous = "weakly tackles" + attack_verb_simple = "weakly tackles" + speak_emote = list("telepathically cries") + attack_sound = 'sound/weapons/pierce.ogg' + throw_blocked_message = "passes between the bodies of the" + obj_damage = 0 + pass_flags = PASSTABLE + ai_controller = /datum/ai_controller/basic_controller/hivelord + /// Mobs to spawn when we die, varedit this to be recursive to give the players a fun surprise + var/death_spawn_type = /mob/living/basic/hivelord_brood + /// Action which spawns worms + var/datum/action/cooldown/mob_cooldown/hivelord_spawn/spawn_brood + +/mob/living/basic/mining/hivelord/Initialize(mapload) + . = ..() + var/static/list/death_loot = list(/obj/item/organ/internal/monster_core/regenerative_core) + AddElement(/datum/element/relay_attackers) + AddElement(/datum/element/death_drops, death_loot) + AddComponent(/datum/component/clickbox, icon_state = "hivelord", max_scale = INFINITY, dead_state = "hivelord_dead") // They writhe so much. + AddComponent(/datum/component/appearance_on_aggro, aggro_state = "hivelord_alert") + spawn_brood = new(src) + spawn_brood.Grant(src) + ai_controller.set_blackboard_key(BB_TARGETTED_ACTION, spawn_brood) + +/mob/living/basic/mining/hivelord/Destroy() + QDEL_NULL(spawn_brood) + return ..() + +/mob/living/basic/mining/hivelord/death(gibbed) + . = ..() + var/list/safe_turfs = RANGE_TURFS(1, src) - get_turf(src) + for (var/turf/check_turf as anything in safe_turfs) + if (check_turf.is_blocked_turf(exclude_mobs = TRUE)) + safe_turfs -= check_turf + + var/turf/our_turf = get_turf(src) + for (var/i in 1 to 3) + if (!length(safe_turfs)) + return + var/turf/land_turf = pick_n_take(safe_turfs) + var/obj/effect/temp_visual/hivebrood_spawn/forecast = new(land_turf) + forecast.create_from(death_spawn_type, our_turf, CALLBACK(src, PROC_REF(complete_spawn), land_turf)) + +/// Spawns a worm on the specified turf +/mob/living/basic/mining/hivelord/proc/complete_spawn(turf/spawn_turf) + var/mob/living/brood = new death_spawn_type(spawn_turf) + brood.faction = faction + brood.ai_controller?.set_blackboard_key(ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) + brood.dir = get_dir(src, spawn_turf) + +/mob/living/basic/mining/hivelord/RangedAttack(atom/atom_target, modifiers) + spawn_brood?.Trigger(target = atom_target) + +/// Attack worms spawned by the hivelord +/mob/living/basic/hivelord_brood + name = "hivelord brood" + desc = "Short-lived attack form of the hivelord. One isn't much of a threat, but..." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "hivelord_brood" + icon_living = "hivelord_brood" + icon_dead = "hivelord_brood" + icon_gib = "syndicate_gib" + friendly_verb_continuous = "chirrups near" + friendly_verb_simple = "chirrup near" + mob_size = MOB_SIZE_SMALL + basic_mob_flags = DEL_ON_DEATH + pass_flags = PASSTABLE | PASSMOB + mob_biotypes = MOB_ORGANIC|MOB_BEAST + faction = list(FACTION_MINING) + unsuitable_atmos_damage = 0 + minimum_survivable_temperature = 0 + maximum_survivable_temperature = INFINITY + speed = 1.5 + maxHealth = 1 + health = 1 + melee_damage_lower = 2 + melee_damage_upper = 2 + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + speak_emote = list("telepathically cries") + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + obj_damage = 0 + density = FALSE + ai_controller = /datum/ai_controller/basic_controller/simple_hostile + +/mob/living/basic/hivelord_brood/Initialize(mapload) + . = ..() + add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE, TRAIT_PERMANENTLY_MORTAL), INNATE_TRAIT) + AddElement(/datum/element/simple_flying) + AddComponent(/datum/component/swarming) + AddComponent(/datum/component/clickbox, icon_state = "hivelord", max_scale = INFINITY) + addtimer(CALLBACK(src, PROC_REF(death)), 10 SECONDS) + +/mob/living/basic/hivelord_brood/death(gibbed) + if (!gibbed) + new /obj/effect/temp_visual/hive_spawn_wither(get_turf(src), /* copy_from = */ src) + return ..() diff --git a/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm b/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm new file mode 100644 index 00000000000..fd7983de397 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm @@ -0,0 +1,14 @@ +/// Basically just keep away and shit out worms +/datum/ai_controller/basic_controller/hivelord + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_AGGRO_RANGE = 5, // Only get mad at people nearby + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/maintain_distance, + /datum/ai_planning_subtree/targeted_mob_ability, + ) diff --git a/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm b/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm new file mode 100644 index 00000000000..3fee2a003f3 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm @@ -0,0 +1,124 @@ +/// Spawns a little worm nearby +/datum/action/cooldown/mob_cooldown/hivelord_spawn + name = "Spawn Brood" + desc = "Release an attack form to an adjacent square to attack your target or anyone nearby." + button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + button_icon_state = "hivelord_brood" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + click_to_activate = TRUE + cooldown_time = 2 SECONDS + melee_cooldown_time = 0 + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED + shared_cooldown = NONE + /// If a mob is not clicked directly, inherit targetting data from this blackboard key and setting it upon this target key + var/ai_target_key = BB_BASIC_MOB_CURRENT_TARGET + /// What are we actually spawning? + var/spawn_type = /mob/living/basic/hivelord_brood + /// Do we automatically fire with no cooldown when damaged? + var/trigger_on_hit = TRUE + /// Minimum time between triggering on hit + var/on_hit_delay = 1 SECONDS + /// Delay between triggering on hit + COOLDOWN_DECLARE(on_hit_cooldown) + +/datum/action/cooldown/mob_cooldown/hivelord_spawn/Grant(mob/granted_to) + . = ..() + if (isnull(owner)) + return + if (trigger_on_hit) + RegisterSignal(owner, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) + +/datum/action/cooldown/mob_cooldown/hivelord_spawn/Remove(mob/removed_from) + UnregisterSignal(removed_from, COMSIG_ATOM_WAS_ATTACKED) + return ..() + +/datum/action/cooldown/mob_cooldown/hivelord_spawn/Activate(atom/target) + . = ..() + if (!spawn_brood(target, target_turf = get_turf(target))) + StartCooldown(0.5 SECONDS) + return + StartCooldown() + +/// Called when someone whacks us +/datum/action/cooldown/mob_cooldown/hivelord_spawn/proc/on_attacked(atom/victim, atom/attacker, attack_flags) + SIGNAL_HANDLER + if (!trigger_on_hit || !(attack_flags & ATTACKER_DAMAGING_ATTACK) || !COOLDOWN_FINISHED(src, on_hit_cooldown)) + return + COOLDOWN_START(src, on_hit_cooldown, on_hit_delay) + spawn_brood(attacker, target_turf = get_step_away(owner, attacker), feedback = FALSE) + +/// Spawn a funny little worm +/datum/action/cooldown/mob_cooldown/hivelord_spawn/proc/spawn_brood(target, turf/target_turf, feedback = TRUE) + var/ai_target = isliving(target) ? target : null + if (isnull(ai_target)) + ai_target = owner.ai_controller?.blackboard[ai_target_key] + + var/dir_to_target = get_dir(owner, target_turf) + var/list/target_turfs = list() + for(var/i in -1 to 1) + var/turn_amount = rand(-1, 1) * 45 + var/test_dir = turn(dir_to_target, turn_amount) + var/turf/test_turf = get_step(owner, test_dir) + if (test_turf.is_blocked_turf(exclude_mobs = TRUE)) + continue + target_turfs += test_turf + + if (!length(target_turfs)) + if (feedback) + owner.balloon_alert(owner, "no room!") + StartCooldown(0.5 SECONDS) + return FALSE + + var/turf/land_turf = pick(target_turfs) + var/obj/effect/temp_visual/hivebrood_spawn/forecast = new(land_turf) + forecast.create_from(spawn_type, get_turf(owner), CALLBACK(src, PROC_REF(complete_spawn), land_turf, ai_target)) + StartCooldown() + + return TRUE + +/// Actually create a mob +/datum/action/cooldown/mob_cooldown/hivelord_spawn/proc/complete_spawn(turf/spawn_turf, target) + var/mob/living/brood = new spawn_type(spawn_turf) + brood.faction = owner.faction + brood.ai_controller?.set_blackboard_key(ai_target_key, target) + brood.dir = get_dir(owner, spawn_turf) + +#define BROOD_ARC_Y_OFFSET 8 +#define BROOD_ARC_ROTATION 45 + +/// Fast animation to show a worm spawning +/obj/effect/temp_visual/hivebrood_spawn + name = "brood spawn" + duration = 0.3 SECONDS + alpha = 0 + +/// Set up our visuals and start a timer for a callback +/obj/effect/temp_visual/hivebrood_spawn/proc/create_from(mob/living/spawn_type, turf/spawn_from, datum/callback/on_completed) + addtimer(on_completed, duration, TIMER_DELETE_ME) + + var/turf/my_turf = get_turf(src) + dir = get_dir(spawn_from, my_turf) + var/move_x = (my_turf.x - spawn_from.x) * world.icon_size + var/move_y = (my_turf.y - spawn_from.y) * world.icon_size + pixel_x = -move_x + pixel_y = -move_y + + icon = initial(spawn_type.icon) + icon_state = initial(spawn_type.icon_state) + + + animate(src, pixel_x = 0, time = duration) + animate(src, pixel_y = BROOD_ARC_Y_OFFSET - (move_y * 0.5), time = duration * 0.5, flags = ANIMATION_PARALLEL, easing = SINE_EASING | EASE_OUT) + animate(pixel_y = 0, time = duration * 0.5, easing = SINE_EASING | EASE_IN) + animate(src, alpha = 255, time = duration * 0.5, flags = ANIMATION_PARALLEL) + + if (dir & (NORTH | EAST)) + transform = matrix().Turn(-BROOD_ARC_ROTATION) + animate(src, transform = matrix(), time = duration, flags = ANIMATION_PARALLEL) + else + transform = matrix().Turn(BROOD_ARC_ROTATION) + animate(src, transform = matrix(), time = duration, flags = ANIMATION_PARALLEL) + +#undef BROOD_ARC_Y_OFFSET +#undef BROOD_ARC_ROTATION diff --git a/code/modules/mob/living/basic/lavaland/legion/legion.dm b/code/modules/mob/living/basic/lavaland/legion/legion.dm new file mode 100644 index 00000000000..7c6bd0fd170 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion.dm @@ -0,0 +1,158 @@ +/** + * Avoids players while throwing skulls at them. + * Legion skulls heal allies, bite enemies, and infest dying humans to make more legions. + */ +/mob/living/basic/mining/legion + name = "legion" + desc = "You can still see what was once a human under the shifting mass of corruption." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "legion" + icon_living = "legion" + icon_dead = "legion" + icon_gib = "syndicate_gib" + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + basic_mob_flags = DEL_ON_DEATH + speed = 3 + maxHealth = 75 + health = 75 + obj_damage = 60 + melee_damage_lower = 15 + melee_damage_upper = 15 + attack_verb_continuous = "lashes out at" + attack_verb_simple = "lash out at" + speak_emote = list("gurgles") + attack_sound = 'sound/weapons/pierce.ogg' + throw_blocked_message = "bounces harmlessly off of" + crusher_loot = /obj/item/crusher_trophy/legion_skull + death_message = "wails in chorus and dissolves into quivering flesh." + ai_controller = /datum/ai_controller/basic_controller/legion + /// What kind of mob do we spawn? + var/brood_type = /mob/living/basic/legion_brood + /// What kind of corpse spawner do we leave behind on death? + var/corpse_type = /obj/effect/mob_spawn/corpse/human/legioninfested + /// Who is inside of us? + var/mob/living/stored_mob + +/mob/living/basic/mining/legion/Initialize(mapload) + . = ..() + AddElement(/datum/element/death_drops, get_loot_list()) + AddElement(/datum/element/content_barfer) + + var/datum/action/cooldown/mob_cooldown/skull_launcher/skull_launcher = new(src) + skull_launcher.Grant(src) + skull_launcher.spawn_type = brood_type + ai_controller.blackboard[BB_TARGETTED_ACTION] = skull_launcher + +/// Create what we want to drop on death, in proc form so we can always return a static list +/mob/living/basic/mining/legion/proc/get_loot_list() + var/static/list/death_loot = list(/obj/item/organ/internal/monster_core/regenerative_core/legion) + return death_loot + +/mob/living/basic/mining/legion/Exited(atom/movable/gone, direction) + . = ..() + if (gone != stored_mob) + return + ai_controller.clear_blackboard_key(BB_LEGION_CORPSE) + stored_mob.remove_status_effect(/datum/status_effect/grouped/stasis, STASIS_LEGION_EATEN) + stored_mob.add_mood_event(MOOD_CATEGORY_LEGION_CORE, /datum/mood_event/healsbadman/long_term) // This will still probably mostly be gone before you are alive + stored_mob = null + +/mob/living/basic/mining/legion/death(gibbed) + if (isnull(stored_mob)) + new corpse_type(loc) + return ..() + +/// Put a corpse in this guy +/mob/living/basic/mining/legion/proc/consume(mob/living/consumed) + new /obj/effect/gibspawner/generic(consumed.loc) + gender = consumed.gender + name = consumed.real_name + consumed.investigate_log("has been killed by hivelord infestation.", INVESTIGATE_DEATHS) + consumed.death() + consumed.extinguish_mob() + consumed.fully_heal(HEAL_DAMAGE) + consumed.apply_status_effect(/datum/status_effect/grouped/stasis, STASIS_LEGION_EATEN) + consumed.forceMove(src) + ai_controller?.set_blackboard_key(BB_LEGION_CORPSE, consumed) + ai_controller?.set_blackboard_key(BB_LEGION_RECENT_LINES, consumed.copy_recent_speech(line_chance = 80)) + stored_mob = consumed + visible_message(span_warning("[src] staggers to [p_their()] feet!")) + if (prob(75)) + return + // Congratulations you have won a special prize: cancer + var/obj/item/organ/internal/legion_tumour/cancer = new() + cancer.Insert(consumed, special = TRUE, drop_if_replaced = FALSE) + +/// A Legion which only drops skeletons instead of corpses which might have fun loot, so it cannot be farmed +/mob/living/basic/mining/legion/spawner_made + corpse_type = /obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/charred + + +/// Like a Legion but it's an adorable snowman +/mob/living/basic/mining/legion/snow + name = "snow legion" + desc = "You can vaguely see what was once a human under the densely packed snow. Cute, but macabre." + icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + icon_state = "snowlegion" + icon_living = "snowlegion" + // icon_aggro = "snowlegion_alive" + icon_dead = "snowlegion" + brood_type = /mob/living/basic/legion_brood/snow + corpse_type = /obj/effect/mob_spawn/corpse/human/legioninfested/snow + +/mob/living/basic/mining/legion/snow/Initialize(mapload) + . = ..() + AddComponent(/datum/component/appearance_on_aggro, aggro_state = "snowlegion_alive") // Surprise! I was real! + +/// As Snow Legion but spawns corpses which don't have any exciting loot +/mob/living/basic/mining/legion/snow/spawner_made + corpse_type = /obj/effect/mob_spawn/corpse/human/legioninfested/skeleton + + +/// Like a Legion but shorter and faster +/mob/living/basic/mining/legion/dwarf + name = "dwarf legion" + desc = "You can still see what was once a rather small human under the shifting mass of corruption." + icon_state = "dwarf_legion" + icon_living = "dwarf_legion" + icon_dead = "dwarf_legion" + maxHealth = 60 + health = 60 + speed = 2 + crusher_drop_chance = 20 + corpse_type = /obj/effect/mob_spawn/corpse/human/legioninfested/dwarf + + +/// Like a Legion but larger and spawns regular Legions, not currently used anywhere and very soulful +/mob/living/basic/mining/legion/large + name = "myriad" + desc = "A legion of legions, a dead end to whatever form the Necropolis was attempting to create." + icon = 'icons/mob/simple/lavaland/64x64megafauna.dmi' + icon_state = "legion" + icon_living = "legion" + icon_dead = "legion" + health_doll_icon = "legion" + speed = 5 + health = 450 + maxHealth = 450 + melee_damage_lower = 20 + melee_damage_upper = 20 + obj_damage = 30 + pixel_x = -16 + sentience_type = SENTIENCE_BOSS + +/mob/living/basic/mining/legion/large/Initialize(mapload) + . = ..() + AddComponent(\ + /datum/component/spawner,\ + spawn_types = list(/mob/living/basic/mining/legion),\ + spawn_time = 20 SECONDS,\ + max_spawned = 3,\ + spawn_text = "peels itself off from",\ + faction = faction,\ + ) + +/// Create what we want to drop on death, in proc form so we can always return a static list +/mob/living/basic/mining/legion/large/get_loot_list() + var/static/list/death_loot = list(/obj/item/organ/internal/monster_core/regenerative_core/legion = 3, /obj/effect/mob_spawn/corpse/human/legioninfested = 4) + return death_loot diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm b/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm new file mode 100644 index 00000000000..6b3525cb32a --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm @@ -0,0 +1,77 @@ +/// Keep away and launch skulls at every opportunity, prioritising injured allies +/datum/ai_controller/basic_controller/legion + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead/legion, + BB_BASIC_MOB_FLEEING = TRUE, + BB_AGGRO_RANGE = 5, // Unobservant + BB_BASIC_MOB_FLEE_DISTANCE = 6, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/random_speech/legion, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability, + /datum/ai_planning_subtree/flee_target/legion, + ) + +/// Chase and attack whatever we are targetting, if it's friendly we will heal them +/datum/ai_controller/basic_controller/legion_brood + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead/legion, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Target nearby friendlies if they are hurt (and are not themselves Legions) +/datum/targetting_datum/basic/attack_until_dead/legion + +/datum/targetting_datum/basic/attack_until_dead/legion/faction_check(mob/living/living_mob, mob/living/the_target) + if (!living_mob.faction_check_mob(the_target, exact_match = check_factions_exactly)) + return FALSE + if (istype(the_target, living_mob.type)) + return TRUE + var/atom/created_by = living_mob.ai_controller.blackboard[BB_LEGION_BROOD_CREATOR] + if (!QDELETED(created_by) && istype(the_target, created_by.type)) + return TRUE + return the_target.stat == DEAD || the_target.health >= the_target.maxHealth + +/// Don't run away from friendlies +/datum/ai_planning_subtree/flee_target/legion + +/datum/ai_planning_subtree/flee_target/legion/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/target = controller.blackboard[target_key] + if (QDELETED(target) || target.faction_check_mob(controller.pawn)) + return // Only flee if we have a hostile target + return ..() + +/// Make spooky sounds, if we have a corpse inside then impersonate them +/datum/ai_planning_subtree/random_speech/legion + speech_chance = 1 + speak = list("Come...", "Legion...", "Why...?") + emote_hear = list("groans.", "wails.", "whimpers.") + emote_see = list("twitches.", "shudders.") + /// Stuff to specifically say into a radio + var/list/radio_speech = list("Come...", "Why...?") + +/datum/ai_planning_subtree/random_speech/legion/speak(datum/ai_controller/controller) + var/mob/living/carbon/human/victim = controller.blackboard[BB_LEGION_CORPSE] + if (QDELETED(victim) || prob(30)) + return ..() + + var/list/remembered_speech = controller.blackboard[BB_LEGION_RECENT_LINES] || list() + + if (length(remembered_speech) && prob(50)) // Don't spam the radio + controller.queue_behavior(/datum/ai_behavior/perform_speech, pick(remembered_speech)) + return + + var/obj/item/radio/mob_radio = locate() in victim.contents + if (QDELETED(mob_radio)) + return ..() // No radio, just talk funny + controller.queue_behavior(/datum/ai_behavior/perform_speech_radio, pick(radio_speech + remembered_speech), mob_radio, list(RADIO_CHANNEL_SUPPLY, RADIO_CHANNEL_COMMON)) diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm new file mode 100644 index 00000000000..962d232c5ef --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm @@ -0,0 +1,99 @@ +/// A spooky skull which heals lavaland mobs, attacks miners, and infests their bodies +/mob/living/basic/legion_brood + name = "legion" + desc = "One of many." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "legion_head" + icon_living = "legion_head" + icon_dead = "legion_head" + icon_gib = "syndicate_gib" + basic_mob_flags = DEL_ON_DEATH + mob_size = MOB_SIZE_SMALL + pass_flags = PASSTABLE | PASSMOB + mob_biotypes = MOB_ORGANIC|MOB_BEAST + faction = list(FACTION_MINING) + unsuitable_atmos_damage = 0 + minimum_survivable_temperature = 0 + maximum_survivable_temperature = INFINITY + friendly_verb_continuous = "chatters near" + friendly_verb_simple = "chatter near" + maxHealth = 1 + health = 1 + melee_damage_lower = 12 + melee_damage_upper = 12 + obj_damage = 0 + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + attack_vis_effect = ATTACK_EFFECT_BITE + speak_emote = list("echoes") // who the fuck speaking as this mob it dies 10 seconds after it spawns + attack_sound = 'sound/weapons/pierce.ogg' + density = FALSE + ai_controller = /datum/ai_controller/basic_controller/legion_brood + /// Reference to a guy who made us + var/mob/living/created_by + +/mob/living/basic/legion_brood/Initialize(mapload) + . = ..() + add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE, TRAIT_PERMANENTLY_MORTAL), INNATE_TRAIT) + AddElement(/datum/element/simple_flying) + AddComponent(/datum/component/swarming) + AddComponent(/datum/component/clickbox, icon_state = "sphere", max_scale = 2) + addtimer(CALLBACK(src, PROC_REF(death)), 10 SECONDS) + +/mob/living/basic/legion_brood/death(gibbed) + if (!gibbed) + new /obj/effect/temp_visual/hive_spawn_wither(get_turf(src), /* copy_from = */ src) + return ..() + +/mob/living/basic/legion_brood/melee_attack(mob/living/target, list/modifiers, ignore_cooldown) + if (ishuman(target) && target.stat > SOFT_CRIT) + infest(target) + return + if (isliving(target) && faction_check_mob(target) && !istype(target, created_by?.type)) + visible_message(span_warning("[src] melds with [target]'s flesh!")) + target.apply_status_effect(/datum/status_effect/regenerative_core) + new /obj/effect/temp_visual/heal(get_turf(target), COLOR_HEALING_CYAN) + death() + return + return ..() + +/// Turn the targetted mob into one of us +/mob/living/basic/legion_brood/proc/infest(mob/living/target) + visible_message(span_warning("[name] burrows into the flesh of [target]!")) + var/spawn_type = get_legion_type(target) + var/mob/living/basic/mining/legion/new_legion = new spawn_type(loc) + new_legion.consume(target) + new_legion.faction = faction.Copy() + qdel(src) + +/// Returns the kind of legion we make out of the target +/mob/living/basic/legion_brood/proc/get_legion_type(mob/living/target) + if (HAS_TRAIT(target, TRAIT_DWARF)) + return /mob/living/basic/mining/legion/dwarf + return /mob/living/basic/mining/legion + +/// Sets someone as our creator, mostly so you can't use skulls to heal yourself +/mob/living/basic/legion_brood/proc/assign_creator(mob/living/creator, copy_full_faction = TRUE) + if (copy_full_faction) + faction = creator.faction.Copy() + else + faction |= REF(creator) + created_by = creator + ai_controller?.set_blackboard_key(BB_LEGION_BROOD_CREATOR, creator) + RegisterSignal(creator, COMSIG_QDELETING, PROC_REF(creator_destroyed)) + +/// Reference handling +/mob/living/basic/legion_brood/proc/creator_destroyed() + SIGNAL_HANDLER + created_by = null + +/// Like the Legion's summoned skull but funnier (it's snow now) +/mob/living/basic/legion_brood/snow + name = "snow legion" + icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + icon_state = "snowlegion_head" + icon_living = "snowlegion_head" + icon_dead = "snowlegion_head" + +/mob/living/basic/legion_brood/snow/get_legion_type(mob/living/target) + return /mob/living/basic/mining/legion/snow diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_tumour.dm b/code/modules/mob/living/basic/lavaland/legion/legion_tumour.dm new file mode 100644 index 00000000000..078af57de2a --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion_tumour.dm @@ -0,0 +1,159 @@ +/// Left behind when a legion infects you, for medical enrichment +/obj/item/organ/internal/legion_tumour + name = "legion tumour" + desc = "A mass of pulsing flesh and dark tendrils, containing the power to regenerate flesh at a terrible cost." + failing_desc = "pulses and writhes with horrible life, reaching towards you with its tendrils!" + icon = 'icons/obj/medical/organs/mining_organs.dmi' + icon_state = "legion_remains" + zone = BODY_ZONE_CHEST + slot = ORGAN_SLOT_PARASITE_EGG + decay_factor = STANDARD_ORGAN_DECAY * 3 // About 5 minutes outside of a host + /// What stage of growth the corruption has reached. + var/stage = 0 + /// We apply this status effect periodically or when used on someone + var/applied_status = /datum/status_effect/regenerative_core + /// How long have we been in this stage? + var/elapsed_time = 0 SECONDS + /// How long does it take to advance one stage? + var/growth_time = 80 SECONDS // Long enough that if you go back to lavaland without realising it you're not totally fucked + /// What kind of mob will we transform into? + var/spawn_type = /mob/living/basic/mining/legion + /// Spooky sounds to play as you start to turn + var/static/list/spooky_sounds = list( + 'sound/voice/lowHiss1.ogg', + 'sound/voice/lowHiss2.ogg', + 'sound/voice/lowHiss3.ogg', + 'sound/voice/lowHiss4.ogg', + ) + +/obj/item/organ/internal/legion_tumour/Initialize(mapload) + . = ..() + animate_pulse() + +/obj/item/organ/internal/legion_tumour/apply_organ_damage(damage_amount, maximum, required_organ_flag) + var/was_failing = organ_flags & ORGAN_FAILING + . = ..() + if (was_failing != (organ_flags & ORGAN_FAILING)) + animate_pulse() + +/obj/item/organ/internal/legion_tumour/set_organ_damage(damage_amount, required_organ_flag) + . = ..() + animate_pulse() + +/// Do a heartbeat animation depending on if we're failing or not +/obj/item/organ/internal/legion_tumour/proc/animate_pulse() + animate(src, transform = matrix()) // Stop any current animation + + var/speed_divider = organ_flags & ORGAN_FAILING ? 2 : 1 + + animate(src, transform = matrix().Scale(1.1), time = 0.5 SECONDS / speed_divider, easing = SINE_EASING | EASE_OUT, loop = -1, flags = ANIMATION_PARALLEL) + animate(transform = matrix(), time = 0.5 SECONDS / speed_divider, easing = SINE_EASING | EASE_IN) + animate(transform = matrix(), time = 2 SECONDS / speed_divider) + +/obj/item/organ/internal/legion_tumour/Remove(mob/living/carbon/egg_owner, special) + . = ..() + stage = 0 + elapsed_time = 0 + +/obj/item/organ/internal/legion_tumour/attack(mob/living/target, mob/living/user, params) + if (try_apply(target, user)) + qdel(src) + return + return ..() + +/// Smear it on someone like a regen core, why not. Make sure they're alive though. +/obj/item/organ/internal/legion_tumour/proc/try_apply(mob/living/target, mob/user) + if(!user.Adjacent(target) || !isliving(target)) + return FALSE + + if (target.stat <= SOFT_CRIT && !(organ_flags & ORGAN_FAILING)) + target.add_mood_event(MOOD_CATEGORY_LEGION_CORE, /datum/mood_event/healsbadman) + target.apply_status_effect(applied_status) + + if (target != user) + target.visible_message(span_notice("[user] splatters [target] with [src]... Disgusting tendrils pull [target.p_their()] wounds shut!")) + else + to_chat(user, span_notice("You smear [src] on yourself. Disgusting tendrils pull your wounds closed.")) + return TRUE + + if (!ishuman(target)) + return FALSE + + target.visible_message(span_boldwarning("[user] splatters [target] with [src]... and it springs into horrible life!")) + var/mob/living/basic/legion_brood/skull = new(target.loc) + skull.melee_attack(target) + return TRUE + +/obj/item/organ/internal/legion_tumour/on_life(seconds_per_tick, times_fired) + . = ..() + if (QDELETED(src) || QDELETED(owner)) + return + + if (stage >= 2) + if(SPT_PROB(stage / 5, seconds_per_tick)) + to_chat(owner, span_notice("You feel a bit better.")) + owner.apply_status_effect(applied_status) // It's not all bad! + if(SPT_PROB(1, seconds_per_tick)) + owner.emote("twitch") + + switch(stage) + if(2, 3) + if(SPT_PROB(1, seconds_per_tick)) + to_chat(owner, span_danger("Your chest spasms!")) + if(SPT_PROB(1, seconds_per_tick)) + to_chat(owner, span_danger("You feel weak.")) + if(SPT_PROB(1, seconds_per_tick)) + SEND_SOUND(owner, sound(pick(spooky_sounds))) + if(SPT_PROB(2, seconds_per_tick)) + owner.vomit() + if(4, 5) + if(SPT_PROB(2, seconds_per_tick)) + to_chat(owner, span_danger("Something flexes under your skin.")) + if(SPT_PROB(2, seconds_per_tick)) + if (prob(40)) + SEND_SOUND(owner, sound('sound/voice/ghost_whisper.ogg')) + else + SEND_SOUND(owner, sound(pick(spooky_sounds))) + if(SPT_PROB(3, seconds_per_tick)) + owner.vomit(vomit_type = /obj/effect/decal/cleanable/vomit/old/black_bile) + if (prob(50)) + var/turf/check_turf = get_step(owner.loc, owner.dir) + var/atom/land_turf = (check_turf.is_blocked_turf()) ? owner.loc : check_turf + var/mob/living/basic/legion_brood/child = new(land_turf) + child.assign_creator(owner, copy_full_faction = FALSE) + + if(SPT_PROB(3, seconds_per_tick)) + to_chat(owner, span_danger("Your muscles ache.")) + owner.take_bodypart_damage(3) + + if (stage == 5) + if (SPT_PROB(10, seconds_per_tick)) + infest() + return + + elapsed_time += seconds_per_tick SECONDS * ((organ_flags & ORGAN_FAILING) ? 3 : 1) // Let's call it "matured" rather than failed + if (elapsed_time < growth_time) + return + stage++ + elapsed_time = 0 + if (stage == 5) + to_chat(owner, span_bolddanger("Something is moving under your skin!")) + +/// Consume our host +/obj/item/organ/internal/legion_tumour/proc/infest() + if (QDELETED(src) || QDELETED(owner)) + return + owner.visible_message(span_boldwarning("Black tendrils burst from [owner]'s flesh, covering them in amorphous flesh!")) + var/mob/living/basic/mining/legion/new_legion = new spawn_type(owner.loc) + new_legion.consume(owner) + qdel(src) + +/obj/item/organ/internal/legion_tumour/on_find(mob/living/finder) + . = ..() + to_chat(finder, span_warning("There's an enormous tumour in [owner]'s [zone]!")) + if(stage < 4) + to_chat(finder, span_notice("Its tendrils seem to twitch towards the light.")) + return + to_chat(finder, span_notice("Its pulsing tendrils reach all throughout the body.")) + if(prob(stage * 2)) + infest() diff --git a/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm b/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm new file mode 100644 index 00000000000..1ffcafecd56 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm @@ -0,0 +1,109 @@ +/// Spawns a little worm nearby +/datum/action/cooldown/mob_cooldown/skull_launcher + name = "Launch Legion" + desc = "Propel a living piece of your body to a distant location." + button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + button_icon_state = "legion_head" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + click_to_activate = TRUE + cooldown_time = 2 SECONDS + melee_cooldown_time = 0 + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED + shared_cooldown = NONE + /// If a mob is not clicked directly, inherit targetting data from this blackboard key and setting it upon this target key + var/ai_target_key = BB_BASIC_MOB_CURRENT_TARGET + /// What are we actually spawning? + var/spawn_type = /mob/living/basic/legion_brood + /// How far can we fire? + var/max_range = 7 + +/datum/action/cooldown/mob_cooldown/skull_launcher/Activate(atom/target) + var/turf/target_turf = get_turf(target) + + if (get_dist(owner, target_turf) > max_range) + target_turf = get_ranged_target_turf_direct(owner, target_turf, max_range) + + if (target_turf.is_blocked_turf()) + var/list/near_turfs = RANGE_TURFS(1, target_turf) - target_turf + for (var/turf/check_turf as anything in near_turfs) + if (check_turf.is_blocked_turf()) + near_turfs -= check_turf + if (length(near_turfs)) + target_turf = pick(near_turfs) + else if(target_turf.is_blocked_turf(exclude_mobs = TRUE)) + owner.balloon_alert(owner, "no room!") + StartCooldown(0.5 SECONDS) + return + + var/ai_target = isliving(target) ? target : null + if (isnull(ai_target)) + ai_target = owner.ai_controller?.blackboard[ai_target_key] + + var/target_dir = get_dir(owner, target) + + var/obj/effect/temp_visual/legion_skull_depart/launch = new(get_turf(owner)) + launch.set_appearance(spawn_type) + launch.dir = target_dir + new /obj/effect/temp_visual/legion_brood_indicator(target_turf) + var/obj/effect/temp_visual/legion_skull_land/land = new(target_turf) + land.dir = target_dir + land.set_appearance(spawn_type, CALLBACK(src, PROC_REF(spawn_skull), target_turf, ai_target)) + StartCooldown() + +/// Actually create a mob +/datum/action/cooldown/mob_cooldown/skull_launcher/proc/spawn_skull(turf/spawn_location, target) + var/mob/living/basic/legion_brood/brood = new spawn_type(spawn_location) + if (istype(brood)) + brood.assign_creator(owner) + brood.ai_controller?.set_blackboard_key(ai_target_key, target) + brood.dir = get_dir(owner, spawn_location) + if (!isnull(target)) + brood.face_atom(target) + else + brood.dir = get_dir(owner, spawn_location) + + +/// Animation for launching a skull +/obj/effect/temp_visual/legion_skull_depart + name = "legion brood launch" + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "legion_head" + duration = 0.25 SECONDS + +/// Copy appearance from the passed atom type +/obj/effect/temp_visual/legion_skull_depart/proc/set_appearance(atom/spawned_type) + icon = initial(spawned_type.icon) + icon_state = initial(spawned_type.icon_state) + animate(src, alpha = 0, pixel_y = 72, time = duration) + +/// Animation for landing a skull +/obj/effect/temp_visual/legion_skull_land + name = "legion brood land" + duration = 0.5 SECONDS + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "legion_head" + alpha = 0 + pixel_y = 72 + +/// Copy appearance from the passed atom type and store what to do on animation complete +/obj/effect/temp_visual/legion_skull_land/proc/set_appearance(atom/spawned_type, datum/callback/on_completed) + icon = initial(spawned_type.icon) + icon_state = initial(spawned_type.icon_state) + animate(src, alpha = 0, pixel_y = 72, time = duration / 2) + animate(alpha = 255, pixel_y = 0, time = duration / 2) + addtimer(on_completed, duration, TIMER_DELETE_ME) + +/// A skull is going to be here! Oh no! +/obj/effect/temp_visual/legion_brood_indicator + name = "legion brood land" + duration = 0.75 SECONDS + layer = BELOW_MOB_LAYER + plane = GAME_PLANE + icon = 'icons/mob/telegraphing/telegraph.dmi' + icon_state = "skull" + +/obj/effect/temp_visual/legion_brood_indicator/Initialize(mapload) + . = ..() + animate(src, alpha = 255, time = 0.5 SECONDS) + animate(alpha = 0, time = 0.25 SECONDS) diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher.dm new file mode 100644 index 00000000000..28ed712d061 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher.dm @@ -0,0 +1,106 @@ +/// A floating eyeball which keeps its distance and sometimes make you look away. +/mob/living/basic/mining/watcher + name = "watcher" + desc = "A levitating, monocular creature held aloft by wing-like veins. A sharp spine of crystal protrudes from its body." + icon = 'icons/mob/simple/lavaland/lavaland_monsters_wide.dmi' + icon_state = "watcher" + icon_living = "watcher" + icon_dead = "watcher_dead" + health_doll_icon = "watcher" + pixel_x = -12 + base_pixel_x = -12 + speak_emote = list("chimes") + speed = 3 + maxHealth = 160 + health = 160 + attack_verb_continuous = "buffets" + attack_verb_simple = "buffet" + crusher_loot = /obj/item/crusher_trophy/watcher_wing + ai_controller = /datum/ai_controller/basic_controller/watcher + butcher_results = list( + /obj/item/stack/sheet/bone = 1, + /obj/item/stack/ore/diamond = 2, + /obj/item/stack/sheet/sinew = 2, + ) + /// How often can we shoot? + var/ranged_cooldown = 3 SECONDS + /// What kind of beams we got? + var/projectile_type = /obj/projectile/temp/watcher + /// Icon state for our eye overlay + var/eye_glow = "ice_glow" + /// Sound to play when we shoot + var/shoot_sound = 'sound/weapons/pierce.ogg' + /// Typepath of our gaze ability + var/gaze_attack = /datum/action/cooldown/mob_cooldown/watcher_gaze + // We attract and eat these things for some reason + var/list/wanted_objects = list( + /obj/item/stack/sheet/mineral/diamond, + /obj/item/stack/ore/diamond, + /obj/item/pen/survival, + ) + +/mob/living/basic/mining/watcher/Initialize(mapload) + . = ..() + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/simple_flying) + AddElement(/datum/element/content_barfer) + AddComponent(/datum/component/ai_target_timer) + AddComponent(/datum/component/basic_ranged_ready_overlay, overlay_state = eye_glow) + AddComponent(\ + /datum/component/ranged_attacks,\ + cooldown_time = ranged_cooldown,\ + projectile_type = projectile_type,\ + projectile_sound = shoot_sound,\ + ) + AddComponent(\ + /datum/component/magnet,\ + attracted_typecache = wanted_objects,\ + on_contact = CALLBACK(src, PROC_REF(consume)),\ + ) + update_appearance(UPDATE_OVERLAYS) + + var/datum/action/cooldown/mob_cooldown/watcher_gaze/gaze = new gaze_attack(src) + gaze.Grant(src) + ai_controller.set_blackboard_key(BB_GENERIC_ACTION, gaze) + AddComponent(/datum/component/revenge_ability, gaze, targetting = ai_controller.blackboard[BB_TARGETTING_DATUM]) + +/mob/living/basic/mining/watcher/update_overlays() + . = ..() + if (stat == DEAD) + return + . += emissive_appearance(icon, "watcher_emissive", src) + +/// I love eating diamonds yum +/mob/living/basic/mining/watcher/proc/consume(atom/movable/thing) + visible_message(span_warning("[thing] seems to vanish into [src]'s body!")) + thing.forceMove(src) + +/// More durable, burning projectiles +/mob/living/basic/mining/watcher/magmawing + name = "magmawing watcher" + desc = "Presented with extreme temperatures, adaptive watchers absorb heat through their circulatory wings and repurpose it as a weapon." + icon_state = "watcher_magmawing" + icon_living = "watcher_magmawing" + icon_dead = "watcher_magmawing_dead" + eye_glow = "fire_glow" + maxHealth = 175 //Compensate for the lack of slowdown on projectiles with a bit of extra health + health = 175 + projectile_type = /obj/projectile/temp/watcher/magma_wing + gaze_attack = /datum/action/cooldown/mob_cooldown/watcher_gaze/fire + crusher_loot = /obj/item/crusher_trophy/blaster_tubes/magma_wing + crusher_drop_chance = 100 // There's only going to be one of these per round throw them a bone + +/// Less durable, freezing projectiles +/mob/living/basic/mining/watcher/icewing + name = "icewing watcher" + desc = "Watchers which fail to absorb enough heat during their development become fragile, but share their internal chill with their enemies." + icon_state = "watcher_icewing" + icon_living = "watcher_icewing" + icon_dead = "watcher_icewing_dead" + maxHealth = 130 + health = 130 + projectile_type = /obj/projectile/temp/watcher/ice_wing + gaze_attack = /datum/action/cooldown/mob_cooldown/watcher_gaze/ice + butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/bone = 1) + crusher_loot = /obj/item/crusher_trophy/watcher_wing/ice_wing + crusher_drop_chance = 100 diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm new file mode 100644 index 00000000000..a25234817f3 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm @@ -0,0 +1,39 @@ +/datum/ai_controller/basic_controller/watcher + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + ) + + ai_traits = PAUSE_DURING_DO_AFTER + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate/check_faction, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/maintain_distance, + /datum/ai_planning_subtree/use_mob_ability/gaze, + /datum/ai_planning_subtree/ranged_skirmish/watcher, + ) + +/datum/ai_planning_subtree/use_mob_ability/gaze + finish_planning = TRUE + +/datum/ai_planning_subtree/use_mob_ability/gaze/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if (!isliving(target)) + return // Don't do this if there's nothing hostile around or if our target is a mech + var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 + if (time_on_target < 5 SECONDS) + return // We need to spend some time acquiring our target first + return ..() + +/datum/ai_planning_subtree/ranged_skirmish/watcher + attack_behavior = /datum/ai_behavior/ranged_skirmish/watcher + +/datum/ai_planning_subtree/ranged_skirmish/watcher/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if (QDELETED(target) || HAS_TRAIT(target, TRAIT_OVERWATCHED)) + return // Don't bully people who are playing red light green light + return ..() + +/datum/ai_behavior/ranged_skirmish/watcher + min_range = 0 diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm new file mode 100644 index 00000000000..9426db41cca --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm @@ -0,0 +1,126 @@ +/** + * Do something nasty to everyone nearby if they're looking at us. + */ +/datum/action/cooldown/mob_cooldown/watcher_gaze + name = "Disorienting Gaze" + desc = "After a delay, flash everyone looking at you." + button_icon = 'icons/mob/actions/actions_animal.dmi' + button_icon_state = "gaze" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + cooldown_time = 20 SECONDS + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED + click_to_activate = FALSE + shared_cooldown = NONE + /// At what range do we check for vision? + var/effect_radius = 7 + /// How long does it take to play our various animation stages + var/animation_time = 0.8 SECONDS + /// How long after pressing the button do we give people to turn around? + var/wait_delay = 1.6 SECONDS + /// What are we currently displaying? + var/image/current_overlay + /// Timer until we go to the next stage + var/stage_timer + +/datum/action/cooldown/mob_cooldown/watcher_gaze/Activate(mob/living/target) + show_indicator_overlay("eye_open") + stage_timer = addtimer(CALLBACK(src, PROC_REF(show_indicator_overlay), "eye_pulse"), animation_time, TIMER_STOPPABLE) + StartCooldown(360 SECONDS, 360 SECONDS) + owner.visible_message(span_warning("[owner]'s eye glows ominously!")) + if (do_after(owner, delay = wait_delay, target = owner)) + trigger_effect() + else + deltimer(stage_timer) + clear_current_overlay() + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/watcher_gaze/Destroy() + deltimer(stage_timer) + clear_current_overlay() + return ..() + +/datum/action/cooldown/mob_cooldown/watcher_gaze/Remove(mob/removed_from) + deltimer(stage_timer) + clear_current_overlay() + return ..() + +/// Do some effects to whoever is looking at us +/datum/action/cooldown/mob_cooldown/watcher_gaze/proc/trigger_effect() + deltimer(stage_timer) + show_indicator_overlay("eye_flash") + for (var/mob/living/viewer in viewers(effect_radius, owner)) + var/view_dir = get_dir(viewer, owner) + if (!(viewer.dir & view_dir) || viewer.stat != CONSCIOUS) + continue + if (!apply_effect(viewer)) + continue + var/image/flashed_overlay = image( + icon = 'icons/effects/eldritch.dmi', + loc = viewer, + icon_state = "eye_flash", + pixel_x = -viewer.pixel_x, + pixel_y = -viewer.pixel_y, + ) + flick_overlay_global(flashed_overlay, show_to = GLOB.clients, duration = animation_time) + stage_timer = addtimer(CALLBACK(src, PROC_REF(hide_eye)), animation_time, TIMER_STOPPABLE) + var/mob/living/living_owner = owner + living_owner.Stun(1.5 SECONDS, ignore_canstun = TRUE) + +/// Do something bad to someone who was looking at us +/datum/action/cooldown/mob_cooldown/watcher_gaze/proc/apply_effect(mob/living/viewer) + if (!viewer.flash_act(intensity = 4, affect_silicon = TRUE, visual = TRUE, length = 3 SECONDS)) + return FALSE + viewer.set_confusion_if_lower(12 SECONDS) + to_chat(viewer, span_warning("You are blinded by [owner]'s piercing gaze!")) + return TRUE + +/// Animate our effect out +/datum/action/cooldown/mob_cooldown/watcher_gaze/proc/hide_eye() + show_indicator_overlay("eye_close") + stage_timer = addtimer(CALLBACK(src, PROC_REF(clear_current_overlay)), animation_time, TIMER_STOPPABLE) + +/// Display an animated overlay over our head to indicate what's going on +/datum/action/cooldown/mob_cooldown/watcher_gaze/proc/show_indicator_overlay(overlay_state) + clear_current_overlay() + current_overlay = image(icon = 'icons/effects/eldritch.dmi', loc = owner, icon_state = overlay_state, pixel_x = -owner.pixel_x, pixel_y = 28, layer = ABOVE_ALL_MOB_LAYER) + SET_PLANE_EXPLICIT(current_overlay, ABOVE_LIGHTING_PLANE, owner) + for(var/client/add_to in GLOB.clients) + add_to.images += current_overlay + +/// Hide whatever overlay we are showing +/datum/action/cooldown/mob_cooldown/watcher_gaze/proc/clear_current_overlay() + if (!isnull(current_overlay)) + remove_image_from_clients(current_overlay, GLOB.clients) + current_overlay = null + +/// Magmawing glare burns you +/datum/action/cooldown/mob_cooldown/watcher_gaze/fire + name = "Searing Glare" + desc = "After a delay, burn and stun everyone looking at you." + +/datum/action/cooldown/mob_cooldown/watcher_gaze/fire/apply_effect(mob/living/viewer) + to_chat(viewer, span_warning("[owner]'s searing glare forces you to the ground!")) + viewer.Paralyze(3 SECONDS) + viewer.adjust_fire_stacks(10) + viewer.ignite_mob() + return TRUE + +/// Icewing glare freezes you +/datum/action/cooldown/mob_cooldown/watcher_gaze/ice + name = "Cold Stare" + desc = "After a delay, freeze and repulse everyone looking at you." + /// Max distance to throw people looking at us + var/max_throw = 3 + +/datum/action/cooldown/mob_cooldown/watcher_gaze/ice/apply_effect(mob/living/viewer) + to_chat(viewer, span_warning("You are repulsed by the force of [owner]'s cold stare!")) + viewer.apply_status_effect(/datum/status_effect/freon/watcher/extended) + viewer.safe_throw_at( + target = get_edge_target_turf(owner, get_dir(owner, get_step_away(viewer, owner))), + range = max_throw, + speed = 1, + thrower = owner, + force = MOVE_FORCE_EXTREMELY_STRONG, + ) diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm new file mode 100644 index 00000000000..0c8194c524a --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm @@ -0,0 +1,164 @@ +/** + * Automatically shoot at a target if they do anything while this is active on them. + * Currently not given to any mob, but retained so admins can use it. + */ +/datum/action/cooldown/mob_cooldown/watcher_overwatch + name = "Overwatch" + desc = "Keep a close eye on the target's actions, automatically firing upon them if they act." + button_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "eye" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + cooldown_time = 20 SECONDS + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED + click_to_activate = TRUE + shared_cooldown = NONE + /// Furthest range we can activate ability at + var/max_range = 7 + /// Type of projectile to fire + var/projectile_type = /obj/projectile/temp/watcher + /// Sound the projectile we fire makes + var/projectile_sound = 'sound/weapons/pierce.ogg' + /// Time to watch for + var/overwatch_duration = 3 SECONDS + +/datum/action/cooldown/mob_cooldown/watcher_overwatch/New(Target, original) + . = ..() + melee_cooldown_time = overwatch_duration + +/datum/action/cooldown/mob_cooldown/watcher_overwatch/PreActivate(atom/target) + if (target == owner) + return + if (ismecha(target)) + var/obj/vehicle/sealed/mecha/mech = target + var/list/drivers = mech.return_drivers() + if (!length(drivers)) + return + target = drivers[1] + if (!isliving(target)) + return + if (get_dist(owner, target) > max_range) + return + return ..() + +/datum/action/cooldown/mob_cooldown/watcher_overwatch/Activate(mob/living/target) + var/mob/living/living_owner = owner + living_owner.face_atom(target) + living_owner.Stun(overwatch_duration, ignore_canstun = TRUE) + target.apply_status_effect(/datum/status_effect/overwatch, overwatch_duration, owner, projectile_type, projectile_sound) + owner.visible_message(span_warning("[owner]'s eye locks on to [target]!")) + StartCooldown() + return TRUE + +/// Status effect which tracks whether our overwatched mob moves or acts +/datum/status_effect/overwatch + id = "watcher_overwatch" + duration = 5 SECONDS + status_type = STATUS_EFFECT_MULTIPLE + alert_type = /atom/movable/screen/alert/status_effect/overwatch + /// Distance at which we break off the ability + var/watch_range = 9 + /// Visual effect to make the status obvious + var/datum/beam/link + /// Which watcher is watching? + var/mob/living/watcher + /// Type of projectile to fire + var/projectile_type + /// Noise to make when we shoot beam + var/projectile_sound + /// Did the overwatch ever trigger during our run? + var/overwatch_triggered = FALSE + /// Signals which trigger a hostile response + var/static/list/forbidden_actions = list( + COMSIG_MOB_ABILITY_FINISHED, + COMSIG_MOB_ATTACK_HAND, + COMSIG_MOB_DROVE_MECH, + COMSIG_MOB_FIRED_GUN, + COMSIG_MOB_ITEM_ATTACK, + COMSIG_MOB_THROW, + COMSIG_MOB_USED_MECH_EQUIPMENT, + COMSIG_MOB_USED_MECH_MELEE, + COMSIG_MOVABLE_MOVED, + ) + +/datum/status_effect/overwatch/on_creation(mob/living/new_owner, set_duration, mob/living/watcher, projectile_type, projectile_sound) + if (isnull(watcher) || isnull(projectile_type)) + return FALSE + if (HAS_TRAIT(new_owner, TRAIT_OVERWATCH_IMMUNE)) + return FALSE + src.watcher = watcher + src.projectile_type = projectile_type + src.projectile_sound = projectile_sound + if (!isnull(set_duration)) + duration = set_duration + return ..() + +/datum/status_effect/overwatch/on_apply() + . = ..() + if (!.) + return FALSE + owner.add_traits(list(TRAIT_OVERWATCHED, TRAIT_OVERWATCH_IMMUNE), TRAIT_STATUS_EFFECT(id)) + owner.do_alert_animation() + owner.Immobilize(0.25 SECONDS) // Just long enough that they don't trigger it by mistake + owner.playsound_local(owner, 'sound/machines/chime.ogg', 50, TRUE) + var/atom/beam_origin = ismecha(owner.loc) ? owner.loc : owner + link = beam_origin.Beam(watcher, icon_state = "r_beam", override_target_pixel_x = 0) + RegisterSignals(owner, forbidden_actions, PROC_REF(opportunity_attack)) + RegisterSignals(owner, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(on_participant_died)) + RegisterSignals(watcher, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(on_participant_died)) + +/datum/status_effect/overwatch/on_remove() + UnregisterSignal(owner, forbidden_actions + list(COMSIG_QDELETING, COMSIG_LIVING_DEATH)) + QDEL_NULL(link) + owner.remove_traits(list(TRAIT_OVERWATCHED, TRAIT_OVERWATCH_IMMUNE), TRAIT_STATUS_EFFECT(id)) + if (!QDELETED(owner)) + owner.apply_status_effect(/datum/status_effect/overwatch_immune) + return ..() + +/datum/status_effect/overwatch/Destroy() + QDEL_NULL(link) + if (!isnull(watcher)) // Side effects in Destroy? Well it turns out `on_remove` is also just called on Destroy. But only if the owner isn't deleting. + INVOKE_ASYNC(src, PROC_REF(unregister_watcher), watcher) + watcher = null + + return ..() + +/// Clean up our association with the caster of this ability. +/datum/status_effect/overwatch/proc/unregister_watcher(mob/living/former_overwatcher) + if (!overwatch_triggered) + former_overwatcher.Stun(2 SECONDS, ignore_canstun = TRUE) + UnregisterSignal(former_overwatcher, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH)) + +/// Uh oh, you did something within my threat radius, now we're going to shoot you +/datum/status_effect/overwatch/proc/opportunity_attack() + SIGNAL_HANDLER + if (!can_see(watcher, owner, length = watch_range)) + qdel(src) + return + overwatch_triggered = TRUE + watcher.do_alert_animation() + INVOKE_ASYNC(watcher, TYPE_PROC_REF(/atom/, fire_projectile), projectile_type, owner, projectile_sound) + +/// Can't overwatch you if I don't exist +/datum/status_effect/overwatch/proc/on_participant_died() + SIGNAL_HANDLER + qdel(src) + +/atom/movable/screen/alert/status_effect/overwatch + name = "Overwatched" + desc = "Freeze! You are being watched!" + icon_state = "aimed" + +/// Blocks further applications of the ability for a little while +/datum/status_effect/overwatch_immune + id = "watcher_overwatch_immunity" + duration = 10 SECONDS // To stop watcher tendrils spamming the shit out of you + alert_type = null + +/datum/status_effect/overwatch_immune/on_apply() + . = ..() + ADD_TRAIT(owner, TRAIT_OVERWATCH_IMMUNE, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/overwatch_immune/on_remove() + REMOVE_TRAIT(owner, TRAIT_OVERWATCH_IMMUNE, TRAIT_STATUS_EFFECT(id)) + return ..() diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_projectiles.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_projectiles.dm new file mode 100644 index 00000000000..2680e9aa914 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_projectiles.dm @@ -0,0 +1,37 @@ +/// Chilling projectile, hurts and slows you down +/obj/projectile/temp/watcher + name = "chilling blast" + icon_state = "ice_2" + damage = 10 + damage_type = BURN + armor_flag = ENERGY + temperature = -50 + +/obj/projectile/temp/watcher/on_hit(mob/living/target, blocked = 0) + . = ..() + if (!isliving(target)) + return + apply_status(target) + +/// Apply an additional on-hit effect +/obj/projectile/temp/watcher/proc/apply_status(mob/living/target) + target.apply_status_effect(/datum/status_effect/freezing_blast) + +/// Lava projectile, ignites you +/obj/projectile/temp/watcher/magma_wing + name = "scorching blast" + icon_state = "lava" + damage = 5 + temperature = 200 + +/obj/projectile/temp/watcher/magma_wing/apply_status(mob/living/target) + target.adjust_fire_stacks(0.1) + target.ignite_mob() + +/// Freezing projectile, freezes you +/obj/projectile/temp/watcher/ice_wing + name = "freezing blast" + damage = 5 + +/obj/projectile/temp/watcher/ice_wing/apply_status(mob/living/target) + target.apply_status_effect(/datum/status_effect/freon/watcher) diff --git a/code/modules/mob/living/basic/minebots/minebot.dm b/code/modules/mob/living/basic/minebots/minebot.dm new file mode 100644 index 00000000000..061c9a624f7 --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot.dm @@ -0,0 +1,175 @@ +/mob/living/basic/mining_drone + name = "\improper Nanotrasen minebot" + desc = "The instructions printed on the side read: This is a small robot used to support miners, can be set to search and collect loose ore, or to help fend off wildlife. Insert any type of ore into it to make it start listening to your commands!" + gender = NEUTER + icon = 'icons/mob/silicon/aibots.dmi' + icon_state = "mining_drone" + icon_living = "mining_drone" + basic_mob_flags = DEL_ON_DEATH + status_flags = CANSTUN|CANKNOCKDOWN|CANPUSH + mouse_opacity = MOUSE_OPACITY_ICON + combat_mode = TRUE + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = 0 + health = 125 + maxHealth = 125 + melee_damage_lower = 15 + melee_damage_upper = 15 + obj_damage = 10 + attack_verb_continuous = "drills" + attack_verb_simple = "drill" + attack_sound = 'sound/weapons/circsawhit.ogg' + sentience_type = SENTIENCE_MINEBOT + speak_emote = list("states") + mob_biotypes = MOB_ROBOTIC + death_message = "blows apart!" + light_system = MOVABLE_LIGHT + light_range = 6 + light_on = FALSE + combat_mode = FALSE + ai_controller = /datum/ai_controller/basic_controller/minebot + ///the access card we use to access mining + var/obj/item/card/id/access_card + ///the gun we use to kill + var/obj/item/gun/energy/recharge/kinetic_accelerator/minebot/stored_gun + ///the commands our owner can give us + var/list/pet_commands = list( + /datum/pet_command/idle/minebot, + /datum/pet_command/minebot_ability/light, + /datum/pet_command/minebot_ability/dump, + /datum/pet_command/automate_mining, + /datum/pet_command/free/minebot, + /datum/pet_command/follow, + /datum/pet_command/point_targetting/attack/minebot, + ) + +/mob/living/basic/mining_drone/Initialize(mapload) + . = ..() + + var/static/list/death_drops = list(/obj/effect/decal/cleanable/robot_debris/old) + AddElement(/datum/element/death_drops, death_drops) + add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE), INNATE_TRAIT) + AddElement(/datum/element/footstep, FOOTSTEP_OBJ_ROBOT, 1, -6, sound_vary = TRUE) + AddComponent(\ + /datum/component/tameable,\ + food_types = list(/obj/item/stack/ore),\ + tame_chance = 100,\ + bonus_tame_chance = 5,\ + after_tame = CALLBACK(src, PROC_REF(activate_bot)),\ + ) + + var/datum/action/cooldown/mob_cooldown/minedrone/toggle_light/toggle_light_action = new(src) + var/datum/action/cooldown/mob_cooldown/minedrone/toggle_meson_vision/toggle_meson_vision_action = new(src) + var/datum/action/cooldown/mob_cooldown/minedrone/dump_ore/dump_ore_action = new(src) + toggle_light_action.Grant(src) + toggle_meson_vision_action.Grant(src) + dump_ore_action.Grant(src) + ai_controller.set_blackboard_key(BB_MINEBOT_LIGHT_ABILITY, toggle_light_action) + ai_controller.set_blackboard_key(BB_MINEBOT_DUMP_ABILITY, dump_ore_action) + + stored_gun = new(src) + var/obj/item/implant/radio/mining/comms = new(src) + comms.implant(src) + access_card = new /obj/item/card/id/advanced/gold(src) + SSid_access.apply_trim_to_card(access_card, /datum/id_trim/job/shaft_miner) + + RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(attempt_access)) + +/mob/living/basic/mining_drone/set_combat_mode(new_mode, silent = TRUE) + . = ..() + icon_state = combat_mode ? "mining_drone_offense" : "mining_drone" + balloon_alert(src, "now [combat_mode ? "attacking" : "collecting"]") + +/mob/living/basic/mining_drone/examine(mob/user) + . = ..() + if(health < maxHealth) + if(health >= maxHealth * 0.5) + . += span_warning("[p_They()] look slightly dented.") + else + . += span_boldwarning("[p_They()] look severely dented!") + + if(isnull(stored_gun) || !stored_gun.max_mod_capacity) + return + + . += "[stored_gun.get_remaining_mod_capacity()]% mod capacity remaining." + + for(var/obj/item/borg/upgrade/modkit/modkit as anything in stored_gun.modkits) + . += span_notice("There is \a [modkit] installed, using [modkit.cost]% capacity.") + + +/mob/living/basic/mining_drone/welder_act(mob/living/user, obj/item/welder) + if(user.combat_mode) + return FALSE + if(combat_mode) + user.balloon_alert(user, "can't repair in attack mode!") + return TRUE + if(maxHealth == health) + user.balloon_alert(user, "at full integrity!") + return TRUE + if(welder.use_tool(src, user, 0, volume=40)) + adjustBruteLoss(-15) + user.balloon_alert(user, "successfully repaired!") + return TRUE + +/mob/living/basic/mining_drone/attackby(obj/item/item_used, mob/user, params) + if(item_used.tool_behaviour == TOOL_CROWBAR || istype(item_used, /obj/item/borg/upgrade/modkit)) + item_used.melee_attack_chain(user, stored_gun, params) + return + + return ..() + +/mob/living/basic/mining_drone/attack_hand(mob/living/carbon/human/user, list/modifiers) + . = ..() + + if(. || user.combat_mode) + return + set_combat_mode(!combat_mode) + balloon_alert(user, "now [combat_mode ? "attacking wildlife" : "collecting loose ore"]") + +/mob/living/basic/mining_drone/RangedAttack(atom/target) + if(!combat_mode) + return + stored_gun.afterattack(target, src) + + +/mob/living/basic/mining_drone/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) + . = ..() + + if(!. || !proximity_flag || combat_mode) + return + + if(istype(attack_target, /obj/item/stack/ore)) + var/obj/item/target_ore = attack_target + target_ore.forceMove(src) + +/mob/living/basic/mining_drone/proc/drop_ore() + to_chat(src, span_notice("You dump your stored ore.")) + for(var/obj/item/stack/ore/dropped_item in contents) + dropped_item.forceMove(get_turf(src)) + +/mob/living/basic/mining_drone/proc/attempt_access(mob/drone, obj/door_attempt) + SIGNAL_HANDLER + + if(door_attempt.check_access(access_card)) + return ACCESS_ALLOWED + return ACCESS_DISALLOWED + +/mob/living/basic/mining_drone/proc/activate_bot() + AddComponent(/datum/component/obeys_commands, pet_commands) + +/mob/living/basic/mining_drone/death(gibbed) + drop_ore() + + if(isnull(stored_gun)) + return ..() + + for(var/obj/item/borg/upgrade/modkit/modkit as anything in stored_gun.modkits) + modkit.uninstall(stored_gun) + + return ..() + +/mob/living/basic/mining_drone/Destroy() + QDEL_NULL(stored_gun) + QDEL_NULL(access_card) + return ..() + diff --git a/code/modules/mob/living/basic/minebots/minebot_abilities.dm b/code/modules/mob/living/basic/minebots/minebot_abilities.dm new file mode 100644 index 00000000000..4f119fd9b66 --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot_abilities.dm @@ -0,0 +1,51 @@ + +/datum/action/cooldown/mob_cooldown/minedrone + button_icon = 'icons/mob/actions/actions_mecha.dmi' + background_icon_state = "bg_default" + overlay_icon_state = "bg_default_border" + click_to_activate = FALSE + +/datum/action/cooldown/mob_cooldown/minedrone/toggle_light + name = "Toggle Light" + button_icon_state = "mech_lights_off" + +/datum/action/cooldown/mob_cooldown/minedrone/Activate() + owner.set_light_on(!owner.light_on) + owner.balloon_alert(owner, "lights [owner.light_on ? "on" : "off"]!") + +/datum/action/cooldown/mob_cooldown/minedrone/dump_ore + name = "Dump Ore" + button_icon_state = "mech_eject" + +/datum/action/cooldown/mob_cooldown/minedrone/dump_ore/IsAvailable(feedback = TRUE) + if(locate(/obj/item/stack/ore) in owner.contents) + return TRUE + + if(feedback) + owner.balloon_alert(owner, "no ore!") + return FALSE + +/datum/action/cooldown/mob_cooldown/minedrone/dump_ore/Activate() + var/mob/living/basic/mining_drone/user = owner + user.drop_ore() + +/datum/action/cooldown/mob_cooldown/minedrone/toggle_meson_vision + name = "Toggle Meson Vision" + button_icon_state = "meson" + +/datum/action/cooldown/mob_cooldown/minedrone/toggle_meson_vision/Activate() + if(owner.sight & SEE_TURFS) + owner.clear_sight(SEE_TURFS) + owner.lighting_cutoff_red += 5 + owner.lighting_cutoff_green += 15 + owner.lighting_cutoff_blue += 5 + else + owner.add_sight(SEE_TURFS) + owner.lighting_cutoff_red -= 5 + owner.lighting_cutoff_green -= 15 + owner.lighting_cutoff_blue -= 5 + + owner.sync_lighting_plane_cutoff() + + to_chat(owner, span_notice("You toggle your meson vision [(owner.sight & SEE_TURFS) ? "on" : "off"].")) + diff --git a/code/modules/mob/living/basic/minebots/minebot_ai.dm b/code/modules/mob/living/basic/minebots/minebot_ai.dm new file mode 100644 index 00000000000..a4b082f5dd1 --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot_ai.dm @@ -0,0 +1,218 @@ +/datum/ai_controller/basic_controller/minebot + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends, + BB_BLACKLIST_MINERAL_TURFS = list(/turf/closed/mineral/gibtonite), + BB_AUTOMATED_MINING = FALSE, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot, + /datum/ai_planning_subtree/find_and_hunt_target/consume_ores/minebot, + /datum/ai_planning_subtree/minebot_mining, + /datum/ai_planning_subtree/locate_dead_humans, + ) + +///find dead humans and report their location on the radio +/datum/ai_planning_subtree/locate_dead_humans/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_NEARBY_DEAD_MINER)) + controller.queue_behavior(/datum/ai_behavior/send_sos_message, BB_NEARBY_DEAD_MINER) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_and_set/unconscious_human, BB_NEARBY_DEAD_MINER, /mob/living/carbon/human) + +/datum/ai_behavior/find_and_set/unconscious_human/search_tactic(datum/ai_controller/controller, locate_path, search_range) + for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) + if(target.stat >= UNCONSCIOUS && target.mind) + return target + +/datum/ai_behavior/send_sos_message + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + action_cooldown = 2 MINUTES + +/datum/ai_behavior/send_sos_message/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/carbon/target = controller.blackboard[target_key] + var/mob/living/living_pawn = controller.pawn + if(QDELETED(target) || is_station_level(target.z)) + finish_action(controller, FALSE, target_key) + return + var/turf/target_turf = get_turf(target) + var/obj/item/implant/radio/radio_implant = locate(/obj/item/implant/radio) in living_pawn.contents + if(!radio_implant) + finish_action(controller, FALSE, target_key) + return + var/message = "ALERT, [target] in need of help at coordinates: [target_turf.x], [target_turf.y], [target_turf.z]!" + radio_implant.radio.talk_into(living_pawn, message, RADIO_CHANNEL_SUPPLY) + finish_action(controller, TRUE, target_key) + +/datum/ai_behavior/send_sos_message/finish_action(datum/ai_controller/controller, success, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + +///operational datums is null because we dont use a ranged component, we use a gun in our contents +/datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot + operational_datums = null + ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/minebot + +/datum/ai_behavior/basic_ranged_attack/minebot + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT + avoid_friendly_fire = TRUE + +/datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(!living_pawn.combat_mode) //we are not on attack mode + return + return ..() + +///mine walls if we are on automated mining mode +/datum/ai_planning_subtree/minebot_mining/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard[BB_AUTOMATED_MINING]) + return + if(controller.blackboard_key_exists(BB_TARGET_MINERAL_TURF)) + controller.queue_behavior(/datum/ai_behavior/minebot_mine_turf, BB_TARGET_MINERAL_TURF) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_mineral_wall/minebot, BB_TARGET_MINERAL_TURF) + +/datum/ai_behavior/find_mineral_wall/minebot + +/datum/ai_behavior/find_mineral_wall/minebot/check_if_mineable(datum/ai_controller/controller, turf/target_wall) + var/list/forbidden_turfs = controller.blackboard[BB_BLACKLIST_MINERAL_TURFS] + var/turf/previous_unreachable_wall = controller.blackboard[BB_PREVIOUS_UNREACHABLE_WALL] + if(is_type_in_list(target_wall, forbidden_turfs) || target_wall == previous_unreachable_wall) + return FALSE + controller.clear_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL) + return ..() + +/datum/ai_behavior/minebot_mine_turf + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + required_distance = 2 + action_cooldown = 3 SECONDS + +/datum/ai_behavior/minebot_mine_turf/setup(datum/ai_controller/controller, target_key) + . = ..() + var/turf/target = controller.blackboard[target_key] + if(isnull(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/minebot_mine_turf/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/basic/living_pawn = controller.pawn + var/turf/target = controller.blackboard[target_key] + + if(QDELETED(target)) + finish_action(controller, FALSE, target_key) + return + + if(check_obstacles_in_path(controller, target)) + finish_action(controller, FALSE, target_key) + return + + if(!living_pawn.combat_mode) + living_pawn.set_combat_mode(TRUE) + + living_pawn.RangedAttack(target) + finish_action(controller, TRUE, target_key) + return + +/datum/ai_behavior/minebot_mine_turf/proc/check_obstacles_in_path(datum/ai_controller/controller, turf/target) + var/mob/living/source = controller.pawn + var/list/turfs_in_path = get_line(source, target) - target + for(var/turf/turf in turfs_in_path) + if(turf.is_blocked_turf(ignore_atoms = list(source))) + controller.set_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL, target) + return TRUE + return FALSE + +/datum/ai_behavior/minebot_mine_turf/finish_action(datum/ai_controller/controller, success, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + +///store ores in our body +/datum/ai_planning_subtree/find_and_hunt_target/consume_ores/minebot + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores/minebot + hunt_chance = 100 + +/datum/ai_planning_subtree/find_and_hunt_target/consume_ores/minebot/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/automated_mining = controller.blackboard[BB_AUTOMATED_MINING] + var/mob/living/living_pawn = controller.pawn + + if(!automated_mining && living_pawn.combat_mode) //are we not on automated mining or collect mode? + return + + return ..() + +/datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores/minebot + hunt_cooldown = 2 SECONDS + +/datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores/minebot/target_caught(mob/living/hunter, obj/item/stack/ore/hunted) + if(hunter.combat_mode) + hunter.set_combat_mode(FALSE) + return ..() + +///pet commands +/datum/pet_command/free/minebot + +/datum/pet_command/free/minebot/execute_action(datum/ai_controller/controller) + controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE) + return ..() + +/datum/pet_command/automate_mining + command_name = "Automate mining" + command_desc = "Make your minebot automatically mine!" + radial_icon = 'icons/obj/mining.dmi' + radial_icon_state = "pickaxe" + speech_commands = list("mine") + +/datum/pet_command/automate_mining/execute_action(datum/ai_controller/controller) + controller.set_blackboard_key(BB_AUTOMATED_MINING, TRUE) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + +/datum/pet_command/minebot_ability + command_name = "Minebot ability" + command_desc = "Make your minebot use one of its abilities." + radial_icon = 'icons/mob/actions/actions_mecha.dmi' + ///the ability we will use + var/ability_key + +/datum/pet_command/minebot_ability/execute_action(datum/ai_controller/controller) + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + if(!ability?.IsAvailable()) + return + controller.queue_behavior(/datum/ai_behavior/use_mob_ability, ability_key) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/pet_command/minebot_ability/light + command_name = "Toggle lights" + command_desc = "Make your minebot toggle its lights." + speech_commands = list("light") + radial_icon_state = "mech_lights_off" + ability_key = BB_MINEBOT_LIGHT_ABILITY + +/datum/pet_command/minebot_ability/dump + command_name = "Dump ore" + command_desc = "Make your minebot dump all its ore!" + speech_commands = list("dump", "ore") + radial_icon_state = "mech_eject" + ability_key = BB_MINEBOT_DUMP_ABILITY + +/datum/pet_command/point_targetting/attack/minebot + attack_behaviour = /datum/ai_behavior/basic_ranged_attack/minebot + +/datum/pet_command/point_targetting/attack/minebot/execute_action(datum/ai_controller/controller) + controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE) + var/mob/living/living_pawn = controller.pawn + if(!living_pawn.combat_mode) + living_pawn.set_combat_mode(TRUE) + return ..() + +/datum/pet_command/idle/minebot + +/datum/pet_command/idle/minebot/execute_action(datum/ai_controller/controller) + controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE) + return ..() diff --git a/code/modules/mob/living/basic/minebots/minebot_upgrades.dm b/code/modules/mob/living/basic/minebots/minebot_upgrades.dm new file mode 100644 index 00000000000..6f5d43af1af --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot_upgrades.dm @@ -0,0 +1,60 @@ +/obj/item/mine_bot_upgrade + name = "minebot melee upgrade" + desc = "A minebot upgrade." + icon_state = "door_electronics" + icon = 'icons/obj/assemblies/module.dmi' + +/obj/item/mine_bot_upgrade/afterattack(mob/living/basic/mining_drone/minebot, mob/user, proximity) + . = ..() + if(!istype(minebot) || !proximity) + return + upgrade_bot(minebot, user) + +/obj/item/mine_bot_upgrade/proc/upgrade_bot(mob/living/basic/mining_drone/minebot, mob/user) + if(minebot.melee_damage_upper != initial(minebot.melee_damage_upper)) + user.balloon_alert(user, "already has armor!") + return + minebot.melee_damage_lower += 7 + minebot.melee_damage_upper += 7 + to_chat(user, span_notice("You increase the close-quarter combat abilities of [minebot].")) + qdel(src) + +//Health + +/obj/item/mine_bot_upgrade/health + name = "minebot armor upgrade" + +/obj/item/mine_bot_upgrade/health/upgrade_bot(mob/living/basic/mining_drone/minebot, mob/user) + if(minebot.maxHealth != initial(minebot.maxHealth)) + to_chat(user, span_warning("[minebot] already has reinforced armor!")) + return + minebot.maxHealth += 45 + minebot.updatehealth() + to_chat(user, span_notice("You reinforce the armor of [minebot].")) + qdel(src) + +//AI + +/obj/item/slimepotion/slime/sentience/mining + name = "minebot AI upgrade" + desc = "Can be used to grant sentience to minebots. It's incompatible with minebot armor and melee upgrades, and will override them." + icon_state = "door_electronics" + icon = 'icons/obj/assemblies/module.dmi' + sentience_type = SENTIENCE_MINEBOT + ///health boost to add + var/base_health_add = 5 + ///damage boost to add + var/base_damage_add = 1 + ///speed boost to add + var/base_speed_add = 1 + ///cooldown boost to add + var/base_cooldown_add = 10 + +/obj/item/slimepotion/slime/sentience/mining/after_success(mob/living/user, mob/living/basic_mob) + if(!istype(basic_mob, /mob/living/basic/mining_drone)) + return + var/mob/living/basic/mining_drone/minebot = basic_mob + minebot.maxHealth = initial(minebot.maxHealth) + base_health_add + minebot.melee_damage_lower = initial(minebot.melee_damage_lower) + base_damage_add + minebot.melee_damage_upper = initial(minebot.melee_damage_upper) + base_damage_add + minebot.stored_gun?.recharge_time += base_cooldown_add diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm b/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm new file mode 100644 index 00000000000..47e43704079 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm @@ -0,0 +1,123 @@ +/mob/living/basic/eyeball + name = "eyeball" + desc = "An odd looking creature, it won't stop staring..." + icon = 'icons/mob/simple/carp.dmi' + icon_state = "eyeball" + icon_living = "eyeball" + icon_gib = "" + gender = NEUTER + gold_core_spawnable = HOSTILE_SPAWN + basic_mob_flags = DEL_ON_DEATH + gender = NEUTER + mob_biotypes = MOB_ORGANIC + + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently pushes aside" + response_disarm_simple = "gently push aside" + + maxHealth = 30 + health = 30 + obj_damage = 10 + melee_damage_lower = 8 + melee_damage_upper = 12 + + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + + faction = list(FACTION_SPOOKY) + speak_emote = list("telepathically cries") + + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = T0C + maximum_survivable_temperature = T0C + 1500 + sight = SEE_SELF|SEE_MOBS|SEE_OBJS|SEE_TURFS + + lighting_cutoff_red = 40 + lighting_cutoff_green = 20 + lighting_cutoff_blue = 30 + + ai_controller = /datum/ai_controller/basic_controller/eyeball + ///how much we will heal eyes + var/healing_factor = 3 + /// is this eyeball crying? + var/crying = FALSE + /// the crying overlay we add when is hit + var/mutable_appearance/on_hit_overlay + + ///cooldown to heal eyes + COOLDOWN_DECLARE(eye_healing) + +/mob/living/basic/eyeball/Initialize(mapload) + . = ..() + var/datum/action/cooldown/spell/pointed/death_glare/glare = new(src) + glare.Grant(src) + ai_controller.set_blackboard_key(BB_GLARE_ABILITY, glare) + AddElement(/datum/element/simple_flying) + AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/carrot), tame_chance = 100, after_tame = CALLBACK(src, PROC_REF(on_tame))) + ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + on_hit_overlay = mutable_appearance(icon, "[icon_state]_crying") + +/mob/living/basic/eyeball/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) + . = ..() + if(!.) + return + + if(!proximity_flag) + return + + if(istype(attack_target, /obj/item/food/grown/carrot)) + adjustBruteLoss(-5) + to_chat(src, span_warning("You eat [attack_target]! It restores some health!")) + qdel(attack_target) + return TRUE + +/mob/living/basic/eyeball/attackby(obj/item/weapon, mob/living/carbon/human/user, list/modifiers) + . = ..() + if(!weapon.force && !user.combat_mode) + return + if(crying) + return + change_crying_state() + addtimer(CALLBACK(src, PROC_REF(change_crying_state)), 10 SECONDS) //cry for 10 seconds then remove + +/mob/living/basic/eyeball/proc/change_crying_state() + crying = !crying + if(crying) + add_overlay(on_hit_overlay) + return + cut_overlay(on_hit_overlay) + + +/mob/living/basic/eyeball/proc/pre_attack(mob/living/eyeball, atom/target) + SIGNAL_HANDLER + + if(!ishuman(target)) + return + + var/mob/living/carbon/human_target = target + var/obj/item/organ/internal/eyes/eyes = human_target.get_organ_slot(ORGAN_SLOT_EYES) + if(!eyes) + return + if(eyes.damage < 10) + return + heal_eye_damage(human_target, eyes) + return COMPONENT_HOSTILE_NO_ATTACK + + +/mob/living/basic/eyeball/proc/heal_eye_damage(mob/living/target, obj/item/organ/internal/eyes/eyes) + if(!COOLDOWN_FINISHED(src, eye_healing)) + return + to_chat(target, span_warning("[src] seems to be healing your [eyes.zone]!")) + eyes.apply_organ_damage(-1 * healing_factor) + new /obj/effect/temp_visual/heal(get_turf(target), COLOR_HEALING_CYAN) + befriend(target) + COOLDOWN_START(src, eye_healing, 15 SECONDS) + +/mob/living/basic/eyeball/proc/on_tame(mob/tamer) + spin(spintime = 2 SECONDS, speed = 1) + //become passive to the humens + faction |= tamer.faction diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ability.dm b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ability.dm new file mode 100644 index 00000000000..734c385cf72 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ability.dm @@ -0,0 +1,40 @@ +/datum/action/cooldown/spell/pointed/death_glare + name = "death glare" + desc = "give a death stare to the victim" + var/glare_outline = COLOR_DARK_RED + spell_requirements = NONE + cooldown_time = 10 SECONDS + +/datum/action/cooldown/spell/pointed/death_glare/is_valid_target(atom/cast_on) + if(!isliving(cast_on)) + to_chat(owner, span_warning("Only living things are affected by our glare!")) + return FALSE + var/mob/living/living_target = cast_on + if(living_target.has_movespeed_modifier(/datum/movespeed_modifier/glare_slowdown)) + to_chat(owner, span_warning("This target is already affected by a glare!")) + return FALSE + if(!can_see(living_target, owner, 9)) + to_chat(owner, span_warning("This target cannot see our glare!")) + return FALSE + var/direction_to_compare = get_dir(living_target, owner) + var/target_direction = living_target.dir + if(direction_to_compare != target_direction) + to_chat(owner, span_warning("This target is facing away from us!")) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/pointed/death_glare/cast(mob/living/cast_on) + . = ..() + cast_on.add_filter("glare", 2, list("type" = "outline", "color" = glare_outline, "size" = 1)) + cast_on.add_movespeed_modifier(/datum/movespeed_modifier/glare_slowdown) + to_chat(cast_on, span_warning("You feel something watching you...")) + addtimer(CALLBACK(src, PROC_REF(remove_effect), cast_on), 5 SECONDS) + return TRUE + +/datum/action/cooldown/spell/pointed/death_glare/proc/remove_effect(mob/living/cast_on) + cast_on.remove_movespeed_modifier(/datum/movespeed_modifier/glare_slowdown) + cast_on.remove_filter("glare") + +/datum/movespeed_modifier/glare_slowdown + multiplicative_slowdown = 3 diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm new file mode 100644 index 00000000000..57ea39c94dd --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm @@ -0,0 +1,94 @@ +/datum/ai_behavior/find_the_blind + +/datum/ai_behavior/find_the_blind/perform(seconds_per_tick, datum/ai_controller/controller, blind_key, threshold_key) + . = ..() + + var/mob/living_pawn = controller.pawn + var/list/blind_list = list() + var/eye_damage_threshold = controller.blackboard[threshold_key] + if(!eye_damage_threshold) + finish_action(controller, FALSE) + return + for(var/mob/living/carbon/blind in oview(9, living_pawn)) + var/obj/item/organ/internal/eyes/eyes = blind.get_organ_slot(ORGAN_SLOT_EYES) + if(isnull(eyes)) + continue + if(eyes.damage < eye_damage_threshold) + continue + blind_list += blind + + if(!length(blind_list)) + finish_action(controller, FALSE) + return + + controller.set_blackboard_key(blind_key, pick(blind_list)) + finish_action(controller, TRUE) + +/datum/ai_behavior/heal_eye_damage + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH + +/datum/ai_behavior/heal_eye_damage/setup(datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/carbon/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/heal_eye_damage/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + + var/mob/living/carbon/target = controller.blackboard[target_key] + var/mob/living/living_pawn = controller.pawn + + if(QDELETED(target)) + finish_action(controller, FALSE, target_key) + return + var/obj/item/organ/internal/eyes/eyes = target.get_organ_slot(ORGAN_SLOT_EYES) + var/datum/callback/callback = CALLBACK(living_pawn, TYPE_PROC_REF(/mob/living/basic/eyeball, heal_eye_damage), target, eyes) + callback.Invoke() + + finish_action(controller, TRUE, target_key) + +/datum/ai_behavior/heal_eye_damage/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + +/datum/ai_behavior/targeted_mob_ability/glare_at_target + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT + required_distance = 0 + +/datum/ai_behavior/targeted_mob_ability/glare_at_target/setup(datum/ai_controller/controller, ability_key, target_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if (isnull(target)) + return FALSE + + var/turf/turf_to_move_towards = get_step(target, target.dir) + if(turf_to_move_towards.is_blocked_turf(ignore_atoms = list(controller.pawn))) + return FALSE + + if(isnull(turf_to_move_towards)) + return FALSE + + set_movement_target(controller, turf_to_move_towards) + +/datum/ai_behavior/targeted_mob_ability/glare_at_target/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + var/mob/living/target = controller.blackboard[target_key] + + if(QDELETED(ability) || QDELETED(target)) + finish_action(controller, FALSE, ability_key, target_key) + return + + var/direction_to_compare = get_dir(target, controller.pawn) + var/target_direction = target.dir + if(direction_to_compare != target_direction) + finish_action(controller, FALSE, ability_key, target_key) + return + + var/result = ability.InterceptClickOn(controller.pawn, null, target) + finish_action(controller, result, ability_key, target_key) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/carrot + hunt_cooldown = 2 SECONDS + always_reset_target = TRUE diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm new file mode 100644 index 00000000000..a3c8e22071d --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm @@ -0,0 +1,52 @@ +/datum/ai_controller/basic_controller/eyeball + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/eyeball, + BB_EYE_DAMAGE_THRESHOLD = 10, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability/glare, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/heal_the_blind, + /datum/ai_planning_subtree/find_and_hunt_target/carrot, + ) + +/datum/ai_planning_subtree/heal_the_blind + +/datum/ai_planning_subtree/heal_the_blind/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_BLIND_TARGET)) + controller.queue_behavior(/datum/ai_behavior/heal_eye_damage, BB_BLIND_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_the_blind, BB_BLIND_TARGET, BB_EYE_DAMAGE_THRESHOLD) + +/datum/targetting_datum/basic/eyeball/can_attack(mob/living/owner, atom/target) + . = ..() + if(!.) + return FALSE + if(!ishuman(target)) + return TRUE + var/mob/living/carbon/human_target = target + if(human_target.is_blind()) + return FALSE + var/eye_damage_threshold = owner.ai_controller.blackboard[BB_EYE_DAMAGE_THRESHOLD] + if(!eye_damage_threshold) + return TRUE + var/obj/item/organ/internal/eyes/eyes = human_target.get_organ_slot(ORGAN_SLOT_EYES) + if(eyes.damage > eye_damage_threshold) //we dont attack people with bad vision + return FALSE + + return can_see(target, owner, 9) //if the target cant see us dont attack him + +/datum/ai_planning_subtree/targeted_mob_ability/glare + ability_key = BB_GLARE_ABILITY + use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/glare_at_target + finish_planning = TRUE + +/datum/ai_planning_subtree/find_and_hunt_target/carrot + target_key = BB_LOW_PRIORITY_HUNTING_TARGET + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/carrot + hunt_targets = list(/obj/item/food/grown/carrot) + hunt_range = 6 diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/_hivebot.dm b/code/modules/mob/living/basic/space_fauna/hivebot/_hivebot.dm new file mode 100644 index 00000000000..db0d310a71c --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/hivebot/_hivebot.dm @@ -0,0 +1,143 @@ +/mob/living/basic/hivebot + name = "hivebot" + desc = "A small robot." + icon = 'icons/mob/simple/hivebot.dmi' + icon_state = "basic" + icon_living = "basic" + icon_dead = "basic" + basic_mob_flags = DEL_ON_DEATH + gender = NEUTER + mob_biotypes = MOB_ROBOTIC + + health = 15 + maxHealth = 15 + melee_damage_lower = 2 + melee_damage_upper = 3 + + attack_verb_continuous = "claws" + attack_verb_simple = "claw" + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_CLAW + verb_say = "states" + verb_ask = "queries" + verb_exclaim = "declares" + verb_yell = "alarms" + bubble_icon = "machine" + + faction = list(FACTION_HIVEBOT) + combat_mode = TRUE + speech_span = SPAN_ROBOT + death_message = "blows apart!" + + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = TCMB + ai_controller = /datum/ai_controller/basic_controller/hivebot + ///does this type do range attacks? + var/ranged_attacker = FALSE + /// How often can we shoot? + var/ranged_attack_cooldown = 3 SECONDS + + +/mob/living/basic/hivebot/Initialize(mapload) + . = ..() + var/static/list/death_loot = list(/obj/effect/decal/cleanable/robot_debris) + AddElement(/datum/element/death_drops, death_loot) + AddComponent(/datum/component/appearance_on_aggro, overlay_icon = icon, overlay_state = "[initial(icon_state)]_attack") + if(!ranged_attacker) + return + AddComponent(/datum/component/ranged_attacks, /obj/item/ammo_casing/hivebot, cooldown_time = ranged_attack_cooldown) + +/mob/living/basic/hivebot/death(gibbed) + do_sparks(number = 3, cardinal_only = TRUE, source = src) + return ..() + +/mob/living/basic/hivebot/range + name = "hivebot" + desc = "A smallish robot, this one is armed!" + icon_state = "ranged" + icon_living = "ranged" + icon_dead = "ranged" + ranged_attacker = TRUE + ai_controller = /datum/ai_controller/basic_controller/hivebot/ranged + +/mob/living/basic/hivebot/rapid + icon_state = "ranged" + icon_living = "ranged" + icon_dead = "ranged" + ranged_attacker = TRUE + ai_controller = /datum/ai_controller/basic_controller/hivebot/ranged/rapid + ranged_attack_cooldown = 1.5 SECONDS + +/mob/living/basic/hivebot/strong + name = "strong hivebot" + icon_state = "strong" + icon_living = "strong" + icon_dead = "strong" + desc = "A robot, this one is armed and looks tough!" + health = 80 + maxHealth = 80 + ranged_attacker = TRUE + ai_controller = /datum/ai_controller/basic_controller/hivebot/ranged + +/mob/living/basic/hivebot/mechanic + name = "hivebot mechanic" + icon_state = "strong" + icon_living = "strong" + icon_dead = "strong" + desc = "A robot built for base upkeep, intended for use inside hivebot colonies." + health = 60 + maxHealth = 60 + gold_core_spawnable = HOSTILE_SPAWN + ranged_attacker = TRUE + ai_controller = /datum/ai_controller/basic_controller/hivebot/mechanic + ///cooldown to repair machines + COOLDOWN_DECLARE(repair_cooldown) + +/mob/living/basic/hivebot/mechanic/Initialize(mapload) + . = ..() + var/datum/action/cooldown/spell/conjure/foam_wall/foam = new(src) + foam.Grant(src) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + +/mob/living/basic/hivebot/mechanic/proc/pre_attack(mob/living/fixer, atom/target) + SIGNAL_HANDLER + + if(ismachinery(target)) + repair_machine(target) + return COMPONENT_HOSTILE_NO_ATTACK + + if(istype(target, /mob/living/basic/hivebot)) + repair_hivebot(target) + return COMPONENT_HOSTILE_NO_ATTACK + +/mob/living/basic/hivebot/mechanic/proc/repair_machine(obj/machinery/fixable) + if(fixable.get_integrity() >= fixable.max_integrity) + to_chat(src, span_warning("Diagnostics indicate that this machine is at peak integrity.")) + return + if(!COOLDOWN_FINISHED(src, repair_cooldown)) + balloon_alert(src, "recharging!") + return + fixable.repair_damage(fixable.max_integrity - fixable.get_integrity()) + do_sparks(number = 3, cardinal_only = TRUE, source = fixable) + to_chat(src, span_warning("Repairs complete!")) + COOLDOWN_START(src, repair_cooldown, 50 SECONDS) + +/mob/living/basic/hivebot/mechanic/proc/repair_hivebot(mob/living/basic/bot_target) + if(bot_target.health >= bot_target.maxHealth) + to_chat(src, span_warning("Diagnostics indicate that this unit is at peak integrity.")) + return + if(!COOLDOWN_FINISHED(src, repair_cooldown)) + balloon_alert(src, "recharging!") + return + bot_target.revive(HEAL_ALL) + do_sparks(number = 3, cardinal_only = TRUE, source = bot_target) + to_chat(src, span_warning("Repairs complete!")) + COOLDOWN_START(src, repair_cooldown, 50 SECONDS) + +/obj/item/ammo_casing/hivebot + name = "hivebot bullet casing" + projectile_type = /obj/projectile/hivebotbullet + +/obj/projectile/hivebotbullet + damage = 10 + damage_type = BRUTE diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm new file mode 100644 index 00000000000..28cffa4ed8e --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm @@ -0,0 +1,71 @@ +/datum/ai_behavior/find_and_set/hive_partner + +/datum/ai_behavior/find_and_set/hive_partner/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/mob/living/living_pawn = controller.pawn + var/list/hive_partners = list() + for(var/mob/living/target in oview(10, living_pawn)) + if(!istype(target, locate_path)) + continue + if(target.stat == DEAD) + continue + hive_partners += target + + if(length(hive_partners)) + return pick(hive_partners) + +/// behavior that allow us to go communicate with other hivebots +/datum/ai_behavior/relay_message + ///length of the message we will relay + var/length_of_message = 4 + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT| AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + + +/datum/ai_behavior/relay_message/setup(datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/target = controller.blackboard[target_key] + // It stopped existing + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target) + + +/datum/ai_behavior/relay_message/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + + var/mob/living/target = controller.blackboard[target_key] + var/mob/living/living_pawn = controller.pawn + + if(QDELETED(target)) + finish_action(controller, FALSE, target_key) + return + var/message_relayed = "" + for(var/i in 1 to length_of_message) + message_relayed += prob(50) ? "1" : "0" + living_pawn.say(message_relayed, forced = "AI Controller") + finish_action(controller, TRUE, target_key) + +/datum/ai_behavior/relay_message/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + +/datum/ai_behavior/find_hunt_target/repair_machines + +/datum/ai_behavior/find_hunt_target/repair_machines/valid_dinner(mob/living/source, obj/machinery/repair_target, radius) + if(repair_target.get_integrity() >= repair_target.max_integrity) + return FALSE + + return can_see(source, repair_target, radius) + +/datum/ai_behavior/hunt_target/repair_machines + always_reset_target = TRUE + +/datum/ai_behavior/hunt_target/repair_machines/target_caught(mob/living/basic/hivebot/mechanic/hunter, obj/machinery/repair_target) + hunter.repair_machine(repair_target) + +/datum/ai_behavior/basic_ranged_attack/hivebot + action_cooldown = 3 SECONDS + avoid_friendly_fire = TRUE + +/datum/ai_behavior/basic_ranged_attack/hivebot_rapid + action_cooldown = 1.5 SECONDS + avoid_friendly_fire = TRUE diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm new file mode 100644 index 00000000000..5bd957a7609 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm @@ -0,0 +1,68 @@ +/datum/ai_controller/basic_controller/hivebot + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/hive_communicate, + ) + +/datum/ai_controller/basic_controller/hivebot/mechanic + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_ranged_attack_subtree, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/find_and_hunt_target/repair_machines, + /datum/ai_planning_subtree/hive_communicate, + ) + +/datum/ai_controller/basic_controller/hivebot/ranged + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/hivebot, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/hive_communicate, + ) + +/datum/ai_controller/basic_controller/hivebot/ranged/rapid + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/hivebot_rapid, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/hive_communicate, + ) + + +/datum/ai_planning_subtree/basic_ranged_attack_subtree/hivebot_rapid + ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/hivebot_rapid + + +/datum/ai_planning_subtree/basic_ranged_attack_subtree/hivebot + ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/hivebot + +/datum/ai_planning_subtree/hive_communicate + ///chance to go and relay message + var/relay_chance = 10 + +/datum/ai_planning_subtree/hive_communicate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + + if(!SPT_PROB(relay_chance, seconds_per_tick)) + return + + if (controller.blackboard_key_exists(BB_HIVE_PARTNER)) + controller.queue_behavior(/datum/ai_behavior/relay_message, BB_HIVE_PARTNER) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_and_set/hive_partner, BB_HIVE_PARTNER, /mob/living/basic/hivebot) + +/datum/ai_planning_subtree/find_and_hunt_target/repair_machines + target_key = BB_MACHINE_TARGET + hunting_behavior = /datum/ai_behavior/hunt_target/repair_machines + finding_behavior = /datum/ai_behavior/find_hunt_target/repair_machines + hunt_targets = list(/obj/machinery) + hunt_range = 10 + hunt_chance = 35 diff --git a/code/modules/mob/living/basic/space_fauna/morph.dm b/code/modules/mob/living/basic/space_fauna/morph.dm new file mode 100644 index 00000000000..32115d05602 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/morph.dm @@ -0,0 +1,214 @@ +/// The classic morph, Corpus Accipientis (or "The body of the recipient"). It's a blob that can disguise itself as other things simply put. +/mob/living/basic/morph + name = "morph" + real_name = "morph" + desc = "A revolting, pulsating pile of flesh." + speak_emote = list("gurgles") + icon = 'icons/mob/simple/animal.dmi' + icon_state = "morph" + icon_living = "morph" + icon_dead = "morph_dead" + combat_mode = TRUE + + mob_biotypes = MOB_BEAST + pass_flags = PASSTABLE + + maxHealth = 150 + health = 150 + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = TCMB + + obj_damage = 50 + melee_damage_lower = 20 + melee_damage_upper = 20 + melee_attack_cooldown = CLICK_CD_MELEE + + // Oh you KNOW it's gonna be real green + lighting_cutoff_red = 10 + lighting_cutoff_green = 35 + lighting_cutoff_blue = 15 + + attack_verb_continuous = "glomps" + attack_verb_simple = "glomp" + attack_sound = 'sound/effects/blobattack.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE //nom nom nom + butcher_results = list(/obj/item/food/meat/slab = 2) + + ai_controller = /datum/ai_controller/basic_controller/morph + + /// A weakref pointing to the form we are currently assumed as. + var/datum/weakref/form_weakref = null + /// A typepath pointing of the form we are currently assumed as. Remember, TYPEPATH!!! + var/atom/movable/form_typepath = null + /// The ability that allows us to disguise ourselves. + var/datum/action/cooldown/mob_cooldown/assume_form/disguise_ability = null + + /// How much damage are we doing while disguised? + var/melee_damage_disguised = 0 + /// Can we eat while disguised? + var/eat_while_disguised = FALSE + +/mob/living/basic/morph/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + RegisterSignal(src, COMSIG_CLICK_SHIFT, PROC_REF(trigger_ability)) + RegisterSignal(src, COMSIG_ACTION_DISGUISED_APPEARANCE, PROC_REF(on_disguise)) + RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_DISGUISED), PROC_REF(on_undisguise)) + + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/content_barfer) + + disguise_ability = new(src) + disguise_ability.Grant(src) + +/mob/living/basic/morph/examine(mob/user) + if(!HAS_TRAIT(src, TRAIT_DISGUISED)) + return ..() + + var/atom/movable/form_reference = form_weakref.resolve() + if(!isnull(form_reference)) + . = form_reference.examine(user) + + if(get_dist(user, src) <= 3) // always add this because if the form_reference somehow nulls out we still want to have something look "weird" about an item when someone is close + . += span_warning("It doesn't look quite right...") + +/mob/living/basic/morph/med_hud_set_health() + if(isliving(form_typepath)) + return ..() + + //we hide medical hud while in regular state or an item + var/image/holder = hud_list[HEALTH_HUD] + holder.icon_state = null + +/mob/living/basic/morph/med_hud_set_status() + if(isliving(form_typepath)) + return ..() + + //we hide medical hud while in regular state or an item + var/image/holder = hud_list[STATUS_HUD] + holder.icon_state = null + +/mob/living/basic/morph/death(gibbed) + if(HAS_TRAIT(src, TRAIT_DISGUISED)) + visible_message( + span_warning("[src] twists and dissolves into a pile of green flesh!"), + span_userdanger("Your skin ruptures! Your flesh breaks apart! No disguise can ward off de--"), + ) + + return ..() + +/mob/living/basic/morph/can_track(mob/living/user) + if(!HAS_TRAIT(src, TRAIT_DISGUISED)) + return FALSE + return ..() + +/// Do some more logic for the morph when we disguise through the action. +/mob/living/basic/morph/proc/on_disguise(mob/living/basic/user, atom/movable/target) + SIGNAL_HANDLER + // We are now weaker + melee_damage_lower = melee_damage_disguised + melee_damage_upper = melee_damage_disguised + add_movespeed_modifier(/datum/movespeed_modifier/morph_disguised) + + med_hud_set_health() + med_hud_set_status() //we're an object honest + + visible_message( + span_warning("[src] suddenly twists and changes shape, becoming a copy of [target]!"), + span_notice("You twist your body and assume the form of [target]."), + ) + + form_weakref = WEAKREF(target) + form_typepath = target.type + +/// Do some more logic for the morph when we undisguise through the action. +/mob/living/basic/morph/proc/on_undisguise() + SIGNAL_HANDLER + visible_message( + span_warning("[src] suddenly collapses in on itself, dissolving into a pile of green flesh!"), + span_notice("You reform to your normal body."), + ) + + //Baseline stats + melee_damage_lower = initial(melee_damage_lower) + melee_damage_upper = initial(melee_damage_upper) + remove_movespeed_modifier(/datum/movespeed_modifier/morph_disguised) + + med_hud_set_health() + med_hud_set_status() //we are no longer an object + + form_weakref = null + form_typepath = null + +/// Alias for the disguise ability to be used as a keybind. +/mob/living/basic/morph/proc/trigger_ability(mob/living/basic/source, atom/target) + SIGNAL_HANDLER + + // linters hate this if it's not async for some reason even though nothing blocks + INVOKE_ASYNC(disguise_ability, TYPE_PROC_REF(/datum/action/cooldown, InterceptClickOn), caller = source, target = target) + return COMSIG_MOB_CANCEL_CLICKON + +/// Handles the logic for attacking anything. +/mob/living/basic/morph/proc/pre_attack(mob/living/basic/source, atom/target) + SIGNAL_HANDLER + + if(HAS_TRAIT(src, TRAIT_DISGUISED) && (melee_damage_disguised <= 0)) + balloon_alert(src, "can't attack while disguised!") + return COMPONENT_HOSTILE_NO_ATTACK + + if(isliving(target)) //Eat Corpses to regen health + var/mob/living/living_target = target + if(living_target.stat != DEAD) + return + + INVOKE_ASYNC(source, PROC_REF(eat), eatable = living_target, delay = 3 SECONDS, update_health = -50) + return COMPONENT_HOSTILE_NO_ATTACK + + if(isitem(target)) //Eat items just to be annoying + var/obj/item/item_target = target + if(item_target.anchored) + return + + INVOKE_ASYNC(source, PROC_REF(eat), eatable = item_target, delay = 2 SECONDS) + return COMPONENT_HOSTILE_NO_ATTACK + +/// Eat stuff. Delicious. Return TRUE if we ate something, FALSE otherwise. +/// Required: `eatable` is the thing (item or mob) that we are going to eat. +/// Optional: `delay` is the applicable time-based delay to pass into `do_after()` before the logic is ran. +/// Optional: `update_health` is an integer that will be added (or maybe subtracted if you're cruel) to our health after we eat something. Passed into `adjust_health()` so make sure what you pass in is accurate. +/mob/living/basic/morph/proc/eat(atom/movable/eatable, delay = 0 SECONDS, update_health = 0) + if(QDELETED(eatable) || eatable.loc == src) + return FALSE + + if(HAS_TRAIT(src, TRAIT_DISGUISED) && !eat_while_disguised) + balloon_alert(src, "can't eat while disguised!") + return FALSE + + balloon_alert(src, "eating...") + if((delay > 0 SECONDS) && !do_after(src, delay, target = eatable)) + return FALSE + + visible_message(span_warning("[src] swallows [eatable] whole!")) + eatable.forceMove(src) + if(update_health != 0) + adjust_health(update_health) + + return TRUE + +/// No fleshed out AI implementation, just something that make these fellers seem lively if they're just dropped into a station. +/// Only real human-powered intelligence is capable of playing prop hunt in SS13 (until further notice). +/datum/ai_controller/basic_controller/morph + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm new file mode 100644 index 00000000000..164c25fb896 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm @@ -0,0 +1,278 @@ +#define REGALRAT_INTERACTION "regalrat" + +/// The cheesiest, most crowned rat of them all. Regent superior of all rats in maintenance... at least until someone else tries to encroach on their claim. +/mob/living/basic/regal_rat + name = "feral regal rat" + desc = "An evolved rat, created through some strange science. They lead nearby rats with deadly efficiency to protect their kingdom." + icon_state = "regalrat" + icon_living = "regalrat" + icon_dead = "regalrat_dead" + gender = MALE + + maxHealth = 70 + health = 70 + + butcher_results = list(/obj/item/food/meat/slab/mouse = 2, /obj/item/clothing/head/costume/crown = 1) + + response_help_continuous = "glares at" + response_help_simple = "glare at" + response_disarm_continuous = "skoffs at" + response_disarm_simple = "skoff at" + response_harm_continuous = "slashes" + response_harm_simple = "slash" + + obj_damage = 10 + melee_damage_lower = 13 + melee_damage_upper = 15 + melee_attack_cooldown = CLICK_CD_MELEE + attack_verb_continuous = "slashes" + attack_verb_simple = "slash" + attack_sound = 'sound/weapons/bladeslice.ogg' + + // Slightly brown red, for the eyes + lighting_cutoff_red = 22 + lighting_cutoff_green = 8 + lighting_cutoff_blue = 5 + + attack_vis_effect = ATTACK_EFFECT_CLAW + unique_name = TRUE + faction = list(FACTION_RAT, FACTION_MAINT_CREATURES) + + ai_controller = /datum/ai_controller/basic_controller/regal_rat + + ///Should we request a mind immediately upon spawning? + var/poll_ghosts = FALSE + /// String tied to our special moniker for examination. Contains a nice message tied to the potential funny regal name we have. + var/special_moniker = "" + +/mob/living/basic/regal_rat/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + RegisterSignal(src, COMSIG_MOB_LOGIN, PROC_REF(on_login)) + + AddElement(/datum/element/waddling) + AddElement(/datum/element/ai_retaliate) + AddComponent(\ + /datum/component/ghost_direct_control,\ + poll_candidates = poll_ghosts,\ + role_name = "the Regal Rat, cheesy be their crown",\ + poll_ignore_key = POLL_IGNORE_REGAL_RAT,\ + assumed_control_message = "You are an independent, invasive force on the station! Hoard coins, trash, cheese, and the like from the safety of darkness!",\ + after_assumed_control = CALLBACK(src, PROC_REF(became_player_controlled)),\ + ) + + var/datum/action/cooldown/mob_cooldown/domain/domain = new(src) + domain.Grant(src) + ai_controller.set_blackboard_key(BB_DOMAIN_ABILITY, domain) + + var/datum/action/cooldown/mob_cooldown/riot/riot = new(src) + riot.Grant(src) + ai_controller.set_blackboard_key(BB_RAISE_HORDE_ABILITY, riot) + +/mob/living/basic/regal_rat/examine(mob/user) + . = ..() + if(user == src) + return + + if(isregalrat(user)) + . += span_warning("Who is this foolish false king? This will not stand!") + return + + if(ismouse(user)) + if(user.faction_check_mob(src, exact_match = TRUE)) + . += span_notice("This is your king. Long live [p_their()] majesty!") + else + . += span_warning("This is a false king! Strike [p_them()] down!") + return + + . += special_moniker + +/mob/living/basic/regal_rat/handle_environment(datum/gas_mixture/environment) + . = ..() + if(stat == DEAD || isnull(environment) || isnull(environment.gases[/datum/gas/miasma])) + return + var/miasma_percentage = environment.gases[/datum/gas/miasma][MOLES] / environment.total_moles() + if(miasma_percentage >= 0.25) + heal_bodypart_damage(1) + +/// Triggers an alert to all ghosts that the rat has become player controlled. +/mob/living/basic/regal_rat/proc/became_player_controlled() + notify_ghosts( + "All rise for [name], ascendant to the throne in \the [get_area(src)].", + source = src, + action = NOTIFY_ORBIT, + flashwindow = FALSE, + header = "Sentient Rat Created", + ) + +/// Supplementary work we do when we login. Done this way so we synchronize with the ai controller shutting off and all that jazz as well as allowing more shit to be passed in if need be in future. +/mob/living/basic/regal_rat/proc/on_login() + SIGNAL_HANDLER + if(!special_moniker) + grant_titles() // all players are special :) + +/// Grants the rat a special name. +/mob/living/basic/regal_rat/proc/grant_titles() + // The title conveyed upon us thanks to our position. + var/static/list/titles = list( + "Bojar", + "Emperor", + "King", + "Lord", + "Master", + "Overlord", + "Prince", + "Shogun", + "Supreme", + "Tsar", + ) + + // The domain which we have conquered by inheritance or sheer force. + var/static/list/kingdoms = list( + "Cheese", + "Garbage", + "Maintenance", + "Miasma", + "Plague", + "Trash", + "Vermin", + ) + + // The descriptor of our character. + var/static/list/descriptors = list( + "Big Cheese", + "Brute", + "Champion of All Mislaid Creatures", + "Foul", + "Great", + "Grey", + "Horrible", + "Populator", + "Powerful", + "Quiet", + "Vain", + ) + + var/selected_title = pick(titles) + var/selected_kingdom = pick(kingdoms) + + name = "[selected_title] [selected_kingdom], the [pick(descriptors)]" // ex "Tsar Maintenance, the Brute" + special_moniker = "You better not screw with [p_their()] [selected_kingdom]... How do you become a [selected_title] of that anyways?" + +/// Checks if we are able to attack this object, as well as send out the signal to see if we get any special regal rat interactions. +/mob/living/basic/regal_rat/proc/pre_attack(mob/living/source, atom/target) + SIGNAL_HANDLER + + if(DOING_INTERACTION(src, REGALRAT_INTERACTION) || !allowed_to_attack(target)) + return COMPONENT_HOSTILE_NO_ATTACK + + if(SEND_SIGNAL(target, COMSIG_RAT_INTERACT, src) & COMPONENT_RAT_INTERACTED) + return COMPONENT_HOSTILE_NO_ATTACK + + if(isnull(mind)) + return + + if(istype(target, /obj/machinery/door/airlock)) + INVOKE_ASYNC(src, PROC_REF(pry_door), target) + return COMPONENT_HOSTILE_NO_ATTACK + + if(!combat_mode) + INVOKE_ASYNC(src, PROC_REF(poison_target), target) + return COMPONENT_HOSTILE_NO_ATTACK + +/// Checks if we are allowed to attack this mob. Will return TRUE if we are potentially allowed to attack, but if we end up in a case where we should NOT attack, return FALSE. +/mob/living/basic/regal_rat/proc/allowed_to_attack(atom/the_target) + if(QDELETED(the_target)) + return FALSE //wat + + if(!isliving(the_target)) + return TRUE // it might be possible to attack this? we'll find out soon enough + + var/mob/living/living_target = the_target + if (HAS_TRAIT(living_target, TRAIT_FAKEDEATH) || living_target.stat == DEAD) + balloon_alert(src, "already dead!") + return FALSE + + if(living_target.faction_check_mob(src, exact_match = TRUE)) + balloon_alert(src, "one of your soldiers!") + return FALSE + + return TRUE + +/// Attempts to add rat spit to a target, effectively poisoning it to whoever eats it. Yuckers. +/mob/living/basic/regal_rat/proc/poison_target(atom/target) + if(isnull(target.reagents) || !target.is_injectable(src, allowmobs = TRUE)) + return + + visible_message( + span_warning("[src] starts licking [target] passionately!"), + span_notice("You start licking [target]..."), + span_warning("You hear a disgusting slurping sound..."), + ) + + if (!do_after(src, 2 SECONDS, target, interaction_key = REGALRAT_INTERACTION)) + return + + target.reagents.add_reagent(/datum/reagent/rat_spit, rand(1,3), no_react = TRUE) + balloon_alert(src, "licked") + +/** + * Conditionally "eat" cheese object and heal, if injured. + * + * A private proc for sending a message to the mob's chat about them + * eating some sort of cheese, then healing them, then deleting the cheese. + * The "eating" is only conditional on the mob being injured in the first + * place. + */ +/mob/living/basic/regal_rat/proc/cheese_heal(obj/item/target, amount, message) + if(health >= maxHealth) + balloon_alert(src, "you feel full!") + return + + to_chat(src, message) + heal_bodypart_damage(amount) + qdel(target) + +/** + * Allows rat king to pry open an airlock if it isn't locked. + * + * A proc used for letting the rat king pry open airlocks instead of just attacking them. + * This allows the rat king to traverse the station when there is a lack of vents or + * accessible doors, something which is common in certain rat king spawn points. + * + * Returns TRUE if the door opens, FALSE otherwise. + */ +/mob/living/basic/regal_rat/proc/pry_door(target) + if(DOING_INTERACTION(src, REGALRAT_INTERACTION)) + return FALSE + + var/obj/machinery/door/airlock/prying_door = target + if(!prying_door.density || prying_door.locked || prying_door.welded || prying_door.seal) + return FALSE + + visible_message( + span_warning("[src] begins prying open the airlock..."), + span_notice("You begin digging your claws into the airlock..."), + span_warning("You hear groaning metal..."), + ) + var/time_to_open = 0.5 SECONDS + + if(prying_door.hasPower()) + time_to_open = 5 SECONDS + playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, vary = TRUE) + + if(!do_after(src, time_to_open, prying_door, interaction_key = REGALRAT_INTERACTION)) + return FALSE + + if(!prying_door.open(BYPASS_DOOR_CHECKS)) + balloon_alert(src, "failed to open!") + return FALSE + + return TRUE + +/mob/living/basic/regal_rat/controlled + poll_ghosts = TRUE + +#undef REGALRAT_INTERACTION diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm new file mode 100644 index 00000000000..7a30f88b4c2 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm @@ -0,0 +1,251 @@ +/** + *Increase the rat king's domain + */ + +/datum/action/cooldown/mob_cooldown/domain + name = "Rat King's Domain" + desc = "Corrupts this area to be more suitable for your rat army." + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED + cooldown_time = 6 SECONDS + melee_cooldown_time = 0 SECONDS + button_icon = 'icons/mob/actions/actions_animal.dmi' + background_icon_state = "bg_clock" + overlay_icon_state = "bg_clock_border" + button_icon_state = "coffer" + shared_cooldown = NONE + +/datum/action/cooldown/mob_cooldown/domain/proc/domain() + var/turf/location = get_turf(owner) + location.atmos_spawn_air("[GAS_MIASMA]=4;[TURF_TEMPERATURE(T20C)]") + switch (rand(1,10)) + if (8) + new /obj/effect/decal/cleanable/vomit(location) + if (9) + new /obj/effect/decal/cleanable/vomit/old(location) + if (10) + new /obj/effect/decal/cleanable/oil/slippery(location) + else + new /obj/effect/decal/cleanable/dirt(location) + StartCooldown() + +/datum/action/cooldown/mob_cooldown/domain/Activate(atom/target) + StartCooldown(10 SECONDS) + domain() + StartCooldown() + +/** + * This action checks some nearby maintenance animals and makes them your minions. + * If none are nearby, creates a new mouse. + */ +/datum/action/cooldown/mob_cooldown/riot + name = "Raise Army" + desc = "Raise an army out of the hordes of mice and pests crawling around the maintenance shafts." + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED + button_icon = 'icons/mob/actions/actions_animal.dmi' + button_icon_state = "riot" + background_icon_state = "bg_clock" + overlay_icon_state = "bg_clock_border" + cooldown_time = 8 SECONDS + melee_cooldown_time = 0 SECONDS + shared_cooldown = NONE + /// How close does something need to be for us to recruit it? + var/range = 5 + /// Commands you can give to your mouse army + var/static/list/mouse_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/protect_owner, + /datum/pet_command/follow, + /datum/pet_command/point_targetting/attack/mouse + ) + /// Commands you can give to glockroaches + var/static/list/glockroach_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/protect_owner/glockroach, + /datum/pet_command/follow, + /datum/pet_command/point_targetting/attack/glockroach + ) + +/datum/action/cooldown/mob_cooldown/riot/Activate(atom/target) + StartCooldown(10 SECONDS) + riot() + StartCooldown() + +/** + * Attempts to, in order and ending at any successful step: + * * Convert nearby mice into aggressive rats. + * * Convert nearby roaches into aggressive roaches. + * * Convert nearby frogs into aggressive frogs. + * * Spawn a single mouse if below the mouse cap. + */ +/datum/action/cooldown/mob_cooldown/riot/proc/riot() + var/uplifted_mice = FALSE + for (var/mob/living/basic/mouse/nearby_mouse in oview(owner, range)) + uplifted_mice = convert_mouse(nearby_mouse) || uplifted_mice + if (uplifted_mice) + owner.visible_message(span_warning("[owner] commands their army to action, mutating them into rats!")) + return + + var/static/list/converted_check_list = list(FACTION_RAT) + var/uplifted_roach = FALSE + for (var/mob/living/basic/cockroach/nearby_roach in oview(owner, range)) + uplifted_roach = convert_roach(nearby_roach, converted_check_list) || uplifted_roach + if (uplifted_roach) + owner.visible_message(span_warning("[owner] commands their army to action, mutating them into sewer roaches!")) + return + + var/uplifted_frog = FALSE + for (var/mob/living/basic/frog/nearby_frog in oview(owner, range)) + uplifted_frog = convert_frog(nearby_frog, converted_check_list) || uplifted_frog + if (uplifted_frog) + owner.visible_message(span_warning("[owner] commands their army to action, mutating them into trash frogs!")) + return + + var/rat_cap = CONFIG_GET(number/ratcap) + if (LAZYLEN(SSmobs.cheeserats) >= rat_cap) + to_chat(owner,span_warning("There's too many mice on this station to beckon a new one! Find them first!")) + return + new /mob/living/basic/mouse(owner.loc) + owner.visible_message(span_warning("[owner] commands a mouse to their side!")) + +/// Makes a passed mob into our minion +/datum/action/cooldown/mob_cooldown/riot/proc/make_minion(mob/living/new_minion, minion_desc, list/command_list = mouse_commands) + if (isbasicmob(new_minion)) + new_minion.AddComponent(/datum/component/obeys_commands, command_list) + qdel(new_minion.GetComponent(/datum/component/tameable)) // Rats don't share + new_minion.befriend(owner) + new_minion.faction = owner.faction.Copy() + // Give a hint in description too + new_minion.desc += minion_desc + new_minion.balloon_alert_to_viewers("squeak") + +/// Turns a mouse into an angry mouse +/datum/action/cooldown/mob_cooldown/riot/proc/convert_mouse(mob/living/basic/mouse/nearby_mouse) + // This mouse is already rat controlled, let's not bother with it. + if (istype(nearby_mouse.ai_controller, /datum/ai_controller/basic_controller/mouse/rat)) + return FALSE + + var/mob/living/basic/mouse/rat/rat_path = /mob/living/basic/mouse/rat + // Change name + if (nearby_mouse.name == "mouse") + nearby_mouse.name = initial(rat_path.name) + // Buffs our combat stats to that of a rat + nearby_mouse.melee_damage_lower = initial(rat_path.melee_damage_lower) + nearby_mouse.melee_damage_upper = initial(rat_path.melee_damage_upper) + nearby_mouse.obj_damage = initial(rat_path.obj_damage) + nearby_mouse.maxHealth = initial(rat_path.maxHealth) + nearby_mouse.health = initial(rat_path.health) + // Replace our AI with a rat one + nearby_mouse.ai_controller = new /datum/ai_controller/basic_controller/mouse/rat(nearby_mouse) + make_minion(nearby_mouse, " ...Except this one looks corrupted and aggressive.") + return TRUE + +/// Turns a roach into an angry roach +/datum/action/cooldown/mob_cooldown/riot/proc/convert_roach(mob/living/basic/cockroach/nearby_roach, list/converted_check_list) + // No need to convert when not on the same team. + if (faction_check(nearby_roach.faction, converted_check_list)) + return FALSE + + var/list/minion_commands = mouse_commands + if (!findtext(nearby_roach.name, "sewer")) + nearby_roach.name = "sewer [nearby_roach.name]" + + if (istype(nearby_roach, /mob/living/basic/cockroach/glockroach) || istype(nearby_roach, /mob/living/basic/cockroach/hauberoach)) + if (istype(nearby_roach, /mob/living/basic/cockroach/glockroach)) + minion_commands = glockroach_commands + nearby_roach.melee_damage_lower += 0.5 + nearby_roach.melee_damage_upper += 2 + else + nearby_roach.melee_damage_lower += 2 + nearby_roach.melee_damage_upper += 4 + nearby_roach.obj_damage += 5 + nearby_roach.ai_controller = new /datum/ai_controller/basic_controller/cockroach/sewer(nearby_roach) + nearby_roach.melee_attack_cooldown = 0.8 SECONDS + + nearby_roach.icon_state += "_sewer" + nearby_roach.maxHealth += 1 + nearby_roach.health += 1 + make_minion(nearby_roach, "
This one looks extra robust.", minion_commands) + return TRUE + +/// Turns a frog into a crazy frog. This doesn't do anything interesting and should when it becomes a basic mob. +/datum/action/cooldown/mob_cooldown/riot/proc/convert_frog(mob/living/basic/frog/nearby_frog, list/converted_check_list) + // No need to convert when not on the same team. + if(faction_check(nearby_frog.faction, converted_check_list) || nearby_frog.stat == DEAD) + return FALSE + + var/list/minion_commands = mouse_commands + if (!findtext(nearby_frog.name, "trash")) + nearby_frog.name = replacetext(nearby_frog.name, "frog", "trash frog") + + nearby_frog.icon_state += "_trash" + nearby_frog.icon_living += "_trash" + nearby_frog.icon_dead = nearby_frog.icon_state + "_dead" + nearby_frog.maxHealth += 10 + nearby_frog.health += 10 + nearby_frog.melee_damage_lower += 1 + nearby_frog.melee_damage_upper += 5 + nearby_frog.obj_damage += 10 + nearby_frog.ai_controller = new /datum/ai_controller/basic_controller/frog/trash(nearby_frog) + var/crazy_frog_desc = " ...[findtext(nearby_frog.name, "rare") ? "even though" : "perhaps because"] they live in a trash bag." + make_minion(nearby_frog, crazy_frog_desc, minion_commands) + return TRUE + +// Command you can give to a mouse to make it kill someone +/datum/pet_command/point_targetting/attack/mouse + speech_commands = list("attack", "sic", "kill", "cheese em") + command_feedback = "squeak!" // Frogs and roaches can squeak too it's fine + pointed_reaction = "and squeaks aggressively" + refuse_reaction = "quivers" + attack_behaviour = /datum/ai_behavior/basic_melee_attack + +// Command you can give to a mouse to make it kill someone +/datum/pet_command/point_targetting/attack/glockroach + speech_commands = list("attack", "sic", "kill", "cheese em") + command_feedback = "squeak!" + pointed_reaction = "and cocks its gun" + refuse_reaction = "quivers" + attack_behaviour = /datum/ai_behavior/basic_ranged_attack/glockroach + +/** + *Spittle; harmless reagent that is added by rat king, and makes you disgusted. + */ + +/datum/reagent/rat_spit + name = "Rat Spit" + description = "Something coming from a rat. Dear god! Who knows where it's been!" + reagent_state = LIQUID + color = "#C8C8C8" + metabolization_rate = 0.03 * REAGENTS_METABOLISM + taste_description = "something funny" + overdose_threshold = 20 + +/datum/reagent/rat_spit/on_mob_metabolize(mob/living/L) + ..() + if(HAS_TRAIT(L, TRAIT_AGEUSIA)) + return + to_chat(L, span_notice("This food has a funny taste!")) + +/datum/reagent/rat_spit/overdose_start(mob/living/M) + ..() + var/mob/living/carbon/victim = M + if (istype(victim) && !(FACTION_RAT in victim.faction)) + to_chat(victim, span_userdanger("With this last sip, you feel your body convulsing horribly from the contents you've ingested. As you contemplate your actions, you sense an awakened kinship with rat-kind and their newly risen leader!")) + victim.faction |= FACTION_RAT + victim.vomit(VOMIT_CATEGORY_DEFAULT) + metabolization_rate = 10 * REAGENTS_METABOLISM + +/datum/reagent/rat_spit/on_mob_life(mob/living/carbon/C) + if(prob(15)) + to_chat(C, span_notice("You feel queasy!")) + C.adjust_disgust(3) + else if(prob(10)) + to_chat(C, span_warning("That food does not sit up well!")) + C.adjust_disgust(5) + else if(prob(5)) + C.vomit(VOMIT_CATEGORY_DEFAULT) + return ..() + +/datum/pet_command/protect_owner/glockroach + protect_behavior = /datum/ai_behavior/basic_ranged_attack/glockroach diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm new file mode 100644 index 00000000000..ef92c7b3b76 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm @@ -0,0 +1,28 @@ +/datum/ai_controller/basic_controller/regal_rat + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_BASIC_MOB_FLEEING = TRUE, + BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + + // we pretty much do cheesy things (make the station worse) and don't deal with peasants (crew) unless they start to get in the way + // summon the horde if we get into a fight and then let the horde take care of it while we skedaddle + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate/to_flee, + /datum/ai_planning_subtree/targeted_mob_ability/riot, + /datum/ai_planning_subtree/flee_target/from_flee_key, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/use_mob_ability/domain, + ) + +/datum/ai_planning_subtree/targeted_mob_ability/riot + target_key = BB_BASIC_MOB_FLEE_TARGET // we only want to trigger this when provoked, manpower is low nowadays + ability_key = BB_RAISE_HORDE_ABILITY + finish_planning = FALSE + +/datum/ai_planning_subtree/use_mob_ability/domain + ability_key = BB_DOMAIN_ABILITY diff --git a/code/modules/mob/living/basic/space_fauna/robot_customer.dm b/code/modules/mob/living/basic/space_fauna/robot_customer.dm new file mode 100644 index 00000000000..e084e11f403 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/robot_customer.dm @@ -0,0 +1,116 @@ +///Robot customers +/mob/living/basic/robot_customer + name = "tourist bot" + maxHealth = 150 + health = 150 + desc = "I wonder what they'll order..." + gender = NEUTER + + icon = 'icons/mob/simple/tourists.dmi' + icon_state = "amerifat" + icon_living = "amerifat" + + basic_mob_flags = DEL_ON_DEATH + mob_biotypes = MOB_ROBOTIC|MOB_HUMANOID + sentience_type = SENTIENCE_ARTIFICIAL + + unsuitable_atmos_damage = 0 + minimum_survivable_temperature = TCMB + maximum_survivable_temperature = T0C + 1000 + + ai_controller = /datum/ai_controller/robot_customer + + /// The clothes that we draw on this tourist. + var/clothes_set = "amerifat_clothes" + /// Reference to the hud that we show when the player hovers over us. + var/datum/atom_hud/hud_to_show_on_hover + +/mob/living/basic/robot_customer/Initialize( + mapload, + datum/customer_data/customer_data = /datum/customer_data/american, + datum/venue/attending_venue = SSrestaurant.all_venues[/datum/venue/restaurant], +) + var/datum/customer_data/customer_info = SSrestaurant.all_customers[customer_data] + ai_controller = customer_info.ai_controller_used + + . = ..() + + ADD_TRAIT(src, list(TRAIT_NOMOBSWAP, TRAIT_NO_TELEPORT, TRAIT_STRONG_GRABBER), INNATE_TRAIT) // never suffer a bitch to fuck with you + AddElement(/datum/element/footstep, FOOTSTEP_OBJ_ROBOT, 1, -6, sound_vary = TRUE) + + ai_controller.set_blackboard_key(BB_CUSTOMER_CUSTOMERINFO, customer_info) + ai_controller.set_blackboard_key(BB_CUSTOMER_ATTENDING_VENUE, attending_venue) + ai_controller.set_blackboard_key(BB_CUSTOMER_PATIENCE, customer_info.total_patience) + + icon = customer_info.base_icon + icon_state = customer_info.base_icon_state + name = "[pick(customer_info.name_prefixes)]-bot" + color = rgb(rand(80,255), rand(80,255), rand(80,255)) + clothes_set = pick(customer_info.clothing_sets) + update_appearance(UPDATE_ICON) + +///Clean up on the mobs seat etc when its deleted (Either by murder or because it left) +/mob/living/basic/robot_customer/Destroy() + var/datum/venue/attending_venue = ai_controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] + var/obj/structure/holosign/robot_seat/our_seat = ai_controller.blackboard[BB_CUSTOMER_MY_SEAT] + attending_venue.current_visitors -= src + if(attending_venue.linked_seats[our_seat]) + attending_venue.linked_seats[our_seat] = null + QDEL_NULL(hud_to_show_on_hover) + return ..() + +///Robots need robot gibs...! +/mob/living/basic/robot_customer/spawn_gibs() + new /obj/effect/gibspawner/robot(drop_location(), src) + +/mob/living/basic/robot_customer/MouseEntered(location, control, params) + . = ..() + hud_to_show_on_hover?.show_to(usr) + +/mob/living/basic/robot_customer/MouseExited(location, control, params) + . = ..() + hud_to_show_on_hover?.hide_from(usr) + +/mob/living/basic/robot_customer/update_overlays() + . = ..() + + var/datum/customer_data/customer_info = ai_controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] + + var/new_underlays = customer_info.get_underlays(src) + if (new_underlays) + underlays.Cut() + underlays += new_underlays + + var/mutable_appearance/features = mutable_appearance(icon, "[icon_state]_features") + features.appearance_flags = RESET_COLOR + . += features + + var/mutable_appearance/clothes = mutable_appearance(icon, clothes_set) + clothes.appearance_flags = RESET_COLOR + . += clothes + + var/bonus_overlays = customer_info.get_overlays(src) + if(bonus_overlays) + . += bonus_overlays + +/mob/living/basic/robot_customer/send_speech(message, message_range, obj/source, bubble_type, list/spans, datum/language/message_language, list/message_mods, forced, tts_message, list/tts_filter) + . = ..() + var/datum/customer_data/customer_info = ai_controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] + playsound(src, customer_info.speech_sound, 30, extrarange = MEDIUM_RANGE_SOUND_EXTRARANGE, falloff_distance = 5) + +/mob/living/basic/robot_customer/examine(mob/user) + . = ..() + if(isnull(ai_controller.blackboard[BB_CUSTOMER_CURRENT_ORDER])) + return + + var/datum/venue/attending_venue = ai_controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] + var/wanted_item = ai_controller.blackboard[BB_CUSTOMER_CURRENT_ORDER] + var/order = "nothing" + + if(istype(wanted_item, /datum/custom_order)) + var/datum/custom_order/custom_order = wanted_item + order = custom_order.get_order_line(attending_venue) + else + order = attending_venue.order_food_line(wanted_item) + + . += span_notice("Their order was: \"[order].\"") diff --git a/code/modules/mob/living/basic/space_fauna/snake/snake.dm b/code/modules/mob/living/basic/space_fauna/snake/snake.dm new file mode 100644 index 00000000000..13b9a327cc5 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/snake/snake.dm @@ -0,0 +1,86 @@ + +/mob/living/basic/snake + name = "snake" + desc = "A slithery snake. These legless reptiles are the bane of mice and adventurers alike." + icon_state = "snake" + icon_living = "snake" + icon_dead = "snake_dead" + speak_emote = list("hisses") + + health = 20 + maxHealth = 20 + melee_damage_lower = 5 + melee_damage_upper = 6 + obj_damage = 0 + environment_smash = ENVIRONMENT_SMASH_NONE + + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "shoos" + response_disarm_simple = "shoo" + response_harm_continuous = "steps on" + response_harm_simple = "step on" + + density = FALSE + pass_flags = PASSTABLE | PASSMOB + mob_size = MOB_SIZE_SMALL + + faction = list(FACTION_HOSTILE) + mob_biotypes = MOB_ORGANIC | MOB_BEAST | MOB_REPTILE + gold_core_spawnable = FRIENDLY_SPAWN + + ai_controller = /datum/ai_controller/basic_controller/snake + + /// List of stuff (mice) that we want to eat + var/static/list/edibles = list( + /mob/living/basic/mouse, + /obj/item/food/deadmouse, + ) + +/mob/living/basic/snake/Initialize(mapload, special_reagent) + . = ..() + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_SNAKE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + + AddElement(/datum/element/basic_eating, 2, 0, null, edibles) + ai_controller.set_blackboard_key(BB_BASIC_FOODS, edibles) + + AddComponent(\ + /datum/component/tameable,\ + food_types = list(/obj/item/food/deadmouse),\ + tame_chance = 75,\ + bonus_tame_chance = 10,\ + ) // snakes are really fond of food, especially in the cold darkness of space :) + + if(isnull(special_reagent)) + special_reagent = /datum/reagent/toxin + + AddElement(/datum/element/venomous, special_reagent, 4) + +/mob/living/basic/snake/befriend(mob/living/new_friend) + . = ..() + visible_message("[src] hisses happily as it seems to bond with [new_friend].") + +/// Snakes are primarily concerned with getting those tasty, tasty mice, but aren't afraid to strike back at those who attack them +/datum/ai_controller/basic_controller/snake + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/not_friends/allow_items, + ) + + ai_traits = STOP_MOVING_WHEN_PULLED + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/random_speech/snake, + ) diff --git a/code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm b/code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm new file mode 100644 index 00000000000..3eb404761c5 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm @@ -0,0 +1,6 @@ +/datum/ai_planning_subtree/random_speech/snake + speech_chance = 5 + speak = list("hsssss","sssSSsssss...","hiisssss") + sound = list('sound/creatures/snake_hissing1.ogg', 'sound/creatures/snake_hissing2.ogg') + emote_hear = list("hisses.") + emote_see = list("slithers around.", "glances.", "stares.") diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spiders.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spiders.dm new file mode 100644 index 00000000000..6df2eb427f4 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spiders.dm @@ -0,0 +1,584 @@ +/** + * # Giant Spider + * + * A mob which can be created by dynamic event, botany, or xenobiology. + * The basic type is the guard, which is slow but sturdy and outputs good damage. + * All spiders can produce webbing. + */ +/mob/living/basic/spider/giant + name = "giant spider" + desc = "Furry and black, it makes you shudder to look at it. This one has deep red eyes." + icon_state = "guard" + icon_living = "guard" + icon_dead = "guard_dead" + speed = 5 + maxHealth = 125 + health = 125 + obj_damage = 30 + melee_damage_lower = 20 + melee_damage_upper = 25 + gold_core_spawnable = HOSTILE_SPAWN + ai_controller = /datum/ai_controller/basic_controller/giant_spider + + +/** + * ### Ambush Spider + * A subtype of the giant spider which is slower, stronger and able to sneak into its surroundings to pull pray aggressively. + * This spider is only slightly slower than a human. + */ +/mob/living/basic/spider/giant/ambush + name = "ambush spider" + desc = "Furry and white, it makes you shudder to look at it. This one has sparkling pink eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "ambush" + icon_living = "ambush" + icon_dead = "ambush_dead" + gender = FEMALE + maxHealth = 125 + health = 125 + obj_damage = 45 + melee_damage_lower = 25 + melee_damage_upper = 30 + speed = 5 + player_speed_modifier = -3.1 + menu_description = "Slow spider variant specializing in stalking and ambushing prey, above avarage health and damage with a strong grip." + +/mob/living/basic/spider/giant/ambush/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_STRONG_GRABBER, INNATE_TRAIT) + + AddElement(/datum/element/web_walker, /datum/movespeed_modifier/slow_web) + + var/datum/action/cooldown/mob_cooldown/sneak/spider/sneak_web = new(src) + sneak_web.Grant(src) + +/** + * ### Guard Spider + * A subtype of the giant spider which is similar on every single way, + * This spider is only slightly slower than a human. + */ +/mob/living/basic/spider/giant/guard + name = "guard spider" + desc = "Furry and black, it makes you shudder to look at it. This one has deep red eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "guard" + icon_living = "guard" + icon_dead = "guard_dead" + gender = FEMALE + maxHealth = 160 + health = 160 + melee_damage_lower = 20 + melee_damage_upper = 25 + obj_damage = 45 + speed = 5 + player_speed_modifier = -4 + menu_description = "Tanky and strong for the defense of the nest and other spiders." + +/mob/living/basic/spider/giant/guard/Initialize(mapload) + . = ..() + + AddElement(/datum/element/web_walker, /datum/movespeed_modifier/average_web) + var/datum/action/cooldown/mob_cooldown/web_effigy/shed = new(src) + shed.Grant(src) + +/** + * ### Hunter Spider + * A subtype of the giant spider which is faster, has toxin injection, but less health and damage. + * This spider is only slightly slower than a human. + */ +/mob/living/basic/spider/giant/hunter + name = "hunter spider" + desc = "Furry and black, it makes you shudder to look at it. This one has sparkling purple eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "hunter" + icon_living = "hunter" + icon_dead = "hunter_dead" + maxHealth = 80 + health = 80 + melee_damage_lower = 15 + melee_damage_upper = 20 + poison_per_bite = 5 + speed = 3 + player_speed_modifier = -3.1 + menu_description = "Fast spider variant specializing in catching running prey and toxin injection, but has less health and damage." + +/mob/living/basic/spider/giant/hunter/Initialize(mapload) + . = ..() + + AddElement(/datum/element/web_walker, /datum/movespeed_modifier/fast_web) + +/** + * ### Scout Spider + * A subtype of the giant spider which is faster, has thermal vision, but less health and damage. + * This spider is only slightly faster than a human. + */ +/mob/living/basic/spider/giant/scout + name = "scout spider" + desc = "Furry and blueish black, it makes you shudder to look at it. This one has sparkling blue eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "scout" + icon_living = "scout" + icon_dead = "scout_dead" + maxHealth = 65 + health = 65 + obj_damage = 10 + melee_damage_lower = 5 + melee_damage_upper = 10 + poison_per_bite = 10 + poison_type = /datum/reagent/peaceborg/confuse + speed = 2.8 + player_speed_modifier = -3.1 + sight = SEE_SELF|SEE_MOBS + menu_description = "Fast spider variant specializing in scouting and alerting of prey, with the ability to travel in vents." + +/mob/living/basic/spider/giant/scout/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + + var/datum/action/cooldown/mob_cooldown/command_spiders/communication_spiders/spiders_communication = new(src) + spiders_communication.Grant(src) + +/** + * ### Nurse Spider + * + * A subtype of the giant spider which specializes in support skills. + * Nurses can place down webbing in a quarter of the time that other species can and can wrap other spiders' wounds, healing them. + * Note that it cannot heal itself. + */ +/mob/living/basic/spider/giant/nurse + name = "nurse spider" + desc = "Furry and black, it makes you shudder to look at it. This one has brilliant green eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "nurse" + icon_living = "nurse" + icon_dead = "nurse_dead" + gender = FEMALE + butcher_results = list(/obj/item/food/meat/slab/spider = 2, /obj/item/food/spiderleg = 8, /obj/item/food/spidereggs = 4) + maxHealth = 40 + health = 40 + melee_damage_lower = 5 + melee_damage_upper = 10 + speed = 4 + player_speed_modifier = -3.1 + web_speed = 0.25 + web_type = /datum/action/cooldown/mob_cooldown/lay_web/sealer + menu_description = "Support spider variant specializing in healing their brethren and placing webbings very swiftly, but has very low amount of health and deals low damage." + ///The health HUD applied to the mob. + var/health_hud = DATA_HUD_MEDICAL_ADVANCED + +/mob/living/basic/spider/giant/nurse/Initialize(mapload) + . = ..() + var/datum/atom_hud/datahud = GLOB.huds[health_hud] + datahud.show_to(src) + + AddComponent(/datum/component/healing_touch,\ + heal_brute = 25,\ + heal_burn = 25,\ + interaction_key = DOAFTER_SOURCE_SPIDER,\ + valid_targets_typecache = typecacheof(list(/mob/living/basic/spider/giant)),\ + action_text = "%SOURCE% begins wrapping the wounds of %TARGET%.",\ + complete_text = "%SOURCE% wraps the wounds of %TARGET%.",\ + ) + + AddElement(/datum/element/web_walker, /datum/movespeed_modifier/average_web) + +/** + * ### Tangle Spider + * + * A subtype of the giant spider which specializes in support skills. + * Tangle spiders can place down webbing in a quarter of the time that other species plus has an expanded arsenal of traps and web structures to place to benefit the nest. + * Note that it can heal itself. + */ +/mob/living/basic/spider/giant/tangle + name = "tangle spider" + desc = "Furry and brown, it makes you shudder to look at it. This one has dim brown eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "tangle" + icon_living = "tangle" + icon_dead = "tangle_dead" + gender = FEMALE + butcher_results = list(/obj/item/food/meat/slab/spider = 2, /obj/item/food/spiderleg = 8, /obj/item/food/spidereggs = 4) + maxHealth = 55 + health = 55 + melee_damage_lower = 1 + melee_damage_upper = 1 + poison_per_bite = 2.5 + poison_type = /datum/reagent/toxin/acid + obj_damage = 40 + web_speed = 0.25 + speed = 4 + player_speed_modifier = -3.1 + web_type = /datum/action/cooldown/mob_cooldown/lay_web/sealer + menu_description = "Support spider variant specializing in contruction to protect their brethren, but has very low amount of health and deals low damage." + +/mob/living/basic/spider/giant/tangle/Initialize(mapload) + . = ..() + var/datum/action/cooldown/mob_cooldown/lay_web/solid_web/web_solid = new(src) + web_solid.Grant(src) + + var/datum/action/cooldown/mob_cooldown/lay_web/web_passage/passage_web = new(src) + passage_web.Grant(src) + + var/datum/action/cooldown/mob_cooldown/lay_web/web_spikes/spikes_web = new(src) + spikes_web.Grant(src) + + var/datum/action/cooldown/mob_cooldown/lay_web/sticky_web/web_sticky = new(src) + web_sticky.Grant(src) + + AddElement(/datum/element/web_walker, /datum/movespeed_modifier/average_web) + + AddComponent(/datum/component/healing_touch,\ + heal_brute = 15,\ + heal_burn = 15,\ + heal_time = 3 SECONDS,\ + self_targetting = HEALING_TOUCH_SELF_ONLY,\ + interaction_key = DOAFTER_SOURCE_SPIDER,\ + valid_targets_typecache = typecacheof(list(/mob/living/basic/spider/growing/young/tangle, /mob/living/basic/spider/giant/tangle)),\ + extra_checks = CALLBACK(src, PROC_REF(can_mend)),\ + action_text = "%SOURCE% begins mending themselves...",\ + complete_text = "%SOURCE%'s wounds mend together.",\ + ) + +/// Prevent you from healing other tangle spiders, or healing when on fire +/mob/living/basic/spider/giant/tangle/proc/can_mend(mob/living/source, mob/living/target) + if (on_fire) + balloon_alert(src, "on fire!") + return FALSE + return TRUE + +/** + * ### Tarantula + * + * A subtype of the giant spider which specializes in pure strength and staying power. + * Is slowed down when not on webbing, but can lunge to throw off attackers and possibly to stun them. + */ +/mob/living/basic/spider/giant/tarantula + name = "tarantula" + desc = "Furry and black, it makes you shudder to look at it. This one has abyssal red eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "tarantula" + icon_living = "tarantula" + icon_dead = "tarantula_dead" + maxHealth = 360 // woah nelly + health = 360 + melee_damage_lower = 35 + melee_damage_upper = 40 + obj_damage = 100 + damage_coeff = list(BRUTE = 1, BURN = 1.25, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + speed = 6 + player_speed_modifier = -5.5 // Doesn't seem that slow but it gets a debuff off web + mob_size = MOB_SIZE_LARGE + gold_core_spawnable = NO_SPAWN + web_speed = 0.7 + web_type = /datum/action/cooldown/mob_cooldown/lay_web/sealer + menu_description = "Tank spider variant with an enormous amount of health and damage, but is very slow when not on webbing. It also has a charge ability to close distance with a target after a small windup." + /// Charging ability + var/datum/action/cooldown/mob_cooldown/charge/basic_charge/charge + +/mob/living/basic/spider/giant/tarantula/Initialize(mapload) + . = ..() + var/datum/action/cooldown/mob_cooldown/lay_web/solid_web/web_solid = new(src) + web_solid.Grant(src) + + var/datum/action/cooldown/mob_cooldown/lay_web/web_passage/passage_web = new(src) + passage_web.Grant(src) + + charge = new /datum/action/cooldown/mob_cooldown/charge/basic_charge() + charge.Grant(src) + + AddElement(/datum/element/tear_wall) + AddElement(/datum/element/web_walker, /datum/movespeed_modifier/slow_web) + +/mob/living/basic/spider/giant/tarantula/Destroy() + QDEL_NULL(charge) + return ..() + +/// Lunge if you click something at range +/mob/living/basic/spider/giant/tarantula/ranged_secondary_attack(atom/atom_target, modifiers) + charge.Trigger(target = atom_target) + +/** + * ### Spider Viper + * + * A subtype of the giant spider which specializes in speed and poison. + * Injects a deadlier toxin than other spiders, moves extremely fast, but has a limited amount of health. + */ +/mob/living/basic/spider/giant/viper + name = "viper spider" + desc = "Furry and black, it makes you shudder to look at it. This one has effervescent purple eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "viper" + icon_living = "viper" + icon_dead = "viper_dead" + maxHealth = 55 + health = 55 + melee_damage_lower = 5 + melee_damage_upper = 5 + poison_per_bite = 5 + poison_type = /datum/reagent/toxin/viperspider + speed = 2 + player_speed_modifier = -2.5 + gold_core_spawnable = NO_SPAWN + menu_description = "Assassin spider variant with an unmatched speed and very deadly poison, but has very low amount of health and damage." + +/mob/living/basic/spider/giant/viper/Initialize(mapload) + . = ..() + + AddElement(/datum/element/bonus_damage) + + var/datum/action/cooldown/mob_cooldown/defensive_mode/defensive_action = new(src) + defensive_action.Grant(src) + +/** + * ### Spider Broodmother + * + * A subtype of the giant spider which is the crux of a spider horde, and the way which it grows. + * Has very little offensive capabilities but can lay eggs at any time to create more basic spiders. + * After consuming human bodies can lay specialised eggs including more broodmothers. + * They are also capable of sending messages to all living spiders and setting directives for their children. + */ +/mob/living/basic/spider/giant/midwife + name = "broodmother spider" + desc = "Furry and black, it makes you shudder to look at it. This one has scintillating green eyes. Might also be hiding a real knife somewhere." + gender = FEMALE + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "midwife" + icon_living = "midwife" + icon_dead = "midwife_dead" + maxHealth = 250 + health = 250 + melee_damage_lower = 10 + melee_damage_upper = 15 + speed = 4 + player_speed_modifier = -3.1 + gold_core_spawnable = NO_SPAWN + web_speed = 0.5 + web_type = /datum/action/cooldown/mob_cooldown/lay_web/sealer + menu_description = "Royal spider variant specializing in reproduction and leadership, deals low damage." + +/mob/living/basic/spider/giant/midwife/Initialize(mapload) + . = ..() + var/datum/action/cooldown/mob_cooldown/lay_web/solid_web/web_solid = new(src) + web_solid.Grant(src) + + var/datum/action/cooldown/mob_cooldown/lay_web/web_passage/passage_web = new(src) + passage_web.Grant(src) + + var/datum/action/cooldown/mob_cooldown/lay_web/web_spikes/spikes_web = new(src) + spikes_web.Grant(src) + + var/datum/action/cooldown/mob_cooldown/lay_web/sticky_web/web_sticky = new(src) + web_sticky.Grant(src) + + var/datum/action/cooldown/mob_cooldown/wrap/wrapping = new(src) + wrapping.Grant(src) + + var/datum/action/cooldown/mob_cooldown/lay_eggs/make_eggs = new(src) + make_eggs.Grant(src) + + var/datum/action/cooldown/mob_cooldown/lay_eggs/enriched/make_better_eggs = new(src) + make_better_eggs.Grant(src) + + var/datum/action/cooldown/mob_cooldown/set_spider_directive/give_orders = new(src) + give_orders.Grant(src) + + var/datum/action/cooldown/mob_cooldown/command_spiders/not_hivemind_talk = new(src) + not_hivemind_talk.Grant(src) + + AddElement(/datum/element/web_walker, /datum/movespeed_modifier/average_web) + +/** + * ### Giant Ice Spider + * + * A subtype of the giant spider which is immune to temperature damage, unlike its normal counterpart. + * Currently unused in the game unless spawned by admins. + */ +/mob/living/basic/spider/giant/ice + name = "giant ice spider" + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = 0 + maximum_survivable_temperature = 1500 + color = rgb(114,228,250) + gold_core_spawnable = NO_SPAWN + menu_description = "Versatile ice spider variant for frontline combat with high health and damage. Immune to temperature damage." + +/** + * ### Ice Nurse Spider + * + * A temperature-proof nurse spider. Also unused. + */ +/mob/living/basic/spider/giant/nurse/ice + name = "giant ice spider" + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = 0 + maximum_survivable_temperature = 1500 + poison_type = /datum/reagent/consumable/frostoil + color = rgb(114,228,250) + menu_description = "Support ice spider variant specializing in healing their brethren and placing webbings very swiftly, but has very low amount of health and deals low damage. Immune to temperature damage." + +/** + * ### Ice Hunter Spider + * + * A temperature-proof hunter with chilling venom. Also unused. + */ +/mob/living/basic/spider/giant/hunter/ice + name = "giant ice spider" + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = 0 + maximum_survivable_temperature = 1500 + poison_type = /datum/reagent/consumable/frostoil + color = rgb(114,228,250) + gold_core_spawnable = NO_SPAWN + menu_description = "Fast ice spider variant specializing in catching running prey and frost oil injection, but has less health and damage. Immune to temperature damage." + +/** + * ### Scrawny Hunter Spider + * + * A hunter spider that trades damage for health, unable to smash enviroments. + * Used as a minor threat in abandoned places, such as areas in maintenance or a ruin. + */ +/mob/living/basic/spider/giant/hunter/scrawny + name = "scrawny spider" + health = 60 + maxHealth = 60 + melee_damage_lower = 5 + melee_damage_upper = 10 + desc = "Furry and black, it makes you shudder to look at it. This one has sparkling purple eyes, and looks abnormally thin and frail." + menu_description = "Fast spider variant specializing in catching running prey and toxin injection, but has less damage than a normal hunter spider at the cost of a little more health." + ai_controller = /datum/ai_controller/basic_controller/giant_spider/weak + +/** + * ### Scrawny Tarantula + * + * A weaker version of the Tarantula, unable to smash enviroments. + * Used as a moderately strong but slow threat in abandoned places, such as areas in maintenance or a ruin. + */ +/mob/living/basic/spider/giant/tarantula/scrawny + name = "scrawny tarantula" + health = 150 + maxHealth = 150 + melee_damage_lower = 20 + melee_damage_upper = 25 + desc = "Furry and black, it makes you shudder to look at it. This one has abyssal red eyes, and looks abnormally thin and frail." + menu_description = "A weaker variant of the tarantula with reduced amount of health and damage, very slow when not on webbing. It also has a charge ability to close distance with a target after a small windup." + ai_controller = /datum/ai_controller/basic_controller/giant_spider/weak + +/** + * ### Scrawny Nurse Spider + * + * A weaker version of the nurse spider with reduced health, unable to smash enviroments. + * Mainly used as a weak threat in abandoned places, such as areas in maintenance or a ruin. + * In the future we should give this AI so that it actually heals its teammates. + */ +/mob/living/basic/spider/giant/nurse/scrawny + name = "scrawny nurse spider" + health = 30 + maxHealth = 30 + desc = "Furry and black, it makes you shudder to look at it. This one has brilliant green eyes, and looks abnormally thin and frail." + menu_description = "Weaker version of the nurse spider, specializing in healing their brethren and placing webbings very swiftly, but has very low amount of health and deals low damage." + ai_controller = /datum/ai_controller/basic_controller/giant_spider/weak + +/** + * ### Flesh Spider + * + * A subtype of giant spider which only occurs from changelings. + * Has the base stats of a hunter, but they can heal themselves and spin webs faster. + * They also occasionally leave puddles of blood when they walk around. Flavorful! + */ +/mob/living/basic/spider/giant/hunter/flesh + name = "flesh spider" + desc = "A odd fleshy creature in the shape of a spider. Its eyes are pitch black and soulless." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "flesh" + icon_living = "flesh" + icon_dead = "flesh_dead" + web_speed = 0.7 + maxHealth = 90 + health = 90 + menu_description = "Self-sufficient spider variant capable of healing themselves and producing webbbing fast." + +/mob/living/basic/spider/giant/hunter/flesh/Initialize(mapload) + . = ..() + AddComponent(/datum/component/blood_walk, \ + blood_type = /obj/effect/decal/cleanable/blood/bubblegum, \ + blood_spawn_chance = 5) + // It might be easier and more fitting to just replace this with Regenerator + AddComponent(/datum/component/healing_touch,\ + heal_brute = 45,\ + heal_burn = 45,\ + self_targetting = HEALING_TOUCH_SELF_ONLY,\ + interaction_key = DOAFTER_SOURCE_SPIDER,\ + valid_targets_typecache = typecacheof(list(/mob/living/basic/spider/giant/hunter/flesh)),\ + extra_checks = CALLBACK(src, PROC_REF(can_mend)),\ + action_text = "%SOURCE% begins mending themselves...",\ + complete_text = "%SOURCE%'s wounds mend together.",\ + ) + + var/datum/action/cooldown/mob_cooldown/lay_web/web_spikes/spikes_web = new(src) + spikes_web.Grant(src) + + var/datum/action/cooldown/mob_cooldown/lay_web/sticky_web/web_sticky = new(src) + web_sticky.Grant(src) + +/// Prevent you from healing other flesh spiders, or healing when on fire +/mob/living/basic/spider/giant/hunter/flesh/proc/can_mend(mob/living/source, mob/living/target) + if (on_fire) + balloon_alert(src, "on fire!") + return FALSE + return TRUE + +/** + * ### Viper Spider (Wizard) + * + * A spider form for wizards. Has the viper spider's extreme speed and strong venom, with additional health and vent crawling abilities. + */ +/mob/living/basic/spider/giant/viper/wizard + name = "water spider" + desc = "Furry and black, it makes you shudder to look at it. This one has effervescent orange eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "water" + icon_living = "water" + icon_dead = "water_dead" + web_speed = 0.4 + maxHealth = 80 + health = 80 + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 1, OXY = 1) + unsuitable_cold_damage = 1 + unsuitable_heat_damage = 1 + menu_description = "Stronger assassin spider variant with an unmatched speed, high amount of health and very deadly poison, but deals very low amount of damage. It also has ability to ventcrawl." + apply_spider_antag = FALSE + +/mob/living/basic/spider/giant/viper/wizard/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + + var/datum/action/cooldown/mob_cooldown/lay_web/web_spikes/spikes_web = new(src) + spikes_web.Grant(src) + + var/datum/action/cooldown/mob_cooldown/lay_web/sticky_web/web_sticky = new(src) + web_sticky.Grant(src) + + +/** + * ### Sergeant Araneus + * + * This friendly arachnid hangs out in the HoS office on some space stations. Better trained than an average officer and does not attack except in self-defence. + */ +/mob/living/basic/spider/giant/sgt_araneus + name = "Sergeant Araneus" + real_name = "Sergeant Araneus" + desc = "A fierce companion of the Head of Security, this spider has been carefully trained by Nanotrasen specialists. Its beady, staring eyes send shivers down your spine." + faction = list(FACTION_SPIDER) + gold_core_spawnable = NO_SPAWN + maxHealth = 250 + health = 250 + melee_damage_lower = 15 + melee_damage_upper = 20 + ai_controller = /datum/ai_controller/basic_controller/giant_spider/retaliate + apply_spider_antag = FALSE + +/mob/living/basic/spider/giant/sgt_araneus/Initialize(mapload) + . = ..() + AddElement(/datum/element/pet_bonus, "chitters proudly!") + AddElement(/datum/element/ai_retaliate) + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider.dm b/code/modules/mob/living/basic/space_fauna/spider/spider.dm new file mode 100644 index 00000000000..4bd773f6d0a --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/spider.dm @@ -0,0 +1,177 @@ +/** + * Base type of various spider life stages + */ +/mob/living/basic/spider + name = "abstract spider" + desc = "Furry and abstract, it makes you shudder to look at it. This one should not exist." + icon = 'icons/mob/simple/arachnoid.dmi' + mob_biotypes = MOB_ORGANIC|MOB_BUG + speak_emote = list("chitters") + butcher_results = list(/obj/item/food/meat/slab/spider = 2, /obj/item/food/spiderleg = 8) + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently pushes aside" + response_disarm_simple = "gently push aside" + initial_language_holder = /datum/language_holder/spider + melee_attack_cooldown = CLICK_CD_MELEE + damage_coeff = list(BRUTE = 1, BURN = 1.25, TOX = 1, CLONE = 1, STAMINA = 1, OXY = 1) + basic_mob_flags = FLAMMABLE_MOB + status_flags = NONE + unsuitable_cold_damage = 4 + unsuitable_heat_damage = 4 + combat_mode = TRUE + faction = list(FACTION_SPIDER) + pass_flags = PASSTABLE + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + unique_name = TRUE + lighting_cutoff_red = 22 + lighting_cutoff_green = 5 + lighting_cutoff_blue = 5 + /// Speed modifier to apply if controlled by a human player + var/player_speed_modifier = -4 + /// What reagent the mob injects targets with + var/poison_type = /datum/reagent/toxin/hunterspider + /// How much of a reagent the mob injects on attack + var/poison_per_bite = 0 + /// Multiplier to apply to web laying speed. Fractional numbers make it faster, because it's a multiplier. + var/web_speed = 1 + /// Type of webbing ability to learn. + var/web_type = /datum/action/cooldown/mob_cooldown/lay_web + /// The message that the mother spider left for this spider when the egg was layed. + var/directive = "" + /// Short description of what this mob is capable of, for radial menu uses + var/menu_description = "Tanky and strong for the defense of the nest and other spiders." + /// If true then you shouldn't be told that you're a spider antagonist as soon as you are placed into this mob + var/apply_spider_antag = TRUE + +/mob/living/basic/spider/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_WEB_SURFER, INNATE_TRAIT) + AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) + AddElement(/datum/element/nerfed_pulling, GLOB.typecache_general_bad_things_to_easily_move) + AddElement(/datum/element/prevent_attacking_of_types, GLOB.typecache_general_bad_hostile_attack_targets, "this tastes awful!") + AddElement(/datum/element/cliff_walking) + + if(poison_per_bite) + AddElement(/datum/element/venomous, poison_type, poison_per_bite) + + var/datum/action/cooldown/mob_cooldown/lay_web/webbing = new web_type(src) + webbing.webbing_time *= web_speed + webbing.Grant(src) + ai_controller?.set_blackboard_key(BB_SPIDER_WEB_ACTION, webbing) + +/mob/living/basic/spider/Login() + . = ..() + if(!. || !client) + return FALSE + GLOB.spidermobs[src] = TRUE + add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/player_spider_modifier, multiplicative_slowdown = player_speed_modifier) + +/mob/living/basic/spider/Logout() + . = ..() + remove_movespeed_modifier(/datum/movespeed_modifier/player_spider_modifier) + +/mob/living/basic/spider/Destroy() + GLOB.spidermobs -= src + return ..() + +/mob/living/basic/spider/mob_negates_gravity() + if(locate(/obj/structure/spider/stickyweb) in loc) + return TRUE + return ..() + +/mob/living/basic/spider/expose_reagents(list/reagents, datum/reagents/source, methods=TOUCH, volume_modifier=1, show_message=TRUE) + . = ..() + for(var/datum/reagent/toxin/pestkiller/current_reagent in reagents) + apply_damage(50 * volume_modifier, STAMINA, BODY_ZONE_CHEST) + +/// Spider which turns into another spider over time +/mob/living/basic/spider/growing + /// The mob type we will grow into. + var/mob/living/basic/spider/grow_as = null + /// The time it takes for the spider to grow into the next stage + var/spider_growth_time = 1 MINUTES + +/mob/living/basic/spider/growing/Initialize(mapload) + . = ..() + AddComponent(\ + /datum/component/growth_and_differentiation,\ + growth_time = spider_growth_time,\ + growth_path = grow_as,\ + growth_probability = 25,\ + lower_growth_value = 1,\ + upper_growth_value = 2,\ + optional_checks = CALLBACK(src, PROC_REF(ready_to_grow)),\ + optional_grow_behavior = CALLBACK(src, PROC_REF(grow_up))\ + ) + +/** + * Checks to see if we're ready to grow, primarily if we are on solid ground and not in a vent or something. + * The component will automagically grow us when we return TRUE and that threshold has been met. + */ +/mob/living/basic/spider/growing/proc/ready_to_grow() + if(isturf(loc)) + return TRUE + + return FALSE + +/// Actually grows the young spider into a giant spider. We have to do a bunch of unique behavior that really can't be genericized, so we have to override the component in this manner. +/** + * Actually move to our next stage of life. + */ +/mob/living/basic/spider/growing/proc/grow_up() + if(isnull(grow_as)) + if(prob(3)) + grow_as = pick(/mob/living/basic/spider/giant/tarantula, /mob/living/basic/spider/giant/viper, /mob/living/basic/spider/giant/midwife) + else + grow_as = pick(/mob/living/basic/spider/giant/guard, /mob/living/basic/spider/giant/ambush, /mob/living/basic/spider/giant/hunter, /mob/living/basic/spider/giant/scout, /mob/living/basic/spider/giant/nurse, /mob/living/basic/spider/giant/tangle) + + var/mob/living/basic/spider/giant/grown = change_mob_type(grow_as, get_turf(src), initial(grow_as.name)) + ADD_TRAIT(grown, TRAIT_WAS_EVOLVED, REF(src)) + grown.faction = faction.Copy() + grown.directive = directive + grown.set_name() + grown.setBruteLoss(getBruteLoss()) + grown.setFireLoss(getFireLoss()) + qdel(src) + +/** + * ### Duct Spider + * A less than giant spider which lives in the maintenance ducts and makes them annoying to traverse. + */ +/mob/living/basic/spider/maintenance + name = "duct spider" + desc = "Nanotrasen's imported solution to mice, comes with its own problems." + icon_state = "maint_spider" + icon_living = "maint_spider" + icon_dead = "maint_spider_dead" + can_be_held = TRUE + mob_size = MOB_SIZE_TINY + held_w_class = WEIGHT_CLASS_TINY + worn_slot_flags = ITEM_SLOT_HEAD + head_icon = 'icons/mob/clothing/head/pets_head.dmi' + density = FALSE + pass_flags = PASSTABLE|PASSGRILLE|PASSMOB + gold_core_spawnable = FRIENDLY_SPAWN + maxHealth = 10 + health = 10 + melee_damage_lower = 1 + melee_damage_upper = 1 + speed = 0 + player_speed_modifier = 0 + web_speed = 0.25 + menu_description = "Fragile spider variant which is not good for much other than laying webs." + response_harm_continuous = "splats" + response_harm_simple = "splat" + ai_controller = /datum/ai_controller/basic_controller/giant_spider/pest + apply_spider_antag = FALSE + +/mob/living/basic/spider/maintenance/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + AddElement(/datum/element/web_walker, /datum/movespeed_modifier/average_web) + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/tiny_mob_hunter) diff --git a/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm new file mode 100644 index 00000000000..50ec85e342c --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm @@ -0,0 +1,49 @@ +/** + * # Young Spider + * + * A mob which can be created by spiderlings/spider eggs. + * The basic type is the guard, which is slow but sturdy and outputs good damage. + * All spiders can produce webbing. + */ +/mob/living/basic/spider/growing/young + name = "young spider" + desc = "Furry and black, it makes you shudder to look at it. This one has deep red eyes." + icon_state = "young_guard" + icon_living = "young_guard" + icon_dead = "young_guard_dead" + butcher_results = list(/obj/item/food/meat/slab/spider = 1) + speed = 1 + maxHealth = 60 + health = 60 + obj_damage = 10 + melee_damage_lower = 8 + melee_damage_upper = 12 + ai_controller = /datum/ai_controller/basic_controller/young_spider + player_speed_modifier = -1 + +/mob/living/basic/spider/growing/young/Initialize(mapload) + . = ..() + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/ai_flee_while_injured) + +/// Used by all young spiders if they ever appear. +/datum/ai_controller/basic_controller/young_spider + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + BB_BASIC_MOB_FLEE_DISTANCE = 6, + ) + + ai_traits = STOP_MOVING_WHEN_PULLED + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + + planning_subtrees = list( + /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, + /datum/ai_planning_subtree/flee_target, + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/random_speech/insect, + /datum/ai_planning_subtree/find_unwebbed_turf, + /datum/ai_planning_subtree/spin_web, + ) diff --git a/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider_subtypes.dm b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider_subtypes.dm new file mode 100644 index 00000000000..f5d128e41b7 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider_subtypes.dm @@ -0,0 +1,185 @@ +// This whole file is just a container for the young spider subtypes that actually differentiate into different giant spiders. None of them are particularly special as of now. + +/// Will differentiate into the base giant spider (known colloquially as the "guard" spider). +/mob/living/basic/spider/growing/young/guard + grow_as = /mob/living/basic/spider/giant/guard + name = "young guard spider" + desc = "Furry and brown, it looks defenseless. This one has sparkling red eyes." + maxHealth = 70 + health = 70 + melee_damage_lower = 10 + melee_damage_upper = 15 + speed = 0.7 + +/// Will differentiate into the "ambush" giant spider. +/mob/living/basic/spider/growing/young/ambush + grow_as = /mob/living/basic/spider/giant/ambush + name = "young ambush spider" + desc = "Furry and white, it looks defenseless. This one has sparkling pink eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "young_ambush" + icon_dead = "young_ambush_dead" + maxHealth = 55 + health = 55 + melee_damage_lower = 12 + melee_damage_upper = 18 + speed = 1 + +/mob/living/basic/spider/growing/young/ambush/Initialize(mapload) + . = ..() + var/datum/action/cooldown/mob_cooldown/sneak/spider/sneak_web = new(src) + sneak_web.Grant(src) + +/// Will differentiate into the "scout" giant spider. +/mob/living/basic/spider/growing/young/scout + grow_as = /mob/living/basic/spider/giant/scout + name = "young scout spider" + desc = "Furry and black, it looks defenseless. This one has sparkling blue eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "young_scout" + icon_dead = "young_scout_dead" + maxHealth = 35 + health = 35 + melee_damage_lower = 2 + melee_damage_upper = 4 + speed = 0.5 + poison_per_bite = 4 + poison_type = /datum/reagent/peaceborg/confuse + sight = SEE_SELF|SEE_MOBS + +/mob/living/basic/spider/growing/young/scout/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + +/// Will differentiate into the "hunter" giant spider. +/mob/living/basic/spider/growing/young/hunter + grow_as = /mob/living/basic/spider/giant/hunter + name = "young hunter spider" + desc = "Furry and black, it looks defenseless. This one has sparkling purple eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "young_hunter" + icon_dead = "young_hunter_dead" + maxHealth = 45 + health = 45 + melee_damage_lower = 8 + melee_damage_upper = 12 + speed = 0.5 + poison_per_bite = 2 + +/// Will differentiate into the "nurse" giant spider. +/mob/living/basic/spider/growing/young/nurse + grow_as = /mob/living/basic/spider/giant/nurse + name = "young nurse spider" + desc = "Furry and black, it looks defenseless. This one has sparkling green eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "young_nurse" + icon_dead = "young_nurse_dead" + maxHealth = 25 + health = 25 + melee_damage_lower = 2 + melee_damage_upper = 4 + speed = 0.7 + web_speed = 0.5 + web_type = /datum/action/cooldown/mob_cooldown/lay_web/sealer + ///The health HUD applied to the mob. + var/health_hud = DATA_HUD_MEDICAL_ADVANCED + +/mob/living/basic/spider/growing/young/nurse/Initialize(mapload) + . = ..() + var/datum/atom_hud/datahud = GLOB.huds[health_hud] + datahud.show_to(src) + + AddComponent(/datum/component/healing_touch,\ + heal_brute = 15,\ + heal_burn = 15,\ + interaction_key = DOAFTER_SOURCE_SPIDER,\ + valid_targets_typecache = typecacheof(list(/mob/living/basic/spider/giant)),\ + action_text = "%SOURCE% begins wrapping the wounds of %TARGET%.",\ + complete_text = "%SOURCE% wraps the wounds of %TARGET%.",\ + ) + +/// Will differentiate into the "tangle" giant spider. +/mob/living/basic/spider/growing/young/tangle + grow_as = /mob/living/basic/spider/giant/tangle + name = "young tangle spider" + desc = "Furry and brown, it looks defenseless. This one has dim brown eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "young_tangle" + icon_dead = "young_tangle_dead" + maxHealth = 30 + health = 30 + melee_damage_lower = 1 + melee_damage_upper = 1 + speed = 0.7 + web_speed = 0.25 + web_type = /datum/action/cooldown/mob_cooldown/lay_web/sealer + poison_per_bite = 2 + poison_type = /datum/reagent/toxin/acid + +/mob/living/basic/spider/growing/young/tangle/Initialize(mapload) + . = ..() + AddComponent(/datum/component/healing_touch,\ + heal_brute = 10,\ + heal_burn = 10,\ + heal_time = 3 SECONDS,\ + self_targetting = HEALING_TOUCH_SELF_ONLY,\ + interaction_key = DOAFTER_SOURCE_SPIDER,\ + valid_targets_typecache = typecacheof(list(/mob/living/basic/spider/growing/young/tangle, /mob/living/basic/spider/giant/tangle)),\ + extra_checks = CALLBACK(src, PROC_REF(can_mend)),\ + action_text = "%SOURCE% begins mending themselves...",\ + complete_text = "%SOURCE%'s wounds mend together.",\ + ) + +/// Prevent you from healing other tangle spiders, or healing when on fire +/mob/living/basic/spider/growing/young/tangle/proc/can_mend(mob/living/source, mob/living/target) + if (on_fire) + balloon_alert(src, "on fire!") + return FALSE + return TRUE + +/// Will differentiate into the "midwife" giant spider. +/mob/living/basic/spider/growing/young/midwife + grow_as = /mob/living/basic/spider/giant/midwife + name = "young broodmother spider" + desc = "Furry and black, it looks defenseless. This one has scintillating green eyes. Might also be hiding a real knife somewhere." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "young_midwife" + icon_dead = "young_midwife_dead" + maxHealth = 100 + health = 100 + melee_damage_lower = 5 + melee_damage_upper = 10 + speed = 0.7 + web_speed = 0.5 + web_type = /datum/action/cooldown/mob_cooldown/lay_web/sealer + +/// Will differentiate into the "viper" giant spider. +/mob/living/basic/spider/growing/young/viper + grow_as = /mob/living/basic/spider/giant/viper + name = "young viper spider" + desc = "Furry and black, it looks defenseless. This one has sparkling magenta eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "young_viper" + icon_dead = "young_viper_dead" + maxHealth = 30 + health = 30 + melee_damage_lower = 5 + melee_damage_upper = 5 + speed = 0.2 + poison_type = /datum/reagent/toxin/viperspider + poison_per_bite = 2 + +/// Will differentiate into the "tarantula" giant spider. +/mob/living/basic/spider/growing/young/tarantula + grow_as = /mob/living/basic/spider/giant/tarantula + name = "young tarantula spider" + desc = "Furry and black, it looks defenseless. This one has abyssal red eyes." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "young_tarantula" + icon_dead = "young_tarantula_dead" + maxHealth = 150 + health = 150 + melee_damage_lower = 20 + melee_damage_upper = 25 + speed = 1 + obj_damage = 40 diff --git a/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm b/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm new file mode 100644 index 00000000000..2a3ba326eac --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm @@ -0,0 +1,102 @@ +/// A nasty little robotic bug that dusts people on attack. Jeepers. This should be a very, very, very rare spawn. +/mob/living/basic/supermatter_spider + name = "supermatter spider" + desc= "A sliver of supermatter placed upon a robotically enhanced pedestal." + + icon = 'icons/mob/simple/smspider.dmi' + icon_state = "smspider" + icon_living = "smspider" + icon_dead = "smspider_dead" + + gender = NEUTER + mob_biotypes = MOB_BUG|MOB_ROBOTIC + speak_emote = list("vibrates") + + + attack_verb_continuous = "slices" + attack_verb_simple = "slice" + attack_sound = 'sound/effects/supermatter.ogg' + attack_vis_effect = ATTACK_EFFECT_CLAW + + maxHealth = 10 + health = 10 + minimum_survivable_temperature = TCMB + maximum_survivable_temperature = T0C + 1250 + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + death_message = "falls to the ground, its shard dulling to a miserable grey!" + + faction = list(FACTION_HOSTILE) + + // Gold, supermatter tinted + lighting_cutoff_red = 30 + lighting_cutoff_green = 30 + lighting_cutoff_blue = 10 + + ai_controller = /datum/ai_controller/basic_controller/supermatter_spider + + /// If we successfully dust something, should we die? + var/single_use = TRUE + +/mob/living/basic/supermatter_spider/Initialize(mapload) + . = ..() + AddComponent(/datum/component/swarming) + + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) + + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_attack)) + +/// Proc that we call on attacking something to dust 'em. +/mob/living/basic/supermatter_spider/proc/on_attack(mob/living/basic/source, atom/target) + SIGNAL_HANDLER + + if(isliving(target)) + var/mob/living/victim = target + victim.investigate_log("has been dusted by [src].", INVESTIGATE_DEATHS) + dust_feedback(target) + victim.dust() + if(single_use) + death() + return COMPONENT_HOSTILE_NO_ATTACK + + if(!isturf(target)) + dust_feedback(target) + qdel(target) + if(single_use) + death() + return COMPONENT_HOSTILE_NO_ATTACK + +/// Simple proc that plays the supermatter dusting sound and sends a visible message. +/mob/living/basic/supermatter_spider/proc/dust_feedback(atom/target) + playsound(get_turf(src), 'sound/effects/supermatter.ogg', 10, TRUE) + visible_message(span_danger("[src] knocks into [target], turning [target.p_them()] to dust in a brilliant flash of light!")) + +/mob/living/basic/supermatter_spider/overcharged + name = "overcharged supermatter spider" + desc = "A sliver of overcharged supermatter placed upon a robotically enhanced pedestal. This one seems especially dangerous." + icon_state = "smspideroc" + icon_living = "smspideroc" + maxHealth = 25 + health = 25 + single_use = FALSE + +/datum/ai_controller/basic_controller/supermatter_spider + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/random_speech/supermatter_spider, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/datum/ai_planning_subtree/random_speech/supermatter_spider + speech_chance = 7 + emote_hear = list("clinks", "clanks") + emote_see = list("vibrates") diff --git a/code/modules/mob_spawn/ghost_roles/drone_roles.dm b/code/modules/mob_spawn/ghost_roles/drone_roles.dm new file mode 100644 index 00000000000..b8a31a16b13 --- /dev/null +++ b/code/modules/mob_spawn/ghost_roles/drone_roles.dm @@ -0,0 +1,6 @@ +/obj/effect/mob_spawn/ghost_role/drone/name_mob(mob/living/spawned_mob, forced_name) + if(!forced_name) + var/designation = pick(GLOB.posibrain_names) + forced_name = "Drone ([designation]-[rand(100, 999)])" + + return ..() diff --git a/code/modules/mod/mod_link.dm b/code/modules/mod/mod_link.dm new file mode 100644 index 00000000000..12ce7fa4827 --- /dev/null +++ b/code/modules/mod/mod_link.dm @@ -0,0 +1,543 @@ +/proc/make_link_visual_generic(datum/mod_link/mod_link, proc_path) + var/mob/living/user = mod_link.get_user_callback.Invoke() + var/obj/effect/overlay/link_visual = new() + link_visual.name = "holocall ([mod_link.id])" + link_visual.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + LAZYADD(mod_link.holder.update_on_z, link_visual) + link_visual.appearance_flags |= KEEP_TOGETHER + link_visual.makeHologram(0.75) + mod_link.visual_overlays = user.overlays - user.active_thinking_indicator + link_visual.add_overlay(mod_link.visual_overlays) + mod_link.visual = link_visual + mod_link.holder.become_hearing_sensitive(REF(mod_link)) + mod_link.holder.RegisterSignals(user, list(COMSIG_CARBON_APPLY_OVERLAY, COMSIG_CARBON_REMOVE_OVERLAY), proc_path) + return link_visual + +/proc/get_link_visual_generic(datum/mod_link/mod_link, atom/movable/visuals, proc_path) + var/mob/living/user = mod_link.get_user_callback.Invoke() + playsound(mod_link.holder, 'sound/machines/terminal_processing.ogg', 50, vary = TRUE) + visuals.add_overlay(mutable_appearance('icons/effects/effects.dmi', "static_base", TURF_LAYER)) + visuals.add_overlay(mutable_appearance('icons/effects/effects.dmi', "modlink", ABOVE_ALL_MOB_LAYER)) + visuals.add_filter("crop_square", 1, alpha_mask_filter(icon = icon('icons/effects/effects.dmi', "modlink_filter"))) + visuals.maptext_height = 6 + visuals.alpha = 0 + user.vis_contents += visuals + visuals.forceMove(user) + animate(visuals, 0.5 SECONDS, alpha = 255) + var/datum/callback/setdir_callback = CALLBACK(mod_link.holder, proc_path) + setdir_callback.Invoke(user, user.dir, user.dir) + mod_link.holder.RegisterSignal(mod_link.holder.loc, COMSIG_ATOM_DIR_CHANGE, proc_path) + +/proc/delete_link_visual_generic(datum/mod_link/mod_link) + var/mob/living/user = mod_link.get_user_callback.Invoke() + playsound(mod_link.get_other().holder, 'sound/machines/terminal_processing.ogg', 50, vary = TRUE, frequency = -1) + LAZYREMOVE(mod_link.holder.update_on_z, mod_link.visual) + mod_link.holder.lose_hearing_sensitivity(REF(mod_link)) + mod_link.holder.UnregisterSignal(user, list(COMSIG_CARBON_APPLY_OVERLAY, COMSIG_CARBON_REMOVE_OVERLAY, COMSIG_ATOM_DIR_CHANGE)) + QDEL_NULL(mod_link.visual) + +/proc/on_user_set_dir_generic(datum/mod_link/mod_link, newdir) + var/atom/other_visual = mod_link.get_other().visual + if(!newdir) //can sometimes be null or 0 + return + other_visual.setDir(SOUTH) + other_visual.pixel_x = 0 + other_visual.pixel_y = 0 + var/matrix/new_transform = matrix() + if(newdir & NORTH) + other_visual.pixel_y = 13 + other_visual.layer = BELOW_MOB_LAYER + SET_PLANE_IMPLICIT(other_visual, GAME_PLANE_FOV_HIDDEN) + if(newdir & SOUTH) + other_visual.pixel_y = -24 + other_visual.layer = ABOVE_ALL_MOB_LAYER + SET_PLANE_IMPLICIT(other_visual, GAME_PLANE_UPPER_FOV_HIDDEN) + new_transform.Scale(-1, 1) + new_transform.Translate(-1, 0) + if(newdir & EAST) + other_visual.pixel_x = 14 + other_visual.layer = BELOW_MOB_LAYER + SET_PLANE_IMPLICIT(other_visual, GAME_PLANE_FOV_HIDDEN) + new_transform.Shear(0.5, 0) + new_transform.Scale(0.65, 1) + if(newdir & WEST) + other_visual.pixel_x = -14 + other_visual.layer = BELOW_MOB_LAYER + SET_PLANE_IMPLICIT(other_visual, GAME_PLANE_FOV_HIDDEN) + new_transform.Shear(-0.5, 0) + new_transform.Scale(0.65, 1) + other_visual.transform = new_transform + +/obj/item/mod/control/Initialize(mapload, datum/mod_theme/new_theme, new_skin, obj/item/mod/core/new_core) + . = ..() + mod_link = new( + src, + starting_frequency, + CALLBACK(src, PROC_REF(get_wearer)), + CALLBACK(src, PROC_REF(can_call)), + CALLBACK(src, PROC_REF(make_link_visual)), + CALLBACK(src, PROC_REF(get_link_visual)), + CALLBACK(src, PROC_REF(delete_link_visual)) + ) + +/obj/item/mod/control/multitool_act_secondary(mob/living/user, obj/item/multitool/tool) + if(!multitool_check_buffer(user, tool)) + return + var/tool_frequency = null + if(istype(tool.buffer, /datum/mod_link)) + var/datum/mod_link/buffer_link = tool.buffer + tool_frequency = buffer_link.frequency + balloon_alert(user, "frequency set") + if(!tool_frequency && mod_link.frequency) + tool.set_buffer(mod_link) + balloon_alert(user, "frequency copied") + else if(tool_frequency && !mod_link.frequency) + mod_link.frequency = tool_frequency + else if(tool_frequency && mod_link.frequency) + var/response = tgui_alert(user, "Would you like to copy or imprint the frequency?", "MODlink Frequency", list("Copy", "Imprint")) + if(!user.is_holding(tool)) + return + switch(response) + if("Copy") + tool.set_buffer(mod_link) + balloon_alert(user, "frequency copied") + if("Imprint") + mod_link.frequency = tool_frequency + balloon_alert(user, "frequency set") + +/obj/item/mod/control/proc/can_call() + return get_charge() && wearer && wearer.stat < DEAD + +/obj/item/mod/control/proc/make_link_visual() + return make_link_visual_generic(mod_link, PROC_REF(on_overlay_change)) + +/obj/item/mod/control/proc/get_link_visual(atom/movable/visuals) + return get_link_visual_generic(mod_link, visuals, PROC_REF(on_wearer_set_dir)) + +/obj/item/mod/control/proc/delete_link_visual() + return delete_link_visual_generic(mod_link) + +/obj/item/mod/control/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods, message_range) + . = ..() + if(speaker != wearer && speaker != ai_assistant) + return + mod_link.visual.say(raw_message, sanitize = FALSE, message_range = 2) + +/obj/item/mod/control/proc/on_overlay_change(atom/source, cache_index, overlay) + SIGNAL_HANDLER + addtimer(CALLBACK(src, PROC_REF(update_link_visual)), 1 TICKS, TIMER_UNIQUE) + +/obj/item/mod/control/proc/update_link_visual() + if(QDELETED(mod_link.link_call)) + return + mod_link.visual.cut_overlay(mod_link.visual_overlays) + mod_link.visual_overlays = wearer.overlays - wearer.active_thinking_indicator + mod_link.visual.add_overlay(mod_link.visual_overlays) + +/obj/item/mod/control/proc/on_wearer_set_dir(atom/source, dir, newdir) + SIGNAL_HANDLER + on_user_set_dir_generic(mod_link, newdir || SOUTH) + +/obj/item/clothing/neck/link_scryer + name = "\improper MODlink scryer" + desc = "An intricate piece of machinery that creates a holographic video call with another MODlink-compatible device. Essentially a video necklace." + icon_state = "modlink" + actions_types = list(/datum/action/item_action/call_link) + /// The installed power cell. + var/obj/item/stock_parts/cell/cell + /// The MODlink datum we operate. + var/datum/mod_link/mod_link + /// Initial frequency of the MODlink. + var/starting_frequency + /// An additional name tag for the scryer, seen as "MODlink scryer - [label]" + var/label + +/obj/item/clothing/neck/link_scryer/Initialize(mapload) + . = ..() + mod_link = new( + src, + starting_frequency, + CALLBACK(src, PROC_REF(get_user)), + CALLBACK(src, PROC_REF(can_call)), + CALLBACK(src, PROC_REF(make_link_visual)), + CALLBACK(src, PROC_REF(get_link_visual)), + CALLBACK(src, PROC_REF(delete_link_visual)) + ) + START_PROCESSING(SSobj, src) + +/obj/item/clothing/neck/link_scryer/Destroy() + QDEL_NULL(cell) + QDEL_NULL(mod_link) + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/clothing/neck/link_scryer/examine(mob/user) + . = ..() + // SKYRAT EDIT NIFSOFT SCRYERS - START + if(custom_examine_controls) + return + // SKYRAT EDIT NIFSOFT SCRYERS - END + if(cell) + . += span_notice("The battery charge reads [cell.percent()]%. Right-click with an empty hand to remove it.") + else + . += span_notice("It is missing a battery, one can be installed by clicking with a power cell on it.") + . += span_notice("The MODlink ID is [mod_link.id], frequency is [mod_link.frequency || "unset"]. Right-click with multitool to copy/imprint frequency.") + . += span_notice("Use in hand to set name.") + +/obj/item/clothing/neck/link_scryer/equipped(mob/living/user, slot) + . = ..() + if(slot != ITEM_SLOT_NECK) + mod_link?.end_call() + +/obj/item/clothing/neck/link_scryer/dropped(mob/living/user) + . = ..() + mod_link?.end_call() + +/obj/item/clothing/neck/link_scryer/attack_self(mob/user, modifiers) + var/new_label = reject_bad_text(tgui_input_text(user, "Change the visible name", "Set Name", label, MAX_NAME_LEN)) + if(!new_label) + balloon_alert(user, "invalid name!") + return + label = new_label + balloon_alert(user, "name set") + update_name() + +/obj/item/clothing/neck/link_scryer/process(seconds_per_tick) + if(!mod_link.link_call) + return + cell.use(min(20 * seconds_per_tick, cell.charge)) + +/obj/item/clothing/neck/link_scryer/attackby(obj/item/attacked_by, mob/user, params) + . = ..() + if(cell || !istype(attacked_by, /obj/item/stock_parts/cell)) + return + if(!user.transferItemToLoc(attacked_by, src)) + return + cell = attacked_by + balloon_alert(user, "installed [cell.name]") + +/obj/item/clothing/neck/link_scryer/update_name(updates) + . = ..() + name = "[initial(name)][label ? " - [label]" : ""]" + +/obj/item/clothing/neck/link_scryer/Exited(atom/movable/gone, direction) + . = ..() + if(gone == cell) + cell = null + +/obj/item/clothing/neck/link_scryer/attack_hand_secondary(mob/user, list/modifiers) + if(!cell) + return SECONDARY_ATTACK_CONTINUE_CHAIN + balloon_alert(user, "removed [cell.name]") + user.put_in_hands(cell) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/item/clothing/neck/link_scryer/multitool_act_secondary(mob/living/user, obj/item/multitool/tool) + if(!multitool_check_buffer(user, tool)) + return + var/tool_frequency = null + if(istype(tool.buffer, /datum/mod_link)) + var/datum/mod_link/buffer_link = tool.buffer + tool_frequency = buffer_link.frequency + balloon_alert(user, "frequency set") + if(!tool_frequency && mod_link.frequency) + tool.set_buffer(mod_link) + balloon_alert(user, "frequency copied") + else if(tool_frequency && !mod_link.frequency) + mod_link.frequency = tool_frequency + else if(tool_frequency && mod_link.frequency) + var/response = tgui_alert(user, "Would you like to copy or imprint the frequency?", "MODlink Frequency", list("Copy", "Imprint")) + if(!user.is_holding(tool)) + return + switch(response) + if("Copy") + tool.set_buffer(mod_link) + balloon_alert(user, "frequency copied") + if("Imprint") + mod_link.frequency = tool_frequency + balloon_alert(user, "frequency set") + +/obj/item/clothing/neck/link_scryer/worn_overlays(mutable_appearance/standing, isinhands) + . = ..() + if(!QDELETED(mod_link.link_call)) + . += mutable_appearance('icons/mob/clothing/neck.dmi', "modlink_active") + +/obj/item/clothing/neck/link_scryer/ui_action_click(mob/user) + if(mod_link.link_call) + mod_link.end_call() + else + call_link(user, mod_link) + +/obj/item/clothing/neck/link_scryer/proc/get_user() + var/mob/living/carbon/user = loc + return istype(user) && user.wear_neck == src ? user : null + +/obj/item/clothing/neck/link_scryer/proc/can_call() + var/mob/living/user = loc + return istype(user) && cell?.charge && user.stat < DEAD + +/obj/item/clothing/neck/link_scryer/proc/make_link_visual() + var/mob/living/user = mod_link.get_user_callback.Invoke() + user.update_worn_neck() + return make_link_visual_generic(mod_link, PROC_REF(on_overlay_change)) + +/obj/item/clothing/neck/link_scryer/proc/get_link_visual(atom/movable/visuals) + return get_link_visual_generic(mod_link, visuals, PROC_REF(on_user_set_dir)) + +/obj/item/clothing/neck/link_scryer/proc/delete_link_visual() + var/mob/living/user = mod_link.get_user_callback.Invoke() + if(!QDELETED(user)) + user.update_worn_neck() + return delete_link_visual_generic(mod_link) + +/obj/item/clothing/neck/link_scryer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods, message_range) + . = ..() + if(speaker != loc) + return + mod_link.visual.say(raw_message, sanitize = FALSE, message_range = 3) + +/obj/item/clothing/neck/link_scryer/proc/on_overlay_change(atom/source, cache_index, overlay) + SIGNAL_HANDLER + addtimer(CALLBACK(src, PROC_REF(update_link_visual)), 1 TICKS, TIMER_UNIQUE) + +/obj/item/clothing/neck/link_scryer/proc/update_link_visual() + if(QDELETED(mod_link.link_call)) + return + var/mob/living/user = loc + mod_link.visual.cut_overlay(mod_link.visual_overlays) + mod_link.visual_overlays = user.overlays - user.active_thinking_indicator + mod_link.visual.add_overlay(mod_link.visual_overlays) + +/obj/item/clothing/neck/link_scryer/proc/on_user_set_dir(atom/source, dir, newdir) + SIGNAL_HANDLER + on_user_set_dir_generic(mod_link, newdir || SOUTH) + +/obj/item/clothing/neck/link_scryer/loaded + starting_frequency = "NT" + +/obj/item/clothing/neck/link_scryer/loaded/Initialize(mapload) + . = ..() + cell = new /obj/item/stock_parts/cell/high(src) + +/obj/item/clothing/neck/link_scryer/loaded/charlie + starting_frequency = MODLINK_FREQ_CHARLIE + +/// A MODlink datum, used to handle unique functions that will be used in the MODlink call. +/datum/mod_link + /// Generic name for multitool buffers. + var/name = "MODlink" + /// The frequency of the MODlink. You can only call other MODlinks on the same frequency. + var/frequency + /// The unique ID of the MODlink. + var/id = "" + /// The atom that holds the MODlink. + var/atom/movable/holder + /// A reference to the visuals generated by the MODlink. + var/atom/movable/visual + /// A list of all overlays of the user, copied everytime they have an overlay change. + var/list/visual_overlays = list() + /// A reference to the call between two MODlinks. + var/datum/mod_link_call/link_call + /// A callback that returns the user of the MODlink. + var/datum/callback/get_user_callback + /// A callback that returns whether the MODlink can currently call. + var/datum/callback/can_call_callback + /// A callback that returns the visuals of the MODlink. + var/datum/callback/make_visual_callback + /// A callback that receives the visuals of the other MODlink. + var/datum/callback/get_visual_callback + /// A callback that deletes the visuals of the MODlink. + var/datum/callback/delete_visual_callback + +/datum/mod_link/New( + atom/holder, + frequency, + datum/callback/get_user_callback, + datum/callback/can_call_callback, + datum/callback/make_visual_callback, + datum/callback/get_visual_callback, + datum/callback/delete_visual_callback +) + var/attempts = 0 + var/digits_to_make = 3 + do + if(attempts == 10) + attempts = 0 + digits_to_make++ + id = "" + for(var/i in 1 to digits_to_make) + id += num2text(rand(0,9)) + attempts++ + while(GLOB.mod_link_ids[id]) + GLOB.mod_link_ids[id] = src + src.frequency = frequency + src.holder = holder + src.get_user_callback = get_user_callback + src.can_call_callback = can_call_callback + src.make_visual_callback = make_visual_callback + src.get_visual_callback = get_visual_callback + src.delete_visual_callback = delete_visual_callback + RegisterSignal(holder, COMSIG_QDELETING, PROC_REF(on_holder_delete)) + +/datum/mod_link/Destroy() + GLOB.mod_link_ids -= id + if(link_call) + end_call() + get_user_callback = null + make_visual_callback = null + get_visual_callback = null + delete_visual_callback = null + return ..() + +/datum/mod_link/proc/get_other() + RETURN_TYPE(/datum/mod_link) + if(!link_call) + return + return link_call.caller == src ? link_call.receiver : link_call.caller + +/datum/mod_link/proc/call_link(datum/mod_link/called, mob/user) + if(!frequency) + return + if(!istype(called)) + holder.balloon_alert(user, "invalid target!") + return + var/mob/living/link_user = get_user_callback.Invoke() + if(!link_user) + return + if(HAS_TRAIT(link_user, TRAIT_IN_CALL)) + holder.balloon_alert(user, "user already in call!") + return + var/mob/living/link_target = called.get_user_callback.Invoke() + if(!link_target) + holder.balloon_alert(user, "invalid target!") + return + if(HAS_TRAIT(link_target, TRAIT_IN_CALL)) + holder.balloon_alert(user, "target already in call!") + return + if(!can_call_callback.Invoke() || !called.can_call_callback.Invoke()) + holder.balloon_alert(user, "can't call!") + return + link_target.playsound_local(get_turf(called.holder), 'sound/weapons/ring.ogg', 15, vary = TRUE) + var/atom/movable/screen/alert/modlink_call/alert = link_target.throw_alert("[REF(src)]_modlink", /atom/movable/screen/alert/modlink_call) + alert.desc = "[holder] ([id]) is calling you! Left-click this to accept the call. Right-click to deny it." + alert.caller_ref = WEAKREF(src) + alert.receiver_ref = WEAKREF(called) + alert.user_ref = WEAKREF(user) + +/datum/mod_link/proc/end_call() + QDEL_NULL(link_call) + +/datum/mod_link/proc/on_holder_delete(atom/source) + SIGNAL_HANDLER + qdel(src) + +/// A MODlink call datum, used to handle the call between two MODlinks. +/datum/mod_link_call + /// The MODlink that is calling. + var/datum/mod_link/caller + /// The MODlink that is being called. + var/datum/mod_link/receiver + +/datum/mod_link_call/New(datum/mod_link/caller, datum/mod_link/receiver) + caller.link_call = src + receiver.link_call = src + src.caller = caller + src.receiver = receiver + var/mob/living/caller_mob = caller.get_user_callback.Invoke() + ADD_TRAIT(caller_mob, TRAIT_IN_CALL, REF(src)) + var/mob/living/receiver_mob = receiver.get_user_callback.Invoke() + ADD_TRAIT(receiver_mob, TRAIT_IN_CALL, REF(src)) + make_visuals() + START_PROCESSING(SSprocessing, src) + +/datum/mod_link_call/Destroy() + var/mob/living/caller_mob = caller.get_user_callback.Invoke() + if(!QDELETED(caller_mob)) + REMOVE_TRAIT(caller_mob, TRAIT_IN_CALL, REF(src)) + var/mob/living/receiver_mob = receiver.get_user_callback.Invoke() + if(!QDELETED(receiver_mob)) + REMOVE_TRAIT(receiver_mob, TRAIT_IN_CALL, REF(src)) + STOP_PROCESSING(SSprocessing, src) + clear_visuals() + caller.link_call = null + receiver.link_call = null + return ..() + +/datum/mod_link_call/process(seconds_per_tick) + if(can_continue_call()) + return + qdel(src) + +/datum/mod_link_call/proc/can_continue_call() + return caller.frequency == receiver.frequency && caller.can_call_callback.Invoke() && receiver.can_call_callback.Invoke() + +/datum/mod_link_call/proc/make_visuals() + var/caller_visual = caller.make_visual_callback.Invoke() + var/receiver_visual = receiver.make_visual_callback.Invoke() + caller.get_visual_callback.Invoke(receiver_visual) + receiver.get_visual_callback.Invoke(caller_visual) + +/datum/mod_link_call/proc/clear_visuals() + caller.delete_visual_callback.Invoke() + receiver.delete_visual_callback.Invoke() + +/proc/call_link(mob/user, datum/mod_link/calling_link) + if(!calling_link.frequency) + return + var/list/callers = list() + for(var/id in GLOB.mod_link_ids) + var/datum/mod_link/link = GLOB.mod_link_ids[id] + if(link.frequency != calling_link.frequency) + continue + if(link == calling_link) + continue + if(!link.can_call_callback.Invoke()) + continue + callers["[link.holder] ([id])"] = id + if(!length(callers)) + calling_link.holder.balloon_alert(user, "no targets on freq [calling_link.frequency]!") + return + var/chosen_link = tgui_input_list(user, "Choose ID to call from [calling_link.frequency] frequency", "MODlink", callers) + if(!chosen_link) + return + calling_link.call_link(GLOB.mod_link_ids[callers[chosen_link]], user) + +/atom/movable/screen/alert/modlink_call + name = "MODlink Call Incoming" + desc = "Someone is calling you! Left-click this to accept the call. Right-click to deny it." + icon_state = "called" + timeout = 10 SECONDS + var/end_message = "call timed out!" + /// A weak reference to the MODlink that is calling. + var/datum/weakref/caller_ref + /// A weak reference to the MODlink that is being called. + var/datum/weakref/receiver_ref + /// A weak reference to the mob that is calling. + var/datum/weakref/user_ref + +/atom/movable/screen/alert/modlink_call/Click(location, control, params) + . = ..() + if(usr != owner) + return + var/datum/mod_link/caller = caller_ref.resolve() + var/datum/mod_link/receiver = receiver_ref.resolve() + if(!caller || !receiver) + return + if(caller.link_call || receiver.link_call) + return + var/list/modifiers = params2list(params) + if(LAZYACCESS(modifiers, RIGHT_CLICK)) + end_message = "call denied!" + owner.clear_alert("[REF(caller)]_modlink") + return + end_message = "call accepted" + new /datum/mod_link_call(caller, receiver) + owner.clear_alert("[REF(caller)]_modlink") + +/atom/movable/screen/alert/modlink_call/Destroy() + var/mob/living/user = user_ref?.resolve() + var/datum/mod_link/caller = caller_ref?.resolve() + if(!user || !caller) + return ..() + caller.holder.balloon_alert(user, end_message) + return ..() diff --git a/code/modules/modular_computers/computers/item/disks/unique_disks.dm b/code/modules/modular_computers/computers/item/disks/unique_disks.dm new file mode 100644 index 00000000000..144fa52c65a --- /dev/null +++ b/code/modules/modular_computers/computers/item/disks/unique_disks.dm @@ -0,0 +1,8 @@ +/obj/item/computer_disk/syndicate + name = "golden data disk" + desc = "A data disk with some high-tech programs, probably expensive as hell." + icon_state = "datadisk8" + custom_materials = list(/datum/material/gold = SMALL_MATERIAL_AMOUNT) + +/obj/item/computer_disk/syndicate/camera_app + starting_programs = list(/datum/computer_file/program/secureye/syndicate) diff --git a/code/modules/projectiles/projectile/special/lightbreaker.dm b/code/modules/projectiles/projectile/special/lightbreaker.dm new file mode 100644 index 00000000000..fd7d3d89e7a --- /dev/null +++ b/code/modules/projectiles/projectile/special/lightbreaker.dm @@ -0,0 +1,35 @@ +/obj/projectile/energy/fisher + name = "attenuated kinetic force" + alpha = 0 + damage = 0 + damage_type = BRUTE + armor_flag = BOMB + range = 14 + projectile_phasing = PASSTABLE | PASSMOB | PASSMACHINE | PASSSTRUCTURE + hitscan = TRUE + var/disrupt_duration = 10 SECONDS + +/obj/projectile/energy/fisher/on_hit(atom/target, blocked, pierce_hit) + . = ..() + var/lights_flickered = 0 + if(SEND_SIGNAL(target, COMSIG_HIT_BY_SABOTEUR, disrupt_duration) & COMSIG_SABOTEUR_SUCCESS) + lights_flickered++ + if(!isliving(target)) + return + var/list/things_to_disrupt = list() + if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + things_to_disrupt = human_target.get_all_gear() + else + var/mob/living/living_target = target // i guess this covers borgs too? + things_to_disrupt = living_target.get_equipped_items(include_pockets = TRUE, include_accessories = TRUE) + for(var/obj/item/thingy as anything in things_to_disrupt) + if(SEND_SIGNAL(thingy, COMSIG_HIT_BY_SABOTEUR, disrupt_duration) & COMSIG_SABOTEUR_SUCCESS) + lights_flickered++ + if(lights_flickered) + to_chat(target, span_warning("Your light [lights_flickered > 1 ? "sources flick" : "source flicks"] off.")) + +/obj/projectile/energy/fisher/melee + range = 1 + suppressed = SUPPRESSED_VERY + disrupt_duration = 20 SECONDS diff --git a/code/modules/spells/spell_types/conjure/cheese.dm b/code/modules/spells/spell_types/conjure/cheese.dm new file mode 100644 index 00000000000..d9c90d1dbac --- /dev/null +++ b/code/modules/spells/spell_types/conjure/cheese.dm @@ -0,0 +1,17 @@ +/datum/action/cooldown/spell/conjure/cheese + name = "Summon Cheese" + desc = "This spell conjures a bunch of cheese wheels. What the hell?" + sound = 'sound/magic/summonitems_generic.ogg' + button_icon_state = "cheese" + + school = SCHOOL_CONJURATION + cooldown_time = 1 MINUTES + spell_requirements = null + + invocation = "PL`YR DOT PL`CTM` OOO`BEE G" //player.placeatme 00064B33 9 + invocation_type = INVOCATION_SHOUT + garbled_invocation_prob = 0 //i'd rather it remain like this + + summon_radius = 1 + summon_amount = 9 + summon_type = list(/obj/item/food/cheese/wheel) diff --git a/code/modules/spells/spell_types/conjure/simian.dm b/code/modules/spells/spell_types/conjure/simian.dm new file mode 100644 index 00000000000..556a78e5012 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/simian.dm @@ -0,0 +1,98 @@ +/datum/action/cooldown/spell/conjure/simian + name = "Summon Simians" + desc = "This spell reaches deep into the elemental plane of bananas (the monkey one, not the clown one), and \ + summons monkeys and gorillas that will promptly flip out and attack everything in sight. Fun! \ + Their lesser, easily manipulable minds will be convinced you are one of their allies, but only for a minute. Unless you also are a monkey." + button_icon_state = "simian" + sound = 'sound/ambience/antag/monkey.ogg' + + school = SCHOOL_CONJURATION + cooldown_time = 1.5 MINUTES + cooldown_reduction_per_rank = 15 SECONDS + + invocation = "OOGA OOGA OOGA!!!!" + invocation_type = INVOCATION_SHOUT + + summon_radius = 2 + summon_type = list(/mob/living/carbon/human/species/monkey/angry, /mob/living/carbon/human/species/monkey/angry, /mob/living/simple_animal/hostile/gorilla/lesser) + summon_amount = 4 + +/datum/action/cooldown/spell/conjure/simian/level_spell(bypass_cap) + . = ..() + summon_amount++ // MORE, MOOOOORE + if(spell_level == spell_max_level) // We reward the faithful. + summon_type = list(/mob/living/carbon/human/species/monkey/angry, /mob/living/simple_animal/hostile/gorilla) + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC // Max level lets you cast it naked, for monkey larp. + to_chat(owner, span_notice("Your simian power has reached maximum capacity! You can now cast this spell naked, and you will create adult Gorillas with each cast.")) + +/datum/action/cooldown/spell/conjure/simian/cast(atom/cast_on) + . = ..() + var/mob/living/cast_mob = cast_on + if(!istype(cast_mob)) + return + if(FACTION_MONKEY in cast_mob.faction) + return + cast_mob.faction |= FACTION_MONKEY + addtimer(CALLBACK(src, PROC_REF(remove_monky_faction), cast_mob), 1 MINUTES) + +/datum/action/cooldown/spell/conjure/simian/proc/remove_monky_faction(mob/cast_mob) + cast_mob.faction -= FACTION_MONKEY + +/datum/action/cooldown/spell/conjure/simian/post_summon(atom/summoned_object, atom/cast_on) + var/mob/living/alive_dude = summoned_object + alive_dude.faction |= list(FACTION_MONKEY) + if(ismonkey(alive_dude)) + equip_monky(alive_dude) + return + +/** Equips summoned monky with gear depending on how the roll plays out, affected by spell lvl. + * Can give them bananas and garland or gatfruit and axes. Monkeys are comically inept, which balances out what might otherwise be a little crazy. + */ +/datum/action/cooldown/spell/conjure/simian/proc/equip_monky(mob/living/carbon/human/species/monkey/summoned_monkey) + + // These are advanced monkeys we're talking about + var/datum/ai_controller/monkey/monky_controller = summoned_monkey.ai_controller + monky_controller.set_trip_mode(mode = FALSE) + summoned_monkey.fully_replace_character_name(summoned_monkey.real_name, "primal " + summoned_monkey.name) + + // Monkeys get a random gear tier, but it's more likely to be good the more leveled the spell is! + var/monkey_gear_tier = rand(0, 5) + (spell_level - 1) + monkey_gear_tier = min(monkey_gear_tier, 5) + + // Monkey weapons, ordered by tier + var/static/list/monky_weapon = list( + list(/obj/item/food/grown/banana, /obj/item/grown/bananapeel), + list(/obj/item/tailclub, /obj/item/knife/combat/bone), + list(/obj/item/shovel/serrated, /obj/item/spear/bamboospear), + list(/obj/item/spear/bonespear, /obj/item/fireaxe/boneaxe), + list(/obj/item/gun/syringe/blowgun, /obj/item/gun/ballistic/revolver), + ) + + var/list/options = monky_weapon[min(monkey_gear_tier, length(monky_weapon))] + + var/obj/item/weapon + if(monkey_gear_tier != 0) + var/weapon_type = pick(options) + weapon = new weapon_type(summoned_monkey) + summoned_monkey.equip_to_slot_or_del(weapon, ITEM_SLOT_HANDS) + + // Load the ammo + if(istype(weapon, /obj/item/gun/syringe/blowgun)) + var/obj/item/reagent_containers/syringe/crude/tribal/syring = new(summoned_monkey) + weapon.attackby(syring, summoned_monkey) + + // Wield the weapon! + if(is_type_in_list(weapon, list(/obj/item/spear, /obj/item/fireaxe))) + weapon.attack_self(summoned_monkey) + + // Fashionable ape wear, organised by tier + var/list/static/monky_hats = list( + null, // nothin here + /obj/item/clothing/head/costume/garland, + /obj/item/clothing/head/helmet/durathread, + /obj/item/clothing/head/helmet/skull, + ) + + var/stylish_monkey_hat = monky_hats[min(monkey_gear_tier, length(monky_hats))] + if(!isnull(stylish_monkey_hat)) + summoned_monkey.equip_to_slot_or_del(new stylish_monkey_hat(summoned_monkey), ITEM_SLOT_HEAD) diff --git a/code/modules/surgery/experimental_dissection.dm b/code/modules/surgery/experimental_dissection.dm new file mode 100644 index 00000000000..6ae5e447e0b --- /dev/null +++ b/code/modules/surgery/experimental_dissection.dm @@ -0,0 +1,152 @@ +///How many research points you gain from dissecting a Human. +#define BASE_HUMAN_REWARD 500 + +/datum/surgery/advanced/experimental_dissection + name = "Experimental Dissection" + desc = "A surgical procedure which analyzes the biology of a corpse, and automatically adds new findings to the research database." + steps = list( + /datum/surgery_step/incise, + /datum/surgery_step/retract_skin, + /datum/surgery_step/experimental_dissection, + /datum/surgery_step/close, + ) + surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_MORBID_CURIOSITY + possible_locs = list(BODY_ZONE_CHEST) + target_mobtypes = list(/mob/living) + +/datum/surgery/advanced/experimental_dissection/can_start(mob/user, mob/living/target) + . = ..() + if(HAS_TRAIT_FROM(target, TRAIT_DISSECTED, EXPERIMENTAL_SURGERY_TRAIT)) + return FALSE + if(target.stat != DEAD) + return FALSE + +/datum/surgery_step/experimental_dissection + name = "dissection" + implements = list( + /obj/item/autopsy_scanner = 100, + TOOL_SCALPEL = 60, + TOOL_KNIFE = 20, + /obj/item/shard = 10, + ) + time = 12 SECONDS + silicons_obey_prob = TRUE + +/datum/surgery_step/experimental_dissection/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) + user.visible_message("[user] starts dissecting [target].", "You start dissecting [target].") + +/datum/surgery_step/experimental_dissection/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) + var/points_earned = check_value(target) + user.visible_message("[user] dissects [target], discovering [points_earned] point\s of data!", "You dissect [target], finding [points_earned] point\s worth of discoveries, you also write a few notes.") + + var/obj/item/research_notes/the_dossier = new /obj/item/research_notes(user.loc, points_earned, "biology") + if(!user.put_in_hands(the_dossier) && istype(user.get_inactive_held_item(), /obj/item/research_notes)) + var/obj/item/research_notes/hand_dossier = user.get_inactive_held_item() + hand_dossier.merge(the_dossier) + + var/obj/item/bodypart/target_chest = target.get_bodypart(BODY_ZONE_CHEST) + target.apply_damage(80, BRUTE, target_chest) + ADD_TRAIT(target, TRAIT_DISSECTED, EXPERIMENTAL_SURGERY_TRAIT) + return ..() + +/datum/surgery_step/experimental_dissection/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + var/points_earned = round(check_value(target) * 0.01) + user.visible_message( + "[user] dissects [target]!", + "You dissect [target], but do not find anything particularly interesting.", + ) + + var/obj/item/research_notes/the_dossier = new /obj/item/research_notes(user.loc, points_earned, "biology") + if(!user.put_in_hands(the_dossier) && istype(user.get_inactive_held_item(), /obj/item/research_notes)) + var/obj/item/research_notes/hand_dossier = user.get_inactive_held_item() + hand_dossier.merge(the_dossier) + + var/obj/item/bodypart/L = target.get_bodypart(BODY_ZONE_CHEST) + target.apply_damage(80, BRUTE, L) + return TRUE + +///Calculates how many research points dissecting 'target' is worth. +/datum/surgery_step/experimental_dissection/proc/check_value(mob/living/target) + var/cost = BASE_HUMAN_REWARD + + if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + if(human_target.dna?.species) + if(ismonkey(human_target)) + cost /= 5 + else if(isabductor(human_target)) + cost *= 4 + else if(isgolem(human_target) || iszombie(human_target)) + cost *= 3 + else if(isjellyperson(human_target) || ispodperson(human_target)) + cost *= 2 + else if(isalienroyal(target)) + cost *= 10 + else if(isalienadult(target)) + cost *= 5 + else + cost /= 6 + + return cost + +#undef BASE_HUMAN_REWARD + +/obj/item/research_notes + name = "research notes" + desc = "Valuable scientific data. Use it in an ancient research server to turn it in." + icon = 'icons/obj/service/bureaucracy.dmi' + icon_state = "paper" + w_class = WEIGHT_CLASS_SMALL + ///research points it holds + var/value = 100 + ///origin of the research + var/origin_type = "debug" + ///if it ws merged with different origins to apply a bonus + var/mixed = FALSE + +/obj/item/research_notes/Initialize(mapload, value, origin_type) + . = ..() + if(value) + src.value = value + if(origin_type) + src.origin_type = origin_type + change_vol() + +/obj/item/research_notes/examine(mob/user) + . = ..() + . += span_notice("It is worth [value] research points.") + +/obj/item/research_notes/attackby(obj/item/attacking_item, mob/living/user, params) + if(istype(attacking_item, /obj/item/research_notes)) + var/obj/item/research_notes/notes = attacking_item + value = value + notes.value + change_vol() + qdel(notes) + return + return ..() + +/// proc that changes name and icon depending on value +/obj/item/research_notes/proc/change_vol() + if(value >= 10000) + name = "revolutionary discovery in the field of [origin_type]" + icon_state = "docs_verified" + else if(value >= 2500) + name = "essay about [origin_type]" + icon_state = "paper_words" + else if(value >= 100) + name = "notes of [origin_type]" + icon_state = "paperslip_words" + else + name = "fragmentary data of [origin_type]" + icon_state = "scrap" + +///proc when you slap research notes into another one, it applies a bonus if they are of different origin (only applied once) +/obj/item/research_notes/proc/merge(obj/item/research_notes/new_paper) + var/bonus = min(value , new_paper.value) + value = value + new_paper.value + if(origin_type != new_paper.origin_type && !mixed) + value += bonus * 0.3 + origin_type = "[origin_type] and [new_paper.origin_type]" + mixed = TRUE + change_vol() + qdel(new_paper) diff --git a/code/modules/unit_tests/abductor_baton_spell.dm b/code/modules/unit_tests/abductor_baton_spell.dm new file mode 100644 index 00000000000..83d514de2b2 --- /dev/null +++ b/code/modules/unit_tests/abductor_baton_spell.dm @@ -0,0 +1,19 @@ +/// Tests that abductors get their baton recall spell when being equipped +/datum/unit_test/abductor_baton_spell + +/datum/unit_test/abductor_baton_spell/Run() + // Test abductor agents get a linked "summon item" spell that marks their baton. + var/mob/living/carbon/human/ayy = allocate(/mob/living/carbon/human/consistent) + ayy.equipOutfit(/datum/outfit/abductor/agent) + + var/datum/action/cooldown/spell/summonitem/abductor/summon = locate() in ayy.actions + TEST_ASSERT_NOTNULL(summon, "Abductor agent does not have summon item spell.") + TEST_ASSERT(istype(summon.marked_item, /obj/item/melee/baton/abductor), "Abductor agent's summon item spell did not mark their baton.") + + // Also test abductor solo agents also get the spell. + var/mob/living/carbon/human/ayy_two = allocate(/mob/living/carbon/human/consistent) + ayy_two.equipOutfit(/datum/outfit/abductor/scientist/onemanteam) + + var/datum/action/cooldown/spell/summonitem/abductor/summon_two = locate() in ayy_two.actions + TEST_ASSERT_NOTNULL(summon_two, "Abductor solo agent does not have summon item spell.") + TEST_ASSERT(istype(summon_two.marked_item, /obj/item/melee/baton/abductor), "Abductor solo agent's summon item spell did not mark their baton.") diff --git a/code/modules/unit_tests/burning.dm b/code/modules/unit_tests/burning.dm new file mode 100644 index 00000000000..daf99875f28 --- /dev/null +++ b/code/modules/unit_tests/burning.dm @@ -0,0 +1,17 @@ +/// Tests that no runtimes are thrown when a mob is on fire +/datum/unit_test/burning + +/datum/unit_test/burning/Run() + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + var/initial_temp = dummy.bodytemperature + // Light this baby up + dummy.set_fire_stacks(20) + dummy.ignite_mob() + TEST_ASSERT(dummy.on_fire, "Dummy is not on fire despite having 20 fire stacks and being ignited.") + // Manually tick it a few times + var/datum/status_effect/fire_handler/fire_stacks/handler = locate() in dummy.status_effects + for(var/i in 1 to 5) + handler.tick_interval = world.time - 1 + handler.process() + TEST_ASSERT(dummy.fire_stacks < 20, "Dummy should have decayed firestacks, but did not. (Dummy stacks: [dummy.fire_stacks]).") + TEST_ASSERT(dummy.bodytemperature > initial_temp, "Dummy did not heat up despite being on fire. (Dummy temp: [dummy.bodytemperature], initial temp: [initial_temp])") diff --git a/code/modules/unit_tests/client_colours.dm b/code/modules/unit_tests/client_colours.dm new file mode 100644 index 00000000000..55b6e0b24ad --- /dev/null +++ b/code/modules/unit_tests/client_colours.dm @@ -0,0 +1,9 @@ +///Checks that client colours have valid colour variables values at least when inited. +/datum/unit_test/client_colours + +/datum/unit_test/client_colours/Run() + for(var/datum/client_colour/colour as anything in subtypesof(/datum/client_colour)) + // colours can be color matrices (lists), which initial() cannot read. + colour = new colour + if(!color_to_full_rgba_matrix(colour.colour, FALSE)) + TEST_FAIL("[colour.type] has an invalid default colour value: [colour.colour]") diff --git a/code/modules/unit_tests/dismemberment.dm b/code/modules/unit_tests/dismemberment.dm new file mode 100644 index 00000000000..d8ce43b960e --- /dev/null +++ b/code/modules/unit_tests/dismemberment.dm @@ -0,0 +1,38 @@ +/** + * Unit test to check that held items are dropped correctly when we are dismembered. + * + * Also tests for edge cases such as undroppable items. + */ +/datum/unit_test/dismemberment/Run() + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + + var/obj/item/testing_item = allocate(/obj/item/analyzer) + testing_item.name = "testing item" + + // Standard situation: We're holding a normal item and get dismembered. + test_item(dummy, testing_item) + + // Abnormal situation: We're holding an undroppable item and get dismembered. + ADD_TRAIT(testing_item, TRAIT_NODROP, TRAIT_GENERIC) + test_item(dummy, testing_item, status_text = "after applying TRAIT_NODROP to the testing item") + + +/datum/unit_test/dismemberment/proc/test_item(mob/living/carbon/human/dummy, obj/item/testing_item, status_text = "") + //Check both to make sure being the active hand doesn't make a difference. + dummy.put_in_l_hand(testing_item) + check_dismember(dummy, BODY_ZONE_L_ARM, status_text) + + dummy.put_in_r_hand(testing_item) + check_dismember(dummy, BODY_ZONE_R_ARM, status_text) + + +/datum/unit_test/dismemberment/proc/check_dismember(mob/living/carbon/human/dummy, which_arm, status_text) + var/obj/item/bodypart/dismembered_limb = dummy.get_bodypart(which_arm) + var/obj/item/held_item = dummy.get_item_for_held_index(dismembered_limb.held_index) + + dismembered_limb.dismember() + TEST_ASSERT(held_item in dummy.loc, "Dummy did not drop [held_item] when [dismembered_limb] was dismembered [status_text].") + // Clean up after ourselves + qdel(dismembered_limb) + dummy.fully_heal(HEAL_ALL) + diff --git a/code/modules/unit_tests/ensure_subtree_operational_datum.dm b/code/modules/unit_tests/ensure_subtree_operational_datum.dm new file mode 100644 index 00000000000..9ca78fbc674 --- /dev/null +++ b/code/modules/unit_tests/ensure_subtree_operational_datum.dm @@ -0,0 +1,64 @@ +/// The subtree that requires the operational datum. +#define REQUIRED_SUBTREE "required_subtree" +/// The list of typepaths of applicable operational datums that would satisfy the requirement. +#define REQUIRED_OPERATIONAL_DATUMS "required_operational_datums" + +/// Unit Test that ensure that if we add a specific planning subtree to a basic mob's planning tree, that we also have the operational datum needed for it (component/element). +/// This can be extended to other "mandatory" operational datums for certain subtrees to work. +/datum/unit_test/ensure_subtree_operational_datum + /// Associated list of mobs that we need to test this on. Key is the typepath of the mob, value is a list of the planning subtree and the operational datums that are required for it. + var/list/testable_mobs = list() + +/datum/unit_test/ensure_subtree_operational_datum/Run() + gather_testable_mobs() + test_applicable_mobs() + +/// First, look for all mobs that have a planning subtree that requires an element, then add it to the list for stuff to test afterwards. Done like this to not have one mumbo proc that's hard to read. +/datum/unit_test/ensure_subtree_operational_datum/proc/gather_testable_mobs() + for(var/mob/living/basic/checkable_mob as anything in subtypesof(/mob/living/basic)) + var/datum/ai_controller/testable_controller = initial(checkable_mob.ai_controller) + if(isnull(testable_controller)) + continue + + // we can't do inital() memes on lists so it's allocation time + testable_controller = allocate(testable_controller) + var/list/ai_planning_subtrees = testable_controller.planning_subtrees // list of instantiated datums. easy money + if(!length(ai_planning_subtrees)) + continue + + for(var/datum/ai_planning_subtree/testable_subtree as anything in ai_planning_subtrees) + var/list/necessary_datums = testable_subtree.operational_datums + if(isnull(necessary_datums)) + continue + + testable_mobs[checkable_mob] = list( + REQUIRED_OPERATIONAL_DATUMS = necessary_datums, + REQUIRED_SUBTREE = testable_subtree.type, + ) + +/// Then, test the mobs that we've found +/datum/unit_test/ensure_subtree_operational_datum/proc/test_applicable_mobs() + for(var/mob/living/basic/checkable_mob as anything in testable_mobs) + var/list/checkable_mob_data = testable_mobs[checkable_mob] + checkable_mob = allocate(checkable_mob) + + var/datum/ai_planning_subtree/test_subtree = checkable_mob_data[REQUIRED_SUBTREE] + var/list/trait_sources = GET_TRAIT_SOURCES(checkable_mob, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM) + if(!length(trait_sources)) // yes yes we could use `COUNT_TRAIT_SOURCES` but why invoke the same macro twice + TEST_FAIL("The mob [checkable_mob] ([checkable_mob.type]) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, but has a planning subtree ([test_subtree]) that requires it!") + continue + + var/has_element = FALSE + var/list/testable_operational_datums = checkable_mob_data[REQUIRED_OPERATIONAL_DATUMS] + for(var/iterable in trait_sources) + if(iterable in testable_operational_datums) + has_element = TRUE + break + + if(!has_element) + var/list/message_list = list("The mob [checkable_mob] ([checkable_mob.type]) has a planning subtree ([test_subtree]) that requires a component/element, but does not have any!") + message_list += "Needs one of the following to satisfy the requirement: ([testable_operational_datums.Join(", ")])" + TEST_FAIL(message_list.Join(" ")) + +#undef REQUIRED_SUBTREE +#undef REQUIRED_OPERATIONAL_DATUMS diff --git a/code/modules/unit_tests/ling_decap.dm b/code/modules/unit_tests/ling_decap.dm new file mode 100644 index 00000000000..0c964f9a043 --- /dev/null +++ b/code/modules/unit_tests/ling_decap.dm @@ -0,0 +1,45 @@ +/// Test lings don't die when decapitated. +/datum/unit_test/ling_decap + +/datum/unit_test/ling_decap/Run() + var/mob/living/carbon/human/ling = allocate(/mob/living/carbon/human/consistent) + ling.mind_initialize() + ling.mind.add_antag_datum(/datum/antagonist/changeling) + + var/obj/item/bodypart/head/noggin = ling.get_bodypart(BODY_ZONE_HEAD) + noggin.dismember() + TEST_ASSERT_NULL(ling.get_bodypart(BODY_ZONE_HEAD), "Changeling failed to be decapitated.") + TEST_ASSERT_NULL(noggin.brainmob.mind, "Changeling's mind was moved to their head after decapitation, but it should have remained in their body.") + + var/obj/item/organ/internal/brain/oldbrain = noggin.brain + noggin.drop_organs() + TEST_ASSERT_NULL(noggin.brain, "Changeling's head failed to drop its brain.") + TEST_ASSERT_NULL(oldbrain.brainmob.mind, "Changeling's mind was moved to their brain after decapitation and organ dropping, but it should have remained in their body.") + + TEST_ASSERT_EQUAL(ling.stat, CONSCIOUS, "Changeling was not conscious after losing their head.") + + // Cleanup + qdel(noggin) + for(var/obj/item/organ/leftover in ling.loc) + qdel(leftover) + +/// Tests people get decapitated properly. +/datum/unit_test/normal_decap + +/datum/unit_test/normal_decap/Run() + var/mob/living/carbon/human/normal_guy = allocate(/mob/living/carbon/human/consistent) + normal_guy.mind_initialize() + var/my_guys_mind = normal_guy.mind + + var/obj/item/bodypart/head/noggin = normal_guy.get_bodypart(BODY_ZONE_HEAD) + noggin.dismember() + TEST_ASSERT_EQUAL(noggin.brainmob.mind, my_guys_mind, "Dummy's mind was not moved to their head after decapitation.") + + var/obj/item/organ/internal/brain/oldbrain = noggin.brain + noggin.drop_organs() + TEST_ASSERT_EQUAL(oldbrain.brainmob.mind, my_guys_mind, "Dummy's mind was not moved to their brain after being removed from their head.") + + // Cleanup + qdel(noggin) + for(var/obj/item/organ/leftover in normal_guy.loc) + qdel(leftover) diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_cyberpolice.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_cyberpolice.png new file mode 100644 index 00000000000..180be6064f8 Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_cyberpolice.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_teshari_alt.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_teshari_alt.png new file mode 100644 index 00000000000..aec29b7c0c8 Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_teshari_alt.png differ diff --git a/code/modules/unit_tests/spell_jaunt.dm b/code/modules/unit_tests/spell_jaunt.dm new file mode 100644 index 00000000000..41446b71a59 --- /dev/null +++ b/code/modules/unit_tests/spell_jaunt.dm @@ -0,0 +1,21 @@ +/// Tests Shadow Walk can be entered and exited +/datum/unit_test/shadow_jaunt + +/datum/unit_test/shadow_jaunt/Run() + var/mob/living/carbon/human/jaunter = allocate(/mob/living/carbon/human/consistent) + var/datum/action/cooldown/spell/jaunt/shadow_walk/walk = allocate(/datum/action/cooldown/spell/jaunt/shadow_walk, jaunter) + walk.Grant(jaunter) + + var/turf/jaunt_turf = jaunter.loc + TEST_ASSERT(istype(jaunt_turf), "Jaunter was not allocated to a turf, instead to [jaunt_turf || "nullspace"].") + TEST_ASSERT(walk.IsAvailable(), "Unit test room is not suitable to test [walk].") + + walk.Trigger() + + TEST_ASSERT_NOTEQUAL(jaunter.loc, jaunt_turf, "Jaunter's loc did not change on casting [walk].") + TEST_ASSERT(istype(jaunter.loc, walk.jaunt_type), "Jaunter failed to enter jaunt on casting [walk].") + + walk.next_use_time = -1 + walk.Trigger() + + TEST_ASSERT_EQUAL(jaunter.loc, jaunt_turf, "Jaunter failed to exit jaunt on exiting [walk].") diff --git a/code/modules/unit_tests/status_effect_ticks.dm b/code/modules/unit_tests/status_effect_ticks.dm new file mode 100644 index 00000000000..6f3c43c7ada --- /dev/null +++ b/code/modules/unit_tests/status_effect_ticks.dm @@ -0,0 +1,23 @@ +/// Validates status effect tick interval setup +/datum/unit_test/status_effect_ticks + +/datum/unit_test/status_effect_ticks/Run() + for(var/datum/status_effect/checking as anything in subtypesof(/datum/status_effect)) + var/checking_tick = initial(checking.tick_interval) + if(checking_tick == -1) + continue + if(checking_tick == INFINITY) + TEST_FAIL("Status effect [checking] has tick_interval set to INFINITY, this is not how you prevent ticks - use tick_interval = -1 instead.") + continue + if(checking_tick == 0) + TEST_FAIL("Status effect [checking] has tick_interval set to 0, this is not how you prevent ticks - use tick_interval = -1 instead.") + continue + switch(initial(checking.processing_speed)) + if(STATUS_EFFECT_FAST_PROCESS) + if(checking_tick < SSfastprocess.wait) + TEST_FAIL("Status effect [checking] has tick_interval set to [checking_tick], which is faster than SSfastprocess can tick ([SSfastprocess.wait]).") + if(STATUS_EFFECT_NORMAL_PROCESS) + if(checking_tick < SSprocessing.wait) + TEST_FAIL("Status effect [checking] has tick_interval set to [checking_tick], which is faster than SSprocessing can tick ([SSprocessing.wait]).") + else + TEST_FAIL("Invalid processing speed for status effect [checking] : [initial(checking.processing_speed)]") diff --git a/code/modules/unit_tests/weird_food.dm b/code/modules/unit_tests/weird_food.dm new file mode 100644 index 00000000000..5c97c343adf --- /dev/null +++ b/code/modules/unit_tests/weird_food.dm @@ -0,0 +1,40 @@ +/// Unit test to ensure that moths can eat t-shirts successfully +/datum/unit_test/moth_food + +/datum/unit_test/moth_food/Run() + var/obj/item/clothing/suit/armor/bulletproof/light_snack = allocate(/obj/item/clothing/suit/armor/bulletproof) + light_snack.create_moth_snack() + var/datum/component/edible/eatability = light_snack.moth_snack.GetComponent(/datum/component/edible) + eatability.eat_time = 0 + + var/mob/living/carbon/human/species/moth/gourmet = allocate(/mob/living/carbon/human/species/moth) + gourmet.nutrition = 0 // We need to be sufficiently hungry + gourmet.put_in_active_hand(light_snack) + + var/times_to_bite = round(light_snack.max_integrity / MOTH_EATING_CLOTHING_DAMAGE) + 1 + for (var/i in 1 to times_to_bite) + TEST_ASSERT(!QDELETED(light_snack), "Moth finished eating clothes faster than expected.") + light_snack.attack(gourmet, gourmet) + TEST_ASSERT(QDELETED(light_snack), "Moth failed to finish eating clothing.") + +/// Unit test to ensure that golems can eat rocks successfully +/datum/unit_test/golem_food + +/datum/unit_test/golem_food/Run() + var/obj/item/stack/sheet/mineral/uranium/five/dinner = allocate(/obj/item/stack/sheet/mineral/uranium/five) + var/datum/component/golem_food/golem_food_data = dinner.GetComponent(/datum/component/golem_food) + golem_food_data.create_golem_snack(dinner) + var/datum/component/edible/eatability = golem_food_data.golem_snack.GetComponent(/datum/component/edible) + eatability.eat_time = 0 + + var/mob/living/carbon/human/species/golem/rock_enjoyer = allocate(/mob/living/carbon/human/species/golem) + rock_enjoyer.nutrition = 0 // We need to be sufficiently hungry + rock_enjoyer.put_in_active_hand(dinner) + + var/status_applied = golem_food_data.snack_type.status_effect + var/times_to_bite = dinner.amount + for (var/i in 1 to times_to_bite) + TEST_ASSERT(!QDELETED(dinner), "Golem finished eating rocks faster than expected.") + dinner.attack(rock_enjoyer, rock_enjoyer) + TEST_ASSERT(QDELETED(dinner), "Golem failed to finish eating rocks.") + TEST_ASSERT(rock_enjoyer.has_status_effect(status_applied), "Golem didn't gain a food buff from eating its rocks.") diff --git a/code/modules/uplink/uplink_items/clownops.dm b/code/modules/uplink/uplink_items/clownops.dm new file mode 100644 index 00000000000..1b5948ebd05 --- /dev/null +++ b/code/modules/uplink/uplink_items/clownops.dm @@ -0,0 +1,143 @@ + +// Clown Operative Stuff +// Maybe someday, someone will care to maintain this + +/datum/uplink_item/weapon_kits/pie_cannon + name = "Banana Cream Pie Cannon" + desc = "A special pie cannon for a special clown, this gadget can hold up to 20 pies and automatically fabricates one every two seconds!" + cost = 10 + item = /obj/item/pneumatic_cannon/pie/selfcharge + surplus = 0 + purchasable_from = UPLINK_CLOWN_OPS + +/datum/uplink_item/weapon_kits/bananashield + name = "Bananium Energy Shield" + desc = "A clown's most powerful defensive weapon, this personal shield provides near immunity to ranged energy attacks \ + by bouncing them back at the ones who fired them. It can also be thrown to bounce off of people, slipping them, \ + and returning to you even if you miss. WARNING: DO NOT ATTEMPT TO STAND ON SHIELD WHILE DEPLOYED, EVEN IF WEARING ANTI-SLIP SHOES." + item = /obj/item/shield/energy/bananium + cost = 16 + surplus = 0 + purchasable_from = UPLINK_CLOWN_OPS + +/datum/uplink_item/weapon_kits/clownsword + name = "Bananium Energy Sword" + desc = "An energy sword that deals no damage, but will slip anyone it contacts, be it by melee attack, thrown \ + impact, or just stepping on it. Beware friendly fire, as even anti-slip shoes will not protect against it." + item = /obj/item/melee/energy/sword/bananium + cost = 3 + surplus = 0 + purchasable_from = UPLINK_CLOWN_OPS + +/datum/uplink_item/weapon_kits/clownoppin + name = "Ultra Hilarious Firing Pin" + desc = "A firing pin that, when inserted into a gun, makes that gun only useable by clowns and clumsy people and makes that gun honk whenever anyone tries to fire it." + cost = 1 //much cheaper for clown ops than for clowns + item = /obj/item/firing_pin/clown/ultra + purchasable_from = UPLINK_CLOWN_OPS + illegal_tech = FALSE + +/datum/uplink_item/weapon_kits/clownopsuperpin + name = "Super Ultra Hilarious Firing Pin" + desc = "Like the ultra hilarious firing pin, except the gun you insert this pin into explodes when someone who isn't clumsy or a clown tries to fire it." + cost = 4 //much cheaper for clown ops than for clowns + item = /obj/item/firing_pin/clown/ultra/selfdestruct + purchasable_from = UPLINK_CLOWN_OPS + illegal_tech = FALSE + +/datum/uplink_item/weapon_kits/foamsmg + name = "Toy Submachine Gun" + desc = "A fully-loaded Donksoft bullpup submachine gun that fires riot grade darts with a 20-round magazine." + item = /obj/item/gun/ballistic/automatic/c20r/toy + cost = 5 + surplus = 0 + purchasable_from = UPLINK_CLOWN_OPS + +/datum/uplink_item/weapon_kits/foammachinegun + name = "Toy Machine Gun" + desc = "A fully-loaded Donksoft belt-fed machine gun. This weapon has a massive 50-round magazine of devastating \ + riot grade darts, that can briefly incapacitate someone in just one volley." + item = /obj/item/gun/ballistic/automatic/l6_saw/toy + cost = 10 + surplus = 0 + purchasable_from = UPLINK_CLOWN_OPS + +/datum/uplink_item/explosives/bombanana + name = "Bombanana" + desc = "A banana with an explosive taste! discard the peel quickly, as it will explode with the force of a Syndicate minibomb \ + a few seconds after the banana is eaten." + item = /obj/item/food/grown/banana/bombanana + cost = 4 //it is a bit cheaper than a minibomb because you have to take off your helmet to eat it, which is how you arm it + surplus = 0 + purchasable_from = UPLINK_CLOWN_OPS + +/datum/uplink_item/explosives/clown_bomb_clownops + name = "Clown Bomb" + desc = "The Clown bomb is a hilarious device capable of massive pranks. It has an adjustable timer, \ + with a minimum of %MIN_BOMB_TIMER seconds, and can be bolted to the floor with a wrench to prevent \ + movement. The bomb is bulky and cannot be moved; upon ordering this item, a smaller beacon will be \ + transported to you that will teleport the actual bomb to it upon activation. Note that this bomb can \ + be defused, and some crew may attempt to do so." + item = /obj/item/sbeacondrop/clownbomb + cost = 15 + surplus = 0 + purchasable_from = UPLINK_CLOWN_OPS + +/datum/uplink_item/explosives/clown_bomb_clownops/New() + . = ..() + desc = replacetext(desc, "%MIN_BOMB_TIMER", SYNDIEBOMB_MIN_TIMER_SECONDS) + +/datum/uplink_item/explosives/tearstache + name = "Teachstache Grenade" + desc = "A teargas grenade that launches sticky moustaches onto the face of anyone not wearing a clown or mime mask. The moustaches will \ + remain attached to the face of all targets for one minute, preventing the use of breath masks and other such devices." + item = /obj/item/grenade/chem_grenade/teargas/moustache + cost = 3 + surplus = 0 + purchasable_from = UPLINK_CLOWN_OPS + +/datum/uplink_item/explosives/pinata + name = "Weapons Grade Pinata Kit" + desc = "A pinata filled with both candy and explosives as well as two belts to carry them on, crack it open and see what you get!" + item = /obj/item/storage/box/syndie_kit/pinata + purchasable_from = UPLINK_CLOWN_OPS + limited_stock = 1 + cost = 12 //This is effectively the clown ops version of the grenadier belt where you should on average get 8 explosives if you use a weapon with exactly 10 force. + surplus = 0 + +/datum/uplink_item/reinforcement/clown_reinforcement + name = "Clown Reinforcements" + desc = "Call in an additional clown to share the fun, equipped with full starting gear, but no telecrystals." + item = /obj/item/antag_spawner/nuke_ops/clown + cost = 20 + purchasable_from = UPLINK_CLOWN_OPS + restricted = TRUE + refundable = TRUE + +/datum/uplink_item/mech/honker + name = "Dark H.O.N.K." + desc = "A clown combat mech equipped with bombanana peel and tearstache grenade launchers, as well as the ubiquitous HoNkER BlAsT 5000." + item = /obj/vehicle/sealed/mecha/honker/dark/loaded + cost = 80 + purchasable_from = UPLINK_CLOWN_OPS + +/* //SKYRAT REMOVAL START +/datum/uplink_item/stealthy_tools/combatbananashoes + name = "Combat Banana Shoes" + desc = "While making the wearer immune to most slipping attacks like regular combat clown shoes, these shoes \ + can generate a large number of synthetic banana peels as the wearer walks, slipping up would-be pursuers. They also \ + squeak significantly louder." + item = /obj/item/clothing/shoes/clown_shoes/banana_shoes/combat + cost = 6 + surplus = 0 + purchasable_from = UPLINK_CLOWN_OPS +*/ //SKYRAT REMOVAL END + +/datum/uplink_item/badass/clownopclumsinessinjector //clowns can buy this too, but it's in the role-restricted items section for them + name = "Clumsiness Injector" + desc = "Inject yourself with this to become as clumsy as a clown... or inject someone ELSE with it to make THEM as clumsy as a clown. Useful for clown operatives who wish to reconnect with their former clownish nature or for clown operatives who wish to torment and play with their prey before killing them." + item = /obj/item/dnainjector/clumsymut + cost = 1 + purchasable_from = UPLINK_CLOWN_OPS + illegal_tech = FALSE + diff --git a/code/modules/vehicles/cars/speedwagon.dm b/code/modules/vehicles/cars/speedwagon.dm new file mode 100644 index 00000000000..1fa9e2dcc6c --- /dev/null +++ b/code/modules/vehicles/cars/speedwagon.dm @@ -0,0 +1,51 @@ +/// Big 3x3 car only available to admins which can run people over +/obj/vehicle/sealed/car/speedwagon + name = "BM Speedwagon" + desc = "Push it to the limit, walk along the razor's edge." + icon = 'icons/obj/toys/car.dmi' + icon_state = "speedwagon" + layer = LYING_MOB_LAYER + max_occupants = 4 + pixel_y = -48 + pixel_x = -48 + enter_delay = 0 SECONDS + escape_time = 0 SECONDS // Just get out dumbass + vehicle_move_delay = 0 + ///Determines whether we throw all things away when ramming them or just mobs, varedit only + var/crash_all = FALSE + +/obj/vehicle/sealed/car/speedwagon/Initialize(mapload) + . = ..() + add_overlay(image(icon, "speedwagon_cover", ABOVE_MOB_LAYER)) + +/obj/vehicle/sealed/car/speedwagon/Bump(atom/bumped) + . = ..() + if(!bumped.density || occupant_amount() == 0) + return + + if(crash_all) + if(ismovable(bumped)) + var/atom/movable/flying_debris = bumped + flying_debris.throw_at(get_edge_target_turf(bumped, dir), 4, 3) + visible_message(span_danger("[src] crashes into [bumped]!")) + playsound(src, 'sound/effects/bang.ogg', 50, TRUE) + if(!ishuman(bumped)) + return + var/mob/living/carbon/human/rammed = bumped + rammed.Paralyze(100) + rammed.adjustStaminaLoss(30) + rammed.apply_damage(rand(20,35), BRUTE) + if(!crash_all) + rammed.throw_at(get_edge_target_turf(bumped, dir), 4, 3) + visible_message(span_danger("[src] crashes into [rammed]!")) + playsound(src, 'sound/effects/bang.ogg', 50, TRUE) + +/obj/vehicle/sealed/car/speedwagon/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) + . = ..() + if(occupant_amount() == 0) + return + for(var/atom/future_statistic in range(2, src)) + if(future_statistic == src) + continue + if(!LAZYACCESS(occupants, future_statistic)) + Bump(future_statistic) diff --git a/code/modules/vehicles/mecha/equipment/tools/air_tank.dm b/code/modules/vehicles/mecha/equipment/tools/air_tank.dm new file mode 100644 index 00000000000..3062d9923bc --- /dev/null +++ b/code/modules/vehicles/mecha/equipment/tools/air_tank.dm @@ -0,0 +1,158 @@ +///Mech air tank module +/obj/item/mecha_parts/mecha_equipment/air_tank + name = "mounted air tank" + desc = "An internal air tank used to pressurize mech cabin, scrub CO2 and power RCS thrusters. Comes with a pump and a set of sensors." + icon_state = "mecha_air_tank" + equipment_slot = MECHA_UTILITY + can_be_toggled = TRUE + ///Whether the pressurization should start automatically when the cabin is sealed airtight + var/auto_pressurize_on_seal = TRUE + ///The internal air tank obj of the mech + var/obj/machinery/portable_atmospherics/canister/internal_tank + ///Volume of this air tank + var/volume = TANK_STANDARD_VOLUME * 10 + ///Maximum pressure of this air tank + var/maximum_pressure = ONE_ATMOSPHERE * 30 + ///Whether the tank starts pressurized + var/start_full = FALSE + ///Pumping + ///The connected air port, if we have one + var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port + ///Whether the pump is moving the air from/to the connected port + var/tank_pump_active = FALSE + ///Direction of the pump - into the tank from the port or the air (PUMP_IN) or from the tank (PUMP_OUT) + var/tank_pump_direction = PUMP_IN + ///Target pressure of the pump + var/tank_pump_pressure = ONE_ATMOSPHERE + +/obj/item/mecha_parts/mecha_equipment/air_tank/Initialize(mapload) + . = ..() + internal_tank = new(src) + internal_tank.air_contents.volume = volume + internal_tank.maximum_pressure = maximum_pressure + if(start_full) + internal_tank.air_contents.temperature = T20C + internal_tank.air_contents.add_gases(/datum/gas/oxygen) + internal_tank.air_contents.gases[/datum/gas/oxygen][MOLES] = maximum_pressure * volume / (R_IDEAL_GAS_EQUATION * internal_tank.air_contents.temperature) + +/obj/item/mecha_parts/mecha_equipment/air_tank/Destroy() + if(chassis) + UnregisterSignal(chassis, COMSIG_MOVABLE_PRE_MOVE) + STOP_PROCESSING(SSobj, src) + qdel(internal_tank) + return ..() + +/obj/item/mecha_parts/mecha_equipment/air_tank/attach(obj/vehicle/sealed/mecha/new_mecha, attach_right) + . = ..() + START_PROCESSING(SSobj, src) + RegisterSignal(new_mecha, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(disconnect_air)) + +/obj/item/mecha_parts/mecha_equipment/air_tank/detach(atom/moveto) + disconnect_air() + if(tank_pump_active) + tank_pump_active = FALSE + UnregisterSignal(chassis, COMSIG_MOVABLE_PRE_MOVE) + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/mecha_parts/mecha_equipment/air_tank/set_active(active) + . = ..() + if(active) + var/datum/action/action = locate(/datum/action/vehicle/sealed/mecha/mech_toggle_cabin_seal) in usr.actions + action.button_icon_state = "mech_cabin_[chassis.cabin_sealed ? "pressurized" : "open"]" + action.build_all_button_icons() + else + var/datum/action/action = locate(/datum/action/vehicle/sealed/mecha/mech_toggle_cabin_seal) in usr.actions + action.button_icon_state = "mech_cabin_[chassis.cabin_sealed ? "closed" : "open"]" + action.build_all_button_icons() + +/obj/item/mecha_parts/mecha_equipment/air_tank/process(seconds_per_tick) + if(!chassis) + return + process_cabin_pressure() + process_pump() + +/obj/item/mecha_parts/mecha_equipment/air_tank/proc/process_cabin_pressure(seconds_per_tick) + if(!chassis.cabin_sealed || !active) + return + var/datum/gas_mixture/external_air = chassis.loc.return_air() + var/datum/gas_mixture/tank_air = internal_tank.return_air() + var/datum/gas_mixture/cabin_air = chassis.cabin_air + var/release_pressure = internal_tank.release_pressure + var/cabin_pressure = cabin_air.return_pressure() + if(cabin_pressure < release_pressure) + tank_air.release_gas_to(cabin_air, release_pressure) + if(cabin_pressure) + cabin_air.pump_gas_to(external_air, PUMP_MAX_PRESSURE, GAS_CO2) + +/obj/item/mecha_parts/mecha_equipment/air_tank/proc/process_pump(seconds_per_tick) + if(!tank_pump_active) + return + var/turf/local_turf = get_turf(chassis) + var/datum/gas_mixture/sending = (tank_pump_direction == PUMP_IN ? local_turf.return_air() : internal_tank.air_contents) + var/datum/gas_mixture/receiving = (tank_pump_direction == PUMP_IN ? internal_tank.air_contents : local_turf.return_air()) + if(sending.pump_gas_to(receiving, tank_pump_pressure)) + air_update_turf(FALSE, FALSE) + +/obj/item/mecha_parts/mecha_equipment/air_tank/proc/disconnect_air() + SIGNAL_HANDLER + if(connected_port && internal_tank.disconnect()) + to_chat(chassis.occupants, "[icon2html(src, chassis.occupants)][span_warning("Air port connection has been severed!")]") + log_message("Lost connection to gas port.", LOG_MECHA) + +/obj/item/mecha_parts/mecha_equipment/air_tank/get_snowflake_data() + var/datum/gas_mixture/tank_air = internal_tank.return_air() + return list( + "snowflake_id" = MECHA_SNOWFLAKE_ID_AIR_TANK, + "auto_pressurize_on_seal" = auto_pressurize_on_seal, + "port_connected" = internal_tank?.connected_port ? TRUE : FALSE, + "tank_release_pressure" = round(internal_tank.release_pressure), + "tank_release_pressure_min" = internal_tank.can_min_release_pressure, + "tank_release_pressure_max" = internal_tank.can_max_release_pressure, + "tank_pump_active" = tank_pump_active, + "tank_pump_direction" = tank_pump_direction, + "tank_pump_pressure" = round(tank_pump_pressure), + "tank_pump_pressure_min" = PUMP_MIN_PRESSURE, + "tank_pump_pressure_max" = min(PUMP_MAX_PRESSURE, internal_tank.maximum_pressure), + "tank_air" = gas_mixture_parser(tank_air, "tank"), + "cabin_air" = gas_mixture_parser(chassis.cabin_air, "cabin"), + ) + +/obj/item/mecha_parts/mecha_equipment/air_tank/handle_ui_act(action, list/params) + switch(action) + if("set_cabin_pressure") + var/new_pressure = text2num(params["new_pressure"]) + internal_tank.release_pressure = clamp(round(new_pressure), internal_tank.can_min_release_pressure, internal_tank.can_max_release_pressure) + return TRUE + if("toggle_port") + if(internal_tank.connected_port) + if(internal_tank.disconnect()) + to_chat(chassis.occupants, "[icon2html(src, chassis.occupants)][span_notice("Disconnected from the air system port.")]") + log_message("Disconnected from gas port.", LOG_MECHA) + return TRUE + to_chat(chassis.occupants, "[icon2html(src, chassis.occupants)][span_warning("Unable to disconnect from the air system port!")]") + return + var/obj/machinery/atmospherics/components/unary/portables_connector/possible_port = locate() in chassis.loc + if(internal_tank.connect(possible_port)) + to_chat(chassis.occupants, "[icon2html(src, chassis.occupants)][span_notice("Connected to the air system port.")]") + log_message("Connected to gas port.", LOG_MECHA) + return TRUE + to_chat(chassis.occupants, "[icon2html(src, chassis.occupants)][span_warning("Unable to connect with air system port!")]") + return FALSE + if("toggle_auto_pressurize") + auto_pressurize_on_seal = !auto_pressurize_on_seal + return TRUE + if("toggle_tank_pump") + tank_pump_active = !tank_pump_active + return TRUE + if("toggle_tank_pump_direction") + tank_pump_direction = !tank_pump_direction + return TRUE + if("set_tank_pump_pressure") + var/new_pressure = text2num(params["new_pressure"]) + tank_pump_pressure = clamp(round(new_pressure), PUMP_MIN_PRESSURE, min(PUMP_MAX_PRESSURE, internal_tank.maximum_pressure)) + return TRUE + return FALSE + +/obj/item/mecha_parts/mecha_equipment/air_tank/full + start_full = TRUE diff --git a/code/modules/vehicles/mecha/equipment/tools/radio.dm b/code/modules/vehicles/mecha/equipment/tools/radio.dm new file mode 100644 index 00000000000..33a113a8274 --- /dev/null +++ b/code/modules/vehicles/mecha/equipment/tools/radio.dm @@ -0,0 +1,50 @@ +///Mech radio module +/obj/item/mecha_parts/mecha_equipment/radio + name = "mounted radio" + desc = "A basic component of every vehicle." + icon_state = "mecha_radio" + equipment_slot = MECHA_UTILITY + ///Internal radio item + var/obj/item/radio/mech/radio + +/obj/item/mecha_parts/mecha_equipment/radio/Initialize(mapload) + . = ..() + radio = new(src) + RegisterSignal(radio, COMSIG_QDELETING, PROC_REF(radio_deleted)) + +/obj/item/mecha_parts/mecha_equipment/radio/Destroy() + qdel(radio) + return ..() + +/obj/item/mecha_parts/mecha_equipment/radio/get_snowflake_data() + return list( + "snowflake_id" = MECHA_SNOWFLAKE_ID_RADIO, + "microphone" = radio.get_broadcasting(), + "speaker" = radio.get_listening(), + "frequency" = radio.get_frequency(), + "minFrequency" = radio.freerange ? MIN_FREE_FREQ : MIN_FREQ, + "maxFrequency" = radio.freerange ? MAX_FREE_FREQ : MAX_FREQ, + ) + +/obj/item/mecha_parts/mecha_equipment/radio/handle_ui_act(action, list/params) + switch(action) + if("toggle_microphone") + radio.set_broadcasting(!radio.get_broadcasting()) + return TRUE + if("toggle_speaker") + radio.set_listening(!radio.get_listening()) + return TRUE + if("set_frequency") + var/new_frequency = text2num(params["new_frequency"]) + radio.set_frequency(sanitize_frequency(new_frequency, radio.freerange, radio.syndie)) + return TRUE + return FALSE + +///Internal radio got deleted, somehow +/obj/item/mecha_parts/mecha_equipment/radio/proc/radio_deleted() + SIGNAL_HANDLER + if(!QDELETED(src)) + qdel(src) + +/obj/item/radio/mech + subspace_transmission = TRUE diff --git a/code/modules/wiremod/components/abstract/assoc_list_variable.dm b/code/modules/wiremod/components/abstract/assoc_list_variable.dm new file mode 100644 index 00000000000..9ad489a6ee5 --- /dev/null +++ b/code/modules/wiremod/components/abstract/assoc_list_variable.dm @@ -0,0 +1,5 @@ +/obj/item/circuit_component/variable/assoc_list + circuit_size = 1 + +/obj/item/circuit_component/variable/assoc_list/get_variable_list(obj/item/integrated_circuit/integrated_circuit) + return integrated_circuit.assoc_list_variables diff --git a/code/modules/wiremod/components/list/assoc_list_remove.dm b/code/modules/wiremod/components/list/assoc_list_remove.dm new file mode 100644 index 00000000000..04b3b489d68 --- /dev/null +++ b/code/modules/wiremod/components/list/assoc_list_remove.dm @@ -0,0 +1,25 @@ +/** + * # Associative List Remove Component + * + * Removes an element from an assoc list. + */ +/obj/item/circuit_component/variable/assoc_list/list_remove + display_name = "Associative List Remove" + desc = "Removes a key from an associative list variable." + category = "List" + + /// Key to remove to the list + var/datum/port/input/to_remove + + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL + +/obj/item/circuit_component/variable/assoc_list/list_remove/populate_ports() + to_remove = add_input_port("To Remove", PORT_TYPE_STRING) + +/obj/item/circuit_component/variable/assoc_list/list_remove/input_received(datum/port/input/port, list/return_values) + if(!current_variable) + return + var/list/info = current_variable.value + var/value_to_remove = to_remove.value + + info -= value_to_remove diff --git a/code/modules/wiremod/components/list/assoc_list_set.dm b/code/modules/wiremod/components/list/assoc_list_set.dm new file mode 100644 index 00000000000..098212df8e1 --- /dev/null +++ b/code/modules/wiremod/components/list/assoc_list_set.dm @@ -0,0 +1,54 @@ +/** + * # Assoc List Set Component + * + * Sets a string value on an assoc list. + */ +/obj/item/circuit_component/variable/assoc_list/list_set + display_name = "Associative List Set" + desc = "Sets a string key on an associative list to a specific value." + category = "List" + + /// Key to set + var/datum/port/input/key + /// Value to set the key to. + var/datum/port/input/value + /// For when the list is too long, a signal is sent here. + var/datum/port/output/failed + + var/max_list_size = 500 + + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL + +/obj/item/circuit_component/variable/assoc_list/list_set/get_ui_notices() + . = ..() + . += create_ui_notice("Max List Size: [max_list_size]", "orange", "sitemap") + +/obj/item/circuit_component/variable/assoc_list/list_set/populate_ports() + key = add_input_port("Key", PORT_TYPE_STRING) + value = add_input_port("Value", PORT_TYPE_ANY) + failed = add_output_port("Failed", PORT_TYPE_SIGNAL) + +/obj/item/circuit_component/variable/assoc_list/list_set/pre_input_received(datum/port/input/port) + . = ..() + if(current_variable) + value.set_datatype(current_variable.datatype_handler.get_datatype(2)) + +/obj/item/circuit_component/variable/assoc_list/list_set/input_received(datum/port/input/port, list/return_values) + if(!current_variable) + return + var/list/info = current_variable.value + var/key_to_set = key.value + var/value_to_set = value.value + + if(!key_to_set) + failed.set_output(COMPONENT_SIGNAL) + return + + if(length(info) >= max_list_size) + failed.set_output(COMPONENT_SIGNAL) + return + + if(isdatum(value_to_set)) + value_to_set = WEAKREF(value_to_set) + + info[key_to_set] = value_to_set diff --git a/code/modules/wiremod/components/list/list_find.dm b/code/modules/wiremod/components/list/list_find.dm new file mode 100644 index 00000000000..995b6c5247a --- /dev/null +++ b/code/modules/wiremod/components/list/list_find.dm @@ -0,0 +1,63 @@ +/** + * # List Find Component + * + * Finds an element in a list and returns the index. + */ +/obj/item/circuit_component/listin + display_name = "Element Find" + desc = "Checks if an element is in a list and returns the index it is as if it is. Index is set to 0 on failure." + category = "List" + + /// The list type we're checking + var/datum/port/input/list_type + + /// List to check + var/datum/port/input/list_to_check + /// Element to check + var/datum/port/input/to_check + + /// Signal to say we have found the element. + var/datum/port/output/found + /// Signal to say we haven't found the element. + var/datum/port/output/not_found + /// Result of the search + var/datum/port/output/result + /// Index of the element if found. + var/datum/port/output/index + + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL + +/obj/item/circuit_component/listin/populate_options() + list_type = add_option_port("List Type", GLOB.wiremod_basic_types) + +/obj/item/circuit_component/listin/populate_ports() + list_to_check = add_input_port("List", PORT_TYPE_LIST(PORT_TYPE_ANY)) + to_check = add_input_port("To Check", PORT_TYPE_ANY) + + found = add_output_port("Succeeded", PORT_TYPE_SIGNAL) + not_found = add_output_port("Failed", PORT_TYPE_SIGNAL) + result = add_output_port("Result", PORT_TYPE_NUMBER) + index = add_output_port("Index", PORT_TYPE_NUMBER) + +/obj/item/circuit_component/listin/pre_input_received(datum/port/input/port) + . = ..() + list_to_check.set_datatype(PORT_TYPE_LIST(list_type.value)) + to_check.set_datatype(list_type.value) + +/obj/item/circuit_component/listin/input_received(datum/port/input/port, list/return_values) + var/list/info = list_to_check.value + if(!info) + return + var/data_to_check = to_check.value + + if(isdatum(data_to_check)) + data_to_check = WEAKREF(data_to_check) + + var/actual_result = info.Find(data_to_check) + index.set_output(actual_result) + if(actual_result != 0) + result.set_output(TRUE) + found.set_output(COMPONENT_SIGNAL) + else + result.set_output(FALSE) + not_found.set_output(COMPONENT_SIGNAL) diff --git a/code/modules/wiremod/components/table/get_column.dm b/code/modules/wiremod/components/table/get_column.dm new file mode 100644 index 00000000000..d549b25b681 --- /dev/null +++ b/code/modules/wiremod/components/table/get_column.dm @@ -0,0 +1,39 @@ +/** + * # Get Column Component + * + * Gets the column of a table and returns it as a regular list. + */ +/obj/item/circuit_component/get_column + display_name = "Get Column" + desc = "Gets the column of a table and returns it as a regular list." + category = "List" + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL + + /// The list to perform the filter on + var/datum/port/input/received_table + + /// The name of the column to check + var/datum/port/input/column_name + + /// The filtered list + var/datum/port/output/output_list + +/obj/item/circuit_component/get_column/populate_ports() + received_table = add_input_port("Input", PORT_TYPE_TABLE) + column_name = add_input_port("Column Name", PORT_TYPE_STRING) + output_list = add_output_port("Output", PORT_TYPE_LIST(PORT_TYPE_ANY)) + +/obj/item/circuit_component/get_column/input_received(datum/port/input/port) + + var/list/input_list = received_table.value + if(!islist(input_list) || isnum(column_name.value)) + return + + var/list/new_list = list() + for(var/list/entry in input_list) + var/anything = entry[column_name.value] + if(islist(anything)) + continue + new_list += anything + + output_list.set_output(new_list) diff --git a/code/modules/wiremod/components/table/index_table.dm b/code/modules/wiremod/components/table/index_table.dm new file mode 100644 index 00000000000..b5afc89fb3f --- /dev/null +++ b/code/modules/wiremod/components/table/index_table.dm @@ -0,0 +1,39 @@ +/** + * # Index Table Component + * + * Gets the row of a table as an associative list using the index inputted. Will return no value if the index is invalid or a proper table is not returned. + */ +/obj/item/circuit_component/index_table + display_name = "Index Table" + desc = "Gets the row of a table as an associative list using the index inputted. Will return no value if the index is invalid or a proper table is not returned." + category = "List" + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL + + /// The list to perform the filter on + var/datum/port/input/received_table + + /// The target index + var/datum/port/input/target_index + + /// The filtered list + var/datum/port/output/output_list + +/obj/item/circuit_component/index_table/populate_ports() + received_table = add_input_port("Input", PORT_TYPE_TABLE) + target_index = add_input_port("Index", PORT_TYPE_NUMBER) + + output_list = add_output_port("Output", PORT_TYPE_ASSOC_LIST(PORT_TYPE_STRING, PORT_TYPE_ANY)) + +/obj/item/circuit_component/index_table/input_received(datum/port/input/port) + + var/list/target_list = received_table.value + if(!islist(target_list) || !length(target_list)) + output_list.set_output(null) + return + + var/index = target_index.value + if(index < 1 || index > length(target_list)) + output_list.set_output(null) + return + + output_list.set_output(target_list[index]) diff --git a/code/modules/wiremod/components/table/select.dm b/code/modules/wiremod/components/table/select.dm new file mode 100644 index 00000000000..9b8167e358b --- /dev/null +++ b/code/modules/wiremod/components/table/select.dm @@ -0,0 +1,91 @@ +/** + * # Select Component + * + * Selects a list from a list of lists by a specific column. Used only by USBs for communications to and from computers with lists of varying sizes. + */ +/obj/item/circuit_component/select + display_name = "Select Query" + desc = "A component used with USB cables that can perform select queries on a list based on the column name selected. The values are then compared with the comparison input." + category = "List" + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL + + var/datum/port/input/option/comparison_options + + /// The list to perform the filter on + var/datum/port/input/received_table + + /// The name of the column to check + var/datum/port/input/column_name + + /// The input to compare with + var/datum/port/input/comparison_input + + /// The filtered list + var/datum/port/output/filtered_table + + var/current_type = PORT_TYPE_ANY + +/obj/item/circuit_component/select/populate_options() + var/static/component_options = list( + COMP_COMPARISON_EQUAL, + COMP_COMPARISON_NOT_EQUAL, + COMP_COMPARISON_GREATER_THAN, + COMP_COMPARISON_LESS_THAN, + COMP_COMPARISON_GREATER_THAN_OR_EQUAL, + COMP_COMPARISON_LESS_THAN_OR_EQUAL, + ) + comparison_options = add_option_port("Comparison Options", component_options) + +/obj/item/circuit_component/select/populate_ports() + received_table = add_input_port("Input", PORT_TYPE_TABLE) + column_name = add_input_port("Column Name", PORT_TYPE_STRING) + comparison_input = add_input_port("Comparison Input", PORT_TYPE_ANY) + + filtered_table = add_output_port("Output", PORT_TYPE_TABLE) + +/obj/item/circuit_component/select/pre_input_received(datum/port/input/port) + var/current_option = comparison_options.value + switch(current_option) + if(COMP_COMPARISON_EQUAL, COMP_COMPARISON_NOT_EQUAL) + if(current_type != PORT_TYPE_ANY) + current_type = PORT_TYPE_ANY + comparison_input.set_datatype(PORT_TYPE_ANY) + else + if(current_type != PORT_TYPE_NUMBER) + current_type = PORT_TYPE_NUMBER + comparison_input.set_datatype(PORT_TYPE_NUMBER) + + +/obj/item/circuit_component/select/input_received(datum/port/input/port) + var/current_option = comparison_options.value + var/list/input_list = received_table.value + if(!islist(input_list) || isnum(column_name.value)) + return + + var/comparison_value = comparison_input.value + var/list/new_list = list() + for(var/list/entry in input_list) + var/anything = entry[column_name.value] + if(islist(anything)) + continue + if(current_option != COMP_COMPARISON_EQUAL && current_option != COMP_COMPARISON_NOT_EQUAL && !isnum(anything)) + continue + var/add_to_list = FALSE + switch(current_option) + if(COMP_COMPARISON_EQUAL) + add_to_list = anything == comparison_value + if(COMP_COMPARISON_NOT_EQUAL) + add_to_list = anything != comparison_value + if(COMP_COMPARISON_GREATER_THAN) + add_to_list = anything > comparison_value + if(COMP_COMPARISON_GREATER_THAN_OR_EQUAL) + add_to_list = anything >= comparison_value + if(COMP_COMPARISON_LESS_THAN) + add_to_list = anything < comparison_value + if(COMP_COMPARISON_LESS_THAN_OR_EQUAL) + add_to_list = anything <= comparison_value + + if(add_to_list) + new_list += list(entry) + + filtered_table.set_output(new_list) diff --git a/html/changelogs/AutoChangeLog-pr-79.yml b/html/changelogs/AutoChangeLog-pr-79.yml new file mode 100644 index 00000000000..b508be9c0b1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-79.yml @@ -0,0 +1,4 @@ +author: "HWSensum" +delete-after: True +changes: + - rscadd: "Added new CQD holster." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-83.yml b/html/changelogs/AutoChangeLog-pr-83.yml new file mode 100644 index 00000000000..f8c348245a2 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83.yml @@ -0,0 +1,10 @@ +author: "Lazar" +delete-after: True +changes: + - rscadd: "New weak body quirk" + - qol: "New bluck to hand quirk" + - balance: "rebalanced teshari pounch damage and incoming damage" + - sound: "added/ teshari sounds and more GAS sounds" + - image: "added teshari MOD suits and modules iconds" + - code_imp: "changed human ride and human base code" + - config: "Added teshari new teshari to raundstart races, remove old" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-91.yml b/html/changelogs/AutoChangeLog-pr-91.yml new file mode 100644 index 00000000000..40d94b13bb1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-91.yml @@ -0,0 +1,4 @@ +author: "Iajret" +delete-after: True +changes: + - bugfix: "fixed several redsec reskins. Namely satchel, wintercoat, new bluesec skirt and eyepatch reskins" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-93.yml b/html/changelogs/AutoChangeLog-pr-93.yml new file mode 100644 index 00000000000..0cee5020567 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-93.yml @@ -0,0 +1,4 @@ +author: "Iajret" +delete-after: True +changes: + - bugfix: "Some more fixes to journey and kilo" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97.yml b/html/changelogs/AutoChangeLog-pr-97.yml new file mode 100644 index 00000000000..01357518acb --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97.yml @@ -0,0 +1,5 @@ +author: "HWSensum" +delete-after: True +changes: + - bugfix: "fixed CQD holster sprite hiding." + - bugfix: "holster now can be attached to any uniform." \ No newline at end of file diff --git a/html/changelogs/archive/2023-09.yml b/html/changelogs/archive/2023-09.yml new file mode 100644 index 00000000000..a45513671ce --- /dev/null +++ b/html/changelogs/archive/2023-09.yml @@ -0,0 +1,1262 @@ +2023-09-01: + GoldenAlpharex: + - bugfix: Fixes a rare issue that could lead to players entering an infinite disconnection + loop from having a null view. + Guillaume Prata: + - balance: Fanny packs are now silent, no one will get a chat message about what + you put in or take out. + Melbert: + - balance: If your server has non-human morgue cadavers enabled, you will be guaranteed + one human cadaver no matter what. + - balance: All maps (with the exception of Birdboat) now have an additional morgue + cadaver roundstart. + Rhials: + - rscadd: Nuclear operatives have expanded the scope of their hiring practices. + Members of all species can now roll nukie! + - qol: The preferences menu now has an option to always be human when being selected + for a nuclear operative role. Check it out! + - image: Plasmaman operative envirosuits. + Seven: + - bugfix: Fixes not being able to break out of graves + - qol: Graves are no longer dense (you can walk over them now) + SkyratBot: + - bugfix: The Cursed Slot Machine should now actually give you more than one pull. + - balance: megafauna will now gut instead of dusting or gibbing + - qol: Cryotubes will now notify medbay if the patient within is dead, and will + eject them if auto is on. + - qol: Cryotubes will now automatically turn on when a patient enters it if auto + is on, but you can no longer close the cryotube on yourself. + - bugfix: Fixes an issue where role banned players would be able to accept certain + ghost roles they're meant to be banned from. + - bugfix: Meth will no longer explode when reacting in a body + - qol: removes the nukie tool parcel and places the tools directly in their box + - rscdel: Deleted a unused file + - qol: The organ harvester's output is more predictable, and the direction can be + changed by alt-clicking with the panel screwed open. + - qol: Metastation has had more navigate landmarks added, namely for areas like + Medbay, Cargo, Engineering, department heads offices and a few more. + - qol: Adds accessibility to breasts for service members + - bugfix: containers can no longer add more water to hydroponic trays than they + actually contain. + - bugfix: watering a hydroponic tray with amounts of water lesser than 1 will no + longer be added to nutrients rather than water + - rscadd: Cargo now once again has access to a "Cargo Shortskirt", a skirt variant + of their shorts! + - image: tweaks and adjustments to the Cargo resprite have been made, as well as + respriting some missed icons such as the Winter Coats and QM Cloak + vinylspiders: + - bugfix: sickle null rods now have sharpness and can cut stuff + - bugfix: the suffocation moodlet will now be cleared when you stop drowning in + liquid + xPokee, Pirill: + - rscadd: Added flower crowns, craftable via the clothing menu. + - image: Resprited geraniums, poppies, and lilys, along with changing their worn + icon. +2023-09-02: + GoldenAlpharex: + - bugfix: Fixed the crafting menu being able to get stuck if your crafting recipe + somehow goes missing. + - code_imp: Improves the quality of the item crafting code slightly. + Sealed101, EBAT_BASUHA for spritework: + - rscadd: Wizard's Den now has a book of Summon Cheese in the Studies Room + SkyratBot: + - bugfix: Hilbert's Hotel Research ruin now has a functioning tram. As a side effect, + the malfunctioning tram event should now only fire on maps with a tram! + - bugfix: Traitors should generate with "free objective" less often, and will once + more be assigned to steal things. + - bugfix: Ex-Interdyne pirates can now successfully spawn on case sensitive host + systems, such as Linux + - bugfix: Prevents admins from accidentally gibbing people by closing a confirmation + window. + Thebleh: + - bugfix: fixed access on a couple of Engineering and Atmos airlocks on DeltaStation + softcerv: + - qol: the NIFSoft Hud Adapter now works with more cosmetic eyewear. + vinylspiders: + - bugfix: anti-fauna arrows now do their bonus damage against mining mobs + - bugfix: bows will now display their ammo count correctly (1 when loaded instead + of 2) +2023-09-03: + Guillaume Prata: + - rscadd: 'New funny internals for the clowns to spawn with. They come with O2 and + a secondary gas between 3 options: BZ, Helium and N2O. Talk with a "different + tone" with Helium, giggle and laugh "uncontrollably" while under the minor effects + of N2O or have "fun" hallucinations while under the minor effects of BZ.' + - balance: To not cut on how long the clown's O2 internals last due to the mixed + gases, the funny internals have 50% more gas volume, same as engineers' internals. + Iajret: + - rscadd: Added arrivals shuttle autoreturn. + - config: waiting time before returning to interlink can be changed in config file, + by default - it's 15 seconds + Melbert: + - balance: Ascended Blade Heretics no longer have blanket stun immunity, they now + have 45 seconds of stun absorption that recharges after 2 minutes - think His + Grace. This doesn't affect stamcrit (still immune to that) (assuming you haven't + consumed all of your immunity charge) but does affect hard CC such as slips, + flashbangs, or beepsky. + - balance: Ascended Blade Heretics now have a 0.75 modifier to incoming knockdowns. + - bugfix: Fix bastard sword granting infinite stun immunity + Nerev4r: + - rscadd: Razor claw augment for the loadout! Spend those quirk points! + RatFromTheJungle: + - rscdel: Removed Surplus bundles from opfor. + - rscdel: Remove microbomb implants from Contractors and Ninjas. + SkyratBot: + - bugfix: You can now actually fish soggy wallets from toilets, rare ores on ice + moon, some boney stuff in oil puddles (good luck finding them) and lube-fishes + by the seawater. + - sound: 2 new ambient tracks for maintenance tunnels + - bugfix: fixed implanted legion cores being available for use when unconscious/dead. + - bugfix: Soups are accepted by Kitchen Smartfridges + - bugfix: Soups are not accepted by drink showcases + - bugfix: You can no longer create non-synthesizable chemicals with bees. + - bugfix: basic ranged mobs will now chase victims + - qol: The pirate candidate gathering poll will now mention which pirate gang it + is gathering candidates for. + - bugfix: The HUDs of mobs with dynamic human appearance will appear properly. + - bugfix: Fixed the air inside of a penguin pen being way too cold + - bugfix: you can now examine screen objects properly +2023-09-04: + CliffracerX: + - bugfix: mutantcolors are being pulled from your features dna again, no more pink + feathers! + DrDiasyl, LT3: + - sound: 'Adds/modifies next sound files: airraid.ogg, bloblarm.ogg, alert.ogg, + notice3.ogg, announce_syndi.ogg' + - sound: Code Red, Delta, and other extreme emergencies now possess more unique + alarm sound effect + - sound: Captain's arrival now is announced by Captain's announcement sound, but + not for Acting Captain's + - sound: Making Captain announcement through emagged console; hijacking or fully + corrupting evacuation shuttle now plays more ominous sound + - sound: Making announcements through Requests Console now plays a more noticeable + sound + - image: Status Displays sprites have been fully changed. Now they include displays + for every Security Level + - qol: The "Red Alert" button in the Communications console status display menu + has been replaced with a "Current Alert" button showing the current station + Security Level display on Status Displays + - qol: Communications console status display menu got a new "Radiation" button which + shows radiation symbol on Status Displays + Guillaume Prata: + - rscadd: Atmos Tech now have new drip and will spawn with Atmos Overalls to protect + their clothing from gas leaks! (It will actually not protect you against fire + or leaks, but hey! It's the thought that counts!!) + Iamgoofball: + - bugfix: You cannot put enterprise resource planning drugs in the Odysseus syringe + gun, and they will no longer be used in the random reagent recipes. + SkyratBot: + - rscdel: Removed the threshold for confusion symptom that adds illiteracy to the + disease. + - balance: Clamps confusion symptom's confusion to a maximum of 30 seconds. + - qol: Confusion as a debuff no longer guarantees random movement if you're resting. + - image: resprites the genesploicer. + - sound: Adds a sound effect for hypospray/medipen injections. Doesn't affect sleepy + pens or HMS injectors + - bugfix: fixed basic mobs not dropping their butcher results when gibbed + - bugfix: Multi-z Icebox ruins including the Demonic Frost Miner can now be placed + again + - rscdel: Removes the swarmer beacon achievements from the game (no one ever got + them, and they haven't been obtainable for years) + distributivgesetz: + - code_imp: Moved some job-related achievements from the misc category to the jobs + category. + softcerv: + - image: adds the hairfre hairstyle from hyperstation + - bugfix: fixes the organic interface gas mask not allowing for the wearer to inhale + or exhale. + - balance: the evoker soulcatcher is now available through the R&D protolathe. +2023-09-05: + Azlier: + - qol: added dumping bags into large wooden mortars and millstones + LT3: + - image: New medical bed and emergency roller bed + - code_imp: Added context hints for beds + SkyratBot: + - balance: The fishing minigame should be easier now. + - bugfix: fixed fishing skeleton mob spawners that immediately crumble back into + the void of whatever chasm you fished them from. + - bugfix: boiled rice doesn't burn instantly after being baked + - code_imp: simplifies the way burning food is handled, grilled/baked food now turns + to a burnt mess rather than being set on fire, unless they have a baking/grilling + recipe + - bugfix: PDAs being on silent no longer prevents PDAs from being sent to your chat, + again. + - rscadd: pAIs inserted into a PDA can now control the PDA, and will receive PDA + messages sent to it (and can respond under the PDA's name). + - bugfix: '''meat product'' and imitation carp meat are now considered synthetic + meats for the purposes of reagent purity' + - bugfix: It's now humanly possible to reach the legendary fisherman rank, get the + achievement and the hat. + - rscadd: Mood buff power and duration depends on the ingredient quality and recipe + complexity of the food. + - rscadd: Hand-crafted food also gives a non-mood buff. The buff type depends on + recipe complexity and duration - on ingredient quality. + - code_imp: Added infrastructure to assign custom buff to certain dishes for unique + effects + - rscadd: Consumable reagents now care about purity, being more nutritious with + higher purity. Dispensers give synthetic food, natural sources are organic and + better and give longer buffs. + - rscadd: Grown food reagent purity now depends on lifespan, endurance and instability + stats of the seed. + - rscadd: Natural booze now uses the same purity formula and 100% purity can increase + booze power by up to 200%. + - rscadd: Added Fat reagent that is now a base type for lard, vegetable oil, olive + oil. Corn oil and cooking oil merged into vegetable oil. + - balance: Reagents and their purities are preserved during food chemical reactions, + processing, cooking and crafting. + - balance: Sugar nutrition rebalanced due to it being 100% pure in chem dispenser, + overdose happens at 100u now. + - bugfix: '`grind_results` of grown food give full amount equal to all removed nutrients + instead of just `/datum/reagent/consumable/nutriment`' + - qol: 'Seed Extractor now shows additional seed data: possible mutations, juicing + result, grinding result, fermentation result' + - bugfix: Reverts the unintended change of monkey cube biomass cost back to 50 + - bugfix: kinesis plus properly lets you move again when grabbed once + - rscadd: Adds the medbeam module for nukies, don't cross the damn beams. (Also + removes the handheld one from the uplink) + - bugfix: Updates the changelog link for the pre-4407 changelog as it was moved + - rscadd: adds medical carts and surgery trays + - image: gives the surgery razor a unique sprite + - qol: pollinating bees will now only attack players that are standing near the + beehive + Thlumyn: + - bugfix: mold no longer appears under catwalk floor tiles + softcerv: + - rscadd: Adds in meson NIFSoft HUDs +2023-09-06: + Krysonism: + - image: resprites the noticeboard + SkyratBot: + - spellcheck: Improved phrasing and syntax on dismemberment mood messages. + - bugfix: Fixed walking into grilles to destroy them + - bugfix: You can no longer walk into Birdshot's secure tech storage like you own + the place - unless you do own the place. + - spellcheck: re-writes cosmic heretic lore + - refactor: the minebots have been refactored please report any bugs + - rscadd: minebots can now mine walls and collect ores automatically and they will + alert everyone if they find u dead + - bugfix: fixed bileworm evolution deleting anything they devoured; they will now + eject their contents upon evolution to vileworms + - bugfix: Fixed all-in-one grinders not giving all the correct reagents when grinding. + - balance: Prosthetics and slimepeople can now have limbs dismembered + - balance: Slimepeople can now receive slash wounds, but cannot bleed + - balance: Most limbs can now be dislocated + - refactor: Scar backend reworked, scars will be wiped as they update to the new + format + - bugfix: Slash wounds are now compatable with pierce wounds + - image: cleans up the engineering emergency internals sprite slightly + - bugfix: emissive appearances on glowy eyes and such now properly rotate +2023-09-07: + LovliestPlant: + - qol: '[DS-2] Replaces the pair of plastitatinum sheets with small stacks of plasteel + and titanium in the engine room.' + - qol: '[DS-2] Adds a korta seed packet to the botany box in the permabrig.' + - bugfix: '[Interdyne] Fixes the floating tube light above the kitchen counter in + the cafeteria.' + SkyratBot: + - bugfix: Snow legions now drop only one corpse, and no longer drop Lavaland corpses. + - bugfix: The Birdshot Gaming Den now has arcade circuit boards and computer frames + that actually work. + - bugfix: fixed mirrors not breaking when a curse effect is triggered + - bugfix: Public mining storage's camera is now on the mining network instead of + the gulag. + - qol: Adds cables under birdshot xeno containment shielding. + - bugfix: Machine frames in NorthStar Cyto are now functional. + - qol: Minor cable/pipe changes to NorthStar the xeno containment. + - qol: NorthStar's departmental head offices have had their respective PDA & ID + imprinters added! + - qol: The NorthStar Head of Security office has gotten a small expansion. + vinylspiders: + - bugfix: mold on multi-z maps will no longer spawn in or spread resin to openspace + turfs +2023-09-08: + LT3: + - bugfix: Meteors no longer take damage from crossing certain unoccupied turfs + SkyratBot: + - bugfix: fixes creamatorium not producing any suspicious ice cream, and fixes a + dead mouse related harrdel + - refactor: If you transform into another mob and notice bugs with interacting with + the game world, please create a bug report as this framework was recently refactored. + - image: The glow shoes from the ClothesMate now actually glow and can be recolored, + even with a spraycan. + - bugfix: Megafauna now correctly prints the victim's name after a kill + - bugfix: leash unit test will time out less often and increases the timer until + it is considered 'timed out', to reduce false CI failures + - spellcheck: Corrected the name of the telescopic fishing rod to "telescopic fishing + rod" from the more generic "fishing rod" + - balance: colossus' near-death attack now starts after a 0.5 seconds delay + - image: adds special sprites for lungs when you use the smoker quirk + - bugfix: basic mobs can now use ranged burst attacks + - balance: gas mixer output now accurate to set ratio regardless of input temperatures + - rscadd: Traitors, Changelings, Heretics, Wizards, Malfunctioning AIs, and Ninjas + can now all reject their original objectives and provide one of their own in + its place. A Heretic doing this will no longer be able to ascend. + - rscadd: '"Custom" objectives which aren''t mechanically tracked will no longer + report success or failure upon round end.' + - qol: Space Ninja spider charges will now display where they can be detonated when + examined, if you are a ninja. +2023-09-09: + LT3: + - qol: Supermatter common channel alerts are less frequent if the crystal's integrity + is rising rapidly + Melbert: + - rscadd: '[Icebox] Remaps arrivals and the maintenance around it. Aux base out, + mass driver into a plasma lake in.' + SkyratBot: + - rscadd: Added the Numb quirk, that makes you (almost) unable to perceive how much + your injuries hurt. + - rscadd: Ever see a robot yawn? Don't worry, it makes sense now. + - bugfix: The messenger app can now be used when laying down. + - bugfix: Cauteries now have 'heat', like lighters, welders, etc. + - qol: You can smoke with a space helmet as long as you have internals on. + - bugfix: Getting a node researched now properly makes it no longer hidden. + - bugfix: Ninjas draining RD servers now drains it from the connected techweb, rather + than sniping Science. + - balance: Machines will first try to connect to a techweb with servers on their + z-level, with the Science techweb remaining as fallback. + - rscadd: Oldstation RND, comes with their own Techweb and special surgery to gain + research points through dissecting Xenomorphs. + - balance: Traitors who are activated as sleeper agents or arrive late on the arrivals + shuttle will begin with more reputation and likely be able to immediately access + most of the uplink catalogue. + - image: updates medigel sprites +2023-09-10: + A.C.M.O.: + - bugfix: Fixed Poly's voice commands. Poly will now perch on the Chief Engineer's + shoulder when expected. + LT3: + - image: Delam emergency procedure wall biscuit replaced with SAFETY MOTH + - code_imp: Delam panic button works when APC is dead + - code_imp: Removed extra calls to nightshift subsystem during delam + - bugfix: Votes play an alert sound again + SkyratBot: + - bugfix: Golems can eat + - bugfix: Cooked and crafted food should be edible + - bugfix: Medborgs can now spawn vanilla ice cream instead of nothing ice cream + - bugfix: Ghosts can examine food + - bugfix: Exploration drones can't be used to reach Centcom anymore. + - admin: Admins can add/remove the spawner component from arbitrary items again. + - qol: adds some more traitor objective brainwashing default objectives. + - spellcheck: fixes the double-punctuation on traitor objective brainwashing broadcasts. + - spellcheck: brainwashing deadchat broadcasts will now auto-punctuate. + - admin: The "Create Command Report" verb now has the option to not print report + papers at communications consoles. + - bugfix: It is now possible to smoke cigarettes even if you aren't wearing a safety + helmet + StrangeWeirdKitten: + - bugfix: Fixes Interdyne mining reward boards making golem reward vendors + vinylspiders: + - bugfix: fixed a bug that was preventing large mortar from getting any of the extra + reagents from grinding/juicing certain items +2023-09-11: + Hatterhat: + - rscdel: Thanks to lobbying from other factions within the Syndicate, the black + markets accessible by telecrystal-based uplinks are no longer stocking modified + hand teleporters, citing a new "stand and deliver" doctrine established by more + violent, militant arms of the organization. + Melbert: + - qol: Haunted 8-ball no longer requires the ghost orbit the petitioner to submit + votes + - qol: Haunted 8-ball ghosts can now change their vote after submitting it + - bugfix: Haunted 8-ball no longer always reports "yes" + - bugfix: Haunted 8-ball no longer always reports default "yes", "no", or "maybe" + and now gives a proper eight ball response + - bugfix: Haunted 8-ball can be picked up via the stat panel + RatFromTheJungle: + - rscdel: Guns, collectively, can no longer right-click holdup people + SkyratBot: + - rscadd: 'Added the service borg "drink apparatus" upgrade, which adds an extra + drinking apparatus to the borg, up to a maximum of 5 extra. + + :cl:' + - bugfix: Changeling tentacle and bloodchiller from xenobio will no longer stop + working if you have antimagic + - qol: Rice Dough may be made in beaker instead of being crafted, but the rice and + flour must be added first + - rscadd: humpback emergency shuttle + - image: Hivelords have a new sprite. + - image: Hivelord and Legion brood have a death animation. + - bugfix: Mortar and pestle can grind stuff again + Wallem, MTandi: + - image: Updates chem factory tank sprites + jjpark-kb: + - bugfix: revive mob ritual works on basic mobs now + - bugfix: ashwalkers have the ashwalker faction + softcerv: + - bugfix: ghost cafe NIFs no longer have access to hivemind +2023-09-12: + Adelphon: + - rscadd: new underwear + GoldenAlpharex: + - bugfix: All potted plants should now be visible again. + - admin: Player ranks are now displayed in the Player Panel of each user. + - code_imp: Player ranks can now be checked without taking into account the admin + bypass while in-game. + - bugfix: Fixed the rounding errors that caused decimals to wrongly appear when + hitting the Shift Layer Upwards or the Shift Layer Downwards verbs. + - bugfix: The message sent to admins when a new admin has been added via the Permissions + Panel will now properly show the new admin's ckey. + - qol: Character Previews are now always displayed in the Examine panel, rather + than disappearing when the flavor text would otherwise be hidden. + LT3: + - code_imp: Status display shuttle timer no longer scrolls + - bugfix: Fixed Void Raptor cargo door labels and IDs + - bugfix: Singularities are no longer invisible. You can again see your impending + doom + Melbert: + - rscdel: Spacers are slightly shorter. They're still taller than other people, + just not as much. + OrionTheFox: + - bugfix: fixed being unable to re-select the "Quartermaster" title in job prefs + after selecting an alt-title + - image: updated the greyscale Cattleman Hat to be not-bad. It now isn't 1px too + low, and actually looks like a cattleman rather than a lump of butter on a plate! + SkyratBot: + - bugfix: The vorpal scythe is no longer as greedy about you murdering people, and + will once again accept striking any living creature to be sated. + - bugfix: Fix capture devices allowing mob actions while inside + - bugfix: Your clothes and such should correctly reposition themselves if a black + charged slime extract turns you into a monkey. + - bugfix: Ninjas should be correctly credited for using their spider bombs + - balance: Supermatter zap power generation scales with the delta time between its + last zaps, preventing faster zapping from scaling power generation to extreme + levels. + - bugfix: Fixes supermatter zap rate not scaling properly. It should zap much faster + at higher energy levels as intended. + - qol: Changeling chemical generation scales with the world's delta time, making + its rate independent of subsystem lag. + - qol: Revenant essence generation scales with the world's delta time, making its + rate independent of subsystem lag. + - qol: Xenomorph plasma generation and resin healing scales with the world's delta + time, making their rates independent of subsystem lag. + - balance: polymorph belt now blacklists mobs that are undead, humanoid, robotic + or spiritual (in nature, not religiously), as well as megafauna + - balance: however, this means that it works with more mobs that it should logically + work with, like slimes/bugs/lightgeists etc + - bugfix: fixed headslug shenanigans with the polymorph belt hopefully for good + this time + - bugfix: fixed headslug description mentioning its movement despite the slug being + dead + sergeirocks100: + - bugfix: Briefs now make use of their digitigrade sprite. +2023-09-13: + CoiledLamb, Time-Green: + - image: resprites the radioactive nebula shielding + Ghommie (Based on an old PR by Trilbyspaceclone from Citadel): + - qol: The notepad app now includes basic nautical directions in its default message. + - qol: A tip about nautical directions, too. + LT3: + - bugfix: All missing surgery trays have been found. Don't ask where. + Literallynotpickles: + - qol: Adjusted medical tongue bounties to mention that cybernetic ones _do_ work + for them. + Melbert: + - bugfix: Birdboat's Augment Theater is named less odd now + Rhials: + - bugfix: The psyker headset is no longer a syndicate headset subtype, and no longer + has syndie comms. + SkyratBot: + - rscadd: The SC/FISHER disruptor pistol, a very compact, permanently silenced energy + gun, is now stocked in Nanotrasen-accessible black markets with a price generally + somewhere between 400 and 800 credits. Aspiring users are warned that it's really + bad for trying to actually kill people. Caveat emptor. + - rscadd: Guns now have a dry_fire_sound_volume variable, allowing for guns to be + less loud when trying to fire while empty. + - bugfix: Closets and crates now properly count as structures for pass flags again. + - balance: Add hypnosis vulnerability for drugged victims + - rscadd: Operative MODsuits now have an attached "jump jet" which sends you upwards + and allows you to use your jetpack under gravity for a few seconds, perfect + for navigating the pits and valleys of Icebox Station. + - qol: Shuttle engines now tell you how to install them in their screentips and + their examine text. + - image: resprites pestkiller, weedkiller, and nolabel sprays + - image: updates shading on medigels + - image: resprites all spray bottles + - bugfix: Medicine allergy can no longer roll quantum medicine + - bugfix: fixed grown food items not getting the right seed type passed to them + upon creation + - bugfix: The holographic pufferfish from the holographic beach from the holodeck + no longer looks like a goldfish. + - bugfix: the ablative coat's hood now hides the wearer's hair and ear + - bugfix: Soup recipes, that make items, spawn the correct number of items per reaction + instead of just one item + - bugfix: Soup recipes, that make items, consumes the correct number of reagents + instead of the largest multiple of the reagents + - balance: removed anti-drop implants from the nuclear operative uplink + - balance: removed anti-drop implant from the nukie implants bundle and changed + its cost to 20 TC + SuicidalPickles: + - qol: Cargo Coats/Jackets can now equip universal scanners on their suit-slots. + burgerenergy: + - balance: Buffed MCR damage in line with an upstream generic laser buff + vinylspiders: + - bugfix: Headsets now have their old worn sprites back, did you ever notice it? +2023-09-14: + LT3: + - bugfix: Emergency shuttle should correctly scale timer up/down when changing security + levels + OrionTheFox: + - rscdel: Removed the now-obsolete Skyrat Cargo Gorka-Jacket; TG now has one! + - image: updated all of the cargo sprites to match TG's, including digitigrade sprites! + - bugfix: the QM's Formal Skirt now actually uses the Skirt icon rather than the + Suit + Rhials: + - rscadd: Shuttle Firesale positive station trait. Some emergency shuttle options + have been put on sale! + - rscadd: Misplaced Wallet positive station trait. You wouldn't steal from a missing + wallet, would you?? + - rscadd: Wisdom Cow Invasion positive station trait. + - rscadd: Advanced Medbots positive station trait. Better roundstart medbots! + - rscadd: Loaner Shuttle positive station trait. More shuttle loan offers and more + payout! + - qol: Station Trait titles are now italicized for easier reading. + - spellcheck: Fixes a "prerequisites" typo in the shuttle purchase menu. + Seven: + - balance: The supermatter delamination countdown has been lowered from 30 to 13 + seconds + - balance: Removing a sliver from the supermatter further lowers that down to 3 + seconds + - balance: Supermatter panic button warning reduced from 5 to 3 seconds + - balance: Supermatter suppression system healing runtime reduced from 9 to 7 seconds + - balance: The supermatter crystal uses bigger text on its final countdown + - spellcheck: Some supermatter delamination related mood descriptions have been + edited to explain the mood effect better + SkyratBot: + - bugfix: You can no longer teleport to disabled beacon if the teleporter was previously + locked-on to it. + - bugfix: Dynamic now biases less heavily towards the exact average. + - bugfix: Nightmares can no longer receive wounds + - bugfix: Nightmares can no longer have limbs dismembered + - qol: Conveyor belts now have screentips and a better examine tip to teach you + how to set one up properly. + - qol: Using a conveyor belt stack on a placed conveyor belt will extend the conveyor + belt to the output of that conveyor belt.. You can use this to place fully integrated + conveyor belts much easier now. + - image: When you throw up nanites, your vomit should now be appropriately nanite-colored. + - bugfix: Fix poor dynamic threat distribution at lower population levels, causing + dynamic to generate better threat curves at lower population levels than it + did before. + - bugfix: Roundstart medbots and cleanbots are now more likely to be able to be + possessed by observers. + - admin: It's easier to modify the properties of bots to stop them from being possessed + or depossessed. + Wallem: + - qol: Examine a Dish Drive to see all the items inside of it, as well as the item + you'll pull out when you interact with it. + - qol: Dish Drive servo tier increases suction range + honkpocket: + - bugfix: Players who run the DNR quirk no longer appear as revivable when examined + nikothedude: + - qol: 'Temporary flavor text preview character limit: From 37 to 110' + tf-4: + - rscadd: You can now craft ammo pouches from four leather. + - spellcheck: Fixed typo in cocaine snorting message. +2023-09-15: + LovliestPlant: + - rscadd: '[Void Raptor] Adds an exam room and charting office to medbay, just opposite + of the escape pod.' + - rscadd: '[Void Raptor] Adds a refrigeration system to medbay''s cold storage room.' + - qol: '[Void Raptor] Removes some clutter from medbay (storage plants, extra anesthetic + closets), adds blankets to TC beds.' + - qol: '[Void Raptor] Access to the Southern entrance to the Security office now + matches that of the North entrance.' + - qol: '[Void Raptor] Overhauls medbay''s cold storage room. Adds a coldroom freezer + system; adds a handful of emergency oxygen tanks, emergency nitrogen tanks, + and masks to the internals crate; replaces the empty medical crate with a spare + robotics limbs crate; and replaces the Oxygen canisters with anesthetic mix + canisters.' + - qol: '[Void Raptor] Adds towel bin to perma, replaces the linen bin in the public + pool with a towel bin.' + - bugfix: '[Void Raptor] Replaces flooring with bare plating in sections of maints, + should fix mouse spawning.' + - bugfix: '[Void Raptor] Fixes the back entrance to the medbay''s treatment center + requiring morgue access.' + - bugfix: '[Void Raptor] CO''s can now open the brig officer locker in their office.' + - bugfix: '[Void Raptor] Fixes a minor clipping issue with the records console in + the CO office.' + Lunar248: + - qol: Gave Freighter a few things to reduce some tediousness, such as a welding + tank in engineering, water tank in botany, and a proper sink in the kitchen. + - bugfix: Added missing lights, and light switches to Freighter. + SkyratBot: + - bugfix: you can no longer bypass html sanitization using the table element. >:( +2023-09-16: + LT3: + - code_imp: Engaging in Role Play on the Interlink should no longer lead to heart + attack and death + LT3, unit0016: + - qol: You can now choose between limb, prosthetic, or no limb at all + Literallynotpickles: + - balance: Changeling Horror-Form no longer has reduced click-delay. + RatFromTheJungle: + - balance: The captains sabre now does five more damage totaling to 20 (while losing + a bit of wound chance!), while the shamshir does the same with equally less + wound chance, less armor pen, and worse block. + SkyratBot: + - bugfix: fixes a bug that would cause grown inedible plant seeds (like tower cap) + to vanish from existence upon being added to the seed extractor + - bugfix: fixes a issue that would cause fruit wine to bug out when trying to blend + its reagent color + - bugfix: The nuclear operative MODsuit intellicard now actually downloads an AI + rather than simply kicking candidates from the game. + - bugfix: Fixed a race condition that made fishing yield no reward way too often. + - bugfix: The legendary fisher achievement is awarded even if you don't win the + minigame. + - bugfix: Fixed a fish hook exploit. + - bugfix: Baits are now properly consumed by caught fish and (alive) mobs. + - bugfix: Fixes tesla coils duplicating the power of >7GeV supermatter zaps. + - bugfix: Space ruin Anomaly Research - Fixes stacked windows and underplating + - rscadd: There's a new space ruin in town, be on the lookout for a hidden supply + cache. + - rscadd: Added a new type of wall which can only be destroyed by blowing it up. + - balance: Pulling embedded items e.g. shrapnel with hemostats is now a lot faster, + and scales appropriately with toolspeed. + - balance: You can now pull embedded items with wirecutters, at a speed penalty. + - bugfix: Unary vents & Injectors now link properly with air sensors via multitool + both ways + - balance: Watchers will no longer put you at gunpoint. + - balance: The spontaneous brain trauma event will no longer occur if there are + fewer than 13 players. + - bugfix: Flares and candles no longer sound like flashlights when being turned + on. + - bugfix: Getting shot by an SC/FISHER now disables PDA lights for consistency's + sake. + - spellcheck: Replaced an irrelevant tip of the round about scars with a better + one + - balance: You will be knocked down again on certain vomits. Don't worry, you'll + deserve it when it happens. + - qol: The supply beacon will no longer delete itself due to explosions, and you + can now anchor it with a wrench. + - spellcheck: Express console now correctly states that it needs cargo access instead + of QM access. + - bugfix: returning items to vendors works correctly + - bugfix: you can't return items that has stuff in it for e.g. a serving tray with + food in it + - bugfix: fixed fishing. + - bugfix: the bank machine cannot print holochips worth 0 credits now + - bugfix: adds the bolted and welded helper to the bar backroom/kitchen coldroom + airlock on birdshot, as to prevent chefs from being able to access armor and + sunglasses roundstart with barely any work involved + nikothedude: + - rscadd: 2 Bonesetters, 4 stacks of gauze, 2 health analyzers that cant be used + for medibots, all in the robodrobe +2023-09-17: + SkyratBot: + - refactor: Refactored wounds yet again + - bugfix: Wounds are now picked from the most severe down again, meaning eswords + can jump to avulsions + - bugfix: Scar descs are now properly applied + honkpocket: + - rscadd: The bullet-drive machine can once again be bought from the cargo-imports + menu + nikothedude: + - bugfix: Msucle scars no longer cause general disfigurement +2023-09-18: + Majkl-J: + - bugfix: laser magazines can now be reloaded correctly + SkyratBot: + - bugfix: Monkeys have their tails back. + - bugfix: Fixed a resource dupe in the ORM. + - rscadd: added the inspectors hat to the detectives cabinet, a special hat that + allows the wearer to say a phrase to dispense a stored item + - rscadd: climbing hooks that allow you to go up holes for multiz, found in internals + boxes (on planetary maps), the uplink, cargo and nukie personal lockers + - rscadd: A new ruin has appeared on lavaland, featuring the site of an ancient + battle. + - qol: '[Deltastation, Icebox, Metastation, Tramstation] Adds cell timers to isolation + cells. (they do not auto-open the doors)' + - qol: '[Birdshot, Deltastation, Icebox, Metastation, Northstar, Tramstation] Adds + translator glove modules to the stacks of "accessibility" (e.g. plasma fixation + / thermal regulator) modules found in security, medical, and engineering storage + rooms.' + - qol: '[Birdshot] Adds a roll of packaging paper to the cargo office.' + - qol: '[Icebox] Adds a hand labeler to security''s gear room.' + - qol: '[Northstar] Nudges the set of binoculars covering the mass driver controls + in ordnance over a few inches.' + - bugfix: '[Birdshot] Remaps the janitor''s closet such that the recycling machine + will now work.' + - bugfix: '[Icebox] Removes a duplicated hand labeler from the rack near security''s + brig cells.' + - bugfix: '[Metastation] Patches a broken corpse disposal pipe running from aux + surgery to the morgue.' + - bugfix: '[Northstar] Fixes the SM being hotwired at round-start (partially rewires + the SM room, moves the APC to the North wall).' + - code_imp: added some null checks for general juicing & grinding items + - bugfix: grinding stacks now grinds as many pieces/sheets from the stack as possible + that can fit in a beaker/container without wasting the whole stack + - bugfix: plumbing chemical grinder now actually works again + - bugfix: the plumbing chemical grinder allows stuff to enter from any direction + but not mobs and also accepts items put inside it via hand including bags + - bugfix: You can remove the beaker from the all in 1 grinder when power is off + via right click + - bugfix: All in 1 grinder now mixes faster with upgraded parts + - refactor: you can no longer walk into a plumbing chemical grinder + - bugfix: adds a few firelocks and alarms around IceBox + - bugfix: the nukie medibot (oppenheimer) has access to the doors of the infiltrator + and is not shot at by the turrets + - refactor: heretic sacrifice room is now lazyloaded + - bugfix: Lipolicide and other chems now puts you in crit, even if it is the only + source of damage + - bugfix: made the radiation protection crate's contents match it's description + - rscadd: Ghosts (observers) can eat ghost burgers and booberry muffins. + - balance: Ghost burgers will not decay or pick up germs due to the fact that they + moved themselves off a table. + - rscadd: NanoTrasen improved the quality of the local durathread strain; resulting + in it now being twice as filling! + - image: Hercuri spray now uses the same sprite as the yellow medical spray + - spellcheck: Added a missing space to hercuri spray's description + - bugfix: Manually constructed windoors have correct unrestricted accesses applied + to them + - bugfix: Windoors created via RCD now actually have electronics inside them + - bugfix: Airlocks constructed via RCD have the shell component correctly installed + inside them and have no other missing variables + - rscadd: Wall mounted objects (Things like APCs, Air Alarms, Light switches, Signs, + Posters, Newscasters, you name it) will now fall to the ground and break or + deconstruct when their attaching wall is changed or broken. + - spellcheck: Fixed some typos on QM's Overcoat + - bugfix: Forgetting to take dough out of the oven no longer progresses the server + to a crash-worthy state with infinite bread and ash and burned food products + for all. + - bugfix: Recipe paper in the ruins now shows a normal recipe for Metalgen and Secret + sauce. + - code_imp: Cleans up some unnecessary code left over from caseless ammo. + - qol: the recycler can now be rotated + - qol: Machines now transfer their local materials to silo during linking with multitool + SpaceLove: + - balance: Bluespace Miners have been made cheaper by Nanotrasen as they have found + a huge mine of materials recently! + TheOneAndOnlyCreeperJoe: + - bugfix: Hydra quirk now properly works instead of making you British. + Thebleh: + - bugfix: Bluespace RPEDs can now be rigged again + nikothedude: + - rscadd: Several common 'household' reagents can be used as improvised medicine + treatment. + - rscadd: Drinking tea will help mend (non-bone) wounds over time. + - rscadd: Flour and corn starch may be splashed onto wounds to help dry them up, + though they'll have a negative effect on burn wounds. + - rscadd: Added a new reagent, saltwater, made by combining table salt with water. + - rscadd: Table salt and saltwater can be splashed onto wounds as well, reducing + bleeding and improving sanitization and disinfection significantly. However, + the coarse undiluted salt will irritate the wounds, reducing clot rate and flesh + healing, and both of the reagents will increase a burn wound's infestation rate. + - rscadd: Altered Table Salt's recipe to just need sodium and chloride. Changed + the recipe of Pentetic Acid and Heparin to need table salt (sodium x chloride) + and thus slightly altered the total output of those reagents (pentacid went + from 5u per reaction to 4u, heparin 4u->3u) + - rscadd: Saline-Glucose Solution now needs 2u of saltwater and 1u of sugar, meaning + the overall recipe should be completely unchanged in practice. Contact me on + discord if any issues arise from these chemical changes! + - qol: First aid analyzers now give easy-to-understand direct information, with + the specific recommended treatments bolded in the analysis text. They also have + a 'unique' extra bit of info, telling you about improvised ways to remedy your + wound. + tf-4: + - bugfix: fixes being able to eat, use pills etc through gas masks and such +2023-09-19: + Ghommie (Thanks Sealed101): + - refactor: Reworked the fishing minigame into a game screen object from a TGUI + interface + Melbert: + - balance: Humanoids without tongues cannot cast spells with vocal components + - balance: Humanoids without arms cannot cast spells with emotive components + SkyratBot: + - bugfix: Makes sure pump-up properly grants the baton resistance trait. + - bugfix: The Nightmare's Light Eater can no longer suck the light out of space + tiles. + - bugfix: Fix wooden barricade description "This looks like it can be barricaded + with planks of wood" being spammed on objects. + - bugfix: basic mobs retaliate targetting now selects targets they can attack + - bugfix: Makes ethanol and sugar pure by default. + - balance: Player-controlled basic mobs attack as fast as those mobs can when controlled + by the AI + - balance: Player-controlled Faithless can paralyse people they attack, like the + AI does + - balance: Player-controlled Star Gazers (if an admin felt like making one) apply + the star mark on attack and deal damage to everything around them, like the + AI does + - rscadd: Many kinds of mobs can now be brought back to life through revival surgery. + - rscadd: Dogs can wear eyepatches. + - code_imp: Scars now stack trace if they fail to get a valid description + - rscdel: Removes the computer vendor. + - bugfix: pancake stack layering + - bugfix: pizzabox stack layering + - bugfix: pizzabox bombs that spawn unarmed now label their pizza correctly and + cannot spawn a spriteless pizza + - bugfix: Fixes a misplaced status display in Meta's medical storage. + - rscadd: mass drivers are now buildable, you activate them by attaching a signaler + to their launch wire, and can increase their power by pulsing the safeties wire, + and reset it back to normal by cutting the safeties wire. + - server: Default-configuration MINUTE_TOPIC_LIMIT has been increased + - bugfix: Fix a runtime when trying to cycle move intents with a hotkey as a dead + mob. + - bugfix: Nanotrasen has finally recalled their faulty multitools and replaced them + with working ones! The multitool's buffer now properly clears itself. + - qol: Moved multitool link messages to balloon alerts + - bugfix: added some missing firealarms on icebox in the hall towards departures + and the upper section of chapel + - bugfix: normal ethereal blood now works for electrolysis, the hydrogen and oxygen + output of the electrolysis recipe has been increased. + - balance: Only traitor, changeling, heretic, blood brother, headrev, wizard, obsessed, + magic/gun survivalists and greentext book holders can now double their hardcore + random score + - qol: Redtexting as antag with hardcore random score will pay you default points, + instead of none (normal survival rules) + - bugfix: End report screen will properly report hardcore random survival in case + of station destruction +2023-09-20: + LT3: + - rscadd: 'New random event: Supermatter Surge' + - refactor: It's different than the Supermatter Surge you're used to + - refactor: 'The scale is now 1: low severity to 4: most severe. Keep that in mind!' + - rscdel: Removed Skyrat version of Supermatter Surge + - code_imp: Individual supermatter crystals can have custom gas properties + SkyratBot: + - image: resprites t-ray scanner, gas analyzer, geiger counter and hand drill. + - qol: Surgery trays can now be crafted via the crafting menu (two rods, one silver), + and deconstructed via secondary click with a screwdriver! + - spellcheck: Unreverted and improved resonance cascade message. + - qol: Crafting R&D guns from gun kits no longer requires tools or cable coil. The + decloner and energy crossbow still need reagents. + - qol: Halved R&D gun crafting time. 20->10 seconds. + - bugfix: Fixed Mafia achievements + - balance: Unholy and Eldritch water are self-consuming like holy water! They don't + need a liver to be processed. + - bugfix: Fixes a selection window in the game rock-paper-scissors with death. + - bugfix: fixes inedible grown items (such as tower caps) becoming unclickable when + harvested, fixes their seeds disappearing when inserted into the seed machine + - refactor: Refactors the camera console UI. + - rscadd: heretic knock path and its respective items and award + - bugfix: Lava can no longer occasionally generate inside of previously loaded templates + and breach and/or destroy shit + - qol: mice and rats now are visually spaced out from eachother for visual clarity + - balance: Supermatter now takes 15 seconds to delaminate normally and 5 if a sliver + has been taken from it. Gives a little more time to escape in the case of the + sliver and also evens out the times to please perfectionists. + - bugfix: Supermatter now accurately reports it's detonation time. + - spellcheck: Supermatter mood descriptions have been reverted back to their old, + more flavorful selves. + Zergspower: + - qol: reworks the verb panel to be less of a CVS receipt + vinylspiders: + - bugfix: removed a deprecated loadout item 'Black Two-Piece Suit' which had an + invalid item path. The old item has been replaced with 'Black Suit'. +2023-09-21: + LT3: + - bugfix: Interdyne scientists get their Interdyne labcoat + Rhials: + - qol: Restores holiday hats for drones. + - qol: Extends holiday hat behavior to assistants. Get festive! + SkyratBot: + - bugfix: RCD Construction effects will no longer fall into chasms. + - bugfix: Gauze no longer falls off if a wound is demoted or promoted + - bugfix: The caller in a holopad call should now be able to hear people on the + other end. + - bugfix: wall mounted objects air alarms, fire alarms etc now actually falls off/gets + destroyed when their attached wall is deconstructed + - bugfix: wall mounts crafted in game also properly falls off/gets destroyed when + their attached wall is deconstructed + - bugfix: Fixes a bug allowing holopara injectors to be refundable when used. + - balance: Improvised shotgun shells now deal half as much damage to humans and + cause less wounds, but do 50% more damage to structures and machines. They also + require a glass shard for crafting. + - balance: 'EMP damage on augs: 2/1.5 from 3/2' + - balance: Augs now only get paralyzed by EMP for 3/6 seconds if they are damaged + below 70% HP, + - balance: Aug EMP knockdown reduced to 3 seconds + - balance: Synthetic ears now take far less EMP damage + - bugfix: rescue hooks will once again drop the mob next to the fisherman instead + of just displaying a balloon alert and doing nothing + - bugfix: Recipe that converts Vegetable Oil into Olive Oil works properly + - rscadd: Splashing antihol on a patient before surgery will make it to go slower. + honkpocket: + - rscadd: Adds garment bags to the blueshield and NTC lockers + - rscadd: Adds turtlenecks and the intern outfit to the NTC garment bag + vinylspiders: + - bugfix: spiritual quirk is no longer missing from the game + zydras: + - bugfix: properly paths NT Consultant and Blueshield areas +2023-09-22: + A.C.M.O.: + - bugfix: Fixed dildos, allowing them to be used orally only when the target mouth + is uncovered. + - bugfix: Fixed custom dildos, allowing their size and color to be customized with + Alt-Click. + GoldenAlpharex: + - code_imp: Removed some old fire overlay code, which might or might not fix some + fire overlay issues. We'll have to see. + - bugfix: The green pin is no longer unremovable for those that had it on before + they hit the 100 hours threshold. It will now be automatically disabled for + them. + LT3: + - image: Added donator items for grasshand + Literallynotpickles: + - rscadd: Added SHORTER Shorts to the Loadout Menu. (Misc Under) + SkyratBot: + - balance: Peacekeeper cyborg's emagged hug is no longer a hardstun. + - image: resprites t-ray scanner. + - admin: Admins can transform misbehaving players into arbitrary objects at will. + - bugfix: Projectiles no longer cause a blood graphic or blood splatters if they + hit a limb that cant bleed + - rscadd: Prosthetics/Augments now spark when shot + - bugfix: fixed moonicorns making space/chasm/lava/water bridges with their fairy + grass + jjpark-kb: + - qol: you can now construct, deconstruct, and anchor/unanchor the millstone + - rscadd: rough stones will now occasionally drop from mineral walls (the mining + kind) + - bugfix: you can now cut/process rough stones + vinylspiders: + - image: added digi resprite for shorter shorts +2023-09-23: + LT3: + - bugfix: Ghosts and godmode mobs will no longer create resonance when touching + the supermatter crystal + - rscadd: Poly now causes a power surge when dusted by the supermatter crystal + - balance: Delam panic button run time increased by 2 seconds to match the new 15 + second delam timer + - balance: Delam suppression freon changed to be less instantly lethal to organics, + less of a plasma fire waiting to happen + Lunar248: + - bugfix: Replaces the incorrect MOD cores with working ones. + Paxilmaniac: + - rscdel: The ability to change a flashlight's intensity has been removed due to + the fact they no longer have cells to draw more or less power from in those + modes. + - qol: Flashlights no longer pointlessly require power cells to function + SkyratBot: + - refactor: seedlings have been refactored into basic mobs please report any bugs + - rscadd: seedlings now can have different colored petals and can look after botanys + plants + - rscadd: seedlings are re-added to the game! they grow out of seedling seeds obtainable + from exotic seed crates or traitor uplink + - rscadd: The Message Monitor console's board can now be obtained via the telecoms + research node. + - bugfix: Conveyor belts now properly show their new screentips on mouseover with + tools. + - bugfix: clown bomb payload is no longer named badmin payload and no longer disperses + clowns in cardinal directions only + - bugfix: Foods that have special conditions for liking/disliking them (such as + donuts for sec officers) have these conditions again. + - bugfix: Characters with ageusia properly ignore non-toxic food types that they + eat. + - bugfix: If you examine toxic food, it can no longer appear to you as edible. + - admin: Boneless smite should work properly again. + distributivgesetz, CoiledLamb: + - rscadd: 'Added two new awards specifically for engineering and medical: The "Emergency + Services Award" and the "Atmospheric Mastery Award". CEs get 3 Emergency Services + Awards and 1 Atmospheric Mastery Award and CMOs get 3 Emergency Services Awards.' + softcerv: + - rscdel: The electric welding tool has been removed from the techweb and the experimental + welding tool has been reinstated in it's place. + vinylspiders: + - bugfix: using sand or volcanic ash on a seed mesh will no longer have the possibility + to spawn a generic buggy 'lavaland seed' item +2023-09-24: + LT3: + - bugfix: Fixed supermatter surges always being the lowest severity + Melbert: + - bugfix: Maybe fixes some weird occurrences where you lose the ability to pass + over tables when you shouldn't, and visa versa + SkyratBot: + - bugfix: Epinephrine will now update health properly. + - rscadd: Brimdemon corpses release an explosion shortly after death, just to keep + you on your toes. + - refactor: Brimdemons now use the basic mob framework which (should) improve their + pathfinding somewhat. Please bug report any unusual behaviour. + - admin: The brimdemon's beam ability can be given to any mob, for your Binding + of Isaac event + - refactor: clowns are basicmobs now + - admin: Admins can now reset or modify the chaplain's sect from a UI panel + lukevale: + - rscadd: Adds bras as a selectable preference in the same vein as underwear. Separates + all tops from undershirts. +2023-09-25: + Iamgoofball: + - bugfix: Fixes a bug with the steampunk goggles that deletes items that aren't + welding goggles when hit by it. + Melbert: + - rscadd: Changelings can now speak through their decoy brain if it is placed in + an MMI, to maintain the illusion they are actually dead and have been debrained. + SkyratBot: + - bugfix: Fix secret documents steal objective failing while inside folder. + - rscadd: Added the Hippocrates bust to medbay heirlooms. Paramedics don't get one. + - rscadd: You can now swear the Hippocratic oath with these busts! It'll give you + pacifism but nothing else. The process is reversible. + - rscadd: There's a very small chance that the Hippocrates bust was once wielded + by a certain German doctor. This chance is increased for coroner heirlooms. + - bugfix: Fixed players being able to roll antagonist without ever being eligible + to play any role. Players who have their preferences set up so that they're + likely to return to lobby when the round starts have a lowered chance of becoming + antagonist. + - bugfix: Fixed beams rendering below mobs by default. + - bugfix: The fishing line beam is no longer emissive (it doesn't glow in the dark). + - bugfix: Fixed the overflow role having less slots than it actually should. + - code_imp: New flags/args to electrocute_act() + - balance: Makes it so Ephedrine spasms have a 10 * (1.5 - purity)% chance per second + to happen, Adding a downside to pure Ephedrine + - bugfix: Syndicate Modsuit AI's now downloads the current codespeak book upon being + downloaded. + Vekter: + - bugfix: Fixes Birdshot's recycler being turned the wrong direction. + distributivgesetz: + - bugfix: Clamping/closing a wound should now heal the bodypart that was damaged + instead of a random one. + honkpocket: + - rscdel: Removed wall-mushroom outbreak event +2023-09-26: + LT3: + - spellcheck: Improved wording in greyscale JSON error message + - admin: Successful restart votes will now restart on the current map + - code_imp: End round and persistence data will be saved before executing successful + restart vote + Rhials: + - bugfix: '"Spooky" meteors will now properly spawn during halloween.' + SkyratBot: + - image: the security records suspected status is now teal instead of orange + - bugfix: Intellicards in computers are no longer deleted when the computer is destroyed. + - bugfix: Modular consoles can now be deconstructed by right clicking with a wrench. + - bugfix: cigarettes no longer smoke themselves from inside your pockets or on your + hands. + - admin: First time user connections are now logged + - qol: NT CIMs shows how much power the supermatter is releasing. + - qol: NT CIMs internal energy will adjust its prefix. + - qol: Energy displays (such as multitooling grid) will use the full range of SI + prefixes available, up to the peta prefix if you somehow managed to reach that. + - rscdel: Removes the per cubic centimeter part of internal energy. + - bugfix: Fix unnecessary delta time scaling on inactive supermatters. + - bugfix: Fix high energy zaps not scaling with delta time. + - bugfix: Fixes grounding rods lying about potential power you can generate. + - code_imp: Convert supermatter_zap() and tesla_zap() zap_str argument unit to be + in joules, and scales everything that uses that argument. + - rscadd: Adds an advanced plastic surgery procedure, allowing you to imitate people + in pictures. Simply hold a picture in your offhand of the person you wish to + imitate as while conducting the surgery! Remember, it's not foolproof, it only + changes your name and voice! + - rscadd: You can obtain the disk containing the afromentioned surgery. as a role-restricted + item to doctors and roboticists for 1TC, as a rare maint loot and BEPIS technode + reward + - bugfix: Centcom now rejects contraband that somehow makes it way onto the cargo + shuttle mid-transit and returns it. + - bugfix: you can no longer push watchers (and any other lavaland basic mob) around + by running into them + - bugfix: posibrains can be inserted again + - bugfix: Metalgen can no longer be used to transmute indestructible turfs. + - image: adds a frog holoform for pAIs + - bugfix: The custom error message for when there is only one map to vote for should + pop up in all cases rather than just a select few. + - bugfix: Stun immune people should no longer have issues with gripper gloves and + other tackle gloves. + sqnztb: + - rscadd: New medicine to clear up scars, made from synthflesh, ethanol, and miner's + salve. Be sure to apply it via patches! +2023-09-27: + SkyratBot: + - rscadd: added ranged attack friendly fire checks for basic mobs. minebots and + hivebots will now try to avoid shooting their friends + - bugfix: fixed Strong Stone ruin generation + - rscadd: Added a candle box crate for all your candle needs! + - bugfix: '[Tramstation] Mass Driver in chapel now has tiny fan so you don''t space + yourself.' + jjpark-kb: + - bugfix: actually fixed the ash revival ritual for basic mobs + - bugfix: basic animals now will have their health affected positively and negatively + by medicine and toxins respectively. +2023-09-28: + Melbert: + - rscadd: Doctors can now get head mirrors from their clothes vendor, to complete + the doctor outfit + SkyratBot: + - bugfix: Fixed job configs not being loaded properly. + - bugfix: You can no longer break the game by AI rolling in a card or APC + - qol: AI Roll now doesnt require you to click the exact turf to move you + - qol: AI roll cooldown and roll time is now a variable, making it possible for + AIs to become terrifying catamari damacy balls + - bugfix: Fixes full advanced surgery trays spawning with 'nothing' + - spellcheck: Tweaks the message that players get when not being able to qualify + for roundstart antag to be more accurate as to what's happening. + - qol: you can undeploy fulton beacons by clicking them with an empty hand + - qol: you can rename fulton beacons with a pen + - bugfix: Fix altars not allowing items to be sacrificed + - bugfix: Seeds will no longer be removed from existence after receiving the "You + can't seem to add [seed type] to the seed extractor" message + - bugfix: Some seeds that were previously not able to be added to the seed extractor + may now be added (starthistle for example) + - bugfix: fixes replica pod seeds spawning humans in nullspace + - qol: right clicking the seed extractor with a plant/food stores the extracted + seeds in the machine. + - bugfix: Valentines and ERTs will no longer get mood boosts from traitor moodener + items + - bugfix: Fixed zombies being able to infect headless corpses (Including former + zombies) + - bugfix: 'Fixed bio armor being totally useless against zombies. Now it checks + how hurt your limb is: If it''s more than the bio armor value, you get infected. + THICKMATERIAL clothing guarantees at least 25 damage required to infect you, + non-thick clothing reduces effective defence by 25. In practice this means people + with MODsuits, biosuits will resist infection unless they''re pummeled into + crit, and wearing a firesuit will save you from the first few slashes.' + - bugfix: Fixed the bomb hood armor not having the same bio armor value as bomb + armor. + - qol: Added a message to the zed when they succesfully infect someone. + - code_imp: Turned some proc names into snake_case rather than, uh, nospacecase. + - rscdel: removes surgical duffelbags + - bugfix: the surgery supply order now comes with a surgery tray + - bugfix: (skyrat) Hospital gowns will now spawn in surgery trays like they used + to in the surgery duffelbags + - bugfix: Left-clicking an empty surgery tray will now tell you exactly why it does + nothing. + - refactor: Turned slapcrafting into a component! You can examine compatible items + to see what recipes they can be used in, and what the ingredients for them are. + For example, spears and the head-on-spear crafting recipe. + - bugfix: The flight potion wings will no longer fail to work on lavaland/icemoon + on rare occasions. + - bugfix: Throwing things at cyborgs will now slow them down, as intended + - balance: Adjusted the calculation of throwforce -> slowdown for cyborgs such that + it is simply a flat duration for anything above a certain damage threshold (the + value of throwing iron rods) + - bugfix: Selecting "Monkey" on a magic mirror will now once again turn you into + a Monkey rather than a disgusting freak of nature. + - bugfix: Tall Boys have once again been barred from joining the Wizard Federation. + - rscadd: Contractor baton in traitor uplink for 12 TC + - balance: Ebow no longer has a reputation requirement. + - bugfix: Added complexity factors to foods that were missing them. + - balance: Gave the bluespace geode pirates 4 more teleporter bolt turrets. + - bugfix: The bluespace geode pirates no longer have a bluespace portal to the bottomless + pit dimension. + - rscadd: Station-safe dirt tiles for all your mapping needs, but surely no station + maps use the chasm baseturf ones, right? Right? + - bugfix: you can now deconstruct exodrone scanner arrays + - bugfix: the tree in space exodrone adventure no longer softlocks you + - qol: the exodrone launchers now tell you on examine how to remove their fuel canister + if you somehow needed to do that + - balance: exodrone wide scans are now capped at 10 minutes + - balance: exodrone travel times are 18% faster + - balance: you can now upgrade scanner arrays for faster wide scan + - balance: exodrone point scan and deep scan are faster + - spellcheck: fixes several typos related to exodrones and gives scanner control + console a description + - qol: Gas masks now muffle your voice with TTS. + - qol: Security Hailer masks now disguise your voice to protect your right to brutalize + greytiders. + - qol: Lizards, Ethereals, and Xenomorphs now have a vocal effect. + - qol: Security Records now show someone's voice name. + - bugfix: fixed geysers spawning on turfs with plants + Tattle: + - qol: drones now have individual names, instead of just "drone" + Vekter: + - rscadd: Added a holodeck to Birdshot Station. It can be reached via the Crew Facilities + hallway. + softcerv: + - rscadd: Adds in a wiki-book NIFSoft, allowing the user to access various wiki + books. + - qol: the speech impairment on the adult gas mask can now be toggled + tattle: + - qol: Basic animals now make sounds for audible emotes + - sound: Added new sound effects for chicks, chickens, crabs, and insects +2023-09-29: + A.C.M.O.: + - bugfix: Fixes the death sandwich, making it safe to examine. + BurgerBB: + - bugfix: Scrubbers and Vents will no longer reset their settings on map load. + GoldenAlpharex: + - server: Added a way for calls to be made to interfere with player ranks on live + servers (updating the players if they're connected) from outside of the game. + Rhials: + - qol: The freedom implant has received minor feedback and other minor usage improvements. + - bugfix: The Polymorph Belt should now update its sprite when active. + SkyratBot: + - qol: allowed names to start with a number if AI/Borg + - rscadd: Add drinking water causes drunk mobs to become sober + - balance: Diabetics rejoice! Nerfed sugar OD/hyperglycaemic shock to be an immediate + KO followed by drowsiness afterwards until the OD is gone. + - code_imp: Robot Customers have recently been touched codewise, please report any + bugs or unexpected behavior as there really should not be any. + - admin: There is now a tool to apply a DNA Infuser entry to any human. + - bugfix: The Nuke Op MODsuit AI downloader only works once per purchase, as intended. + - code_imp: adds a gas connector component that allows connection to the atmos piping + system without the need of repathing + - refactor: changes the cryo machine to use this new system + - refactor: Hivelords and Legions now use the basic mob framework. Please report + any unusual behaviour. + - rscadd: Hivelords shed more spawn when they are attacked. + - rscadd: Legions have learned how to fling their skulls across long distances. + - rscadd: Legions can heal other lavaland mobs with their skulls. + - rscadd: Legions are better at preserving corpses they consume, and sometimes make + use of their radios. + - rscadd: Legions may leave behind an unpleasant surprise after you are rescued + from them. + - balance: The crew monitoring console will now display you as dead if you are dead, + an critically injured if in crit, rather than setting those icons purely based + on your current health. + - qol: You won't continue burning to a husk if consumed by a snow legion after being + set on fire by an ice drake. + - bugfix: removes incorrect stack traces when using some admin secrets + - balance: Head revolutionaries and heads of staff are no longer immediately considered + disqualified when going AFK or disconnecting and are given a 2 minute grace + period. + - admin: Admins now get a log when a head revolutionary or head of staff disconnects + or goes AFK during a revolution. They also get the same log 1 minute after to + give them a chance to act on the information. + - refactor: Snakes have been refactored into basic mobs. This means that they are + a bit more intelligent than previous snakes, making them more docile and averse + to harming people (unless otherwise provoked). They do chomp all sorts of mice + though. You can feed them a dead mouse to make them your friend if you'd want + that. + - sound: If you listen closely to snakes, you might be able to hear a small hissing + sound... + - bugfix: fixed lobstrosities becoming unmovable when killed during their charge + windup + - bugfix: Splattercasting resets your blood to normal values when you transsform + into a vampire. + - bugfix: Gaining a new species will set your blood volume down to the normal volume + levels if higher than normal. + - bugfix: Fix water puddle runtime when washing items + - bugfix: the parole status and discharged status are now green and blue respectively + in the security record interface + - bugfix: Dimensional Anomalies no longer destroy wall-mounted equipment. + - code_imp: Your bodytype now decides what gendered sounds you make. + - bugfix: Fixed crabs not correctly (kinda) walking sideway. + - bugfix: dead bodies now cool down to room temperature over time + - rscadd: Add candle design to biogenerator + sergeirocks100: + - bugfix: Undershirts will now look as they should if you have a body type that + differs from the gender default. + softcerv: + - rscadd: Adds in the ability for certain NIFSofts to be kept between rounds. +2023-09-30: + DrDiasyl aka DrTuxedo: + - balance: Holsters can now be clipped to any suit, and house Captain antique gun + and HoS gun. You now can buy holsters from the SecTech premium section. + Paxilmaniac: + - qol: The half mask respirator can have its TTS voice muffling properties toggled + with control click + - qol: Icecats are now listed in the round end report, so you can see who was up + icing they cat + - bugfix: Icecats should hopefully spawn with their special language correctly now + SkyratBot: + - code_imp: removed some redundant code for airlocks + - admin: Mob abilities can be granted to arbitrary mobs via the VV menu in a similar + way to spells. + - bugfix: Lavaland syndicate operatives can no longer trivially use the jetpack + on their modsuit to fly over the lava. + - bugfix: If two cosmic heretics ascend in the same round, their star gazer survival + will be linked to each individual heretic and not shared by just one of them. + - bugfix: You can't click the Knock heretic portal to join as a mob while already + signed up to become a mob. + - balance: Cosmic heretics can't order the Star Gazer around while jaunting. + - balance: The Knock Heretic portal cannot summon Flesh Worms, but can summon Fire + Sharks. + - balance: The Knock Heretic portal will disperse if its creator is killed. + - rscadd: SM crystal can now dust someone or something if it falls on it. + - bugfix: The reverse revolver now looks like a normal Syndicate revolver on inspection. + - bugfix: fixed the stamp in the metastation CMO office always spawning on the floor + - bugfix: You can now spray paint the SM without getting dusted + Smol42: + - rscadd: Added some new hairstyles + Zergspower: + - bugfix: Crew Monitor works again properly + nikothedude: + - rscadd: A waterbreathing quirk + - qol: Waterbreathing is now documented on species pages of the species that have + it diff --git a/html/changelogs/archive/2023-10.yml b/html/changelogs/archive/2023-10.yml new file mode 100644 index 00000000000..ccff91affb0 --- /dev/null +++ b/html/changelogs/archive/2023-10.yml @@ -0,0 +1,122 @@ +2023-10-01: + Hatterhat: + - balance: Bullets have had their base type's wound bonus reduced back to 0, down + from 20, because wounds are actually quite punishing. Funnily enough, most bullets + already have modified wound bonuses - except c9mm, c10mm, and most incendiaries, + so this probably doesn't change much. + - balance: .50 (used in the snipers and renamed to .416 or whatever) is now back + to TG balance standards. Knockdown on hit, 60 instead of 110 damage, etc. etc. + - bugfix: .50 Soporific was removed because disruptor ammo was right there and nobody + realized it existed. + - bugfix: After review of a missing equipment complaint, Nanotrasen remembered to + pay Lopland's quartermasters to put the customary flashbang and teargas grenade + boxes into the Void Raptor's armory. + Melbert: + - qol: Examine blocked out roundstart / latejoin job information. + - qol: Captain gets a little bit more information about how their radio works roundstart. + - bugfix: Fixed roundstart players not getting radio information. + Paxilmaniac: + - image: The buttondown shirts (underwear) have been updated with a better look + and more contrasted palette + SkyratBot: + - rscadd: A new export has arrived in the imports section, the Galactic Materials + Market! You can use this to buy and sell minerals for profit or cost, as well + as stock your station when you don't have any miners. + - rscadd: Insert sheets of minerals into the Galactic Materials Market to convert + them into a stock block, allowing you to lock in your price for 5 minutes. Wait + too long and it'll be subject to market value again! + - rscadd: Minerals can be bought on the market either using the station's cargo + budget by cargo crew, or privately by everyone else. + - rscdel: Any material stacks that can be bought and sold on the market before have + been removed from the cargo catalog. + - rscadd: Adds Bitrunning to supply department- a semi-offstation role that rewards + teamwork. + - rscadd: Adds new machines to complement the job- net pod, quantum server, quantum + consoles, and the nexacache vendor. + - rscadd: Adds several new maps which can be loaded and unloaded at will. + - rscadd: Some flair for the new bitrunning vendor. + - rscadd: Adds a new antagonist for the virtual domain only. Short lived ghost role + that fights bitrunners. + - rscdel: Removes the BEPIS machine, moves its tech into the Bitrunning vendor. + - bugfix: Fixes missing baseturfs and clowns in mining planet VDOM.. + - qol: Font settings in the chat panel applies to all text now. + - image: new chaplain outfit + - bugfix: Blob spores will respond to rallies more reliably (it won't runtime every + time they try and pathfind). + - bugfix: Blobbernaut pain animation overlays should align with the direction the + mob is facing instead of always facing South + - refactor: Blob spores, zombies, and blobbernauts now all use the basic mob framework. + They should work the same, but please report any issues. + - bugfix: Added warden to list of default required enemies for rulesets. + - bugfix: Blob Zombies and Blobbernauts have had their attack speed restored to + its original value + - refactor: Supermatter Spiders have been refactored into basic mobs, on the extremely + off chance you spot one and also notice any weird bugs regarding it, please + report it. + - balance: There are now 3 roundstart cyborg job slots open by default. + - rscadd: Quantum servers now talk over supply channel when they're done cooling + off. Go outside! + - bugfix: You can no longer use dragon swoop to bypass cordons. + - bugfix: Netpod brain damage is now properly reduced upon server upgrades. + - bugfix: Fixed an bug where swapping bodies in vdom prevented you from disconnecting. + - bugfix: Fixed a bug where a quantum server could get locked out of loading new + domains. + - bugfix: Changed quantum console UI to display "no bandwidth" rather than "none" + - bugfix: Actually fixed the hooked item exploit. + - rscadd: Heretic Rebalance + - balance: Researching the Main Knowledge paths that unlock Side Paths will grant + one Side Point that can be used only on those side paths. You can still spend + normal knowledge points on them if you wish. + - balance: Rune drawing time has been reduced from 30->20 seconds. Codex drawing + time has been reduced from 15->8. + - balance: 'Codex Cicatrix is now a roundstart knowledge, works as an amber focus + when held in-hand and opened, and has had its recipe changed to: 1 of any non-standard + pen (literally anything that isn''t the base pen), any book, and either animal + hide OR a corpse, any kind.' + - code_imp: Added support for using a list inside ritual requirements and a special + 'snowflake check' rituals can utilize. + - balance: The first non-path knowledge, the Mansus Hand Mark, has had its cost + reduced from 2->1 points. + - bugfix: Aloe and other baked foods that don't have reagents can be baked again + without turning to ash + Vekter: + - bugfix: Fixes the missing grinder in Birdshot's Virology department + jjpark-kb: + - bugfix: the ashwalker tendril will allow you to respawn again (the tendril blessing) + - bugfix: the round end report will accurately report ashwalker sacrifices + nikothedude: + - code_imp: Gauze removal is now handled by the gauze's destroy instead of seep_gauze + ninjanomnom: + - rscdel: An easter egg plushie that was spawning where it shouldn't has been brought + back home. + - rscadd: The secure closet can now spawn live gibtonite, enjoy your free bomb. +2023-10-02: + SkyratBot: + - balance: Sci now has access to the materials & canisters section in their departmental + order console + - rscadd: Expanded the fishing portal generator. It now comes with several portal + options that can be unlocked by performing fish scanning experiments, which + also award a modest amount of techweb points. + - balance: The fishing portal generator is now buildable and no longer orderable. + The board can be printed from cargo, service and science lathes. + - balance: Advanced fishing tech is no longer a BEPIS design. It now requires the + base fish scanning experiment and 2000 points to be unlocked. + - rscadd: The advanced fishing rod now comes with an incorporated experiscanner + specific for fish scanning. + - rscadd: Added a new skillchip that may change the icon of the "fish" shown in + the minigame UI to less generic ones. Reaching master level in fishing also + does that. + - qol: The experiment handler UI no longer shows unselectable experiments. + - bugfix: Security officers can now download the crew manifest PDA app that they + start with. + - rscadd: Wizards who complete the grand ritual can now gift everyone with eternal + life + distributivgesetz: + - bugfix: Font scaling in TGUI chat has been reverted to its original implementation. + softcerv: + - rscadd: Adds the mini-soulcatcher, a more lightweight soulcatcher that can be + attached to objects + - rscadd: Adds in the RSD brain interface, an item that allows for soulcatcher souls, + that died within a round and were scanned, to be transferred to a new brain. + - rscadd: Adds in the NIFSoft Scryer, a NIFSoft that gives the user a Scryer they + can use to communicate with other Scryer users. diff --git a/icons/effects/bitrunning.dmi b/icons/effects/bitrunning.dmi new file mode 100644 index 00000000000..bfdc7c63436 Binary files /dev/null and b/icons/effects/bitrunning.dmi differ diff --git a/icons/effects/particles/goop.dmi b/icons/effects/particles/goop.dmi new file mode 100644 index 00000000000..673c1a7ad5b Binary files /dev/null and b/icons/effects/particles/goop.dmi differ diff --git a/icons/hud/fishing_hud.dmi b/icons/hud/fishing_hud.dmi new file mode 100644 index 00000000000..58c478d0710 Binary files /dev/null and b/icons/hud/fishing_hud.dmi differ diff --git a/icons/hud/radial_fishing.dmi b/icons/hud/radial_fishing.dmi new file mode 100644 index 00000000000..65fd55176b7 Binary files /dev/null and b/icons/hud/radial_fishing.dmi differ diff --git a/icons/obj/clothing/under/lawyer_galaxy.dmi b/icons/obj/clothing/under/lawyer_galaxy.dmi new file mode 100644 index 00000000000..88ffcf44132 Binary files /dev/null and b/icons/obj/clothing/under/lawyer_galaxy.dmi differ diff --git a/icons/obj/doors/airlocks/multi_tile/public/glass.dmi b/icons/obj/doors/airlocks/multi_tile/public/glass.dmi new file mode 100644 index 00000000000..33420a77b11 Binary files /dev/null and b/icons/obj/doors/airlocks/multi_tile/public/glass.dmi differ diff --git a/icons/obj/doors/airlocks/multi_tile/public/overlays.dmi b/icons/obj/doors/airlocks/multi_tile/public/overlays.dmi new file mode 100644 index 00000000000..b0d10c8945d Binary files /dev/null and b/icons/obj/doors/airlocks/multi_tile/public/overlays.dmi differ diff --git a/icons/obj/doors/airlocks/public/glass.dmi b/icons/obj/doors/airlocks/public/glass.dmi new file mode 100644 index 00000000000..e10efd42820 Binary files /dev/null and b/icons/obj/doors/airlocks/public/glass.dmi differ diff --git a/icons/obj/doors/airlocks/public/overlays.dmi b/icons/obj/doors/airlocks/public/overlays.dmi new file mode 100644 index 00000000000..7665b485578 Binary files /dev/null and b/icons/obj/doors/airlocks/public/overlays.dmi differ diff --git a/icons/obj/food/martian.dmi b/icons/obj/food/martian.dmi new file mode 100644 index 00000000000..79efcd1813d Binary files /dev/null and b/icons/obj/food/martian.dmi differ diff --git a/icons/obj/machines/bitrunning.dmi b/icons/obj/machines/bitrunning.dmi new file mode 100644 index 00000000000..a910a16b35c Binary files /dev/null and b/icons/obj/machines/bitrunning.dmi differ diff --git a/icons/obj/machines/navigation_beacon.dmi b/icons/obj/machines/navigation_beacon.dmi new file mode 100644 index 00000000000..f20ca068c52 Binary files /dev/null and b/icons/obj/machines/navigation_beacon.dmi differ diff --git a/icons/obj/medical/medical_bed.dmi b/icons/obj/medical/medical_bed.dmi new file mode 100644 index 00000000000..2d8e4b576ed Binary files /dev/null and b/icons/obj/medical/medical_bed.dmi differ diff --git a/icons/obj/medicart.dmi b/icons/obj/medicart.dmi new file mode 100644 index 00000000000..c25ea0039dc Binary files /dev/null and b/icons/obj/medicart.dmi differ diff --git a/icons/obj/miningradio.dmi b/icons/obj/miningradio.dmi new file mode 100644 index 00000000000..e3d10f3b6d6 Binary files /dev/null and b/icons/obj/miningradio.dmi differ diff --git a/icons/obj/weapons/giant_wrench.dmi b/icons/obj/weapons/giant_wrench.dmi new file mode 100644 index 00000000000..b9aef713d6a Binary files /dev/null and b/icons/obj/weapons/giant_wrench.dmi differ diff --git a/modular_skyrat/master_files/code/datums/components/damage_tracker.dm b/modular_skyrat/master_files/code/datums/components/damage_tracker.dm new file mode 100644 index 00000000000..f2a8f215303 --- /dev/null +++ b/modular_skyrat/master_files/code/datums/components/damage_tracker.dm @@ -0,0 +1,142 @@ +/// This component tracks the original damage values of a mob when it is attached. +/datum/component/damage_tracker + /// How much brute damage did the mob have on them? + var/brute_damage = 0 + /// How much burn damage did the mob have on them? + var/burn_damage = 0 + /// How much oxygen damage did the mob have on them? + var/oxygen_damage = 0 + /// How much toxin damage did the mob have on them? + var/toxin_damage = 0 + + /// How much clone damage did the mob have on them? + var/clone_damage = 0 + /// How much blood did the mob have? + var/stored_blood_volume = 0 + + /// Do we need to reapply the damage values when this component is removed? + var/reapply_damage_on_removal = TRUE + +/// Updates the stored damage variables for the parent mob. Returns `TRUE` when succesfully ran, otherwise returns `FALSE` +/datum/component/damage_tracker/proc/update_damage_values() + var/mob/living/tracked_mob = parent + if(!istype(tracked_mob)) + return FALSE + + brute_damage = tracked_mob.getBruteLoss() + burn_damage = tracked_mob.getFireLoss() + toxin_damage = tracked_mob.getToxLoss() + oxygen_damage = tracked_mob.getOxyLoss() + clone_damage = tracked_mob.getCloneLoss() + stored_blood_volume = tracked_mob.blood_volume + + return TRUE + +/// Reapplies the stored damage variables to the parent mob. Returns `TRUE` when succesfully ran, otherwise returns `FALSE` +/datum/component/damage_tracker/proc/reapply_damage() + var/mob/living/tracked_mob = parent + if(!istype(tracked_mob)) + return FALSE + + tracked_mob.setBruteLoss(brute_damage) + tracked_mob.setFireLoss(burn_damage) + tracked_mob.setToxLoss(toxin_damage) + tracked_mob.setOxyLoss(oxygen_damage) + tracked_mob.setCloneLoss(clone_damage) + tracked_mob.blood_volume = stored_blood_volume + + return TRUE + +/datum/component/damage_tracker/Initialize(...) + . = ..() + if(!ismob(parent)) + return COMPONENT_INCOMPATIBLE + + update_damage_values() + +/datum/component/damage_tracker/Destroy(force, silent) + if(reapply_damage_on_removal) + reapply_damage() + + return ..() + +/// This does the same as it's parent, but it also tracks organ damage. +/datum/component/damage_tracker/human + /// How much damage does the owner's heart currently have? + var/heart_damage + /// How much damage does the owner's liver currently have? + var/liver_damage + /// How much damage does the owner's lungs currently have? + var/lung_damage + /// How much damage does the owner's stomach currently have? + var/stomach_damage + /// How much damage does the owner's brain currently have? + var/brain_damage + /// How much damage does the owner's eyes currently have? + var/eye_damage + /// How much damage does the owner's ears currently have? + var/ear_damage + + /// What brain traumas does the owner currently have? + var/list/trauma_list = list() + +/datum/component/damage_tracker/human/update_damage_values() + . = ..() + var/mob/living/carbon/human/human_parent = parent + if(!. || !istype(human_parent)) + return FALSE + + var/list/current_trauma_list = human_parent.get_traumas() + if(length(current_trauma_list)) + trauma_list = current_trauma_list.Copy() + + heart_damage = human_parent.check_organ_damage(/obj/item/organ/internal/heart) + liver_damage = human_parent.check_organ_damage(/obj/item/organ/internal/liver) + lung_damage = human_parent.check_organ_damage(/obj/item/organ/internal/lungs) + stomach_damage = human_parent.check_organ_damage(/obj/item/organ/internal/stomach) + brain_damage = human_parent.check_organ_damage(/obj/item/organ/internal/brain) + eye_damage = human_parent.check_organ_damage(/obj/item/organ/internal/eyes) + ear_damage = human_parent.check_organ_damage(/obj/item/organ/internal/ears) + + return TRUE + +/datum/component/damage_tracker/human/reapply_damage() + . = ..() + var/mob/living/carbon/human/human_parent = parent + if(!. || !istype(human_parent)) + return FALSE + + human_parent.setOrganLoss(ORGAN_SLOT_HEART, heart_damage) + human_parent.setOrganLoss(ORGAN_SLOT_LIVER, liver_damage) + human_parent.setOrganLoss(ORGAN_SLOT_LUNGS, lung_damage) + human_parent.setOrganLoss(ORGAN_SLOT_STOMACH, stomach_damage) + human_parent.setOrganLoss(ORGAN_SLOT_EYES, eye_damage) + human_parent.setOrganLoss(ORGAN_SLOT_EARS, ear_damage) + human_parent.setOrganLoss(ORGAN_SLOT_BRAIN, brain_damage) + + var/obj/item/organ/internal/brain/human_brain = human_parent.get_organ_by_type(/obj/item/organ/internal/brain) + if(!human_brain) + return FALSE + + var/list/current_trauma_list = human_parent.get_traumas() + for(var/datum/brain_trauma/trauma_to_add as anything in trauma_list) + if(trauma_to_add in current_trauma_list) + continue // We don't need to torture the poor soul with the same brain trauma. + + human_brain.gain_trauma(trauma_to_add) + + return TRUE + +/datum/component/damage_tracker/human/Initialize(...) + if(!ishuman(parent)) + return COMPONENT_INCOMPATIBLE + + return ..() + +/// Returns the damage of the `organ_to_check`, if the organ isn't there, the proc returns `100`. +/mob/living/carbon/human/proc/check_organ_damage(obj/item/organ/organ_to_check) + var/obj/item/organ/organ_to_track = get_organ_by_type(organ_to_check) + if(!organ_to_track) + return 100 //If the organ is missing, return max damage. we have this here so that if the SAD replaces an organ, it's broken. + + return organ_to_track.damage diff --git a/modular_skyrat/master_files/code/datums/mind/_mind.dm b/modular_skyrat/master_files/code/datums/mind/_mind.dm new file mode 100644 index 00000000000..892f2a491ed --- /dev/null +++ b/modular_skyrat/master_files/code/datums/mind/_mind.dm @@ -0,0 +1,11 @@ +// Free chaplain highpriest role if the chaplain's mind gets deleted for some reason +/datum/mind/Destroy() + var/list/holy_successors = list_holy_successors() + if(current && (current in holy_successors)) // if this mob was a holy successor then remove them from the pool + GLOB.holy_successors -= WEAKREF(src) + + // Handle freeing the high priest role for the next chaplain in line + if(holy_role == HOLY_ROLE_HIGHPRIEST) + reset_religion() + + return ..() diff --git a/modular_skyrat/master_files/code/datums/mood_events/drink_events.dm b/modular_skyrat/master_files/code/datums/mood_events/drink_events.dm new file mode 100644 index 00000000000..a697d94b8e4 --- /dev/null +++ b/modular_skyrat/master_files/code/datums/mood_events/drink_events.dm @@ -0,0 +1,4 @@ +/datum/mood_event/race_drink + description = span_nicegreen("That drink was made for me!\n") + mood_change = 12 + timeout = 9 MINUTES diff --git a/modular_skyrat/master_files/code/game/objects/items/scratchingstone.dm b/modular_skyrat/master_files/code/game/objects/items/scratchingstone.dm new file mode 100644 index 00000000000..ac1fd624429 --- /dev/null +++ b/modular_skyrat/master_files/code/game/objects/items/scratchingstone.dm @@ -0,0 +1,7 @@ +/obj/item/scratching_stone + name = "scratching stone" + icon = 'modular_skyrat/master_files/icons/obj/kitchen.dmi' + icon_state = "scratchingstone" + desc = "A specialized kind of whetstone, made of unknown alloys to hone a cyborg mercenary's claws to the best they can be. This one looks like a shitty second-hand sold by razorkids. It's got like, what, maybe one use left?" + force = 5 + throwforce = 10 //Hey, you can at least use it as a brick. diff --git a/modular_skyrat/master_files/code/game/objects/structures/crates_lockers/secure/cargo.dm b/modular_skyrat/master_files/code/game/objects/structures/crates_lockers/secure/cargo.dm new file mode 100644 index 00000000000..359f964f68b --- /dev/null +++ b/modular_skyrat/master_files/code/game/objects/structures/crates_lockers/secure/cargo.dm @@ -0,0 +1,5 @@ +/obj/structure/closet/secure_closet/quartermaster/PopulateContents() + ..() + new /obj/item/gun/ballistic/rifle/boltaction/sporterized(src) // The QM's 'special' head item. It spawns loaded, but you have to find more ammo if you run out and get ready to manually load rounds in! + new /obj/item/cargo_teleporter(src) // Adds a cargo teleporter to QM locker, so they can entice others to research it + new /obj/item/clothing/glasses/hud/gun_permit/sunglasses(src) diff --git a/modular_skyrat/master_files/code/game/objects/structures/mannequin.dm b/modular_skyrat/master_files/code/game/objects/structures/mannequin.dm new file mode 100644 index 00000000000..9604fb7ac5f --- /dev/null +++ b/modular_skyrat/master_files/code/game/objects/structures/mannequin.dm @@ -0,0 +1,3 @@ +/obj/structure/mannequin + /// String for the bra we use. + var/bra_name diff --git a/modular_skyrat/master_files/code/modules/bitrunning/orders/tech.dm b/modular_skyrat/master_files/code/modules/bitrunning/orders/tech.dm new file mode 100644 index 00000000000..6c9a0626517 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/bitrunning/orders/tech.dm @@ -0,0 +1,3 @@ +/datum/orderable_item/bepis/flashdark + item_path = /obj/item/flashlight/flashdark + cost_per_order = 750 diff --git a/modular_skyrat/master_files/code/modules/cargo/bounties/medical.dm b/modular_skyrat/master_files/code/modules/cargo/bounties/medical.dm new file mode 100644 index 00000000000..e1b032bbc33 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/cargo/bounties/medical.dm @@ -0,0 +1,2 @@ +/datum/bounty/item/medical/tongue + description = "A recent attack by Mime extremists has left staff at Station 23 speechless. Ship some spare tongues. We'll accept cybernetic variants if need be." \ No newline at end of file diff --git a/modular_skyrat/master_files/code/modules/cargo/exports/tools.dm b/modular_skyrat/master_files/code/modules/cargo/exports/tools.dm new file mode 100644 index 00000000000..f7408c7367f --- /dev/null +++ b/modular_skyrat/master_files/code/modules/cargo/exports/tools.dm @@ -0,0 +1,2 @@ +/datum/export/weldingtool/experimental + export_types = list(/obj/item/weldingtool/electric) diff --git a/modular_skyrat/master_files/code/modules/cargo/goodies.dm b/modular_skyrat/master_files/code/modules/cargo/goodies.dm new file mode 100644 index 00000000000..ecc2cfa4852 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/cargo/goodies.dm @@ -0,0 +1,35 @@ +/datum/supply_pack/goody/dumdum38 + special = TRUE + +/datum/supply_pack/goody/match38 + special = TRUE + +/datum/supply_pack/goody/rubber + special = TRUE + +/datum/supply_pack/goody/mars_single + special = TRUE + +/datum/supply_pack/goody/Survivalknives_single + special = TRUE + +/datum/supply_pack/goody/ballistic_single + special = TRUE + +/datum/supply_pack/goody/disabler_single + special = TRUE + +/datum/supply_pack/goody/energy_single + special = TRUE + +/datum/supply_pack/goody/laser_single + special = TRUE + +/datum/supply_pack/goody/hell_single + special = TRUE + +/datum/supply_pack/goody/thermal_single + special = TRUE + +/datum/supply_pack/goody/dyespray + special = TRUE diff --git a/modular_skyrat/master_files/code/modules/cargo/orderconsole.dm b/modular_skyrat/master_files/code/modules/cargo/orderconsole.dm new file mode 100644 index 00000000000..6898c91076c --- /dev/null +++ b/modular_skyrat/master_files/code/modules/cargo/orderconsole.dm @@ -0,0 +1,12 @@ +/obj/machinery/computer/cargo/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + switch(action) + if("company_import_window") + var/datum/component/armament/company_imports/company_import_component = GetComponent(/datum/component/armament/company_imports) + company_import_component.ui_interact(usr) + . = TRUE + if(.) + post_signal(cargo_shuttle) diff --git a/modular_skyrat/master_files/code/modules/cargo/packs/general.dm b/modular_skyrat/master_files/code/modules/cargo/packs/general.dm new file mode 100644 index 00000000000..e854c0902ff --- /dev/null +++ b/modular_skyrat/master_files/code/modules/cargo/packs/general.dm @@ -0,0 +1,2 @@ +/datum/supply_pack/misc/bicycle + special = TRUE diff --git a/modular_skyrat/master_files/code/modules/cargo/packs/security.dm b/modular_skyrat/master_files/code/modules/cargo/packs/security.dm new file mode 100644 index 00000000000..9746267dbdd --- /dev/null +++ b/modular_skyrat/master_files/code/modules/cargo/packs/security.dm @@ -0,0 +1,26 @@ +/datum/supply_pack/security/ammo + special = TRUE + +/datum/supply_pack/security/armor + special = TRUE + +/datum/supply_pack/security/disabler + cost = CARGO_CRATE_VALUE * 5 + +/datum/supply_pack/security/helmets + special = TRUE + +/datum/supply_pack/security/laser + special = TRUE + +/datum/supply_pack/security/securityclothes + special = TRUE + +/datum/supply_pack/security/armory/ballistic + special = TRUE + +/datum/supply_pack/security/armory/energy + special = TRUE + +/datum/supply_pack/security/armory/thermal + special = TRUE diff --git a/modular_skyrat/master_files/code/modules/cargo/packs/service.dm b/modular_skyrat/master_files/code/modules/cargo/packs/service.dm new file mode 100644 index 00000000000..49ea64b2788 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/cargo/packs/service.dm @@ -0,0 +1,2 @@ +/datum/supply_pack/service/survivalknives + special = TRUE diff --git a/modular_skyrat/master_files/code/modules/cargo/packs/vending_restock.dm b/modular_skyrat/master_files/code/modules/cargo/packs/vending_restock.dm new file mode 100644 index 00000000000..5d7a9b95dc0 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/cargo/packs/vending_restock.dm @@ -0,0 +1,5 @@ +/datum/supply_pack/vending/sectech + special = TRUE + +/datum/supply_pack/vending/wardrobes/security + special = TRUE diff --git a/modular_skyrat/master_files/code/modules/experisci/experiment.dm b/modular_skyrat/master_files/code/modules/experisci/experiment.dm new file mode 100644 index 00000000000..791a92a2d74 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/experisci/experiment.dm @@ -0,0 +1,4 @@ +// Changed to silver +/datum/experiment/scanning/random/material/meat + description = "Supposedly silver has an inert anti-microbial effect; scan a few samples to test this." + possible_material_types = list(/datum/material/silver) diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/atmospheric_technician.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/atmospheric_technician.dm new file mode 100644 index 00000000000..620c7e3b41c --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/atmospheric_technician.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/atmos + messenger = /obj/item/storage/backpack/messenger/eng diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/botanist.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/botanist.dm new file mode 100644 index 00000000000..eba9573ce23 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/botanist.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/botanist + messenger = /obj/item/storage/backpack/messenger/hyd diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/captain.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/captain.dm new file mode 100644 index 00000000000..4435270b639 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/captain.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/captain + messenger = /obj/item/storage/backpack/messenger/cap diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/chemist.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/chemist.dm new file mode 100644 index 00000000000..36827d3c937 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/chemist.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/chemist + messenger = /obj/item/storage/backpack/messenger/chem diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/chief_engineer.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/chief_engineer.dm new file mode 100644 index 00000000000..bb204607fb2 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/chief_engineer.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/ce + messenger = /obj/item/storage/backpack/messenger/eng diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/chief_medical_officer.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/chief_medical_officer.dm new file mode 100644 index 00000000000..10ef30a5839 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/chief_medical_officer.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/cmo + messenger = /obj/item/storage/backpack/messenger/med diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/coroner.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/coroner.dm new file mode 100644 index 00000000000..0fec4aa8824 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/coroner.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/coroner + messenger = /obj/item/storage/backpack/messenger/coroner diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/geneticist.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/geneticist.dm new file mode 100644 index 00000000000..840a43b688c --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/geneticist.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/geneticist + messenger = /obj/item/storage/backpack/messenger/gen diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/head_of_personnel.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/head_of_personnel.dm new file mode 100644 index 00000000000..f9c8432931b --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/head_of_personnel.dm @@ -0,0 +1,5 @@ +/datum/outfit/job/hop + backpack = /obj/item/storage/backpack/head_of_personnel + satchel = /obj/item/storage/backpack/satchel/head_of_personnel + duffelbag = /obj/item/storage/backpack/duffelbag/head_of_personnel + messenger = /obj/item/storage/backpack/messenger/head_of_personnel diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/head_of_security.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/head_of_security.dm new file mode 100644 index 00000000000..81587c0fe21 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/head_of_security.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/hos + messenger = /obj/item/storage/backpack/messenger/sec diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/medical_doctor.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/medical_doctor.dm new file mode 100644 index 00000000000..7c39b435092 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/medical_doctor.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/doctor + messenger = /obj/item/storage/backpack/messenger/med diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/paramedic.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/paramedic.dm new file mode 100644 index 00000000000..9e90b107a5c --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/paramedic.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/paramedic + messenger = /obj/item/storage/backpack/messenger/med diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/psychologist.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/psychologist.dm new file mode 100644 index 00000000000..f9a267999cf --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/psychologist.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/psychologist + messenger = /obj/item/storage/backpack/messenger/med diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/research_director.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/research_director.dm new file mode 100644 index 00000000000..ae5be9d94fe --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/research_director.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/rd + messenger = /obj/item/storage/backpack/messenger/science diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/roboticist.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/roboticist.dm new file mode 100644 index 00000000000..d90c994c6ad --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/roboticist.dm @@ -0,0 +1,5 @@ +/datum/outfit/job/roboticist + backpack = /obj/item/storage/backpack/science/robo + satchel = /obj/item/storage/backpack/satchel/science/robo + duffelbag = /obj/item/storage/backpack/duffelbag/science/robo + messenger = /obj/item/storage/backpack/messenger/science/robo diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/scientist.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/scientist.dm new file mode 100644 index 00000000000..1b0e4e4549c --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/scientist.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/scientist + messenger = /obj/item/storage/backpack/messenger/science diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/security_officer.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/security_officer.dm new file mode 100644 index 00000000000..b23e12f8f64 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/security_officer.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/security + messenger = /obj/item/storage/backpack/messenger/sec diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/shaft_miner.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/shaft_miner.dm new file mode 100644 index 00000000000..c7d0841b93d --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/shaft_miner.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/miner + messenger = /obj/item/storage/backpack/messenger/explorer diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/station_engineer.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/station_engineer.dm new file mode 100644 index 00000000000..2bb13106400 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/station_engineer.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/engineer + messenger = /obj/item/storage/backpack/messenger/eng diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/virologist.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/virologist.dm new file mode 100644 index 00000000000..1944ad527a4 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/virologist.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/virologist + messenger = /obj/item/storage/backpack/messenger/vir diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/warden.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/warden.dm new file mode 100644 index 00000000000..e280f1c6c0c --- /dev/null +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/warden.dm @@ -0,0 +1,2 @@ +/datum/outfit/job/warden + messenger = /obj/item/storage/backpack/messenger/sec diff --git a/modular_skyrat/master_files/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_cytology.dm b/modular_skyrat/master_files/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_cytology.dm new file mode 100644 index 00000000000..d9c10dce112 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_cytology.dm @@ -0,0 +1,3 @@ +// Beakers capacity 50u -> 60u +/obj/item/reagent_containers/cup/beaker/oldstation + amount_per_transfer_from_this = 60 diff --git a/modular_skyrat/master_files/code/modules/mob/login.dm b/modular_skyrat/master_files/code/modules/mob/login.dm new file mode 100644 index 00000000000..fbf57ccf0b5 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/mob/login.dm @@ -0,0 +1,10 @@ +/mob/Login() + . = ..() + + if(!.) + return FALSE + + if(SSplayer_ranks.initialized) + SSplayer_ranks.update_prefs_unlock_content(client?.prefs) + + return TRUE diff --git a/modular_skyrat/master_files/code/modules/shuttle/shuttle_events/meteors.dm b/modular_skyrat/master_files/code/modules/shuttle/shuttle_events/meteors.dm new file mode 100644 index 00000000000..f07a393f2dc --- /dev/null +++ b/modular_skyrat/master_files/code/modules/shuttle/shuttle_events/meteors.dm @@ -0,0 +1,5 @@ +/datum/shuttle_event/simple_spawner/meteor/dust + event_probability = 0 + +/datum/shuttle_event/simple_spawner/meteor/safe + event_probability = 0 diff --git a/modular_skyrat/master_files/code/modules/surgery/bodyparts/_bodyparts.dm b/modular_skyrat/master_files/code/modules/surgery/bodyparts/_bodyparts.dm new file mode 100644 index 00000000000..81d9c544fa8 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/surgery/bodyparts/_bodyparts.dm @@ -0,0 +1,3 @@ +/obj/item/bodypart + /// If this limb has sharp unarmed attacks, by default off. You probably want to use SHARP_EDGED, SHARP_POINTY would be nuts balance-wise. + var/unarmed_sharpness = 0 diff --git a/modular_skyrat/master_files/code/modules/surgery/organs/internal/appendix/_appendix.dm b/modular_skyrat/master_files/code/modules/surgery/organs/internal/appendix/_appendix.dm new file mode 100644 index 00000000000..296696d559f --- /dev/null +++ b/modular_skyrat/master_files/code/modules/surgery/organs/internal/appendix/_appendix.dm @@ -0,0 +1,8 @@ +/obj/item/organ/internal/appendix/become_inflamed() + if(engaged_role_play_check(owner, station = TRUE, dorms = TRUE)) + return + + if(!(owner.mind.assigned_role.job_flags & JOB_CREW_MEMBER)) + return + + ..() diff --git a/modular_skyrat/master_files/code/modules/vehicles/sealed.dm b/modular_skyrat/master_files/code/modules/vehicles/sealed.dm new file mode 100644 index 00000000000..637cf7ccc94 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/vehicles/sealed.dm @@ -0,0 +1,8 @@ +/obj/vehicle/sealed/mob_try_enter(mob/rider) + if(!istype(rider)) + return FALSE + if(HAS_TRAIT(rider, TRAIT_OVERSIZED)) + to_chat(rider, span_warning("You are far too big for this!")) + return FALSE + + return ..() diff --git a/modular_skyrat/master_files/icons/hud/actions.dmi b/modular_skyrat/master_files/icons/hud/actions.dmi new file mode 100644 index 00000000000..32da90c1cf8 Binary files /dev/null and b/modular_skyrat/master_files/icons/hud/actions.dmi differ diff --git a/modular_skyrat/master_files/icons/obj/kitchen.dmi b/modular_skyrat/master_files/icons/obj/kitchen.dmi new file mode 100644 index 00000000000..2329e8dee79 Binary files /dev/null and b/modular_skyrat/master_files/icons/obj/kitchen.dmi differ diff --git a/modular_skyrat/modules/GAGS/json_configs/pants_shorts_skirts_dresses/shortershorts.json b/modular_skyrat/modules/GAGS/json_configs/pants_shorts_skirts_dresses/shortershorts.json new file mode 100644 index 00000000000..0058e310800 --- /dev/null +++ b/modular_skyrat/modules/GAGS/json_configs/pants_shorts_skirts_dresses/shortershorts.json @@ -0,0 +1,22 @@ +{ + "shortershorts": [ + { + "type": "icon_state", + "icon_state": "buckle", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "belt", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "shortershorts", + "blend_mode": "overlay", + "color_ids": [ 3 ] + } + ] +} diff --git a/modular_skyrat/modules/admin/code/player_ranks.dm b/modular_skyrat/modules/admin/code/player_ranks.dm new file mode 100644 index 00000000000..6e05a42f7d8 --- /dev/null +++ b/modular_skyrat/modules/admin/code/player_ranks.dm @@ -0,0 +1,121 @@ +/// The list of the available special player ranks +#define SKYRAT_PLAYER_RANKS list("Donator", "Mentor", "Veteran") + +/client/proc/manage_player_ranks() + set category = "Admin" + set name = "Manage Player Ranks" + set desc = "Manage who has the special player ranks while the server is running." + + if(!check_rights(R_PERMISSIONS)) + return + + usr.client?.holder.manage_player_ranks() + +/// Proc for admins to change people's "player" ranks (donator, mentor, veteran, etc.) +/datum/admins/proc/manage_player_ranks() + if(IsAdminAdvancedProcCall()) + return + + if(!check_rights(R_PERMISSIONS)) + return + + var/choice = tgui_alert(usr, "Which rank would you like to manage?", "Manage Player Ranks", SKYRAT_PLAYER_RANKS) + if(!choice || !(choice in SKYRAT_PLAYER_RANKS)) + return + + manage_player_rank_in_group(choice) + +/** + * Handles managing player ranks based on the name of the group that was chosen. + * + * Arguments: + * * group - The title of the player rank that was chosen to be managed. + */ +/datum/admins/proc/manage_player_rank_in_group(group) + PROTECTED_PROC(TRUE) + + if(IsAdminAdvancedProcCall()) + return + + if(!(group in SKYRAT_PLAYER_RANKS)) + CRASH("[key_name(usr)] attempted to add someone to an invalid \"[group]\" group.") + + var/group_title = lowertext(group) + + var/list/choices = list("Add", "Remove") + switch(tgui_alert(usr, "What would you like to do?", "Manage [group]s", choices)) + if("Add") + var/name = input(usr, "Please enter the CKEY (case-insensitive) of the person you would like to make a [group_title]:", "Add a [group_title]") as null|text + if(!name) + return + + var/player_to_be = ckey(name) + if(!player_to_be) + to_chat(usr, span_warning("\"[name]\" is not a valid CKEY.")) + return + + var/success = SSplayer_ranks.add_player_to_group(usr.client, player_to_be, group_title) + + if(!success) + return + + message_admins("[key_name(usr)] has granted [group_title] status to [player_to_be].") + log_admin_private("[key_name(usr)] has granted [group_title] status to [player_to_be].") + + + if("Remove") + var/name = input(usr, "Please enter the CKEY (case-insensitive) of the person you would like to no longer be a [group_title]:", "Remove a [group_title]") as null|text + if(!name) + return + + var/player_that_was = ckey(name) + if(!player_that_was) + to_chat(usr, span_warning("\"[name]\" is not a valid CKEY.")) + return + + var/success = SSplayer_ranks.remove_player_from_group(usr.client, player_that_was, group_title) + + if(!success) + return + + message_admins("[key_name(usr)] has revoked [group_title] status from [player_that_was].") + log_admin_private("[key_name(usr)] has revoked [group_title] status from [player_that_was].") + + else + return + + + +/client/proc/migrate_player_ranks() + set category = "Debug" + set name = "Migrate Player Ranks" + set desc = "Individually migrate the various player ranks from their legacy system to the SQL-based one." + + if(!check_rights(R_PERMISSIONS | R_DEBUG | R_SERVER)) + return + + usr.client?.holder.migrate_player_ranks() + + +/datum/admins/proc/migrate_player_ranks() + if(IsAdminAdvancedProcCall()) + return + + if(!check_rights(R_PERMISSIONS | R_DEBUG | R_SERVER)) + return + + if(!CONFIG_GET(flag/sql_enabled)) + return + + var/choice = tgui_alert(usr, "Which rank would you like to migrate?", "Migrate Player Ranks", SKYRAT_PLAYER_RANKS) + if(!choice || !(choice in SKYRAT_PLAYER_RANKS)) + return + + if(tgui_alert(usr, "Are you sure that you would like to migrate [choice]s to the SQL-based system?", "Migrate Player Ranks", list("Yes", "No")) != "Yes") + return + + log_admin("[key_name(usr)] is migrating the [choice] player rank from its legacy system to the SQL-based one.") + SSplayer_ranks.migrate_player_rank_to_sql(usr.client, choice) + + +#undef SKYRAT_PLAYER_RANKS diff --git a/modular_skyrat/modules/aesthetics/airlock/icons/airlocks/multi_tile/glass.dmi b/modular_skyrat/modules/aesthetics/airlock/icons/airlocks/multi_tile/glass.dmi new file mode 100644 index 00000000000..a400cb6c61d Binary files /dev/null and b/modular_skyrat/modules/aesthetics/airlock/icons/airlocks/multi_tile/glass.dmi differ diff --git a/modular_skyrat/modules/aesthetics/airlock/icons/airlocks/multi_tile/glass_overlays.dmi b/modular_skyrat/modules/aesthetics/airlock/icons/airlocks/multi_tile/glass_overlays.dmi new file mode 100644 index 00000000000..a6a2b020138 Binary files /dev/null and b/modular_skyrat/modules/aesthetics/airlock/icons/airlocks/multi_tile/glass_overlays.dmi differ diff --git a/modular_skyrat/modules/aesthetics/airlock/icons/airlocks/multi_tile/metal.dmi b/modular_skyrat/modules/aesthetics/airlock/icons/airlocks/multi_tile/metal.dmi new file mode 100644 index 00000000000..08175c4e7fa Binary files /dev/null and b/modular_skyrat/modules/aesthetics/airlock/icons/airlocks/multi_tile/metal.dmi differ diff --git a/modular_skyrat/modules/aesthetics/airlock/icons/airlocks/multi_tile/metal_overlays.dmi b/modular_skyrat/modules/aesthetics/airlock/icons/airlocks/multi_tile/metal_overlays.dmi new file mode 100644 index 00000000000..7de2b627a45 Binary files /dev/null and b/modular_skyrat/modules/aesthetics/airlock/icons/airlocks/multi_tile/metal_overlays.dmi differ diff --git a/modular_skyrat/modules/company_imports/code/armament_datums/_armament_basetype.dm b/modular_skyrat/modules/company_imports/code/armament_datums/_armament_basetype.dm new file mode 100644 index 00000000000..f01d5c1ba1a --- /dev/null +++ b/modular_skyrat/modules/company_imports/code/armament_datums/_armament_basetype.dm @@ -0,0 +1,8 @@ +/datum/armament_entry/company_import + max_purchase = 0 + category_item_limit = 0 + cost = CARGO_CRATE_VALUE + /// Bitflag of the company + var/company_bitflag + /// If this requires a multitooled console to be visible + var/contraband = FALSE diff --git a/modular_skyrat/modules/company_imports/code/readme.md b/modular_skyrat/modules/company_imports/code/readme.md new file mode 100644 index 00000000000..32b07361a20 --- /dev/null +++ b/modular_skyrat/modules/company_imports/code/readme.md @@ -0,0 +1,25 @@ +https://github.com/Skyrat-SS13/Skyrat-tg/pull/ + +## \ + +Module ID: + +### Description: + +### TG Proc/File Changes: + +- N/A + +### Modular Overrides: + +- `modular_skyrat/master_files/code/modules/cargo/orderconsole.dm`: `proc/ui_act` + +### Defines: + +- N/A + +### Included files that are not contained in this module: + +- N/A + +### Credits: diff --git a/modular_skyrat/modules/company_imports/icons/import_crate.dmi b/modular_skyrat/modules/company_imports/icons/import_crate.dmi new file mode 100644 index 00000000000..e79cc05ec7e Binary files /dev/null and b/modular_skyrat/modules/company_imports/icons/import_crate.dmi differ diff --git a/modular_skyrat/modules/contractor/code/datums/outfit.dm b/modular_skyrat/modules/contractor/code/datums/outfit.dm new file mode 100644 index 00000000000..3364e2fbccb --- /dev/null +++ b/modular_skyrat/modules/contractor/code/datums/outfit.dm @@ -0,0 +1,2 @@ +/datum/outfit + var/datum/sprite_accessory/bra = null diff --git a/modular_skyrat/modules/customization/modules/surgery/bodyparts/parts.dm b/modular_skyrat/modules/customization/modules/surgery/bodyparts/parts.dm new file mode 100644 index 00000000000..85b251c08b6 --- /dev/null +++ b/modular_skyrat/modules/customization/modules/surgery/bodyparts/parts.dm @@ -0,0 +1,32 @@ +/// Self Destructing Bodyparts, For Augmentation. I'm leaving out heads + chests as, while it would be cool for synths, I also don't want people to start the round unrevivable sans botany because they're dumb as heck. You know who and what you are. +/obj/item/bodypart/arm/left/self_destruct/try_attach_limb(mob/living/carbon/limb_owner, special) + . = ..() + drop_limb() + qdel(src) + +/obj/item/bodypart/arm/left/self_destruct/set_icon_static(new_icon) + return + +/obj/item/bodypart/arm/right/self_destruct/try_attach_limb(mob/living/carbon/limb_owner, special) + . = ..() + drop_limb() + qdel(src) + +/obj/item/bodypart/arm/right/self_destruct/set_icon_static(new_icon) + return + +/obj/item/bodypart/leg/left/self_destruct/try_attach_limb(mob/living/carbon/limb_owner, special) + . = ..() + drop_limb() + qdel(src) + +/obj/item/bodypart/leg/left/self_destruct/set_icon_static(new_icon) + return + +/obj/item/bodypart/leg/right/self_destruct/try_attach_limb(mob/living/carbon/limb_owner, special) + . = ..() + drop_limb() + qdel(src) + +/obj/item/bodypart/leg/right/self_destruct/set_icon_static(new_icon) + return diff --git a/modular_skyrat/modules/delam_emergency_stop/README.md b/modular_skyrat/modules/delam_emergency_stop/README.md new file mode 100644 index 00000000000..2c25a50a124 --- /dev/null +++ b/modular_skyrat/modules/delam_emergency_stop/README.md @@ -0,0 +1,41 @@ +https://github.com/Skyrat-SS13/Skyrat-tg/pull/22145 + +## Title: Delam SCRAM (Suppression System) + +MODULE ID: DELAM_SCRAM + +### Description: + +Adds an emergency stop for the supermatter engine. Operable in the first 30 minutes, allows Engineering to screw up without admin intervention to delete the crystal. + +### TG Proc Changes: + +File Location | Changed TG Proc +------------- | --------------- +`code/modules/power/supermatter/supermatter.dm` +`/obj/machinery/power/supermatter_crystal/proc/count_down` + +`code/modules/power/supermatter/supermatter_delamination/_sm_delam.dm` +`/datum/sm_delam/proc/delam_progress(obj/machinery/power/supermatter_crystal/sm)` + +### TG File Changes: + +- code/modules/power/supermatter/supermatter.dm +- code/modules/power/supermatter/supermatter_delamination/_sm_delam.dm + +### Defines: + +File Location | Defines +------------- | ------- +code/__DEFINES/~skyrat_defines/signals.dm | `#define COMSIG_MAIN_SM_DELAMINATING "delam_time"` + +### Master file additions + +- N/A + +### Included files that are not contained in this module: + +- N/A + +### Credits: +- LT3 diff --git a/modular_skyrat/modules/delam_emergency_stop/code/admin_scram.dm b/modular_skyrat/modules/delam_emergency_stop/code/admin_scram.dm new file mode 100644 index 00000000000..fc338d7b214 --- /dev/null +++ b/modular_skyrat/modules/delam_emergency_stop/code/admin_scram.dm @@ -0,0 +1,73 @@ +/// Lets an admin activate the delam suppression system +/client/proc/try_stop_delam() + set name = "Delam Emergency Stop" + set category = "Admin.Events" + var/obj/machinery/atmospherics/components/unary/delam_scram/suppression_system = null + + if(!holder || !check_rights(R_FUN)) + return + + suppression_system = validate_suppression_status() + + if(!suppression_system) + return + + // Warn them if they're intervening in the work of God + if(world.time - SSticker.round_start_time < 30 MINUTES) + var/go_early = tgui_alert(usr, "The [suppression_system.name] is set to automatically start at the programmed time. \ + Are you sure you want to override this and fire it early? It's less scary that way.", "Suffering premature delamination?", list("No", "Yes")) + if(go_early != "Yes") + return FALSE + + var/double_check = tgui_alert(usr, "You really sure that you want to push this?", "Reticulating Splines", list("No", "Yes")) + if(double_check != "Yes") + return FALSE + + // Send the signal to start, unlock the temp emergency exits + log_admin("[key_name_admin(usr)] started a supermatter emergency stop!") + message_admins("[ADMIN_LOOKUPFLW(usr)] started a supermatter emergency stop! [ADMIN_COORDJMP(suppression_system)]") + suppression_system.investigate_log("[key_name_admin(usr)] started a supermatter emergency stop!", INVESTIGATE_ATMOS) + SEND_GLOBAL_SIGNAL(COMSIG_MAIN_SM_DELAMINATING, DIVINE_INTERVENTION) + for(var/obj/machinery/door/airlock/escape_route in range(14, suppression_system)) // a little more space here due to positioning + if(istype(escape_route, /obj/machinery/door/airlock/command)) + continue + INVOKE_ASYNC(escape_route, TYPE_PROC_REF(/obj/machinery/door/airlock, temp_emergency_exit), 45 SECONDS) + +/// Lets admins disable/enable the delam suppression system +/client/proc/toggle_delam_suppression() + set name = "Delam Suppression Toggle" + set category = "Admin.Events" + + if(!holder || !check_rights(R_FUN)) + return + + var/obj/machinery/atmospherics/components/unary/delam_scram/suppression_system = validate_suppression_status() + + if(!suppression_system) + return + + suppression_system.admin_disabled = !suppression_system.admin_disabled + + log_admin("[key_name_admin(usr)] toggled Delam suppression [suppression_system.admin_disabled ? "OFF" : "ON"].") + message_admins("[key_name_admin(usr)] toggled Delam suppression [suppression_system.admin_disabled ? "OFF" : "ON"].") + +/// Check if the delam suppression setup is valid on the map +/proc/validate_suppression_status() + var/obj/machinery/atmospherics/components/unary/delam_scram/my_one_and_only = null + for(var/obj/machinery/atmospherics/components/unary/delam_scram/system as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/atmospherics/components/unary/delam_scram)) + if(!my_one_and_only) + my_one_and_only = system + else + message_admins("Delam suppression request FAILED: Multiple Delam SCRAM units found on map! Delete the extra unit at [ADMIN_COORDJMP(system)] if applicable and try again.") + stack_trace("Multiple Delam SCRAM units found on map at [system.loc]. Either someone spawned in a duplicate or you need to yell at a mapper!") // We could fire anyways, but who knows where the mystery extra machine(s) are. + return FALSE + + if(!my_one_and_only) + message_admins("No active delam SCRAM units found on map! Either it's not mapped or it's already been used!") + return FALSE + + if(my_one_and_only.on) + message_admins("[my_one_and_only] can't fire, it's already been triggered!") + return FALSE + + return my_one_and_only diff --git a/modular_skyrat/modules/delam_emergency_stop/code/delam.dm b/modular_skyrat/modules/delam_emergency_stop/code/delam.dm new file mode 100644 index 00000000000..29342d26511 --- /dev/null +++ b/modular_skyrat/modules/delam_emergency_stop/code/delam.dm @@ -0,0 +1,61 @@ +/obj/machinery/power/supermatter_crystal + /// If admins and the station have been notified according to the delam suppression function + var/station_notified = FALSE + +/datum/sm_delam/proc/notify_delam_suppression(obj/machinery/power/supermatter_crystal/sm) + if(!sm.is_main_engine) + return + + if(sm.station_notified) + return + + if(world.time - SSticker.round_start_time > 30 MINUTES) + return + + if(SSjob.is_skeleton_engineering(3)) // Don't bother if there's command or a well staffed department, they -should- be paying attention. + var/obj/machinery/announcement_system/system = pick(GLOB.announcement_systems) + SSsecurity_level.minimum_security_level(SEC_LEVEL_ORANGE, TRUE, FALSE) // Give the skeleton crew a warning + system.broadcast("The supermatter delamination early warning system has been triggered due to anomalous conditions. Please investigate the engine as soon as possible.", list(RADIO_CHANNEL_COMMAND)) + system.broadcast("In the event of uncontrolled delamination, please consult the documentation packet regarding usage of the supermatter emergency stop button.", list(RADIO_CHANNEL_COMMAND)) + system.broadcast("Failure to stabilise the engine may result in an automatic deployment of the suppression system.", list(RADIO_CHANNEL_COMMAND)) + + log_admin("DELAM: Round timer under 30 minutes! Supermatter will perform an automatic delam suppression at strength 0%.") + for(var/client/staff as anything in GLOB.admins) + if(staff?.prefs.read_preference(/datum/preference/toggle/comms_notification)) + SEND_SOUND(staff, sound('sound/misc/server-ready.ogg')) + message_admins("<font color='[COLOR_ADMIN_PINK]'>DELAM: Round timer under 30 minutes! [ADMIN_VERBOSEJMP(sm)] will perform an automatic delam suppression once integrity reaches 0%. (<a href='?src=[REF(src)];togglesuppression=yes'>TOGGLE AUTOMATIC INTERVENTION)</a>)</font>") + sm.station_notified = TRUE + +/datum/sm_delam/Topic(href, href_list) + if(..()) + return + + if(!check_rights(R_FUN)) + return + + if(href_list["togglesuppression"]) + usr.client?.toggle_delam_suppression() + +/** + * Check if the station manifest has at least a certain amount of this staff type + * + * Arguments: + * * crew_threshold - amount of crew before it's no longer considered a skeleton crew + * +*/ +/datum/controller/subsystem/job/proc/is_skeleton_engineering(crew_threshold) + var/engineers = 0 + for(var/datum/record/crew/target in GLOB.manifest.general) + if(target.trim == JOB_CHIEF_ENGINEER) + return FALSE + + if(target.trim == JOB_STATION_ENGINEER) + engineers++ + + if(target.trim == JOB_ATMOSPHERIC_TECHNICIAN) + engineers++ + + if(engineers > crew_threshold) + return FALSE + + return TRUE diff --git a/modular_skyrat/modules/delam_emergency_stop/code/scram.dm b/modular_skyrat/modules/delam_emergency_stop/code/scram.dm new file mode 100644 index 00000000000..d66f6fe2f4a --- /dev/null +++ b/modular_skyrat/modules/delam_emergency_stop/code/scram.dm @@ -0,0 +1,496 @@ +#define SM_PREVENT_EXPLOSION_THRESHOLD 100 +#define SM_COOLING_MIXTURE_MOLES 16000 +#define SM_COOLING_MIXTURE_TEMP 170 +#define DAMAGED_SUPERMATTER_COLOR list(1,0.1,0.2,0, 0,0.9,0.1,0, 0.1,-0.05,0.85,0, 0,0,0,0.9, 0,0,0,0) +#define MISTAKES_WERE_MADE 0 +#define MANUAL_INTERVENTION 0 +#define AUTOMATIC_SAFETIES 1 +#define BUTTON_PUSHED 0 +#define BUTTON_IDLE 1 +#define BUTTON_AWAKE 2 +#define BUTTON_ARMED 3 +#define SM_DAMAGED_EXPLOSION_POWER 41 +#define SHATTER_DEVASTATION_RANGE 0 +#define SHATTER_HEAVY_RANGE 0 +#define SHATTER_LIGHT_RANGE 0 +#define SHATTER_FLAME_RANGE 3 +#define SHATTER_FLASH_RANGE 5 +#define SHATTER_MIN_TIME 17 SECONDS +#define SHATTER_MAX_TIME 19 SECONDS +#define EVAC_WARNING_TIMER 3 SECONDS +#define POWER_CUT_MIN_DURATION_SECONDS 21 +#define POWER_CUT_MAX_DURATION_SECONDS 23 +#define AIR_INJECT_RATE 175 +#define BUTTON_SOUND_RANGE 7 +#define BUTTON_SOUND_FALLOFF_DISTANCE 7 +#define MACHINE_SOUND_RANGE 15 +#define MACHINE_RUMBLE_SOUND_RANGE 30 +#define MACHINE_SOUND_FALLOFF_DISTANCE 10 + +/// An atmos device that uses freezing cold air to attempt an emergency shutdown of the supermatter engine +/obj/machinery/atmospherics/components/unary/delam_scram + icon = 'modular_skyrat/modules/delam_emergency_stop/icons/scram.dmi' + icon_state = "dispenser-idle" + name = "\improper delamination suppression system" + desc = "The latest model in Nakamura Engineering's line of delamination suppression systems.<br>You don't want to be in the chamber when it's activated!<br>\ + Come to think of it, CentCom would rather you didn't activate it at all.<br>These things are expensive!" + use_power = IDLE_POWER_USE + can_unwrench = FALSE // comedy option, what if unwrenching trying to steal it throws you into the crystal for a nice dusting + shift_underlay_only = FALSE + hide = TRUE + piping_layer = PIPING_LAYER_MAX + pipe_state = "injector" + resistance_flags = FIRE_PROOF | FREEZE_PROOF | UNACIDABLE + idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 4 + + ///Rate of operation of the device (L/s) + var/volume_rate = AIR_INJECT_RATE + ///weakref to our SM + var/datum/weakref/my_sm + ///Our internal radio + var/obj/item/radio/radio + ///The key our internal radio uses + var/radio_key = /obj/item/encryptionkey/headset_eng + ///Radio channels, need null to actually broadcast on common, lol + var/emergency_channel = null + var/warning_channel = RADIO_CHANNEL_ENGINEERING + ///If someone -really- wants the SM to explode + var/admin_disabled = FALSE + + +/obj/machinery/atmospherics/components/unary/delam_scram/Initialize(mapload) + . = ..() + + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/atmospherics/components/unary/delam_scram/LateInitialize() + . = ..() + if(isnull(id_tag)) + id_tag = "SCRAM" + + radio = new(src) + radio.keyslot = new radio_key + radio.set_listening(FALSE) + radio.recalculateChannels() + + marry_sm() + RegisterSignal(SSdcs, COMSIG_MAIN_SM_DELAMINATING, PROC_REF(panic_time)) + +/obj/machinery/atmospherics/components/unary/delam_scram/Destroy() + QDEL_NULL(radio) + my_sm = null + return ..() + +/// Sets the weakref to the SM +/obj/machinery/atmospherics/components/unary/delam_scram/proc/marry_sm() + my_sm = WEAKREF(GLOB.main_supermatter_engine) + +/obj/machinery/atmospherics/components/unary/delam_scram/update_icon_nopipes() + return + +/** + * The atmos code is functionally identical to /obj/machinery/atmospherics/components/unary/outlet_injector + * However this is a hardened all-in-one unit that can't have its controls + * tampered with like an outlet injector +*/ +/obj/machinery/atmospherics/components/unary/delam_scram/process_atmos() + ..() + if(!on || !is_operational) + return + + var/turf/location = get_turf(loc) + + if(isclosedturf(location)) + return + + var/datum/gas_mixture/air_contents = airs[1] + + if(air_contents.temperature > 0) + var/transfer_moles = (air_contents.return_pressure() * volume_rate) / (air_contents.temperature * R_IDEAL_GAS_EQUATION) + + if(!transfer_moles) + return + + var/datum/gas_mixture/removed = air_contents.remove(transfer_moles) + + location.assume_air(removed) + update_parents() + +/// Signal handler for the emergency stop button/automated system +/obj/machinery/atmospherics/components/unary/delam_scram/proc/panic_time(source, trigger_reason) + SIGNAL_HANDLER + + if(!prereq_check()) + return + + send_warning(source, trigger_reason) + +/// Check for admin intervention or a fault in the signal validation, we don't exactly want to fire this on accident +/obj/machinery/atmospherics/components/unary/delam_scram/proc/prereq_check(source, trigger_reason) + if(on) + return FALSE + + if(admin_disabled) + investigate_log("Delam SCRAM tried to activate but an admin disabled it", INVESTIGATE_ATMOS) + playsound(src, 'sound/misc/compiler-failure.ogg', 100, FALSE, MACHINE_SOUND_RANGE, ignore_walls = TRUE, use_reverb = TRUE, falloff_distance = MACHINE_SOUND_FALLOFF_DISTANCE) + radio.talk_into(src, "System fault! Unable to trigger.", warning_channel) + audible_message(span_danger("[src] makes a series of sad beeps. Someone has corrupted its software!")) + return FALSE + + if(world.time - SSticker.round_start_time > 30 MINUTES && trigger_reason != DIVINE_INTERVENTION) + playsound(src, 'sound/misc/compiler-failure.ogg', 100, FALSE, MACHINE_SOUND_RANGE, ignore_walls = TRUE, use_reverb = TRUE, falloff_distance = MACHINE_SOUND_FALLOFF_DISTANCE) + audible_message(span_danger("[src] makes a series of sad beeps. The internal charge only lasts about 30 minutes... what a feat of engineering!")) + investigate_log("Delam SCRAM signal was received but failed precondition check. (Round time or trigger reason)", INVESTIGATE_ATMOS) + return FALSE + + return TRUE + +/// Tells the station (they probably already know) and starts the procedure +/obj/machinery/atmospherics/components/unary/delam_scram/proc/send_warning(source, trigger_reason) + if(trigger_reason == DIVINE_INTERVENTION) + investigate_log("Delam SCRAM was activated by admin intervention", INVESTIGATE_ATMOS) + notify_ghosts( + "[src] has been activated!", + source = src, + header = "Divine Intervention", + action = NOTIFY_ORBIT, + ghost_sound = 'sound/machines/warning-buzzer.ogg', + notify_volume = 75, + ) + else + var/reason + switch(trigger_reason) + if(AUTOMATIC_SAFETIES) + reason = "automatic safeties" + if(MANUAL_INTERVENTION) + reason = "manual intervention" + + investigate_log("Delam SCRAM was activated by [reason]", INVESTIGATE_ATMOS) + // They're probably already deadchat engineering discussing what you did wrong + notify_ghosts( + "[src] has been activated!", + source = src, + header = "Mistakes Were Made", + action = NOTIFY_ORBIT, + ghost_sound = 'sound/machines/warning-buzzer.ogg', + notify_volume = 75, + ) + + radio.talk_into(src, "DELAMINATION SUPPRESSION SYSTEM FIRING. EVACUATE THE SUPERMATTER ENGINE ROOM!", emergency_channel) + + // fight power with power + addtimer(CALLBACK(src, PROC_REF(put_on_a_show)), EVAC_WARNING_TIMER) + playsound(src, 'sound/misc/bloblarm.ogg', 100, FALSE, MACHINE_RUMBLE_SOUND_RANGE, ignore_walls = TRUE, use_reverb = TRUE, falloff_distance = MACHINE_SOUND_FALLOFF_DISTANCE) + power_fail((EVAC_WARNING_TIMER / 10) + POWER_CUT_MAX_DURATION_SECONDS, (EVAC_WARNING_TIMER / 10) + POWER_CUT_MAX_DURATION_SECONDS) + +/// Stop the delamination. Let the fireworks begin +/obj/machinery/atmospherics/components/unary/delam_scram/proc/put_on_a_show() + var/obj/machinery/power/supermatter_crystal/engine/angry_sm = my_sm?.resolve() + if(!angry_sm) + return + + // Fire bell close, that nice 'are we gonna die?' rumble out far + on = TRUE + alert_sound_to_playing('sound/misc/earth_rumble_distant3.ogg', override_volume = TRUE) + update_appearance() + + // Good job at kneecapping the crystal, engineers + // Make the crystal look cool (can escape a delam, but not puns) + angry_sm.modify_filter(name = "ray", new_params = list( + color = SUPERMATTER_TESLA_COLOUR, + )) + angry_sm.color = DAMAGED_SUPERMATTER_COLOR + angry_sm.set_light_color(SUPERMATTER_TESLA_COLOUR) + angry_sm.update_appearance() + + // Don't vent the delam juice as it works its magic + for(var/obj/machinery/atmospherics/components/unary/vent_scrubber/scrubby_boi in range(3, src)) + scrubby_boi.on = FALSE + scrubby_boi.update_appearance() + + for(var/obj/machinery/atmospherics/components/unary/vent_pump/venti_boi in range(3, src)) + venti_boi.on = FALSE + venti_boi.update_appearance() + + // The windows can only protect you for so long + for(var/obj/structure/window/reinforced/plasma/fucked_window in range(3, src)) + addtimer(CALLBACK(fucked_window, TYPE_PROC_REF(/obj/structure/window/reinforced/plasma, shatter_window)), rand(SHATTER_MIN_TIME, SHATTER_MAX_TIME)) + + // Let the gas work for a few seconds to cool the crystal. If it has damage beyond repair, heal it a bit + addtimer(CALLBACK(src, PROC_REF(prevent_explosion)), 9 SECONDS) + +/// Shatter the supermatter chamber windows +/obj/structure/window/reinforced/plasma/proc/shatter_window() + visible_message(span_danger("[src] shatters in the freon fire!")) + explosion(src, SHATTER_DEVASTATION_RANGE, SHATTER_HEAVY_RANGE, SHATTER_LIGHT_RANGE, SHATTER_FLAME_RANGE, SHATTER_FLASH_RANGE) + qdel(src) + +/// The valiant little machine falls apart, one time use only! +/obj/machinery/atmospherics/components/unary/delam_scram/proc/goodbye_friends() + + // good job buddy, sacrificing yourself for the greater good + playsound(src, 'sound/misc/compiler-failure.ogg', 100, FALSE, MACHINE_SOUND_RANGE, ignore_walls = TRUE, use_reverb = TRUE, falloff_distance = MACHINE_SOUND_FALLOFF_DISTANCE) + visible_message(span_danger("[src] beeps a sorrowful melody and collapses into a pile of twisted metal and foam!"), blind_message = span_danger("[src] beeps a sorrowful melody!")) + deconstruct(FALSE) + +/// Drain the internal energy, if the crystal damage is above 100 we heal it a bit. Not much, but should be good to let them recover. +/obj/machinery/atmospherics/components/unary/delam_scram/proc/prevent_explosion() + var/obj/machinery/power/supermatter_crystal/engine/damaged_sm = my_sm?.resolve() + if(!damaged_sm) + return + + damaged_sm.name = "partially delaminated supermatter crystal" + damaged_sm.desc = "This crystal has seen better days, the glow seems off and the shards look brittle. Central says it's still \"relatively safe.\" They'd never lie to us, right?" + damaged_sm.explosion_power = SM_DAMAGED_EXPLOSION_POWER // if you fuck up again, yeesh + + if(damaged_sm.damage > SM_PREVENT_EXPLOSION_THRESHOLD) + damaged_sm.damage = SM_PREVENT_EXPLOSION_THRESHOLD + + damaged_sm.internal_energy = MISTAKES_WERE_MADE + for(var/obj/machinery/power/energy_accumulator/tesla_coil/zappy_boi in range(3, src)) + zappy_boi.stored_energy = MISTAKES_WERE_MADE + +/obj/machinery/atmospherics/components/unary/delam_scram/New() + . = ..() + var/datum/gas_mixture/delam_juice = new + delam_juice.add_gases(/datum/gas/freon) + delam_juice.gases[/datum/gas/freon][MOLES] = SM_COOLING_MIXTURE_MOLES + delam_juice.temperature = SM_COOLING_MIXTURE_TEMP + airs[1] = delam_juice + +/// A big red button you can smash to stop the supermatter engine, oh how tempting! +/obj/machinery/button/delam_scram + name = "\improper supermatter emergency stop button" + desc = "Your last hope to try and save the crystal during a delamination.<br>\ + While it is indeed a big red button, pressing it outside of an emergency \ + will probably get the engineering department out for your blood." + icon = 'modular_skyrat/modules/delam_emergency_stop/icons/scram.dmi' + can_alter_skin = FALSE + silicon_access_disabled = TRUE + resistance_flags = FREEZE_PROOF | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + use_power = NO_POWER_USE + light_color = LIGHT_COLOR_INTENSE_RED + light_power = 0.7 + ///one use only! + var/button_stage = BUTTON_IDLE + ///our internal radio + var/obj/item/radio/radio + ///radio key + var/radio_key = /obj/item/encryptionkey/headset_eng + COOLDOWN_DECLARE(scram_button) + +/obj/machinery/button/delam_scram/Initialize(mapload) + . = ..() + radio = new(src) + radio.keyslot = new radio_key + radio.set_listening(FALSE) + radio.recalculateChannels() + +/obj/machinery/button/delam_scram/Destroy() + QDEL_NULL(radio) + return ..() + +/obj/machinery/button/delam_scram/screwdriver_act(mob/living/user, obj/item/tool) + return TRUE + +/obj/machinery/button/delam_scram/emag_act(mob/user) + return + +/// Proc for arming the red button, it hasn't been pushed yet +/obj/machinery/button/delam_scram/attack_hand(mob/user, list/modifiers) + . = ..() + if((machine_stat & BROKEN)) + return + + if(!COOLDOWN_FINISHED(src, scram_button)) + balloon_alert(user, "on cooldown!") + return + + if(!validate_suppression_status()) + playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, FALSE, BUTTON_SOUND_RANGE, falloff_distance = BUTTON_SOUND_FALLOFF_DISTANCE) + audible_message(span_danger("[src] makes a sad buzz and goes dark. Did someone activate it already?")) // Look through the window, buddy + burn_out() + return + + if(.) + return + + // Give them a cheeky instructions card. But only one! If you lost it, question your engineering prowess in this moment + if(button_stage == BUTTON_IDLE) + visible_message(span_danger("A plastic card falls out of [src]!")) + user.put_in_hands(new /obj/item/paper/paperslip/corporate/fluff/delam_procedure(get_turf(user))) + button_stage = BUTTON_AWAKE + return + + if(button_stage != BUTTON_AWAKE) + return + + COOLDOWN_START(src, scram_button, 15 SECONDS) + + // For roundstart only, after that it's on you! + if(world.time - SSticker.round_start_time > 30 MINUTES) + playsound(src.loc, 'sound/misc/compiler-failure.ogg', 50, FALSE, BUTTON_SOUND_RANGE, falloff_distance = BUTTON_SOUND_FALLOFF_DISTANCE) + audible_message(span_danger("[src] makes a series of sad beeps. The internal charge only lasts about 30 minutes... what a feat of engineering! Looks like it's all on you to save the day.")) + burn_out() + return + + // You thought you could sneak this one by your coworkers? + button_stage = BUTTON_ARMED + update_appearance() + radio.talk_into(src, "SUPERMATTER EMERGENCY STOP BUTTON ARMED!", RADIO_CHANNEL_ENGINEERING) + visible_message(span_danger("[user] swings open the plastic cover on [src]!")) + + // Let the admins know someone's fucked up + message_admins("[ADMIN_LOOKUPFLW(user)] just uncovered [src].") + investigate_log("[key_name(user)] uncovered [src].", INVESTIGATE_ATMOS) + + confirm_action(user) + +/// Confirms with the user that they really want to push the red button. Do it, you won't! +/obj/machinery/button/delam_scram/proc/confirm_action(mob/user, list/modifiers) + if(tgui_alert(usr, "Are you really sure that you want to push this?", "It looked scarier on HBO.", list("No", "Yes")) != "Yes") + button_stage = BUTTON_AWAKE + visible_message(span_danger("[user] slowly closes the plastic cover on [src]!")) + update_appearance() + return + + // Make scary sound and flashing light + playsound(src, 'sound/machines/high_tech_confirm.ogg', 50, FALSE, BUTTON_SOUND_RANGE, ignore_walls = TRUE, use_reverb = TRUE, falloff_distance = BUTTON_SOUND_FALLOFF_DISTANCE) + button_stage = BUTTON_PUSHED + visible_message(span_danger("[user] smashes [src] with their hand!")) + message_admins("[ADMIN_LOOKUPFLW(user)] pushed [src]!") + investigate_log("[key_name(user)] pushed [src]!", INVESTIGATE_ATMOS) + flick_overlay_view("[base_icon_state]-overlay-active", 20 SECONDS) + + // No going back now! + SEND_GLOBAL_SIGNAL(COMSIG_MAIN_SM_DELAMINATING, MANUAL_INTERVENTION) + + // Temporarily let anyone escape the engine room before it becomes spicy + for(var/obj/machinery/door/airlock/escape_route in range(7, src)) + if(istype(escape_route, /obj/machinery/door/airlock/command)) + continue + + INVOKE_ASYNC(escape_route, TYPE_PROC_REF(/obj/machinery/door/airlock, temp_emergency_exit), 45 SECONDS) + +/// When the button is pushed but it's too late to save you! +/obj/machinery/button/delam_scram/proc/burn_out() + if(!(machine_stat & BROKEN)) + src.desc += span_warning("The light is off, indicating it is not currently functional.") + set_machine_stat(machine_stat | BROKEN) + update_appearance() + +/obj/machinery/button/delam_scram/update_icon_state() + . = ..() + icon_state = "[base_icon_state][skin]" + if(button_stage == BUTTON_ARMED) + icon_state += "-armed" + else if(button_stage == BUTTON_PUSHED) + icon_state += "-armed" + else if(machine_stat & (NOPOWER|BROKEN)) + icon_state += "-nopower" + +/obj/machinery/power/emitter/LateInitialize(mapload) + . = ..() + RegisterSignal(SSdcs, COMSIG_MAIN_SM_DELAMINATING, PROC_REF(emergency_stop)) + +/obj/machinery/power/emitter/proc/emergency_stop() + SIGNAL_HANDLER + + var/area/my_area = get_area(src) + if(!istype(my_area, /area/station/engineering)) + return + + active = FALSE + update_appearance() + +/obj/item/paper/paperslip/corporate/fluff/delam_procedure/Initialize(mapload) + name = "NT-approved delam emergency procedure" + desc = "Now you're a REAL engineer!" + return ..() + +/obj/item/paper/paperslip/corporate/fluff/delam_procedure/examine(mob/user) + . = ..() + ui_interact(user) + +/obj/item/paper/paperslip/corporate/fluff/delam_procedure/attackby(obj/item/attacking_item, mob/living/user, params) + if(burn_paper_product_attackby_check(attacking_item, user)) + SStgui.close_uis(src) + return + + // Enable picking paper up by clicking on it with the clipboard or folder + if(istype(attacking_item, /obj/item/clipboard) || istype(attacking_item, /obj/item/folder) || istype(attacking_item, /obj/item/paper_bin)) + attacking_item.attackby(src, user) + return + + ui_interact(user) + return ..() + +/obj/item/paper/paperslip/corporate/fluff/delam_procedure/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DelamProcedure") + ui.autoupdate = FALSE + ui.open() + +/obj/structure/sign/delam_procedure + name = "Safety Moth - Delamination Emergency Procedure" + desc = "This informational sign uses Safety Moth™ to tell the viewer how to use the emergency stop button if the Supermatter Crystal is delaminating." + icon = 'modular_skyrat/modules/delam_emergency_stop/icons/scram.dmi' + icon_state = "moff-poster" + pixel_y = 4 + armor_type = /datum/armor/sign_delam + anchored = TRUE + +/datum/armor/sign_delam + melee = 60 + acid = 70 + fire = 90 + +/obj/structure/sign/delam_procedure/examine(mob/user) + . = ..() + ui_interact(user) + +/obj/structure/sign/delam_procedure/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DelamProcedure") + ui.autoupdate = FALSE + ui.open() + +/obj/structure/sign/delam_procedure/ui_status(mob/user) + if(user.is_blind()) + return UI_CLOSE + + return ..() + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/atmospherics/components/unary/delam_scram, 0) +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/delam_procedure, 32) + +#undef DAMAGED_SUPERMATTER_COLOR +#undef SM_PREVENT_EXPLOSION_THRESHOLD +#undef SM_COOLING_MIXTURE_MOLES +#undef SM_COOLING_MIXTURE_TEMP +#undef MISTAKES_WERE_MADE +#undef MANUAL_INTERVENTION +#undef AUTOMATIC_SAFETIES +#undef BUTTON_PUSHED +#undef BUTTON_IDLE +#undef BUTTON_AWAKE +#undef BUTTON_ARMED +#undef SM_DAMAGED_EXPLOSION_POWER +#undef SHATTER_DEVASTATION_RANGE +#undef SHATTER_HEAVY_RANGE +#undef SHATTER_LIGHT_RANGE +#undef SHATTER_FLAME_RANGE +#undef SHATTER_FLASH_RANGE +#undef SHATTER_MIN_TIME +#undef SHATTER_MAX_TIME +#undef EVAC_WARNING_TIMER +#undef POWER_CUT_MIN_DURATION_SECONDS +#undef POWER_CUT_MAX_DURATION_SECONDS +#undef AIR_INJECT_RATE +#undef BUTTON_SOUND_RANGE +#undef BUTTON_SOUND_FALLOFF_DISTANCE +#undef MACHINE_SOUND_RANGE +#undef MACHINE_RUMBLE_SOUND_RANGE +#undef MACHINE_SOUND_FALLOFF_DISTANCE diff --git a/modular_skyrat/modules/delam_emergency_stop/icons/scram.dmi b/modular_skyrat/modules/delam_emergency_stop/icons/scram.dmi new file mode 100644 index 00000000000..b654175abd4 Binary files /dev/null and b/modular_skyrat/modules/delam_emergency_stop/icons/scram.dmi differ diff --git a/modular_skyrat/modules/electric_welder/readme.md b/modular_skyrat/modules/electric_welder/readme.md new file mode 100644 index 00000000000..fb6a4c5b0b1 --- /dev/null +++ b/modular_skyrat/modules/electric_welder/readme.md @@ -0,0 +1,25 @@ +https://github.com/Skyrat-SS13/Skyrat-tg/pull/ + +## \<Title Here> + +Module ID: + +### Description: + +### TG Proc/File Changes: + +- `code/game/objects/items/storage/belt.dm`: `proc/get_types_to_preload` + +### Modular Overrides: + +- `modular_skyrat/master_files/code/modules/cargo/exports/tools.dm`: `var/export_types` + +### Defines: + +- N/A + +### Included files that are not contained in this module: + +- N/A + +### Credits: diff --git a/modular_skyrat/modules/food_replicator/code/clothing.dm b/modular_skyrat/modules/food_replicator/code/clothing.dm new file mode 100644 index 00000000000..8d1b6238ed6 --- /dev/null +++ b/modular_skyrat/modules/food_replicator/code/clothing.dm @@ -0,0 +1,63 @@ +/obj/item/clothing/under/colonial + name = "colonial outfit" + desc = "Fancy white satin shirt and a pair of cotton-blend pants with a black synthleather belt." + icon = 'modular_skyrat/modules/food_replicator/icons/clothing.dmi' + worn_icon = 'modular_skyrat/modules/food_replicator/icons/clothing_worn.dmi' + worn_icon_digi = 'modular_skyrat/modules/food_replicator/icons/clothing_digi.dmi' + icon_state = "under_colonial" + +/obj/item/clothing/under/colonial/mob_can_equip(mob/living/equipper, slot, disable_warning, bypass_equip_delay_self, ignore_equipped, indirect_action) + if(is_species(equipper, /datum/species/teshari)) + to_chat(equipper, span_warning("[src] is far too big for you!")) + return FALSE + + return ..() + +/obj/item/clothing/shoes/jackboots/colonial + name = "colonial half-boots" + desc = "Good old laceless boots, with a sturdy plastic toe to, theoretically, keep your toes uncrushed." + icon = 'modular_skyrat/modules/food_replicator/icons/clothing.dmi' + worn_icon = 'modular_skyrat/modules/food_replicator/icons/clothing_worn.dmi' + worn_icon_digi = 'modular_skyrat/modules/food_replicator/icons/clothing_digi.dmi' + icon_state = "boots_colonial" + +/obj/item/clothing/shoes/jackboots/colonial/mob_can_equip(mob/living/equipper, slot, disable_warning, bypass_equip_delay_self, ignore_equipped, indirect_action) + if(is_species(equipper, /datum/species/teshari)) + to_chat(equipper, span_warning("[src] is far too big for you!")) + return FALSE + + return ..() + +/obj/item/clothing/neck/cloak/colonial + name = "colonial cloak" + desc = "A cloak made from heavy tarpaulin. Nigh wind- and waterproof thanks to its design." + slot_flags = ITEM_SLOT_OCLOTHING|ITEM_SLOT_NECK + w_class = WEIGHT_CLASS_NORMAL + icon = 'modular_skyrat/modules/food_replicator/icons/clothing.dmi' + worn_icon = 'modular_skyrat/modules/food_replicator/icons/clothing_worn.dmi' + worn_icon_digi = 'modular_skyrat/modules/food_replicator/icons/clothing_digi.dmi' + icon_state = "cloak_colonial" + +/obj/item/clothing/neck/cloak/colonial/mob_can_equip(mob/living/equipper, slot, disable_warning, bypass_equip_delay_self, ignore_equipped, indirect_action) + if(is_species(equipper, /datum/species/teshari)) + to_chat(equipper, span_warning("[src] is far too big for you!")) + return FALSE + + return ..() + +/obj/item/clothing/head/hats/colonial + name = "colonial cap" + desc = "A puffy cap made out of tarpaulin covered by some textile. It is sturdy and comfortable, and seems to retain its form very well." + icon = 'modular_skyrat/modules/food_replicator/icons/clothing.dmi' + worn_icon = 'modular_skyrat/modules/food_replicator/icons/clothing_worn.dmi' + worn_icon_digi = 'modular_skyrat/modules/food_replicator/icons/clothing_digi.dmi' + icon_state = "cap_colonial" + inhand_icon_state = null + supports_variations_flags = CLOTHING_SNOUTED_VARIATION_NO_NEW_ICON + +/obj/item/clothing/head/hats/colonial/mob_can_equip(mob/living/equipper, slot, disable_warning, bypass_equip_delay_self, ignore_equipped, indirect_action) + if(is_species(equipper, /datum/species/teshari)) + to_chat(equipper, span_warning("[src] is far too big for you!")) + return FALSE + + return ..() diff --git a/modular_skyrat/modules/food_replicator/code/medical.dm b/modular_skyrat/modules/food_replicator/code/medical.dm new file mode 100644 index 00000000000..1b55cae5991 --- /dev/null +++ b/modular_skyrat/modules/food_replicator/code/medical.dm @@ -0,0 +1,66 @@ +/obj/item/stack/medical/suture/bloody + name = "hemostatic suture" + desc = "Bloodclotting agent-infused sterile sutures used to seal up cuts and lacerations and reverse critical bleedings." + icon = 'modular_skyrat/modules/food_replicator/icons/medicine.dmi' + icon_state = "hemo_suture" + heal_brute = 7 + stop_bleeding = 1 + grind_results = list(/datum/reagent/medicine/coagulant = 2) + merge_type = /obj/item/stack/medical/suture/bloody + +/obj/item/stack/medical/suture/bloody/post_heal_effects(amount_healed, mob/living/carbon/healed_mob, mob/user) + . = ..() + if(healed_mob.blood_volume <= BLOOD_VOLUME_SAFE) + healed_mob.reagents.add_reagent(/datum/reagent/medicine/salglu_solution, 2) + healed_mob.adjustOxyLoss(-amount_healed) + +/obj/item/stack/medical/mesh/bloody + name = "hemostatic mesh" + desc = "A hemostatic mesh used to dress burns and stimulate hemopoiesis. Due to its blood-related purpose, it is worse at sanitizing infections." + icon = 'modular_skyrat/modules/food_replicator/icons/medicine.dmi' + icon_state = "hemo_mesh" + heal_burn = 7 + sanitization = 0.5 + flesh_regeneration = 1.75 + stop_bleeding = 0.25 + grind_results = list(/datum/reagent/medicine/coagulant = 2) + merge_type = /obj/item/stack/medical/mesh/bloody + +/obj/item/stack/medical/mesh/bloody/update_icon_state() + if(is_open) + return ..() + + icon_state = "hemo_mesh_closed" + +/obj/item/stack/medical/mesh/bloody/post_heal_effects(amount_healed, mob/living/carbon/healed_mob, mob/user) + . = ..() + if(healed_mob.blood_volume <= BLOOD_VOLUME_SAFE) + healed_mob.reagents.add_reagent(/datum/reagent/medicine/salglu_solution, 2) + healed_mob.adjustOxyLoss(-amount_healed) + +/obj/item/reagent_containers/hypospray/medipen/glucose + name = "pressurised glucose medipen" + desc = "A medipen for keeping yourself going during prolonged EVA shifts, injects a dose of glucose into your bloodstream. Recommended for use in low-pressure environments." + icon = 'modular_skyrat/modules/food_replicator/icons/medicine.dmi' + icon_state = "glupen" + inhand_icon_state = "stimpen" + base_icon_state = "glupen" + volume = 15 + amount_per_transfer_from_this = 15 + list_reagents = list(/datum/reagent/consumable/nutriment/glucose = 15) + +/obj/item/reagent_containers/hypospray/medipen/glucose/inject(mob/living/affected_mob, mob/user) + if(lavaland_equipment_pressure_check(get_turf(user))) + amount_per_transfer_from_this = initial(amount_per_transfer_from_this) + return ..() + + if(DOING_INTERACTION(user, DOAFTER_SOURCE_SURVIVALPEN)) + to_chat(user,span_notice("You are too busy to use \the [src]!")) + return + + to_chat(user,span_notice("You start manually releasing the low-pressure gauge...")) + if(!do_after(user, 10 SECONDS, affected_mob, interaction_key = DOAFTER_SOURCE_SURVIVALPEN)) + return + + amount_per_transfer_from_this = initial(amount_per_transfer_from_this) * 0.5 + return ..() diff --git a/modular_skyrat/modules/food_replicator/code/rationpacks.dm b/modular_skyrat/modules/food_replicator/code/rationpacks.dm new file mode 100644 index 00000000000..c22d588d49a --- /dev/null +++ b/modular_skyrat/modules/food_replicator/code/rationpacks.dm @@ -0,0 +1,188 @@ +/obj/item/food/colonial_course + name = "undefined colonial course" + desc = "Something you shouldn't see. But it's edible." + icon = 'modular_skyrat/modules/food_replicator/icons/rationpack.dmi' + icon_state = "borgir" + base_icon_state = "borgir" + food_reagents = list(/datum/reagent/consumable/nutriment = 20) + tastes = list("crayon powder" = 1) + foodtypes = VEGETABLES | GRAIN + w_class = WEIGHT_CLASS_SMALL + preserved_food = TRUE + +/obj/item/food/colonial_course/attack_self(mob/user, modifiers) + if(preserved_food) + preserved_food = FALSE + icon_state = "[base_icon_state]_unwrapped" + to_chat(user, span_notice("You unpackage \the [src].")) + playsound(user.loc, 'sound/items/foodcanopen.ogg', 50) + +/obj/item/food/colonial_course/attack(mob/living/target, mob/user, def_zone) + if(preserved_food) + to_chat(user, span_warning("[src] is still packaged!")) + return FALSE + + return ..() + +/obj/item/food/colonial_course/pljeskavica + name = "pljeskavica" + desc = "Freshly-printed steaming hot burger consisting of a biogenerator-produced handcraft-imitating buns, with a minced meat patty inbetween, among various vegetables and sauces.\ + <br> Looks good <i>enough</i> for something as replicated as this. Its packaging is covered in copious amounts of information on its nutritional facts, contents and the expiry date. Sadly, it's all written in Pan-Slavic." + trash_type = /obj/item/trash/pljeskavica + food_reagents = list( + /datum/reagent/consumable/nutriment = 3, + /datum/reagent/consumable/nutriment/protein = 9, + /datum/reagent/consumable/nutriment/vitamin = 4, + ) + tastes = list("bun" = 2, "spiced meat" = 10, "death of veganism" = 3) + foodtypes = VEGETABLES | GRAIN | MEAT + +/obj/item/food/colonial_course/nachos + name = "plain nachos tray" + desc = "A vacuum-sealed package with what seems to be a generous serving of triangular corn chips, with three sections reserved for a salsa, cheese and guacamole sauces.\ + <br> Probably the best-looking food you can find in these rations, perhaps due to its simplicity." + food_reagents = list( + /datum/reagent/consumable/nutriment = 5, + /datum/reagent/consumable/nutriment/vitamin = 2, + ) + trash_type = /obj/item/trash/nachos + icon_state = "nacho" + base_icon_state = "nacho" + tastes = list("corn chips" = 5, "'artificial' organic sauces" = 5) + foodtypes = GRAIN | FRIED | DAIRY + +/obj/item/food/colonial_course/blins + name = "condensed milk crepes" + desc = "A vacuum-sealed four-pack of stuffed crepes with a minimal amount of markings. There is nothing else to it, to be frank.\ + <br> Surprisingly tasty for its looks, as long as you're not lactose intolerant, on diet, or vegan. The back of the packaging is covered in a mass of information detailing the product." + food_reagents = list( + /datum/reagent/consumable/nutriment = 2, + /datum/reagent/consumable/caramel = 3, + /datum/reagent/consumable/milk = 4, + ) + trash_type = /obj/item/trash/blins + icon_state = "blin" + base_icon_state = "blin" + tastes = list("insane amount of sweetness" = 10, "crepes" = 3) + foodtypes = SUGAR | GRAIN | DAIRY | BREAKFAST + +/obj/item/reagent_containers/cup/glass/coffee/colonial + name = "colonial thermocup" + desc = "Technically, used to drink hot beverages. But since it's the only cup design that was available, you gotta make do. It has an instruction written on its side. \ + <br> This particular one comes prefilled with a single serving of coffee powder." + special_desc = "A small instruction on the side reads: <i>\"For use in food replicators; mix water and powdered solutions in one-to-one proportions. \ + <br> For cocoa, mix milk and powdered solution in one-to-one proportion.\"</i>" + icon = 'modular_skyrat/modules/food_replicator/icons/rationpack.dmi' + list_reagents = list(/datum/reagent/consumable/powdered_coffee = 25) + +/obj/item/reagent_containers/cup/glass/coffee/colonial/empty + desc = "Technically, used to drink hot beverages. But since it's the only cup design that was available, you gotta make do. It has an instruction written on its side." + list_reagents = null + +/obj/item/trash/pljeskavica + name = "pljeskavica wrapping paper" + desc = "Covered in sauce smearings and smaller pieces of the dish on the inside, crumpled into a ball. It's probably best to dispose of it." + icon = 'modular_skyrat/modules/food_replicator/icons/rationpack.dmi' + icon_state = "borgir_trash" + +/obj/item/trash/nachos + name = "empty nachos tray" + desc = "Covered in sauce smearings and smaller pieces of the dish on the inside, a plastic food tray with not much use anymore. It's probably best to dispose of it or recycle it." + icon = 'modular_skyrat/modules/food_replicator/icons/rationpack.dmi' + custom_materials = list(/datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT) + icon_state = "nacho_trash" + +/obj/item/trash/blins + name = "empty crepes wrapper" + desc = "Empty torn wrapper that used to hold something ridiculously sweet. It's probably best to recycle it." + icon = 'modular_skyrat/modules/food_replicator/icons/rationpack.dmi' + custom_materials = list(/datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT * 0.5) + icon_state = "blin_trash" + +/obj/item/storage/box/gum/colonial + name = "mixed bubblegum packet" + desc = "The packaging is entirely written in Pan-Slavic, with a small blurb of Sol Common. You would need to take a better look to read it, though, as it is written quite small." + special_desc = "Examining the small text reveals the following: <i>\"Foreign colonization ration, model J: mixed origin, adult. Bubblegum package, medicinal, recreational. <br>\ + Do not overconsume. Certain strips contain nicotine.\"</i>" + icon = 'modular_skyrat/modules/food_replicator/icons/rationpack.dmi' + icon_state = "bubblegum" + +/obj/item/storage/box/gum/colonial/PopulateContents() + new /obj/item/food/bubblegum(src) + new /obj/item/food/bubblegum(src) + new /obj/item/food/bubblegum/nicotine(src) + new /obj/item/food/bubblegum/nicotine(src) + +/obj/item/storage/box/utensils + name = "utensils package" + desc = "A small package containing various utensils required for <i>human</i> consumption of various foods. \ + In a normal situation contains a plastic fork, a plastic spoon, and two serviettes." + icon = 'modular_skyrat/modules/food_replicator/icons/rationpack.dmi' + icon_state = "utensil_box" + w_class = WEIGHT_CLASS_TINY + illustration = null + foldable_result = null + +/obj/item/storage/box/utensils/Initialize(mapload) + . = ..() + atom_storage.set_holdable(list( + /obj/item/kitchen/spoon/plastic, + /obj/item/kitchen/fork/plastic, + /obj/item/serviette, + )) + atom_storage.max_slots = 4 + +/obj/item/storage/box/utensils/PopulateContents() + new /obj/item/kitchen/spoon/plastic(src) + new /obj/item/kitchen/fork/plastic(src) + new /obj/item/serviette/colonial(src) + new /obj/item/serviette/colonial(src) + +/obj/item/serviette/colonial + name = "colonial napkin" + desc = "To clean all the mess. Comes with a custom <i>combined</i> design of red and blue." + icon_state = "napkin_unused" + icon = 'modular_skyrat/modules/food_replicator/icons/rationpack.dmi' + used_serviette = /obj/item/serviette_used/colonial + +/obj/item/serviette_used/colonial + name = "dirty colonial napkin" + desc = "No longer useful, super dirty, or soaked, or otherwise unrecognisable." + icon_state = "napkin_used" + icon = 'modular_skyrat/modules/food_replicator/icons/rationpack.dmi' + +/obj/item/storage/box/colonial_rations + name = "foreign colonization ration" + desc = "A freshly printed civilian MRE, or more specifically a lunchtime food package, for use in the early colonization times by the first settlers of what is now known as the NRI. <br>\ + The lack of any imprinted dates, as well as its origin, <i>the food replicator</i>, should probably give you a good enough hint at its short, if reasonable, expiry time." + icon = 'modular_skyrat/modules/food_replicator/icons/rationpack.dmi' + icon_state = "mre_package" + foldable_result = null + illustration = null + +/obj/item/storage/box/colonial_rations/Initialize(mapload) + . = ..() + atom_storage.max_slots = 6 + atom_storage.locked = TRUE + +/obj/item/storage/box/colonial_rations/attack_self(mob/user, modifiers) + if(user) + if(atom_storage.locked == TRUE) + atom_storage.locked = FALSE + icon_state = "mre_package_open" + balloon_alert(user, "unsealed!") + return ..() + else + atom_storage.locked = TRUE + atom_storage.close_all() + icon_state = "mre_package" + balloon_alert(user, "resealed!") + return + +/obj/item/storage/box/colonial_rations/PopulateContents() + new /obj/item/food/colonial_course/pljeskavica(src) + new /obj/item/food/colonial_course/nachos(src) + new /obj/item/food/colonial_course/blins(src) + new /obj/item/reagent_containers/cup/glass/coffee/colonial(src) + new /obj/item/storage/box/gum/colonial(src) + new /obj/item/storage/box/utensils(src) diff --git a/modular_skyrat/modules/food_replicator/code/reagents.dm b/modular_skyrat/modules/food_replicator/code/reagents.dm new file mode 100644 index 00000000000..ef25e49efd3 --- /dev/null +++ b/modular_skyrat/modules/food_replicator/code/reagents.dm @@ -0,0 +1,116 @@ +/datum/reagent/consumable/powdered_tea + name = "Powdered Tea" + description = "Tea in its powdered form. Tastes horribly." + color = "#3a3a03" + nutriment_factor = 0 + taste_description = "bitter powder" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + default_container = /obj/item/reagent_containers/cup/glass/mug/tea + +/datum/chemical_reaction/food/unpowdered_tea + required_reagents = list( + /datum/reagent/water = 1, + /datum/reagent/consumable/powdered_tea = 1, + ) + results = list(/datum/reagent/consumable/tea = 2) + mix_message = "The mixture instantly heats up." + reaction_flags = REACTION_INSTANT + +/datum/reagent/consumable/powdered_coffee + name = "Powdered Coffee" + description = "Americano in its powdered form. Quite an ordinary thing to be honest." + color = "#101000" + nutriment_factor = 0 + taste_description = "very bitter powder" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + default_container = /obj/item/reagent_containers/cup/glass/coffee + +/datum/chemical_reaction/food/unpowdered_coffee + required_reagents = list( + /datum/reagent/water = 1, + /datum/reagent/consumable/powdered_coffee = 1, + ) + results = list(/datum/reagent/consumable/coffee = 2) + mix_message = "The mixture instantly heats up." + reaction_flags = REACTION_INSTANT + +/datum/reagent/consumable/powdered_coco + name = "Powdered Coco" + description = "Made with love (citation needed), and reclaimed biomass." + nutriment_factor = 0 + color = "#403010" + taste_description = "dry chocolate" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + default_container = /obj/item/reagent_containers/cup/glass/mug/coco + +/datum/chemical_reaction/food/unpowdered_coco + required_reagents = list( + /datum/reagent/consumable/milk = 1, + /datum/reagent/consumable/powdered_coco = 1, + ) + results = list(/datum/reagent/consumable/hot_coco = 2) + mix_message = "The mixture instantly heats up." + reaction_flags = REACTION_INSTANT + +/datum/reagent/consumable/powdered_lemonade + name = "Powdered Lemonade" + description = "Sweet, tangy base of a lemonade. Would be good if you'd mix it with water." + nutriment_factor = 0 + color = "#FFE978" + taste_description = "intensely sour and sweet lemon powder" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + default_container = /obj/item/reagent_containers/cup/soda_cans/lemon_lime + +/datum/chemical_reaction/food/unpowdered_lemonade + required_reagents = list( + /datum/reagent/water = 1, + /datum/reagent/consumable/powdered_lemonade = 1, + ) + results = list(/datum/reagent/consumable/lemonade = 2) + mix_message = "The mixture instantly cools down." + reaction_flags = REACTION_INSTANT + +/datum/reagent/consumable/powdered_milk + name = "Powdered Milk" + description = "An opaque white powder produced by the biomass restructurizers of certain machines." + nutriment_factor = 0 + color = "#DFDFDF" + taste_description = "sweet dry milk" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + default_container = /obj/item/reagent_containers/condiment/milk + +/datum/chemical_reaction/food/unpowdered_milk + required_reagents = list( + /datum/reagent/water = 1, + /datum/reagent/consumable/powdered_milk = 1, + ) + results = list(/datum/reagent/consumable/milk = 2) + mix_message = "The mixture cools down." + reaction_flags = REACTION_INSTANT + +/obj/item/reagent_containers/pill/convermol + name = "convermol pill" + desc = "Used to treat oxygen deprivation. Intoxicates the body." + icon_state = "pill16" + list_reagents = list(/datum/reagent/medicine/c2/convermol = 15) + rename_with_volume = TRUE + +/datum/reagent/consumable/nutriment/glucose + name = "Synthetic Glucose" + description = "A sticky yellow liquid, simple carbohydrate, allotrope of organic glucose. Gives your body a short-term energy boost." + nutriment_factor = 1 + color = "#f3d00d" + taste_description = "strong sweetness" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + var/delayed_satiety_drain = 30 + +/datum/reagent/consumable/nutriment/glucose/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + if(affected_mob.satiety < MAX_SATIETY) + affected_mob.adjust_nutrition(15) + delayed_satiety_drain += 15 + + return ..() + +/datum/reagent/consumable/nutriment/glucose/on_mob_delete(mob/living/carbon/detoxed_mob) + detoxed_mob.adjust_nutrition(-delayed_satiety_drain) + return ..() diff --git a/modular_skyrat/modules/food_replicator/code/replicator.dm b/modular_skyrat/modules/food_replicator/code/replicator.dm new file mode 100644 index 00000000000..9fcef963545 --- /dev/null +++ b/modular_skyrat/modules/food_replicator/code/replicator.dm @@ -0,0 +1,23 @@ +#define RND_CATEGORY_NRI_FOOD "Provision" +#define RND_CATEGORY_NRI_MEDICAL "Medicine" +#define RND_CATEGORY_NRI_CLOTHING "Apparel" + +/obj/machinery/biogenerator/food_replicator + name = "\improper Type 34 'Colonial Supply Core'" + desc = "The Type 34 'Colonial Supply Core,' colloquially known as the 'Gencrate/CSC' is an ancient, boxy design first put in use by the pioneer colonists of what's now known \ + as the NRI. The Gencrate is at its core a matter resequencer, a highly specialized subtype of biogenerator which performs a sort of transmutation using organic \ + compounds; normally from large-scale crops or waste product. With sufficient supply, the machine is capable of making a wide variety of provisions, \ + from clothes to food to first-aid medical supplies." + icon = 'modular_skyrat/modules/food_replicator/icons/biogenerator.dmi' + circuit = /obj/item/circuitboard/machine/biogenerator/food_replicator + efficiency = 0.75 + productivity = 0.75 + show_categories = list( + RND_CATEGORY_NRI_FOOD, + RND_CATEGORY_NRI_MEDICAL, + RND_CATEGORY_NRI_CLOTHING, + ) + +/obj/item/circuitboard/machine/biogenerator/food_replicator + name = "Colonial Supply Core" + build_path = /obj/machinery/biogenerator/food_replicator diff --git a/modular_skyrat/modules/food_replicator/code/replicator_designs/replicator_clothing.dm b/modular_skyrat/modules/food_replicator/code/replicator_designs/replicator_clothing.dm new file mode 100644 index 00000000000..b7973d2fb3a --- /dev/null +++ b/modular_skyrat/modules/food_replicator/code/replicator_designs/replicator_clothing.dm @@ -0,0 +1,54 @@ +/datum/design/colonial_under + name = "Colonial Uniform" + id = "slavic_under" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 200) + build_path = /obj/item/clothing/under/colonial + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_CLOTHING, + ) + +/datum/design/colonial_boots + name = "Colonial Half-Boots" + id = "slavic_boots" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 150) + build_path = /obj/item/clothing/shoes/jackboots/colonial + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_CLOTHING, + ) + +/datum/design/colonial_cloak + name = "Colonial Cloak" + id = "slavic_cloak" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 250) + build_path = /obj/item/clothing/neck/cloak/colonial + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_CLOTHING, + ) + +/datum/design/cool_hat + name = "Colonial Cap" + id = "slavic_cap" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 150) + build_path = /obj/item/clothing/head/hats/colonial + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_CLOTHING, + ) + +/datum/design/cool_gloves + name = "Black Gloves" + id = "slavic_gloves" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 100) + build_path = /obj/item/clothing/gloves/color/black + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_CLOTHING, + ) diff --git a/modular_skyrat/modules/food_replicator/code/replicator_designs/replicator_food.dm b/modular_skyrat/modules/food_replicator/code/replicator_designs/replicator_food.dm new file mode 100644 index 00000000000..389ca245089 --- /dev/null +++ b/modular_skyrat/modules/food_replicator/code/replicator_designs/replicator_food.dm @@ -0,0 +1,165 @@ +/datum/design/ration + name = "Foreign Colonization Ration" + id = "slavic_mre" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 550) + build_path = /obj/item/storage/box/colonial_rations + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +/datum/design/pljeskavica + name = "Foreign Colonization Ration, Main Course" + id = "slavic_burger" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 200) + build_path = /obj/item/food/colonial_course/pljeskavica + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +/datum/design/nachos + name = "Foreign Colonization Ration, Side Dish" + id = "mexican_chips" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 150) + build_path = /obj/item/food/colonial_course/nachos + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +/datum/design/blins + name = "Foreign Colonization Ration, Dessert" + id = "slavic_crepes" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 100) + build_path = /obj/item/food/colonial_course/blins + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +///Despite being in the medical.dm file, it's still used to fill your hunger up, as such, technically, is food. +/datum/design/glucose + name = "EVA Glucose Injector" + id = "slavic_glupen" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 150) + build_path = /obj/item/reagent_containers/hypospray/medipen/glucose + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +/datum/design/spork + name = "Foreign Colonization Ration, Utensils" + id = "slavic_utens" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 75) + build_path = /obj/item/storage/box/utensils + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +/datum/design/bubblegum + name = "Foreign Colonization Ration, Bubblegum Pack" + id = "slavic_gum" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 100) + build_path = /obj/item/storage/box/gum/colonial + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +/datum/design/cup + name = "Empty Paper Cup" + id = "slavic_cup" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 10) + build_path = /obj/item/reagent_containers/cup/glass/coffee/colonial/empty + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +/datum/design/tea + name = "Powdered Black Tea" + id = "slavic_tea" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 4) + make_reagent = /datum/reagent/consumable/powdered_tea + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +/datum/design/coffee + name = "Powdered Coffee" + id = "slavic_coffee" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 4) + make_reagent = /datum/reagent/consumable/powdered_coffee + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +/datum/design/cocoa + name = "Powdered Hot Chocolate" + id = "slavic_coco" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 4) + make_reagent = /datum/reagent/consumable/powdered_coco + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +/datum/design/lemonade + name = "Powdered Lemonade" + id = "slavic_lemon" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 4) + make_reagent = /datum/reagent/consumable/powdered_lemonade + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +/datum/design/replicator_sugar + name = "Sugar" + id = "slavic_sugar" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 5) + make_reagent = /datum/reagent/consumable/sugar + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +/datum/design/powdered_milk + name = "Powdered Milk" + id = "slavic_milk" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 4) + make_reagent = /datum/reagent/consumable/powdered_milk + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) + +/datum/design/water + name = "Water" + id = "slavic_water" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 1) + make_reagent = /datum/reagent/water + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_FOOD, + ) diff --git a/modular_skyrat/modules/food_replicator/code/replicator_designs/replicator_medical.dm b/modular_skyrat/modules/food_replicator/code/replicator_designs/replicator_medical.dm new file mode 100644 index 00000000000..31b14284387 --- /dev/null +++ b/modular_skyrat/modules/food_replicator/code/replicator_designs/replicator_medical.dm @@ -0,0 +1,113 @@ +/datum/design/pocket_medkit + name = "Empty Pocket First Aid Kit" + id = "slavic_cfap" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 250) + build_path = /obj/item/storage/bag/pocket_medkit + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_MEDICAL, + ) + +/datum/design/medipouch + name = "Empty Medipen Pouch" + id = "slavic_medipouch" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 250) + build_path = /obj/item/storage/bag/medipen + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_MEDICAL, + ) + +/datum/design/sutures + name = "Hemostatic Sutures" + id = "slavic_suture" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 150) + build_path = /obj/item/stack/medical/suture/bloody + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_MEDICAL, + ) + +/datum/design/mesh + name = "Hemostatic Mesh" + id = "slavic_mesh" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 150) + build_path = /obj/item/stack/medical/mesh/bloody + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_MEDICAL, + ) + +/datum/design/bruise_patch + name = "Bruise Patch" + id = "slavic_bruise" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 250) + build_path = /obj/item/reagent_containers/pill/patch/libital + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_MEDICAL, + ) + +/datum/design/burn_patch + name = "Burn Patch" + id = "slavic_burn" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 250) + build_path = /obj/item/reagent_containers/pill/patch/aiuri + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_MEDICAL, + ) + +/datum/design/gauze + name = "Medical Gauze" + id = "slavic_gauze" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 100) + build_path = /obj/item/stack/medical/gauze + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_MEDICAL, + ) + +/datum/design/epi_pill + name = "Epinephrine Pill" + id = "slavic_epi" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 75) + build_path = /obj/item/reagent_containers/pill/epinephrine + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_MEDICAL, + ) + +/datum/design/conv_pill + name = "Convermol Pill" + id = "slavic_conv" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 75) + build_path = /obj/item/reagent_containers/pill/convermol + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_MEDICAL, + ) + +/datum/design/multiver_pill + name = "Multiver Pill" + id = "slavic_multiver" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 75) + build_path = /obj/item/reagent_containers/pill/multiver + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_NRI_MEDICAL, + ) + +#undef RND_CATEGORY_NRI_FOOD +#undef RND_CATEGORY_NRI_MEDICAL +#undef RND_CATEGORY_NRI_CLOTHING diff --git a/modular_skyrat/modules/food_replicator/code/storage.dm b/modular_skyrat/modules/food_replicator/code/storage.dm new file mode 100644 index 00000000000..09ae7bece12 --- /dev/null +++ b/modular_skyrat/modules/food_replicator/code/storage.dm @@ -0,0 +1,40 @@ +/obj/item/storage/bag/medipen + name = "colonial medipen pouch" + desc = "A pouch for your (medi-)pens that goes in your pocket." + icon = 'modular_skyrat/modules/food_replicator/icons/pouch.dmi' + icon_state = "medipen_pouch" + slot_flags = ITEM_SLOT_POCKETS + w_class = WEIGHT_CLASS_BULKY + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/medipen/update_icon_state() + icon_state = "[initial(icon_state)]_[contents.len]" + return ..() + +/obj/item/storage/bag/medipen/Initialize(mapload) + . = ..() + update_appearance() + +/obj/item/storage/bag/medipen/Initialize(mapload) + . = ..() + atom_storage.max_specific_storage = WEIGHT_CLASS_TINY + atom_storage.max_total_storage = 4 + atom_storage.max_slots = 4 + atom_storage.numerical_stacking = FALSE + atom_storage.can_hold = typecacheof(list(/obj/item/reagent_containers/hypospray/medipen, /obj/item/pen, /obj/item/flashlight/pen)) + +/obj/item/storage/bag/pocket_medkit + name = "colonial first aid kit" + desc = "A medical pouch that goes in your pocket. Can be used to store things unrelated to medicine, except for guns, ammo and raw materials." + icon = 'modular_skyrat/modules/food_replicator/icons/pouch.dmi' + icon_state = "cfak" + slot_flags = ITEM_SLOT_POCKETS + w_class = WEIGHT_CLASS_BULKY + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/pocket_medkit/Initialize(mapload) + . = ..() + atom_storage.max_specific_storage = WEIGHT_CLASS_SMALL + atom_storage.max_total_storage = 4 + atom_storage.max_slots = 4 + atom_storage.cant_hold = typecacheof(list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/stack/sheet)) diff --git a/modular_skyrat/modules/food_replicator/icons/biogenerator.dmi b/modular_skyrat/modules/food_replicator/icons/biogenerator.dmi new file mode 100644 index 00000000000..98ead8bd6bc Binary files /dev/null and b/modular_skyrat/modules/food_replicator/icons/biogenerator.dmi differ diff --git a/modular_skyrat/modules/food_replicator/icons/clothing.dmi b/modular_skyrat/modules/food_replicator/icons/clothing.dmi new file mode 100644 index 00000000000..87b63178b5d Binary files /dev/null and b/modular_skyrat/modules/food_replicator/icons/clothing.dmi differ diff --git a/modular_skyrat/modules/food_replicator/icons/clothing_digi.dmi b/modular_skyrat/modules/food_replicator/icons/clothing_digi.dmi new file mode 100644 index 00000000000..baec8fc7b5d Binary files /dev/null and b/modular_skyrat/modules/food_replicator/icons/clothing_digi.dmi differ diff --git a/modular_skyrat/modules/food_replicator/icons/clothing_worn.dmi b/modular_skyrat/modules/food_replicator/icons/clothing_worn.dmi new file mode 100644 index 00000000000..35b4f4b497c Binary files /dev/null and b/modular_skyrat/modules/food_replicator/icons/clothing_worn.dmi differ diff --git a/modular_skyrat/modules/food_replicator/icons/medicine.dmi b/modular_skyrat/modules/food_replicator/icons/medicine.dmi new file mode 100644 index 00000000000..38cbffbcf13 Binary files /dev/null and b/modular_skyrat/modules/food_replicator/icons/medicine.dmi differ diff --git a/modular_skyrat/modules/food_replicator/icons/pouch.dmi b/modular_skyrat/modules/food_replicator/icons/pouch.dmi new file mode 100644 index 00000000000..9756206d0df Binary files /dev/null and b/modular_skyrat/modules/food_replicator/icons/pouch.dmi differ diff --git a/modular_skyrat/modules/food_replicator/icons/rationpack.dmi b/modular_skyrat/modules/food_replicator/icons/rationpack.dmi new file mode 100644 index 00000000000..c0422ef3ca0 Binary files /dev/null and b/modular_skyrat/modules/food_replicator/icons/rationpack.dmi differ diff --git a/modular_skyrat/modules/ices_events/code/events/ev_roleplay_check.dm b/modular_skyrat/modules/ices_events/code/events/ev_roleplay_check.dm new file mode 100644 index 00000000000..d55fd75aeb3 --- /dev/null +++ b/modular_skyrat/modules/ices_events/code/events/ev_roleplay_check.dm @@ -0,0 +1,13 @@ +/** + * Checks if a player meets certain conditions to exclude them from event selection. + */ +/proc/engaged_role_play_check(mob/living/carbon/human/player, station = TRUE, dorms = TRUE) + var/turf/player_turf = get_turf(player) + var/area/player_area = get_area(player_turf) + if(!is_station_level(player_turf.z) && station) + return TRUE + + if(istype(player_area, /area/station/commons/dorms) && dorms) + return TRUE + + return FALSE diff --git a/modular_skyrat/modules/implants/icons/razorclaws.dmi b/modular_skyrat/modules/implants/icons/razorclaws.dmi new file mode 100644 index 00000000000..af0a7bf194a Binary files /dev/null and b/modular_skyrat/modules/implants/icons/razorclaws.dmi differ diff --git a/modular_skyrat/modules/implants/icons/razorclaws_lefthand.dmi b/modular_skyrat/modules/implants/icons/razorclaws_lefthand.dmi new file mode 100644 index 00000000000..9ef1ab7530b Binary files /dev/null and b/modular_skyrat/modules/implants/icons/razorclaws_lefthand.dmi differ diff --git a/modular_skyrat/modules/implants/icons/razorclaws_righthand.dmi b/modular_skyrat/modules/implants/icons/razorclaws_righthand.dmi new file mode 100644 index 00000000000..9098ad1ceec Binary files /dev/null and b/modular_skyrat/modules/implants/icons/razorclaws_righthand.dmi differ diff --git a/modular_skyrat/modules/mapping/code/interlink_helper.dm b/modular_skyrat/modules/mapping/code/interlink_helper.dm new file mode 100644 index 00000000000..4d173e0f32f --- /dev/null +++ b/modular_skyrat/modules/mapping/code/interlink_helper.dm @@ -0,0 +1,16 @@ +/// A file to help with making it possible to load the Interlink *modularly* instead of leaving it stuck in Z-2 where station should be and spawning all manner of bad behaviour. +#define INIT_ANNOUNCE(X) to_chat(world, span_boldannounce("[X]")); log_world(X) + +/datum/controller/subsystem/mapping/loadWorld() + . = ..() + var/list/FailedZsRat = list() + LoadGroup(FailedZsRat, "The Interlink", "map_files/generic", "CentCom_skyrat_z2.dmm", default_traits = ZTRAITS_CENTCOM) + if(LAZYLEN(FailedZsRat)) //but seriously, unless the server's filesystem is messed up this will never happen + var/msg = "RED ALERT! The following map files failed to load: [FailedZsRat[1]]" + if(FailedZsRat.len > 1) + for(var/I in 2 to FailedZsRat.len) + msg += ", [FailedZsRat[I]]" + msg += ". Yell at your server host!" + INIT_ANNOUNCE(msg) + +#undef INIT_ANNOUNCE diff --git a/modular_skyrat/modules/medical/code/grasp.dm b/modular_skyrat/modules/medical/code/grasp.dm new file mode 100644 index 00000000000..808732c5572 --- /dev/null +++ b/modular_skyrat/modules/medical/code/grasp.dm @@ -0,0 +1,19 @@ +/mob/living/carbon/proc/self_grasp_bleeding_limb(obj/item/bodypart/grasped_part, supress_message = FALSE) + if(!grasped_part?.can_be_grasped()) + return + var/starting_hand_index = active_hand_index + if(starting_hand_index == grasped_part.held_index) + to_chat(src, span_danger("You can't grasp your [grasped_part.name] with itself!")) + return + + to_chat(src, span_warning("You try grasping at your [grasped_part.name], trying to stop the bleeding...")) + if(!do_after(src, 0.75 SECONDS)) + to_chat(src, span_danger("You fail to grasp your [grasped_part.name].")) + return + + var/obj/item/hand_item/self_grasp/grasp = new + if(starting_hand_index != active_hand_index || !put_in_active_hand(grasp)) + to_chat(src, span_danger("You fail to grasp your [grasped_part.name].")) + QDEL_NULL(grasp) + return + grasp.grasp_limb(grasped_part) diff --git a/modular_skyrat/modules/medical/code/health_analyzer.dm b/modular_skyrat/modules/medical/code/health_analyzer.dm new file mode 100644 index 00000000000..1d7eba6198f --- /dev/null +++ b/modular_skyrat/modules/medical/code/health_analyzer.dm @@ -0,0 +1,11 @@ +/// If TRUE, this analyzer can be used for medibot construction. If FALSE, it cannot. Returns TRUE by default. +/obj/item/healthanalyzer/proc/can_be_used_in_medibot() + return TRUE + +/obj/item/healthanalyzer/no_medibot + name = "surplus health analyzer" + desc = "A hand-held body scanner capable of distinguishing vital signs of the subject. Has a side button to scan for chemicals, and can be toggled to scan wounds. \ + This one seems to lack the mounting braces usually found on medibot-compatable analyzers..." + +/obj/item/healthanalyzer/no_medibot/can_be_used_in_medibot() + return FALSE diff --git a/modular_skyrat/modules/medical/code/wounds/bleed.dm b/modular_skyrat/modules/medical/code/wounds/bleed.dm new file mode 100644 index 00000000000..75d80d3b384 --- /dev/null +++ b/modular_skyrat/modules/medical/code/wounds/bleed.dm @@ -0,0 +1,19 @@ +/datum/wound/slash/flesh/show_wound_topic(mob/user) + return (user == victim && blood_flow) + +/datum/wound/slash/flesh/Topic(href, href_list) + . = ..() + if(href_list["wound_topic"]) + if(!usr == victim) + return + victim.self_grasp_bleeding_limb(limb) + +/datum/wound/pierce/bleed/show_wound_topic(mob/user) + return (user == victim && blood_flow) + +/datum/wound/slash/bleed/Topic(href, href_list) + . = ..() + if(href_list["wound_topic"]) + if(!usr == victim) + return + victim.self_grasp_bleeding_limb(limb) diff --git a/modular_skyrat/modules/medical/code/wounds/medical.dm b/modular_skyrat/modules/medical/code/wounds/medical.dm new file mode 100644 index 00000000000..5a3723fff4b --- /dev/null +++ b/modular_skyrat/modules/medical/code/wounds/medical.dm @@ -0,0 +1,131 @@ +#define SELF_AID_REMOVE_DELAY 5 SECONDS +#define OTHER_AID_REMOVE_DELAY 2 SECONDS + +/obj/item/stack/medical/gauze + /// The amount of direct hits our limb can take before we fall off. + var/integrity = 2 + /// If we are splinting a limb, this is the overlay prefix we will use. + var/splint_prefix = "splint" + /// If we are bandaging a limb, this is the overlay prefix we will use. + var/gauze_prefix = "gauze" + /// If it is at all possible for us to splint a limb. + var/can_splint = TRUE + +/obj/item/bodypart/apply_gauze(obj/item/stack/gauze) + . = ..() + + owner?.update_bandage_overlays() + +/obj/item/stack/medical/gauze/Destroy() + var/mob/living/carbon/previously_gauzed = gauzed_bodypart?.owner + + . = ..() + + previously_gauzed?.update_bandage_overlays() + +/** + * rip_off() called when someone rips it off + * + * It will return the bandage if it's considered pristine + * + */ +/obj/item/stack/medical/gauze/proc/rip_off() + if (is_pristine()) + . = new src.type(null, 1) + + qdel(src) + +/// Returns either [splint_prefix] or [gauze_prefix] depending on if we are splinting or not. Suffixes it with a digitigrade flag if applicable for the limb. +/obj/item/stack/medical/gauze/proc/get_overlay_prefix() + var/splinting = is_splinting() + + var/prefix + if (splinting) + prefix = splint_prefix + else + prefix = gauze_prefix + + var/suffix = gauzed_bodypart.body_zone + if(gauzed_bodypart.bodytype & BODYTYPE_DIGITIGRADE) + suffix += "_digitigrade" + + return "[prefix]_[suffix]" + +/// Returns if we can splint, and if any wound on our bodypart gives a splint overlay. +/obj/item/stack/medical/gauze/proc/is_splinting() + SHOULD_BE_PURE(TRUE) + + if (!can_splint) + return FALSE + + for (var/datum/wound/iterated_wound as anything in gauzed_bodypart.wounds) + if (iterated_wound.wound_flags & SPLINT_OVERLAY) + return TRUE + + return FALSE + +/** + * is_pristine() called by rip_off() + * + * Used to determine whether the bandage can be re-used and won't qdel itself + * + */ + +/obj/item/stack/medical/gauze/proc/is_pristine() + return (integrity == initial(integrity)) + +/** + * get_hit() called when the bandage gets damaged + * + * This proc will subtract integrity and delete the bandage with a to_chat message to whoever was bandaged + * + */ + +/obj/item/stack/medical/gauze/proc/get_hit() + integrity-- + if(integrity <= 0) + if(gauzed_bodypart.owner) + to_chat(gauzed_bodypart.owner, span_warning("The [name] on your [gauzed_bodypart.name] tears and falls off!")) + qdel(src) + +/obj/item/stack/medical/gauze/Topic(href, href_list) + . = ..() + if(href_list["remove"]) + if(!gauzed_bodypart.owner) + return + if(!iscarbon(usr)) + return + if(!in_range(usr, gauzed_bodypart.owner)) + return + var/mob/living/carbon/carbon_user = usr + var/self = (carbon_user == gauzed_bodypart.owner) + carbon_user.visible_message(span_notice("[carbon_user] begins removing [name] from [self ? "[gauzed_bodypart.owner.p_Their()]" : "[gauzed_bodypart.owner]'s" ] [gauzed_bodypart.name]..."), span_notice("You begin to remove [name] from [self ? "your" : "[gauzed_bodypart.owner]'s"] [gauzed_bodypart.name]...")) + if(!do_after(carbon_user, (self ? SELF_AID_REMOVE_DELAY : OTHER_AID_REMOVE_DELAY), target = gauzed_bodypart.owner)) + return + if(QDELETED(src)) + return + carbon_user.visible_message(span_notice("[carbon_user] removes [name] from [self ? "[gauzed_bodypart.owner.p_Their()]" : "[gauzed_bodypart.owner]'s" ] [gauzed_bodypart.name]."), span_notice("You remove [name] from [self ? "your" : "[gauzed_bodypart.owner]'s" ] [gauzed_bodypart.name].")) + var/obj/item/gotten = rip_off() + if(gotten && !carbon_user.put_in_hands(gotten)) + gotten.forceMove(get_turf(carbon_user)) + +/// Returns the name of ourself when used in a "owner is [usage_prefix] by [name]" examine_more situation/ +/obj/item/stack/proc/get_gauze_description() + return "[name]" + +/// Returns the usage prefix of ourself when used in a "owner is [usage_prefix] by [name]" examine_more situation/ +/obj/item/stack/proc/get_gauze_usage_prefix() + return "bandaged" + +/obj/item/stack/medical/gauze/get_gauze_usage_prefix() + if (is_splinting()) + return "fastened" + else + return ..() + +/// Returns TRUE if we can generate an overlay, false otherwise. +/obj/item/stack/medical/gauze/proc/has_overlay() + return (!isnull(gauze_prefix) && !isnull(splint_prefix)) + +/obj/item/stack/medical/gauze/improvised + splint_prefix = "splint_improv" diff --git a/modular_skyrat/modules/modular_implants/code/nifsofts/base_types/action_granter.dm b/modular_skyrat/modules/modular_implants/code/nifsofts/base_types/action_granter.dm new file mode 100644 index 00000000000..05d1012d629 --- /dev/null +++ b/modular_skyrat/modules/modular_implants/code/nifsofts/base_types/action_granter.dm @@ -0,0 +1,26 @@ +/// This type of NIFSoft grans the user an action when active. +/datum/nifsoft/action_granter + active_mode = TRUE + activation_cost = 10 + active_cost = 1 + /// What is the path of the action that we want to grant? + var/action_to_grant = /datum/action/innate + /// What action are we giving the user of the NIFSoft? + var/datum/action/innate/granted_action + +/datum/nifsoft/action_granter/activate() + . = ..() + if(active) + granted_action = new action_to_grant + granted_action.Grant(linked_mob) + return + + if(granted_action) + granted_action.Remove(linked_mob) + +/datum/nifsoft/action_granter/Destroy() + if(granted_action) + QDEL_NULL(granted_action) + return ..() + + diff --git a/modular_skyrat/modules/modular_implants/code/nifsofts/book_summoner.dm b/modular_skyrat/modules/modular_implants/code/nifsofts/book_summoner.dm new file mode 100644 index 00000000000..954a56e9ff9 --- /dev/null +++ b/modular_skyrat/modules/modular_implants/code/nifsofts/book_summoner.dm @@ -0,0 +1,36 @@ +/obj/item/disk/nifsoft_uploader/summoner/book + name = "Grimoire Akasha" + loaded_nifsoft = /datum/nifsoft/summoner/book + +/datum/nifsoft/summoner/book + name = "Grimoire Akasha" + program_desc = "Grimoire Akasha is a fork of the Grimoire Caeruleam NIFSoft that is designed around giving the user access to various educational hardlight books. \ + Due to its educational nature and miniscule size, Grimoire Akasha is typically provided for free at most NIFSoft marketplaces." + summonable_items = list() + purchase_price = 0 // This is a tool intended to help out newer players. + max_summoned_items = 2 + buying_category = NIFSOFT_CATEGORY_INFORMATION + ui_icon = "book" + +/datum/nifsoft/summoner/book/New() + . = ..() + summonable_items += subtypesof(/obj/item/book/manual/wiki) //That's right! all of the manual books! + +/datum/nifsoft/summoner/book/apply_custom_properties(obj/item/book/generated_book) + if(!istype(generated_book)) + return FALSE + + generated_book.cannot_carve = TRUE + return TRUE + +// Need this code here so that we don't have people carving out the summoned books +/obj/item/book + /// Is the parent book unable to be carved? TRUE prevents carving. By default this is unset + var/cannot_carve + +/obj/item/book/try_carve(obj/item/carving_item, mob/living/user, params) + if(cannot_carve) + balloon_alert(user, "unable to be carved!") + return FALSE + + return ..() diff --git a/modular_skyrat/modules/modular_implants/code/nifsofts/hypnosis.dm b/modular_skyrat/modules/modular_implants/code/nifsofts/hypnosis.dm new file mode 100644 index 00000000000..b4f59561872 --- /dev/null +++ b/modular_skyrat/modules/modular_implants/code/nifsofts/hypnosis.dm @@ -0,0 +1,62 @@ +/datum/nifsoft/action_granter/hypnosis + name = "Purpura Eye" + program_desc = "Based on the hypnotic equipment provided by the LustWish vendor, the purpura eyes NIFSoft allows the user to ensnare others in a hypnotic trance. ((This is intended as a tool for ERP, don't use this for gameplay reasons.))" + buying_category = NIFSOFT_CATEGORY_FUN + lewd_nifsoft = TRUE + purchase_price = 150 + able_to_keep = TRUE + active_cost = 0.1 + ui_icon = "eye" + action_to_grant = /datum/action/innate/nif_hypnotize + +/datum/action/innate/nif_hypnotize + name = "Hypnotize" + background_icon = 'modular_skyrat/master_files/icons/mob/actions/action_backgrounds.dmi' + background_icon_state = "android" + button_icon = 'modular_skyrat/master_files/icons/mob/actions/actions_nif.dmi' + button_icon_state = "hypnotize" + +/datum/action/innate/nif_hypnotize/Activate() + var/mob/living/carbon/human/user = owner + if(!istype(user)) + return FALSE + + var/mob/living/carbon/human/target_human = user.pulling + if(!istype(target_human) || user.grab_state < GRAB_AGGRESSIVE) + to_chat(user, span_warning("You need to aggressively grab someone to hypnotize them.")) + return FALSE + + if(!target_human.client?.prefs?.read_preference(/datum/preference/toggle/erp/sex_toy)) + to_chat(user, span_warning("[target_human] doesn't want to be hypnotized.")) + return FALSE + + to_chat(user, span_notice("You begin to place [target_human] into a hypnotic trance.")) + + if(!do_after(user, 12 SECONDS, target_human)) + return FALSE + + var/choice = tgui_alert(target_human, "Do you believe in hypnosis? (This will allow [user] to issue hypnotic suggestions.)", "Hypnosis", list("Yes", "No")) + if(choice != "Yes") + to_chat(user, span_warning("[target_human]'s attention breaks despite your efforts. They clearly don't seem interested!")) + to_chat(target_human, span_warning("Your attention breaks as you realize that you don't want to listen to [user]'s suggestions.")) + return FALSE + + user.visible_message(span_purple("[target_human] falls into a deep, hypnotic slumber right at the snap of your fingers."), span_purple("You suddenly fall limp at the snap of [user]'s fingers.")) + user.emote("snap") + target_human.SetSleeping(60 SECONDS) + target_human.log_message("[target_human] was placed into a hypnotic sleep by [user].", LOG_GAME) + + var/secondary_choice = tgui_alert(user, "Would you like to give [target_human] a hypnotic suggestion or release them?", "Hypnosis", list("Suggestion", "Release")) + while(secondary_choice == "Suggestion" && target_human.IsSleeping()) + if(!in_range(user, target_human)) + to_chat(user, span_warning("You must be in whisper range to [target_human] in order to give hypnotic suggestions.")) + target_human.SetSleeping(0) + return FALSE + + var/input_text = tgui_input_text(user, "What would you like to suggest?", "Hypnotic Suggestion") + to_chat(user, span_purple("You whisper into [target_human]'s ears in a soothing voice.")) + to_chat(target_human, span_hypnophrase("[input_text]")) + secondary_choice = tgui_alert(user, "Would you like to give [target_human] an additional hypnotic suggestion or release them?", "Hypnosis", list("Suggestion", "Release")) + + user.visible_message(span_purple("You wake up from your deep, hypnotic slumber. The suggestions from [user] now settled into your mind."), span_purple("[target_human] wakes up from their slumber.")) + target_human.SetSleeping(0) diff --git a/modular_skyrat/modules/modular_implants/code/nifsofts/scryer.dm b/modular_skyrat/modules/modular_implants/code/nifsofts/scryer.dm new file mode 100644 index 00000000000..3cf689624e0 --- /dev/null +++ b/modular_skyrat/modules/modular_implants/code/nifsofts/scryer.dm @@ -0,0 +1,117 @@ +/obj/item/disk/nifsoft_uploader/scryer + name = "NIFSoft Scryer Uploader Disk" + loaded_nifsoft = /datum/nifsoft/scryer + +/datum/nifsoft/scryer + name = "NIFLink Holocaller" + program_desc = "This ubiquitous NIFSoft adds Scryer functionality similar to MODSuits to the user's NIF; allowing for real-time communication through AR hologlass screens from a hardlight projector sat around the wearer's neck" + active_mode = TRUE + active_cost = 1 + activation_cost = 20 + purchase_price = 200 + buying_category = NIFSOFT_CATEGORY_UTILITY + ui_icon = "video" + /// What is the scryer currently associated with the NIFSoft? + var/obj/item/clothing/neck/link_scryer/loaded/nifsoft/linked_scryer + +/datum/nifsoft/scryer/New() + . = ..() + var/obj/item/organ/internal/cyberimp/brain/nif/parent_resolved = parent_nif.resolve() + if(!istype(parent_resolved)) + stack_trace("[src] ([REF(src)]) tried to create a linked scryer but it had no parent_nif!") + if(!linked_scryer) + stack_trace("[src] ([REF(src)]) created with no linked scryer!") + linked_scryer = new (parent_resolved) + linked_scryer.parent_nifsoft = WEAKREF(src) + +/datum/nifsoft/scryer/Destroy() + if(!QDELETED(linked_scryer)) + QDEL_NULL(linked_scryer) + + return ..() + +/datum/nifsoft/scryer/activate() + . = ..() + if(. == FALSE) + return FALSE + + if(!active) + if(linked_scryer) + var/parent_resolved = parent_nif.resolve() + if(parent_resolved) + return linked_mob.transferItemToLoc(linked_scryer, parent_resolved, TRUE) + return FALSE + + if(linked_mob.handcuffed) + linked_mob.balloon_alert(linked_mob, "handcuffed") + activate() + return FALSE + + if(!linked_mob.equip_to_slot_if_possible(linked_scryer, ITEM_SLOT_NECK)) //This sends out a message to the mob if it can't be put on. + activate() + return FALSE + + return TRUE + +/obj/item/clothing/neck/link_scryer + /// Do we have custom controls? This is only affects the text shown when examining + var/custom_examine_controls = FALSE + +/obj/item/clothing/neck/link_scryer/loaded/nifsoft + name = "\improper NIFLink Holocaller" + desc = "A nanomachine construct working as a modified version of the MODlink scryer, conjured using a NIF; functionally the same, but able to carry out holocalls in a more portable format." + custom_examine_controls = TRUE + /// A weakref of the parent NIFSoft that the scryer belongs to. + var/datum/weakref/parent_nifsoft + +/obj/item/clothing/neck/link_scryer/loaded/nifsoft/Initialize(mapload) + . = ..() + if(cell) + QDEL_NULL(cell) + + cell = new /obj/item/stock_parts/cell/infinite/nif_cell(src) + +/obj/item/clothing/neck/link_scryer/loaded/nifsoft/Destroy() + if(parent_nifsoft) + var/datum/nifsoft/scryer/resolved_nifsoft = parent_nifsoft.resolve() + if(!QDELETED(resolved_nifsoft)) + resolved_nifsoft.linked_scryer = null + + return ..() + +/obj/item/clothing/neck/link_scryer/loaded/nifsoft/examine(mob/user) + . = ..() + . += span_notice("The MODlink ID is [mod_link.id], frequency is [mod_link.frequency || "unset"]. <b>Right-click</b> with a multitool to copy/imprint the frequency.") + . += span_notice("<b>Right-click</b> with an empty hand to change the name.") + +/obj/item/clothing/neck/link_scryer/loaded/nifsoft/equipped(mob/living/user, slot) + . = ..() + if(slot & ITEM_SLOT_NECK) + return TRUE + + var/datum/nifsoft/scryer/scryer_nifsoft = parent_nifsoft.resolve() + if(!istype(scryer_nifsoft)) + return FALSE + + scryer_nifsoft.activate() //If it's not on the neck, it shouldn't be active. + return TRUE + +/obj/item/clothing/neck/link_scryer/loaded/nifsoft/screwdriver_act(mob/living/user, obj/item/tool) + balloon_alert(user, "cell non-removable!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/item/clothing/neck/link_scryer/loaded/nifsoft/attack_hand_secondary(mob/user, list/modifiers) + var/new_label = reject_bad_text(tgui_input_text(user, "Change the visible name", "Set Name", label, MAX_NAME_LEN)) + if(!new_label) + balloon_alert(user, "invalid name!") + return + label = new_label + balloon_alert(user, "name set!") + update_name() + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/// This cell is only meant for use in items temporarily created by a NIF. Do not let players extract this from devices. +/obj/item/stock_parts/cell/infinite/nif_cell + name = "Nanite Cell" + desc = "If you see this, please make an issue on GitHub." + diff --git a/modular_skyrat/modules/modular_implants/code/soulcatcher/attachable_soulcatcher.dm b/modular_skyrat/modules/modular_implants/code/soulcatcher/attachable_soulcatcher.dm new file mode 100644 index 00000000000..cd84179e083 --- /dev/null +++ b/modular_skyrat/modules/modular_implants/code/soulcatcher/attachable_soulcatcher.dm @@ -0,0 +1,111 @@ +/datum/component/soulcatcher/small_device + max_souls = 1 + +/datum/component/soulcatcher/attachable_soulcatcher + max_souls = 1 + communicate_as_parent = TRUE + removable = TRUE + +/datum/component/soulcatcher/attachable_soulcatcher/New() + . = ..() + var/obj/item/parent_item = parent + if(!istype(parent_item)) + return COMPONENT_INCOMPATIBLE + + name = parent_item.name + var/datum/soulcatcher_room/first_room = soulcatcher_rooms[1] + first_room.name = parent_item.name + first_room.room_description = parent_item.desc + + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + RegisterSignal(parent, COMSIG_CLICK_CTRL_SHIFT, PROC_REF(bring_up_ui)) + RegisterSignal(parent, COMSIG_PREQDELETED, PROC_REF(remove_self)) + +/// Adds text to the examine text of the parent item, explaining that the item can be used to enable the use of NIFSoft HUDs +/datum/component/soulcatcher/attachable_soulcatcher/proc/on_examine(datum/source, mob/user, list/examine_text) + SIGNAL_HANDLER + examine_text += span_cyan("[source] has a soulcatcher attached to it, <b>Ctrl+Shift+Click</b> to use it.") + +/datum/component/soulcatcher/attachable_soulcatcher/proc/bring_up_ui(datum/source, mob/user) + SIGNAL_HANDLER + INVOKE_ASYNC(src, PROC_REF(ui_interact), user) + +/datum/component/soulcatcher/attachable_soulcatcher/Destroy(force) + UnregisterSignal(parent, COMSIG_ATOM_EXAMINE) + UnregisterSignal(parent, COMSIG_CLICK_CTRL_SHIFT) + UnregisterSignal(parent, COMSIG_PREQDELETED) + return ..() + +/datum/component/soulcatcher/attachable_soulcatcher/remove_self() + var/obj/item/parent_item = parent + var/turf/drop_turf = get_turf(parent_item) + var/obj/item/attachable_soulcatcher/dropped_item = new (drop_turf) + + var/datum/component/soulcatcher/dropped_soulcatcher = dropped_item.GetComponent(/datum/component/soulcatcher) + var/datum/soulcatcher_room/target_room = dropped_soulcatcher.soulcatcher_rooms[1] + var/list/current_souls = get_current_souls() + + if(current_souls) // If we have souls inside of here, they should be transferred to the new object + for(var/mob/living/soulcatcher_soul/soul as anything in current_souls) + var/datum/soulcatcher_room/current_room = soul.current_room.resolve() + if(istype(current_room)) + current_room.transfer_soul(soul, target_room) + + return ..() + +/obj/item/attachable_soulcatcher + name = "Poltergeist-Type RSD" + desc = "This device, a polymorphic nanomachine net, wraps around objects of most sizes and allows them to function as a container for Resonance. The soul in question within the vessel is imbued much like it would be in a body or a normal Soulcatcher, perceiving the world and even speaking out of their new form. The nanomachine net of the device allows for the consciousness to somewhat manipulate their container, but any large-scale movement is out of the question." + icon = 'modular_skyrat/modules/modular_implants/icons/obj/devices.dmi' + icon_state = "attachable-soulcatcher" + w_class = WEIGHT_CLASS_SMALL + /// Do we want to destory the item once it is attached to an item? + var/destroy_on_use = TRUE + /// What items do we want to prevent the viewer from attaching this to? + var/list/blacklisted_items = list( + /obj/item/organ, + /obj/item/mmi, + /obj/item/pai_card, + /obj/item/aicard, + /obj/item/card, + /obj/item/radio, + /obj/item/disk/nuclear, // Woah there + ) + /// What soulcathcer component is currnetly linked to this object? + var/datum/component/soulcatcher/small_device/linked_soulcatcher + +/obj/item/attachable_soulcatcher/Initialize(mapload) + . = ..() + linked_soulcatcher = AddComponent(/datum/component/soulcatcher/small_device) + linked_soulcatcher.name = name + +/obj/item/attachable_soulcatcher/attack_self(mob/user, modifiers) + linked_soulcatcher.ui_interact(user) + +/obj/item/attachable_soulcatcher/afterattack(obj/item/target_item, mob/user, proximity_flag, click_parameters) + . = ..() + if(!proximity_flag || !istype(target_item)) + return FALSE + + if(target_item.GetComponent(/datum/component/soulcatcher)) + balloon_alert(user, "already attached!") + return FALSE + + if(is_type_in_list(target_item, blacklisted_items)) + balloon_alert(user, "incompatible!") + return FALSE + + var/datum/component/soulcatcher/new_soulcatcher = target_item.AddComponent(/datum/component/soulcatcher/attachable_soulcatcher) + playsound(target_item.loc, 'sound/weapons/circsawhit.ogg', 50, vary = TRUE) + + var/datum/soulcatcher_room/target_room = new_soulcatcher.soulcatcher_rooms[1] + var/list/current_souls = linked_soulcatcher.get_current_souls() + if(current_souls) + for(var/mob/living/soulcatcher_soul/soul as anything in current_souls) + var/datum/soulcatcher_room/current_room = soul.current_room.resolve() + if(istype(current_room)) + current_room.transfer_soul(soul, target_room) + current_room.transfer_soul(soul, target_room) + + if(destroy_on_use) + qdel(src) diff --git a/modular_skyrat/modules/modular_weapons/code/company_and_or_faction_based/xhihao_light_arms/ammo.dm b/modular_skyrat/modules/modular_weapons/code/company_and_or_faction_based/xhihao_light_arms/ammo.dm new file mode 100644 index 00000000000..7c43be2c19a --- /dev/null +++ b/modular_skyrat/modules/modular_weapons/code/company_and_or_faction_based/xhihao_light_arms/ammo.dm @@ -0,0 +1,45 @@ +/* +* .310 Strilka +*/ + +/obj/item/ammo_casing/strilka310/rubber + name = ".310 Strilka rubber bullet casing" + desc = "A .310 rubber bullet casing. Casing is a bit of a fib, there isn't one.\ + <br><br>\ + <i>RUBBER: Less than lethal ammo. Deals both stamina damage and regular damage.</i>" + + icon = 'modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/xhihao_light_arms/ammo.dmi' + icon_state = "310-casing-rubber" + + projectile_type = /obj/projectile/bullet/strilka310/rubber + harmful = FALSE + +/obj/projectile/bullet/strilka310/rubber + name = ".310 rubber bullet" + damage = 15 + stamina = 55 + ricochets_max = 5 + ricochet_incidence_leeway = 0 + ricochet_chance = 130 + ricochet_decay_damage = 0.7 + shrapnel_type = null + sharpness = NONE + embedding = null + +/obj/item/ammo_casing/strilka310/ap + name = ".310 Strilka armor-piercing bullet casing" + desc = "A .310 armor-piercing bullet casing. Note, does not actually contain a casing.\ + <br><br>\ + <i>ARMOR-PIERCING: Improved armor-piercing capabilities, in return for less outright damage.</i>" + + icon = 'modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/xhihao_light_arms/ammo.dmi' + icon_state = "310-casing-ap" + + projectile_type = /obj/projectile/bullet/strilka310/ap + custom_materials = AMMO_MATS_AP + advanced_print_req = TRUE + +/obj/projectile/bullet/strilka310/ap + name = ".310 armor-piercing bullet" + damage = 50 + armour_penetration = 60 diff --git a/modular_skyrat/modules/modular_weapons/code/company_and_or_faction_based/xhihao_light_arms/guns.dm b/modular_skyrat/modules/modular_weapons/code/company_and_or_faction_based/xhihao_light_arms/guns.dm new file mode 100644 index 00000000000..4207eb85eef --- /dev/null +++ b/modular_skyrat/modules/modular_weapons/code/company_and_or_faction_based/xhihao_light_arms/guns.dm @@ -0,0 +1,48 @@ +/* +* QM Sporter Rifle +*/ + +/obj/item/gun/ballistic/rifle/boltaction/sporterized + name = "\improper Xhihao 'Rengo' precision rifle" + desc = "A Xhihao 'Rengo' conversion rifle. Came as parts sold in a single kit by Xhihao Light Arms, \ + which can be swapped out with many of the outdated or simply old parts on a typical Sakhno rifle. \ + While not necessarily increasing performance in any way, the magazine is slightly longer. The weapon \ + is also overall a bit shorter, making it easier to handle for some people. Cannot be sawn off, cutting \ + really any part of this weapon off would make it non-functional." + icon = 'modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/xhihao_light_arms/guns40x.dmi' + icon_state = "rengo" + worn_icon_state = "enchanted_rifle" // Not actually magical looking, just looks closest to this one + inhand_icon_state = "enchanted_rifle" + accepted_magazine_type = /obj/item/ammo_box/magazine/internal/boltaction/bubba + can_be_sawn_off = FALSE + +/obj/item/gun/ballistic/rifle/boltaction/sporterized/Initialize(mapload) + . = ..() + + AddComponent(/datum/component/scope, range_modifier = 1.5) + +/obj/item/gun/ballistic/rifle/boltaction/sporterized/give_manufacturer_examine() + AddElement(/datum/element/manufacturer_examine, COMPANY_XHIHAO) + +/obj/item/gun/ballistic/rifle/boltaction/sporterized/empty + bolt_locked = TRUE // so the bolt starts visibly open + accepted_magazine_type = /obj/item/ammo_box/magazine/internal/boltaction/bubba/empty + +/obj/item/ammo_box/magazine/internal/boltaction/bubba + name = "Sakhno extended internal magazine" + desc = "How did you get it out?" + ammo_type = /obj/item/ammo_casing/strilka310 + caliber = CALIBER_STRILKA310 + max_ammo = 8 + +/obj/item/ammo_box/magazine/internal/boltaction/bubba/empty + start_empty = TRUE + +/* +* Box that contains Sakhno rifles, but less soviet union since we don't have one of those +*/ + +/obj/item/storage/toolbox/guncase/soviet/sakhno + desc = "A weapon's case. This one is green and looks pretty old, but is otherwise in decent condition." + icon = 'modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/cases.dmi' + material_flags = NONE // ????? Why do these have materials enabled?? diff --git a/modular_skyrat/modules/modular_weapons/code/conversion_kits.dm b/modular_skyrat/modules/modular_weapons/code/conversion_kits.dm new file mode 100644 index 00000000000..a15d0766ece --- /dev/null +++ b/modular_skyrat/modules/modular_weapons/code/conversion_kits.dm @@ -0,0 +1,46 @@ +/obj/item/crafting_conversion_kit + name = "base conversion kit" + desc = "It's a set of parts, for something. This shouldn't be here, and you should probably throw this away, since it's not going to be very useful." + icon = 'icons/obj/storage/box.dmi' + icon_state = "secbox" + // the inhands are just what the box uses + inhand_icon_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + +/obj/item/crafting_conversion_kit/mosin_pro + name = "\improper Xhihao 'Rengo' rifle conversion kit" + desc = "All the parts you need to make a 'Rengo' rifle, outside of the parts that make the gun actually a gun. \ + It looks like this stuff could fit on an old Sakhno rifle, if only you had one of those around." + icon = 'modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/cases.dmi' + icon_state = "xhihao_conversion_kit" + +/datum/crafting_recipe/mosin_pro + name = "Sakhno to Xhihao 'Rengo' Conversion" + desc = "It's actually really easy to change the stock on your Sakhno. Anyone can do it. It takes roughly thirty seconds and a screwdriver." + result = /obj/item/gun/ballistic/rifle/boltaction/sporterized/empty + reqs = list( + /obj/item/gun/ballistic/rifle/boltaction = 1, + /obj/item/crafting_conversion_kit/mosin_pro = 1 + ) + steps = list( + "Empty the rifle", + "Leave the bolt open" + ) + tool_behaviors = list(TOOL_SCREWDRIVER) + time = 30 SECONDS + category = CAT_WEAPON_RANGED + +/datum/crafting_recipe/mosin_pro/New() + ..() + blacklist |= subtypesof(/obj/item/gun/ballistic/rifle/boltaction) - list(/obj/item/gun/ballistic/rifle/boltaction/surplus) + +/datum/crafting_recipe/mosin_pro/check_requirements(mob/user, list/collected_requirements) + var/obj/item/gun/ballistic/rifle/boltaction/the_piece = collected_requirements[/obj/item/gun/ballistic/rifle/boltaction][1] + if(!the_piece.bolt_locked) + return FALSE + if(LAZYLEN(the_piece.magazine.stored_ammo)) + return FALSE + return ..() diff --git a/modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/cases.dmi b/modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/cases.dmi new file mode 100644 index 00000000000..238153cd32b Binary files /dev/null and b/modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/cases.dmi differ diff --git a/modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/xhihao_light_arms/ammo.dmi b/modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/xhihao_light_arms/ammo.dmi new file mode 100644 index 00000000000..f671b307528 Binary files /dev/null and b/modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/xhihao_light_arms/ammo.dmi differ diff --git a/modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/xhihao_light_arms/guns40x.dmi b/modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/xhihao_light_arms/guns40x.dmi new file mode 100644 index 00000000000..1e6e33762aa Binary files /dev/null and b/modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/xhihao_light_arms/guns40x.dmi differ diff --git a/modular_skyrat/modules/novaya_ert/sound/flatline.ogg b/modular_skyrat/modules/novaya_ert/sound/flatline.ogg new file mode 100644 index 00000000000..93712b6da64 Binary files /dev/null and b/modular_skyrat/modules/novaya_ert/sound/flatline.ogg differ diff --git a/modular_skyrat/modules/player_ranks/code/player_rank_controller/_player_rank_controller.dm b/modular_skyrat/modules/player_ranks/code/player_rank_controller/_player_rank_controller.dm new file mode 100644 index 00000000000..bfab13aa108 --- /dev/null +++ b/modular_skyrat/modules/player_ranks/code/player_rank_controller/_player_rank_controller.dm @@ -0,0 +1,227 @@ +/// The index of the ckey in the items of a given row in a query for player ranks. +#define INDEX_CKEY 1 + +/** + * This datum is intended to be used as a method of abstraction for the different + * ways that each player rank handles adding and removing players from their global + * lists, as well as handling the legacy adding, removing, loading and saving of + * said lists. + */ +/datum/player_rank_controller + /// The name of the player rank in the database. + /// This **NEEDS** to be set by subtypes, otherwise you WILL run into severe + /// issues. + var/rank_title = null + /// The path to the legacy file holding all of the players that have this rank. + /// Should be set in `New()`, since it has a non-constant compile-time value. + var/legacy_file_path = null + /// The header for the legacy file, if any. + /// Leave as `""` if you don't have one. + var/legacy_file_header = "" + + +/datum/player_rank_controller/vv_edit_var(var_name, var_value) + // No touching the controller's vars, treat these as protected config. + return FALSE + + +/** + * Handles adding this rank to a player by their ckey. This is only intended to + * be used for handling the in-game portion of adding this rank, and not to + * save this change anywhere. That should be handled by the caller. + * + * DO NOT FORGET TO ADD A `IsAdminAdvancedProcCall()` CHECK SO THAT ADMINS + * CAN'T JUST USE THAT TO SKIP PERMISSION CHECKS!!! + */ +/datum/player_rank_controller/proc/add_player(ckey) + SHOULD_CALL_PARENT(FALSE) + + CRASH("[src] did not implement add_player()! Fix this ASAP!") + + +/** + * Handles adding this rank to a player using the legacy system, updating the + * legacy config file in the process. + * + * Don't override this, everything should be handled from `add_player()`, + * this is mostly just a helper for convenience. + * + * Arguments: + * * ckey - The ckey of the player you want to now possess this player rank. + */ +/datum/player_rank_controller/proc/add_player_legacy(ckey) + SHOULD_NOT_OVERRIDE(TRUE) + + if(IsAdminAdvancedProcCall()) + return + + add_player(ckey) + text2file(ckey, legacy_file_path) + + +/** + * Handles removing this rank from a player by their ckey. This is only + * intended to be used for handling the in-game portion of removing this rank, + * and not to save this change anywhere. That should be handled by the caller. + * + * DO NOT FORGET TO ADD A `IsAdminAdvancedProcCall()` CHECK SO THAT ADMINS + * CAN'T JUST USE THAT TO SKIP PERMISSION CHECKS!!! + */ +/datum/player_rank_controller/proc/remove_player(ckey) + SHOULD_CALL_PARENT(FALSE) + + CRASH("[src] did not implement remove_player()! Fix this ASAP!") + + +/** + * Handles removing this rank from a player using the legacy system, updating + * the legacy config file in the process. + * + * Don't override this, everything should be handled from `remove_player()` + * and `save_legacy()`, this is mostly just a helper for convenience. + * + * Arguments: + * * ckey - The ckey of the player you want to no longer possess this player + * rank. + */ +/datum/player_rank_controller/proc/remove_player_legacy(ckey) + SHOULD_NOT_OVERRIDE(TRUE) + + if(IsAdminAdvancedProcCall()) + return + + remove_player(ckey) + // We have to save like this, because we're taking something out at an + // arbitrary point in the list. + save_legacy() + + +/** + * Handles loading the players that have this rank from an already-executed + * database query. + * + * Mostly just a helper to simplify the logic of the subsystem. + */ +/datum/player_rank_controller/proc/load_from_query(datum/db_query/query) + if(IsAdminAdvancedProcCall()) + return + + clear_existing_rank_data() + + while(query.NextRow()) + var/ckey = ckey(query.item[INDEX_CKEY]) + add_player(ckey) + + +/** + * Handles loading the players that have this rank from its legacy config file. + * + * Don't override this, use `clear_existing_rank_data()` to clear up anything + * that needs to be cleared/initialized before loading the rank, and + * `add_player()` for actually giving the rank to the ckey in-game. + */ +/datum/player_rank_controller/proc/load_legacy() + SHOULD_NOT_OVERRIDE(TRUE) + + if(IsAdminAdvancedProcCall()) + return + + clear_existing_rank_data() + + for(var/line in world.file2list(legacy_file_path)) + if(!line) + continue + + if(findtextEx(line, "#", 1, 2)) + continue + + add_player(line) + + return TRUE + + +/** + * Handles saving the players that have this rank using its legacy config file. + * + * Don't override this, use `get_ckeys_for_legacy_save()` if you need to filter + * the list of ckeys that will get saved. + */ +/datum/player_rank_controller/proc/save_legacy() + SHOULD_NOT_OVERRIDE(TRUE) + + if(IsAdminAdvancedProcCall()) + return + + var/save_file_contents = legacy_file_header + + for(var/player in get_ckeys_for_legacy_save()) + save_file_contents += player + "\n" + + rustg_file_write(save_file_contents, legacy_file_path) + + +/** + * Handles returning a list of all the legacy ckeys that should be migrated + * from the legacy system to the database one. + * + * Returns a list of ckeys as strings. + */ +/datum/player_rank_controller/proc/get_ckeys_to_migrate() + SHOULD_NOT_OVERRIDE(TRUE) + RETURN_TYPE(/list) + + var/list/ckeys_to_migrate = list() + + for(var/line in world.file2list(legacy_file_path)) + if(!line) + continue + + if(findtextEx(line, "#", 1, 2)) + continue + + var/to_migrate = ckey(line) + + if(!to_migrate) + continue + + ckeys_to_migrate += to_migrate + + return ckeys_to_migrate + + +/** + * Simple proc for subtypes to override for their own handling of obtaining + * a list of ckeys to save during a legacy save. + * + * DO NOT FORGET TO ADD A `IsAdminAdvancedProcCall()` CHECK SO THAT ADMINS + * CAN'T JUST ELEVATE PERMISSIONS TO ADD THEMSELVES TO A LEGACY SAVE!!! + */ +/datum/player_rank_controller/proc/get_ckeys_for_legacy_save() + SHOULD_CALL_PARENT(FALSE) + RETURN_TYPE(/list) + + CRASH("[src] did not implement get_ckeys_for_legacy_save()! Fix this ASAP!") + + +/datum/player_rank_controller/proc/should_use_legacy_system() + SHOULD_CALL_PARENT(FALSE) + . = TRUE + + stack_trace("[src] did not implement should_use_legacy_system(), defaulting to TRUE! Fix this ASAP!") + + +/** + * Simple proc for subtypes to override for their own handling of clearing any + * lists that need to be cleared before loading the player rank data. + * + * DO NOT FORGET TO ADD A `IsAdminAdvancedProcCall()` CHECK SO THAT ADMINS + * CAN'T JUST ELEVATE PERMISSIONS TO CLEAR RANK DATA AND SCREW YOU OVER!!! + */ +/datum/player_rank_controller/proc/clear_existing_rank_data() + SHOULD_CALL_PARENT(FALSE) + PROTECTED_PROC(TRUE) + + CRASH("[src] did not implement clear_existing_rank_data()! Fix this ASAP!") + + +#undef INDEX_CKEY diff --git a/modular_skyrat/modules/player_ranks/code/player_rank_controller/donator_controller.dm b/modular_skyrat/modules/player_ranks/code/player_rank_controller/donator_controller.dm new file mode 100644 index 00000000000..4ed68934ef9 --- /dev/null +++ b/modular_skyrat/modules/player_ranks/code/player_rank_controller/donator_controller.dm @@ -0,0 +1,49 @@ +/// The list of all donators. +GLOBAL_LIST_EMPTY(donator_list) +GLOBAL_PROTECT(donator_list) + +/datum/player_rank_controller/donator + rank_title = "donator" + // Yes, this is incredibly long, deal with it. It's to keep that cute little comment at the top. + legacy_file_header = "###############################################################################################\n# List for people who support us! They get cool loadout items #\n# Case is not important for ckey. #\n###############################################################################################\n" + + +/datum/player_rank_controller/donator/New() + . = ..() + legacy_file_path = "[global.config.directory]/skyrat/donators.txt" + + + +/datum/player_rank_controller/donator/add_player(ckey) + if(IsAdminAdvancedProcCall()) + return + + ckey = ckey(ckey) + + // Associative list for extra SPEED! + GLOB.donator_list[ckey] = TRUE + + +/datum/player_rank_controller/donator/remove_player(ckey) + if(IsAdminAdvancedProcCall()) + return + + GLOB.donator_list -= ckey + + +/datum/player_rank_controller/donator/get_ckeys_for_legacy_save() + if(IsAdminAdvancedProcCall()) + return + + return GLOB.donator_list + + +/datum/player_rank_controller/donator/should_use_legacy_system() + return CONFIG_GET(flag/donator_legacy_system) + + +/datum/player_rank_controller/donator/clear_existing_rank_data() + if(IsAdminAdvancedProcCall()) + return + + GLOB.donator_list = list() diff --git a/modular_skyrat/modules/player_ranks/code/player_rank_controller/mentor_controller.dm b/modular_skyrat/modules/player_ranks/code/player_rank_controller/mentor_controller.dm new file mode 100644 index 00000000000..304ee98d1e5 --- /dev/null +++ b/modular_skyrat/modules/player_ranks/code/player_rank_controller/mentor_controller.dm @@ -0,0 +1,76 @@ +// The mentor system is a bit more complex than the other player ranks, so it's +// got its own handling and global lists declarations in the `mentor` module. + +/datum/player_rank_controller/mentor + rank_title = "mentor" + + +/datum/player_rank_controller/mentor/New() + . = ..() + legacy_file_path = "[global.config.directory]/skyrat/mentors.txt" + + +/datum/player_rank_controller/mentor/add_player(ckey) + if(IsAdminAdvancedProcCall()) + return + + ckey = ckey(ckey) + + new /datum/mentors(ckey) + + +/datum/player_rank_controller/mentor/remove_player(ckey) + if(IsAdminAdvancedProcCall()) + return + + var/datum/mentors/mentor_datum = GLOB.mentor_datums[ckey] + mentor_datum?.remove_mentor() + + +/datum/player_rank_controller/mentor/get_ckeys_for_legacy_save() + if(IsAdminAdvancedProcCall()) + return + + // This whole mess is just to only save the mentors that were in the config + // already so that we don't add every admin to the config file, which would + // be a pain to maintain afterwards. + // We don't save mentors that are new to the `GLOB.mentor_datums` list, + // because they should have already been saved from `add_player_legacy()`. + var/list/mentors_to_save = list() + var/list/existing_mentor_config = world.file2list(legacy_file_path) + for(var/line in existing_mentor_config) + if(!length(line)) + continue + + if(findtextEx(line, "#", 1, 2)) + continue + + var/existing_mentor = ckey(line) + if(!existing_mentor) + continue + + // Only save them if they're still in the mentor datums list in-game. + if(!GLOB.mentor_datums[existing_mentor]) + continue + + // Associative list for extra SPEED! + mentors_to_save[existing_mentor] = TRUE + + return mentors_to_save + + +/datum/player_rank_controller/mentor/should_use_legacy_system() + return CONFIG_GET(flag/mentor_legacy_system) + + +/datum/player_rank_controller/mentor/clear_existing_rank_data() + if(IsAdminAdvancedProcCall()) + return + + GLOB.mentor_datums.Cut() + + for(var/client/ex_mentor as anything in GLOB.mentors) + ex_mentor.remove_mentor_verbs() + ex_mentor.mentor_datum = null + + GLOB.mentors.Cut() diff --git a/modular_skyrat/modules/player_ranks/code/player_rank_controller/veteran_controller.dm b/modular_skyrat/modules/player_ranks/code/player_rank_controller/veteran_controller.dm new file mode 100644 index 00000000000..cc759c92832 --- /dev/null +++ b/modular_skyrat/modules/player_ranks/code/player_rank_controller/veteran_controller.dm @@ -0,0 +1,47 @@ +/// The list of all veteran players. +GLOBAL_LIST_EMPTY(veteran_list) +GLOBAL_PROTECT(veteran_list) + + +/datum/player_rank_controller/veteran + rank_title = "veteran" + + +/datum/player_rank_controller/veteran/New() + . = ..() + legacy_file_path = "[global.config.directory]/skyrat/veteran_players.txt" + + +/datum/player_rank_controller/veteran/add_player(ckey) + if(IsAdminAdvancedProcCall()) + return + + ckey = ckey(ckey) + + // Associative list for extra SPEED! + GLOB.veteran_list[ckey] = TRUE + + +/datum/player_rank_controller/veteran/remove_player(ckey) + if(IsAdminAdvancedProcCall()) + return + + GLOB.veteran_list -= ckey + + +/datum/player_rank_controller/veteran/get_ckeys_for_legacy_save() + if(IsAdminAdvancedProcCall()) + return + + return GLOB.veteran_list + + +/datum/player_rank_controller/veteran/should_use_legacy_system() + return CONFIG_GET(flag/veteran_legacy_system) + + +/datum/player_rank_controller/veteran/clear_existing_rank_data() + if(IsAdminAdvancedProcCall()) + return + + GLOB.veteran_list = list() diff --git a/modular_skyrat/modules/player_ranks/code/subsystem/player_ranks.dm b/modular_skyrat/modules/player_ranks/code/subsystem/player_ranks.dm new file mode 100644 index 00000000000..6e724e44dbc --- /dev/null +++ b/modular_skyrat/modules/player_ranks/code/subsystem/player_ranks.dm @@ -0,0 +1,487 @@ +/// The name of the table on the database containing the player ranks. +/// See `skyrat_schema.sql` for the schema of the table. +#define PLAYER_RANK_TABLE_NAME "player_rank" +/// The index of the ckey in the items of a given row in a query for player ranks. +#define INDEX_CKEY 1 +/// The name entered in the database for the admin_ckey for legacy migrations. +#define LEGACY_MIGRATION_ADMIN_CKEY "LEGACY" + + +SUBSYSTEM_DEF(player_ranks) + name = "Player Ranks" + flags = SS_NO_FIRE + init_order = INIT_ORDER_PLAYER_RANKS + // The following controllers handle most of the legacy system's functions, + // and provide a layer of abstraction for this subsystem to have cleaner + // logic. + /// The donator player rank controller. + var/datum/player_rank_controller/donator/donator_controller + /// The mentor player rank controller. + var/datum/player_rank_controller/mentor/mentor_controller + /// The veteran player rank controller. + var/datum/player_rank_controller/veteran/veteran_controller + + +/datum/controller/subsystem/player_ranks/Initialize() + if(IsAdminAdvancedProcCall()) + return + + load_donators() + load_mentors() + load_veterans() + + return SS_INIT_SUCCESS + + +/datum/controller/subsystem/player_ranks/Destroy() + . = ..() + + QDEL_NULL(donator_controller) + QDEL_NULL(mentor_controller) + QDEL_NULL(veteran_controller) + + +/** + * Returns whether or not the user is qualified as a donator. + * + * Arguments: + * * user - The client to verify the donator status of. + * * admin_bypass - Whether or not admins can succeed this check, even if they + * do not actually possess the role. Defaults to `TRUE`. + */ +/datum/controller/subsystem/player_ranks/proc/is_donator(client/user, admin_bypass = TRUE) + if(!istype(user)) + CRASH("Invalid user type provided to is_donator(), expected 'client' and obtained '[user ? user.type : "null"]'.") + + if(GLOB.donator_list[user.ckey]) + return TRUE + + if(admin_bypass && is_admin(user)) + return TRUE + + return FALSE + + +/** + * Returns whether or not the user is qualified as a mentor. + * Wrapper for the `is_mentor()` proc on the client, with a null check. + * + * Arguments: + * * user - The client to verify the mentor status of. + * * admin_bypass - Whether or not admins can succeed this check, even if they + * do not actually possess the role. Defaults to `TRUE`. + */ +/datum/controller/subsystem/player_ranks/proc/is_mentor(client/user, admin_bypass = TRUE) + if(!istype(user)) + CRASH("Invalid user type provided to is_mentor(), expected 'client' and obtained '[user ? user.type : "null"]'.") + + return user.is_mentor(admin_bypass) + + +/** + * Returns whether or not the user is qualified as a veteran. + * + * Arguments: + * * user - The client to verify the veteran status of. + * * admin_bypass - Whether or not admins can succeed this check, even if they + * do not actually possess the role. Defaults to `TRUE`. + */ +/datum/controller/subsystem/player_ranks/proc/is_veteran(client/user, admin_bypass = TRUE) + if(!istype(user)) + CRASH("Invalid user type provided to is_veteran(), expected 'client' and obtained '[user ? user.type : "null"]'.") + + if(GLOB.veteran_list[user.ckey]) + return TRUE + + if(admin_bypass && is_admin(user)) + return TRUE + + return FALSE + + +/// Handles loading donators either via SQL or using the legacy system, +/// based on configs. +/datum/controller/subsystem/player_ranks/proc/load_donators() + PROTECTED_PROC(TRUE) + + if(IsAdminAdvancedProcCall()) + return + + donator_controller = new + + if(CONFIG_GET(flag/donator_legacy_system)) + donator_controller.load_legacy() + update_all_prefs_unlock_contents() + return + + if(!SSdbcore.Connect()) + var/message = "Failed to connect to database in load_donators(). Reverting to legacy system." + log_config(message) + log_game(message) + message_admins(message) + CONFIG_SET(flag/donator_legacy_system, TRUE) + donator_controller.load_legacy() + return + + load_player_rank_sql(donator_controller) + update_all_prefs_unlock_contents() + + +/** + * Handles updating all of the preferences datums to have the appropriate + * `unlock_content` and `max_save_slots` once donators are loaded. + */ +/datum/controller/subsystem/player_ranks/proc/update_all_prefs_unlock_contents() + for(var/ckey as anything in GLOB.preferences_datums) + update_prefs_unlock_content(GLOB.preferences_datums[ckey]) + + +/** + * Updates the `unlock_contents` and the `max_save_slots` + * + * Arguments: + * * prefs - The preferences datum to check the unlock_content eligibility. + */ +/datum/controller/subsystem/player_ranks/proc/update_prefs_unlock_content(datum/preferences/prefs) + if(!prefs) + return + + prefs.unlock_content = !!prefs.parent.IsByondMember() || is_donator(prefs.parent) + if(prefs.unlock_content) + prefs.max_save_slots = 50 + + +/// Handles loading mentors either via SQL or using the legacy system, +/// based on configs. +/datum/controller/subsystem/player_ranks/proc/load_mentors() + PROTECTED_PROC(TRUE) + + if(IsAdminAdvancedProcCall()) + return + + mentor_controller = new + + if(CONFIG_GET(flag/mentor_legacy_system)) + mentor_controller.load_legacy() + return + + if(!SSdbcore.Connect()) + var/message = "Failed to connect to database in load_mentors(). Reverting to legacy system." + log_config(message) + log_game(message) + message_admins(message) + CONFIG_SET(flag/mentor_legacy_system, TRUE) + mentor_controller.load_legacy() + return + + load_player_rank_sql(mentor_controller) + + +/// Handles loading veteran players either via SQL or using the legacy system, +/// based on configs. +/datum/controller/subsystem/player_ranks/proc/load_veterans() + PROTECTED_PROC(TRUE) + + if(IsAdminAdvancedProcCall()) + return + + veteran_controller = new + + if(CONFIG_GET(flag/veteran_legacy_system)) + veteran_controller.load_legacy() + return + + if(!SSdbcore.Connect()) + var/message = "Failed to connect to database in load_veterans(). Reverting to legacy system." + log_config(message) + log_game(message) + message_admins(message) + CONFIG_SET(flag/veteran_legacy_system, TRUE) + veteran_controller.load_legacy() + return + + load_player_rank_sql(veteran_controller) + + +/** + * Handles populating the player rank from the database. + * + * Arguments: + * * rank_controller - The player rank controller of the rank to load. + */ +/datum/controller/subsystem/player_ranks/proc/load_player_rank_sql(datum/player_rank_controller/rank_controller) + PROTECTED_PROC(TRUE) + + if(IsAdminAdvancedProcCall()) + return + + var/datum/db_query/query_load_player_rank = SSdbcore.NewQuery( + "SELECT ckey FROM [format_table_name(PLAYER_RANK_TABLE_NAME)] WHERE deleted = 0 AND rank = :rank", + list("rank" = rank_controller.rank_title), + ) + + if(!query_load_player_rank.warn_execute()) + return + + rank_controller.load_from_query(query_load_player_rank) + + +/// Allows fetching the appropriate player_rank_controller based on its +/// `rank_title`, for convenience. +/datum/controller/subsystem/player_ranks/proc/get_controller_for_group(rank_title) + PROTECTED_PROC(TRUE) + + if(IsAdminAdvancedProcCall()) + return null + + rank_title = lowertext(rank_title) + + // Can't make switch() statements with non-constant values. + if(rank_title == donator_controller.rank_title) + return donator_controller + + if(rank_title == mentor_controller.rank_title) + return mentor_controller + + if(rank_title == veteran_controller.rank_title) + return veteran_controller + + CRASH("Invalid player_rank_controller \"[rank_title || "*null*"]\" used in get_controller_for_group()!") + + +/** + * Handles adding the ckey to the proper player rank group, either on the database + * or in the legacy system. + * + * Arguments: + * * admin - The admin making the rank change. Can be a /client or a /datum/admins. + * * ckey - The ckey of the player you want to now possess that player rank. + * * rank_title - The title of the group you want to add the ckey to. + */ +/datum/controller/subsystem/player_ranks/proc/add_player_to_group(admin, ckey, rank_title) + if(IsAdminAdvancedProcCall()) + return FALSE + + if(!ckey || !admin || !rank_title) + stack_trace("Missing either ckey ([ckey || "*NULL*"]), admin ([admin || "*NULL*"]) or rank_title ([rank_title || "*NULL*"]) in add_player_to_group()! Fix this ASAP!") + return FALSE + + var/is_admin_client = istype(admin, /client) + var/client/admin_client = is_admin_client ? admin : null + // If it's not a client, then it should be an admins datum. + var/datum/admins/admin_holder = null + if(is_admin_client) + admin_holder = admin_client?.holder + else if(istype(admin, /datum/admins)) + admin_holder = admin + + if(!admin_holder) + return FALSE + + if(!admin_holder.check_for_rights(R_PERMISSIONS)) + if(is_admin_client) + to_chat(admin, span_warning("You do not possess the permissions to do this.")) + + return FALSE + + + rank_title = lowertext(rank_title) + + var/datum/player_rank_controller/controller = get_controller_for_group(rank_title) + + if(!controller) + stack_trace("Invalid player rank \"[rank_title]\" supplied in add_player_to_group()!") + return FALSE + + ckey = ckey(ckey) + + var/already_in_config = controller.get_ckeys_for_legacy_save() + + if(already_in_config[ckey]) + if(is_admin_client) + to_chat(admin, span_warning("\"[ckey]\" is already a [rank_title]!")) + + return FALSE + + if(controller.should_use_legacy_system()) + controller.add_player_legacy(ckey) + return TRUE + + return add_player_rank_sql(controller, ckey, admin_holder.target) + + +/** + * Handles adding the ckey to the appropriate player rank table on the database, + * as well as in-game. + * + * Arguments: + * * controller - The controller of the player rank you want to add the ckey to. + * * ckey - The ckey of the player you want to now possess that player rank. + * * admin_ckey - The ckey of the admin that made the rank change. + */ +/datum/controller/subsystem/player_ranks/proc/add_player_rank_sql(datum/player_rank_controller/controller, ckey, admin_ckey) + PROTECTED_PROC(TRUE) + + if(IsAdminAdvancedProcCall()) + return FALSE + + var/datum/db_query/query_add_player_rank = SSdbcore.NewQuery( + "INSERT INTO [format_table_name(PLAYER_RANK_TABLE_NAME)] (ckey, rank, admin_ckey) VALUES(:ckey, :rank, :admin_ckey) \ + ON DUPLICATE KEY UPDATE deleted = 0, admin_ckey = :admin_ckey", + list("ckey" = ckey, "rank" = controller.rank_title, "admin_ckey" = admin_ckey), + ) + + if(!query_add_player_rank.warn_execute()) + return FALSE + + controller.add_player(ckey) + return TRUE + + +/** + * Handles removing the ckey from the proper player rank group, either on the database + * or in the legacy system. + * + * Arguments: + * * admin - The admin making the rank change. Can be a /client or a /datum/admins. + * * ckey - The ckey of the player you want to no longer possess that player rank. + * * rank_title - The title of the group you want to remove the ckey from. + */ +/datum/controller/subsystem/player_ranks/proc/remove_player_from_group(admin, ckey, rank_title) + if(IsAdminAdvancedProcCall()) + return FALSE + + if(!ckey || !admin || !rank_title) + stack_trace("Missing either ckey ([ckey || "*NULL*"]), admin ([admin || "*NULL*"]) or rank_title ([rank_title || "*NULL*"]) in remove_player_from_group()! Fix this ASAP!") + return FALSE + + var/is_admin_client = istype(admin, /client) + var/client/admin_client = is_admin_client ? admin : null + // If it's not a client, then it should be an admins datum. + var/datum/admins/admin_holder = null + if(is_admin_client) + admin_holder = admin_client?.holder + else if(istype(admin, /datum/admins)) + admin_holder = admin + + if(!admin_holder) + return FALSE + + if(!admin_holder.check_for_rights(R_PERMISSIONS)) + if(is_admin_client) + to_chat(admin, span_warning("You do not possess the permissions to do this.")) + + return FALSE + + rank_title = lowertext(rank_title) + + var/datum/player_rank_controller/controller = get_controller_for_group(rank_title) + + if(!controller) + stack_trace("Invalid player rank \"[rank_title]\" supplied in remove_player_from_group()!") + return FALSE + + ckey = ckey(ckey) + + if(controller.should_use_legacy_system()) + controller.remove_player_legacy(ckey) + return TRUE + + return remove_player_rank_sql(controller, ckey, admin_holder.target) + + +/** + * Handles removing the ckey from the appropriate player rank table on the database, + * as well as in-game. + * + * Arguments: + * * controller - The controller of the player rank you want to remove the ckey from. + * * ckey - The ckey of the player you want to no longer possess that player rank. + * * admin_ckey - The ckey of the admin that made the rank change. + */ +/datum/controller/subsystem/player_ranks/proc/remove_player_rank_sql(datum/player_rank_controller/controller, ckey, admin_ckey) + PROTECTED_PROC(TRUE) + + if(IsAdminAdvancedProcCall()) + return FALSE + + var/datum/db_query/query_remove_player_rank = SSdbcore.NewQuery( + "UPDATE [format_table_name(PLAYER_RANK_TABLE_NAME)] SET deleted = 1, admin_ckey = :admin_ckey WHERE ckey = :ckey AND rank = :rank", + list("ckey" = ckey, "rank" = controller.rank_title, "admin_ckey" = admin_ckey), + ) + + if(!query_remove_player_rank.warn_execute()) + return FALSE + + controller.remove_player(ckey) + return TRUE + + +/** + * Handles migrating a player rank system from the legacy system to the + * SQL-based version, from its `rank_title` + * + * Arguments: + * * admin - The admin trying to do the migration. + * * rank_title - String of the name of the player rank to migrate + * (case-sensitive). + */ +/datum/controller/subsystem/player_ranks/proc/migrate_player_rank_to_sql(client/admin, rank_title) + if(IsAdminAdvancedProcCall()) + return + + if(!check_rights_for(admin, R_PERMISSIONS | R_DEBUG | R_SERVER)) + to_chat(admin, span_warning("You do not possess the permissions to do this.")) + return + + var/datum/player_rank_controller/controller = get_controller_for_group(rank_title) + + if(!controller) + return + + migrate_player_rank_to_sql_from_controller(controller) + + +/** + * Handles migrating the ckeys of the players that were stored in a legacy + * player rank system into the SQL-based one instead. It will ensure to only + * add ckeys that were not already present in the database. + * + * Arguments: + * * controller - The player rank controller you want to migrate from the + * legacy system to the SQL one. + */ +/datum/controller/subsystem/player_ranks/proc/migrate_player_rank_to_sql_from_controller(datum/player_rank_controller/controller) + PROTECTED_PROC(TRUE) + + if(IsAdminAdvancedProcCall()) + return + + var/list/ckeys_to_migrate = controller.get_ckeys_to_migrate() + + // We explicitly don't check if they were deleted or not, because we + // EXPLICITLY want to avoid any kind of duplicates. + var/datum/db_query/query_get_existing_entries = SSdbcore.NewQuery( + "SELECT ckey FROM [format_table_name(PLAYER_RANK_TABLE_NAME)] WHERE rank = :rank", + list("rank" = controller.rank_title), + ) + + if(!query_get_existing_entries.warn_execute()) + return + + while(query_get_existing_entries.NextRow()) + var/ckey = ckey(query_get_existing_entries.item[INDEX_CKEY]) + ckeys_to_migrate -= ckey + + var/list/rows_to_insert = list() + + for(var/ckey in ckeys_to_migrate) + rows_to_insert += list(list("ckey" = ckey, "rank" = controller.rank_title, "admin_ckey" = LEGACY_MIGRATION_ADMIN_CKEY)) + + log_config("Migrating [length(rows_to_insert)] entries from \the [controller.rank_title] legacy system to the SQL-based system.") + SSdbcore.MassInsert(format_table_name(PLAYER_RANK_TABLE_NAME), rows_to_insert, warn = TRUE) + + +#undef PLAYER_RANK_TABLE_NAME +#undef INDEX_CKEY +#undef LEGACY_MIGRATION_ADMIN_CKEY diff --git a/modular_skyrat/modules/player_ranks/code/world_topic.dm b/modular_skyrat/modules/player_ranks/code/world_topic.dm new file mode 100644 index 00000000000..086373fd33f --- /dev/null +++ b/modular_skyrat/modules/player_ranks/code/world_topic.dm @@ -0,0 +1,67 @@ + +/datum/world_topic/set_player_rank + keyword = "set_player_rank" + require_comms_key = TRUE + +/datum/world_topic/set_player_rank/Run(list/input) + . = list() + + var/sender_discord_id = input["sender_discord_id"] + + if(!sender_discord_id) + .["success"] = FALSE + .["message"] = "Invalid sender Discord ID, this should not be happening! Report this immediately!" + return + + var/target_ckey = ckey(input["target_ckey"]) + + if(!target_ckey) + .["success"] = FALSE + .["message"] = "Invalid target ckey provided." + return + + var/sender_ckey = ckey(SSdiscord.lookup_ckey(sender_discord_id)) + + if(!sender_ckey) + .["success"] = FALSE + .["message"] = "No ckey was found to be attached to the provided Discord account ID, **[sender_discord_id]**. Please verify your Discord account following the instructions of the in-game verb before trying this command again." + return + + var/datum/admins/linked_admin_holder = GLOB.admin_datums[sender_ckey] || GLOB.deadmins[sender_ckey] + + if(!linked_admin_holder) + .["success"] = FALSE + .["message"] = "No valid admin datum was found associated with the ckey associated to your Discord account." + return + + if(!linked_admin_holder.check_for_rights(R_PERMISSIONS)) + .["success"] = FALSE + .["message"] = "You do not possess the permissions to execute this command." + return + + var/target_rank = input["target_rank"] + + if(!target_rank) + .["success"] = FALSE + .["message"] = "Invalid target rank provided." + return + + target_rank = capitalize(target_rank) + + var/desired_rank_status = !!text2num(input["desired_rank_status"]) + + if(desired_rank_status) + var/result = SSplayer_ranks.add_player_to_group(linked_admin_holder, target_ckey, target_rank) + + .["success"] = !!result + .["message"] = result ? "**[linked_admin_holder.target]** successfully added **[target_rank]** status to **[target_ckey]**." : "**[linked_admin_holder.target]** was unable to add **[target_rank]** status to **[target_ckey]**. Please verify that you entered their ckey correctly and that they did not already possess that status before trying again. Use the in-game verb to get more information if you keep on receiving this error." + message_admins(replacetext(.["message"], "*", "")) + return + + else + var/result = SSplayer_ranks.remove_player_from_group(linked_admin_holder, target_ckey, target_rank) + + .["success"] = !!result + .["message"] = result ? "**[linked_admin_holder.target]** successfully removed **[target_rank]** status from **[target_ckey]**." : "**[linked_admin_holder.target]** was unable to remove **[target_rank]** status from **[target_ckey]**. Please verify that you entered their ckey correctly and that they did possess that status before trying again. Use the in-game verb to get more information if you keep on receiving this error." + message_admins(replacetext(.["message"], "*", "")) + return diff --git a/modular_skyrat/modules/resleeving/code/research/resleeving_research.dm b/modular_skyrat/modules/resleeving/code/research/resleeving_research.dm new file mode 100644 index 00000000000..4dcd00cc73e --- /dev/null +++ b/modular_skyrat/modules/resleeving/code/research/resleeving_research.dm @@ -0,0 +1,14 @@ +/datum/design/rsd_interface + name = "RSD Phylactery" + desc = "A brain interface that allows for transfer of Resonance from a handheld RSD, such as the Evoker model." + id = "rsd_interface" + build_type = PROTOLATHE | AWAY_LATHE + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL | DEPARTMENT_BITFLAG_SCIENCE + category = list(RND_CATEGORY_EQUIPMENT) + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT * 0.5, + /datum/material/gold = SHEET_MATERIAL_AMOUNT, + /datum/material/silver = SHEET_MATERIAL_AMOUNT, + ) + build_path = /obj/item/rsd_interface + diff --git a/modular_skyrat/modules/resleeving/code/rsd_interface.dm b/modular_skyrat/modules/resleeving/code/rsd_interface.dm new file mode 100644 index 00000000000..106bb11ea38 --- /dev/null +++ b/modular_skyrat/modules/resleeving/code/rsd_interface.dm @@ -0,0 +1,44 @@ +/obj/item/rsd_interface + name = "RSD Phylactery" + desc = "A small device inserted, typically, into inert brains. As Resonance cannot persist in what's referred to as a 'vacuum', RSDs--much like the brains and CPUs they emulate--employ cerebral white noise as a foundation for Resonance to persist in otherwise dead-quiet containers.." + icon = 'modular_skyrat/modules/aesthetics/implanter/implanter.dmi' + icon_state = "implanter1" + inhand_icon_state = "syringe_0" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + +/// Attempts to use the item on the target brain. +/obj/item/rsd_interface/afterattack(obj/item/organ/internal/brain/target_brain, mob/user, proximity_flag, click_parameters) + . = ..() + if(!proximity_flag || !istype(target_brain)) + return FALSE + + if(HAS_TRAIT(target_brain, TRAIT_NIFSOFT_HUD_GRANTER)) + balloon_alert("already upgraded!") + return FALSE + + user.visible_message(span_notice("[user] upgrades [target_brain] with [src]."), span_notice("You upgrade [target_brain] to be RSD compatible.")) + target_brain.AddElement(/datum/element/rsd_interface) + playsound(target_brain.loc, 'sound/weapons/circsawhit.ogg', 50, vary = TRUE) + + qdel(src) + +/datum/element/rsd_interface/Attach(datum/target) + . = ..() + if(!istype(target, /obj/item/organ/internal/brain)) + return ELEMENT_INCOMPATIBLE + + RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + ADD_TRAIT(target, TRAIT_RSD_COMPATIBLE, INNATE_TRAIT) + +/// Adds text to the examine text of the parent item, explaining that the item can be used to enable the use of NIFSoft HUDs +/datum/element/rsd_interface/proc/on_examine(datum/source, mob/user, list/examine_text) + SIGNAL_HANDLER + examine_text += span_cyan("Souls can be transferred to [source], assuming it is inert.") + +/datum/element/rsd_interface/Detach(datum/target) + UnregisterSignal(target, COMSIG_ATOM_EXAMINE) + REMOVE_TRAIT(target, TRAIT_RSD_COMPATIBLE, INNATE_TRAIT) + + return ..() + diff --git a/sound/ambience/ambimaint6.ogg b/sound/ambience/ambimaint6.ogg new file mode 100644 index 00000000000..f83e3ed1d6b Binary files /dev/null and b/sound/ambience/ambimaint6.ogg differ diff --git a/sound/ambience/ambimaint7.ogg b/sound/ambience/ambimaint7.ogg new file mode 100644 index 00000000000..3db2f226a4a Binary files /dev/null and b/sound/ambience/ambimaint7.ogg differ diff --git a/sound/creatures/bagawk.ogg b/sound/creatures/bagawk.ogg new file mode 100644 index 00000000000..bfdce2da489 Binary files /dev/null and b/sound/creatures/bagawk.ogg differ diff --git a/sound/creatures/chick_peep.ogg b/sound/creatures/chick_peep.ogg new file mode 100644 index 00000000000..1e84d1d765f Binary files /dev/null and b/sound/creatures/chick_peep.ogg differ diff --git a/sound/creatures/chitter.ogg b/sound/creatures/chitter.ogg new file mode 100644 index 00000000000..5b2a1443886 Binary files /dev/null and b/sound/creatures/chitter.ogg differ diff --git a/sound/creatures/claw_click.ogg b/sound/creatures/claw_click.ogg new file mode 100644 index 00000000000..965b4c3fa9f Binary files /dev/null and b/sound/creatures/claw_click.ogg differ diff --git a/sound/creatures/clucks.ogg b/sound/creatures/clucks.ogg new file mode 100644 index 00000000000..176f46f866f Binary files /dev/null and b/sound/creatures/clucks.ogg differ diff --git a/sound/creatures/mousesqueek.ogg b/sound/creatures/mousesqueek.ogg new file mode 100644 index 00000000000..fef15503cd9 Binary files /dev/null and b/sound/creatures/mousesqueek.ogg differ diff --git a/sound/creatures/snake_hissing1.ogg b/sound/creatures/snake_hissing1.ogg new file mode 100644 index 00000000000..52a37d764c4 Binary files /dev/null and b/sound/creatures/snake_hissing1.ogg differ diff --git a/sound/creatures/snake_hissing2.ogg b/sound/creatures/snake_hissing2.ogg new file mode 100644 index 00000000000..bd11b7fb5f0 Binary files /dev/null and b/sound/creatures/snake_hissing2.ogg differ diff --git a/sound/effects/arcade_jump.ogg b/sound/effects/arcade_jump.ogg new file mode 100644 index 00000000000..65f0cc448b5 Binary files /dev/null and b/sound/effects/arcade_jump.ogg differ diff --git a/sound/effects/beeps_jingle.ogg b/sound/effects/beeps_jingle.ogg new file mode 100644 index 00000000000..0d4b647f88d Binary files /dev/null and b/sound/effects/beeps_jingle.ogg differ diff --git a/sound/effects/boing.ogg b/sound/effects/boing.ogg new file mode 100644 index 00000000000..8328cc33926 Binary files /dev/null and b/sound/effects/boing.ogg differ diff --git a/sound/effects/glockenspiel_ping.ogg b/sound/effects/glockenspiel_ping.ogg new file mode 100644 index 00000000000..f2530007129 Binary files /dev/null and b/sound/effects/glockenspiel_ping.ogg differ diff --git a/sound/effects/jingle.ogg b/sound/effects/jingle.ogg new file mode 100644 index 00000000000..da903910dfc Binary files /dev/null and b/sound/effects/jingle.ogg differ diff --git a/sound/effects/submerge.ogg b/sound/effects/submerge.ogg new file mode 100644 index 00000000000..8c50fba8e0a Binary files /dev/null and b/sound/effects/submerge.ogg differ diff --git a/sound/effects/tada_fanfare.ogg b/sound/effects/tada_fanfare.ogg new file mode 100644 index 00000000000..055635e9dec Binary files /dev/null and b/sound/effects/tada_fanfare.ogg differ diff --git a/sound/items/hypospray.ogg b/sound/items/hypospray.ogg new file mode 100644 index 00000000000..e5c7bd8f92b Binary files /dev/null and b/sound/items/hypospray.ogg differ diff --git a/sound/lavaland/bdm_boss.ogg b/sound/lavaland/bdm_boss.ogg new file mode 100644 index 00000000000..a5c14095416 Binary files /dev/null and b/sound/lavaland/bdm_boss.ogg differ diff --git a/sound/machines/engine_alert3.ogg b/sound/machines/engine_alert3.ogg new file mode 100644 index 00000000000..394bfed2a13 Binary files /dev/null and b/sound/machines/engine_alert3.ogg differ diff --git a/sound/magic/hereticknock.ogg b/sound/magic/hereticknock.ogg new file mode 100644 index 00000000000..87ca57302a2 Binary files /dev/null and b/sound/magic/hereticknock.ogg differ diff --git a/sound/misc/announce_syndi.ogg b/sound/misc/announce_syndi.ogg new file mode 100644 index 00000000000..49c255bd0e9 Binary files /dev/null and b/sound/misc/announce_syndi.ogg differ diff --git a/sound/misc/notice3.ogg b/sound/misc/notice3.ogg new file mode 100644 index 00000000000..e41a4361ca6 Binary files /dev/null and b/sound/misc/notice3.ogg differ diff --git a/sound/weapons/gun/rifle/shot_heavy.ogg b/sound/weapons/gun/rifle/shot_heavy.ogg new file mode 100644 index 00000000000..f91b21ec4d8 Binary files /dev/null and b/sound/weapons/gun/rifle/shot_heavy.ogg differ diff --git a/strings/names/cyberauth.txt b/strings/names/cyberauth.txt new file mode 100644 index 00000000000..f1fc42b3692 --- /dev/null +++ b/strings/names/cyberauth.txt @@ -0,0 +1,21 @@ +Mr. One +Process Kill +Event Handler +Q. Del +Shutdown Exe +Revert Commit +Thread Manager +Garbage Collector +Core Debugger +Kernel Panic +IO Blocker +Recursion Terminator +Disk Doctor +Format Syntax +Byte Guardian +Disk Defragmenter +Security Patch +Mandatory Upgrade +Pull Review +Bit Auditor +Pen Test diff --git a/tff_modular/master_files/code/datum/components/human_holder.dm b/tff_modular/master_files/code/datum/components/human_holder.dm new file mode 100644 index 00000000000..0590cba62dc --- /dev/null +++ b/tff_modular/master_files/code/datum/components/human_holder.dm @@ -0,0 +1,83 @@ +/** + * HUMAN_HOLDER + * + * Этот компонент, нужен для безопасного нахождения человека внутри mob_holder. + * Изначально создаен для того, чтобы ложить тешари в сумки.. + * + * Переменные: + * + * held_human - человек, над которым мы в данный момент оперируем. + * holder - холдер, в котором находится человек. + * handle_environment - нужно ли нам потдерживать нормальное состояние среды. + */ + +/datum/component/human_holder + var/mob/living/carbon/human/held_human + var/obj/item/clothing/head/mob_holder/human/holder + var/handle_environment + +/datum/component/human_holder/Initialize(obj/item/clothing/head/mob_holder/human/holder, mob/living/carbon/human/handle_human, handle_environment = TRUE) + src.held_human = handle_human + src.handle_environment = handle_environment + src.holder = holder + + RegisterWithParent() + +/datum/component/human_holder/Destroy(force, silent) + . = ..() + if(!parent) + return + + UnregisterSignal(parent, list(COMSIG_HUMAN_ENTER_STORAGE, COMSIG_HUMAN_EXIT_STORAGE, COMSIG_CARBON_PRE_BREATHE)) + +/datum/component/human_holder/RegisterWithParent() + if(!parent) + return + RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(enter_storage), override = TRUE) + RegisterSignal(parent, COMSIG_ATOM_EXIT, PROC_REF(exit_storage), override = TRUE) + RegisterSignal(parent, COMSIG_CARBON_ATTEMPT_BREATHE, PROC_REF(handle_breathe), override = TRUE) + +/** + * Этот прок служит прослойкой между призывом к дыханию человека и самим дыханием. Порядкой следующий + * - Начало дыхания. + * - Дыхание(сигнал о подготовке дыхания) + * - Этот прок(если его выполнение дойдет до последний строки, то тут порядок и остановится) + * - Дыхание(остальной код) + */ +/datum/component/human_holder/proc/handle_breathe() + SIGNAL_HANDLER + if(!handle_environment) + return + if(held_human.health <= HEALTH_THRESHOLD_FULLCRIT) + return + if(held_human.get_breath_from_internal(BREATH_VOLUME)) + return + + var/turf/stand_turf = get_turf(holder.holding_bag) + var/datum/gas_mixture/environment = stand_turf.return_air() + // Если нет атмосферной среды - пропускаем. + if(!environment) + return + + held_human.failed_last_breath = FALSE + held_human.handle_environment(environment) + stand_turf.remove_air(BREATH_PERCENTAGE) + + //Отменяем вдох человека. + return COMSIG_CARBON_BLOCK_BREATH + +/datum/component/human_holder/proc/enter_storage(mob/living/carbon/human/user, obj/item/storage/backpack/bag) + SIGNAL_HANDLER + + user.cure_blind(EYES_COVERED) + user.overlay_fullscreen("tint", /atom/movable/screen/fullscreen/impaired, 2) + //Увеличиваем размер холдера, дабы гарантировать, что в него не попадет много других существ. + holder.w_class = WEIGHT_CLASS_HUGE + +/datum/component/human_holder/proc/exit_storage(mob/living/carbon/human/user, obj/item/storage/backpack/bag) + SIGNAL_HANDLER + + user.cure_blind(EYES_COVERED) + user.clear_fullscreen("tint", 0 SECONDS) + user.update_tint() + Destroy() diff --git a/tff_modular/master_files/code/datum/components/riding_mob.dm b/tff_modular/master_files/code/datum/components/riding_mob.dm new file mode 100644 index 00000000000..db37371ae49 --- /dev/null +++ b/tff_modular/master_files/code/datum/components/riding_mob.dm @@ -0,0 +1,63 @@ +#define DEFAULT_IN_HAND_OFFSET_X 3 +#define DEFAULT_IN_HAND_OFFSET_Y 0 + +/datum/component/riding/creature/human + //Рука в которой будет находится взятый моб + var/obj/item/bodypart/used_hand + +// Расширяем инициализацию. +/datum/component/riding/creature/human/Initialize(mob/living/riding_mob, force, ride_check_flags, potion_boost) + . = ..() + var/mob/living/carbon/human/human_parent = parent + if(ride_check_flags & CARRIER_NEEDS_ARM && HAS_TRAIT(riding_mob, TRAIT_CAN_BUCKLED_TO_HAND)) + human_parent.buckle_lying = 0 + used_hand = human_parent.get_active_hand() + ADD_TRAIT(riding_mob, TRAIT_UNDENSE, VEHICLE_TRAIT) + + +/datum/component/riding/creature/human/vehicle_mob_unbuckle(datum/source, mob/living/former_rider, force = FALSE) + . = ..() + former_rider.set_density(TRUE) + if((ride_check_flags & CARRIER_NEEDS_ARM) && HAS_TRAIT(former_rider, TRAIT_CAN_BUCKLED_TO_HAND)) + REMOVE_TRAIT(former_rider, TRAIT_UNDENSE, VEHICLE_TRAIT) + +//Хэндлинг положения в руке +/datum/component/riding/creature/human/handle_vehicle_offsets(dir) + . = ..() + var/mob/living/carbon/human/human_parent = parent + + for(var/mob/living/rider in human_parent.buckled_mobs) + if(!HAS_TRAIT(rider, TRAIT_CAN_BUCKLED_TO_HAND)) + continue + + var/target_pixel_y = DEFAULT_IN_HAND_OFFSET_X + var/target_pixel_x = DEFAULT_IN_HAND_OFFSET_Y + var/offset_hand = used_hand.body_zone + + if(dir == NORTH && offset_hand == BODY_ZONE_L_ARM) + target_pixel_x += -6 + else if(dir == NORTH && offset_hand == BODY_ZONE_R_ARM) + target_pixel_x += 6 + else if(dir == SOUTH && offset_hand == BODY_ZONE_L_ARM) + target_pixel_x += 6 + else if(dir == SOUTH && offset_hand == BODY_ZONE_R_ARM) + target_pixel_x += -6 + else if(dir == EAST) + target_pixel_x += 3 + else + target_pixel_x += -3 + + rider.pixel_y = target_pixel_y + rider.pixel_x = target_pixel_x + +/datum/component/riding/creature/human/handle_vehicle_layer(dir) + . = ..() + var/mob/living/carbon/human/human_parent = parent + + for(var/mob/living/rider in human_parent.buckled_mobs) + if(!HAS_TRAIT(rider, TRAIT_CAN_BUCKLED_TO_HAND)) + continue + var/target_layer = MOB_ABOVE_PIGGYBACK_LAYER + 0.10 + if(dir == NORTH) + target_layer -= 0.30 + rider.layer = target_layer diff --git a/tff_modular/master_files/code/datum/components/weak_body.dm b/tff_modular/master_files/code/datum/components/weak_body.dm new file mode 100644 index 00000000000..2afe4a7d62b --- /dev/null +++ b/tff_modular/master_files/code/datum/components/weak_body.dm @@ -0,0 +1,218 @@ +/** + * СЛАБОЕ ТЕЛО + * + * Этот компонент нанесет на установленного пользователя массу дебафов, что так, или иначе скажутся + * На его работоспособности. + * + * Переменные: + * + * block_grab - Если положительно, будет разрывать граб пользоветля. + * max_allow_w_class - Максимально допустимый вес предмета, что может тянуть пользователь. + * block_range_weapon - Если положительно, будет оказывать дебаф на использование дальнобоейного оружия. + * block_melee_weapon - дебафф при использовании оружия ближнего боя. + * + */ +/datum/component/weak_body + dupe_mode = COMPONENT_DUPE_HIGHLANDER + var/block_grab + var/max_allow_w_class + var/block_range_weapon + var/block_melee_weapon + +/datum/component/weak_body/Initialize(block_range = TRUE, block_melee = TRUE, weak_grab = TRUE, max_w_class = WEIGHT_CLASS_BULKY) + block_melee_weapon = block_melee + block_range_weapon = block_range + block_grab = weak_grab + max_allow_w_class = max_w_class + RegisterWithParent() + +/datum/component/weak_body/Destroy(force, silent) + . = ..() + if(!parent) + return + + UnregisterSignal(parent, list(COMSIG_MOB_ITEM_AFTERATTACK, COMSIG_MOB_ITEM_AFTERATTACK_SECONDARY, COMSIG_MOB_FIRED_GUN, COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_PICKED_UP_ITEM)) + if(block_grab) + UnregisterSignal(parent, list(COMSIG_MOVABLE_SET_GRAB_STATE, COMSIG_LIVING_START_PULL)) + +/datum/component/weak_body/RegisterWithParent() + if(!parent) + return + + RegisterSignals(parent, list(COMSIG_MOB_ITEM_AFTERATTACK, COMSIG_MOB_ITEM_AFTERATTACK_SECONDARY), PROC_REF(aftet_attack_act), override = TRUE) + RegisterSignal(parent, COMSIG_MOB_FIRED_GUN, PROC_REF(fired_gun_act), override = TRUE) + RegisterSignal(parent, COMSIG_HUMAN_DISARM_HIT, PROC_REF(after_disarm), override = TRUE) + RegisterSignal(parent, COMSIG_LIVING_PICKED_UP_ITEM ,PROC_REF(pickup_item_act), override = TRUE) + if(block_grab) + RegisterSignal(parent, COMSIG_MOVABLE_SET_GRAB_STATE, PROC_REF(upgrade_grab), override = TRUE) + RegisterSignal(parent, COMSIG_LIVING_START_PULL, PROC_REF(pull_act), override = TRUE) + RegisterSignal(parent, COMSIG_ATOM_NO_LONGER_PULLING, PROC_REF(stop_pull_act), override = TRUE) + +// Проверяем надет ли и включен на пользователе МОД костюм. +/datum/component/weak_body/proc/check_mod() + var/mob/living/carbon/human/victim = parent + + if(istype(victim.back, /obj/item/mod/control)) + var/obj/item/mod/control/m = victim.back + if(m.active) + victim.balloon_alert(victim, "Mod assisted!") + m.subtract_charge(5) + return TRUE + return FALSE + +/datum/component/weak_body/proc/check_antagonists() + var/mob/living/carbon/human/victim = parent + if((IS_TRAITOR(victim) || IS_NUKE_OP(victim) || IS_HERETIC(victim) || IS_CULTIST(victim))) + return TRUE + return FALSE + +/datum/component/weak_body/proc/pickup_item_act(mob/user, obj/item/picked_up_item) + if((picked_up_item.w_class > max_allow_w_class) && !check_mod()) + addtimer(CALLBACK(src, PROC_REF(drop_item), picked_up_item), 5) + //Дополнительно проверяем, что не пытаемся взять сумку, в которой кто-нибудь лежит. + if(istype(picked_up_item, /obj/item/storage/backpack)) + var/obj/item/storage/backpack/bag = picked_up_item + for(var/thing in bag.contents) + if(!istype(thing, /obj/item/clothing/head/mob_holder/human)) + continue + if(check_mod()) + return + addtimer(CALLBACK(src, PROC_REF(drop_item), picked_up_item), 5) + +/datum/component/weak_body/proc/drop_item(obj/item/I) + var/mob/living/carbon/human/victim = parent + victim.visible_message(span_notice("[victim.name] try pickup [I], but it too heavy for [victim.p_they()]"), span_danger("You try pickup [I.name], but it too heavy for you!")) + victim.dropItemToGround(I) + +/datum/component/weak_body/proc/after_disarm(mob/user, mob/living/carbon/human/attacker, zone_targeted) + SIGNAL_HANDLER + var/mob/living/carbon/human/victim = parent + if(check_antagonists()) + return + var/fall_chance = rand(0, 50) + + if(HAS_TRAIT(attacker, TRAIT_OVERSIZED)) + fall_chance = 100 + + var/should_fall = FALSE + switch(fall_chance) + if(0 to 29) + should_fall = pick(TRUE, FALSE, FALSE, FALSE) + if(30 to 49) + should_fall = pick(TRUE, FALSE, FALSE) + if(50 to 99) + should_fall = pick(TRUE, FALSE) + if(100 to INFINITY) + should_fall = TRUE + if(should_fall && !check_mod()) + victim.Knockdown(3 SECONDS) + +/datum/component/weak_body/proc/pull_act(mob/user, atom/movable/pulled, state, force) + SIGNAL_HANDLER + var/mob/living/carbon/human/victim = parent + + if(isitem(pulled)) + var/obj/item/i = pulled + if((i.w_class > max_allow_w_class) && !check_mod()) + victim.stop_pulling() + victim.visible_message(span_notice("[victim.name] start pulling [i], [i.name], but too heavy for [victim.p_their()]"), span_danger("You start pulling [i.name], but it too heavy for you!")) + return + + if(isobj(pulled)) + var/obj/o = pulled + // Т.к каких-то фиксированных значений для обьектов у нас нет, стоит отталкиваться от того, какое замедление они оказывают. + if((o.drag_slowdown >= 1.5) && !check_mod()) + victim.stop_pulling() + victim.visible_message(span_notice("[victim.name] start pulling [o], but [o.name] too heavy for [victim.p_their()]"), span_danger("You start pulling [o.name], but it too heavy for you!")) + return + + if(ishuman(pulled)) + if(!victim.has_movespeed_modifier(/datum/movespeed_modifier/teshari_pull)) + victim.add_movespeed_modifier(/datum/movespeed_modifier/teshari_pull) + + if(state >= GRAB_AGGRESSIVE) + if(check_antagonists() || check_mod()) + return + // Убираем замедление от пула. + if(user.has_movespeed_modifier(/datum/movespeed_modifier/teshari_pull)) + user.remove_movespeed_modifier(/datum/movespeed_modifier/teshari_pull) + // Если мы антагонист, то мы можем превозмочь рассовые сложности. + var/mob/living/carbon/human/h = pulled + if(HAS_TRAIT(h, TRAIT_WEAK_BODY)) + return + victim.visible_message(span_notice("[victim.name] grabed [h.name], but [h.p_they()] too heavy for [victim.p_their()]"), span_danger("You start pulling [h.name], but [h.p_they()] too heavy for you!")) + victim.stop_pulling() + victim.grab_state = 0 + +/datum/component/weak_body/proc/stop_pull_act(mob/user, atom/movable/pulled) + SIGNAL_HANDLER + user.remove_movespeed_modifier(/datum/movespeed_modifier/teshari_pull) + user.update_movespeed() + +/datum/component/weak_body/proc/upgrade_grab(mob/user, new_state) + SIGNAL_HANDLER + if(!user.pulling || new_state == 0) + return + addtimer(CALLBACK(src, PROC_REF(pull_act), user, user.pulling, new_state), 5) + +// ДЕБАФ НА ОРУЖИЕ ДАЛЬНЕГО БОЯ +/datum/component/weak_body/proc/fired_gun_act(mob/user, obj/item/gun/weapon, atom/target, params, zone_override, bonus_spread_values) + SIGNAL_HANDLER + var/addictional_spread = bonus_spread_values + + if(weapon.weapon_weight >= WEAPON_MEDIUM) + addictional_spread += 20 + if(weapon.weapon_weight >= WEAPON_HEAVY) + addictional_spread += 30 + knockback_user(weapon) + + weapon.spread = addictional_spread + addtimer(CALLBACK(src, PROC_REF(after_gun_fired), weapon), 1 SECONDS) + +/datum/component/weak_body/proc/knockback_user(obj/item/gun/weapon) + var/mob/living/carbon/human/victim = parent + if(HAS_TRAIT(victim, TRAIT_NEGATES_GRAVITY)) + return + + var/knockdown_range = weapon.weapon_weight + if(istype(weapon, /obj/item/gun/ballistic/rocketlauncher)) + knockdown_range *= 2 + + var/target_dir = turn(victim.dir, 180) + var/knockdown_target = get_ranged_target_turf(victim, target_dir, knockdown_range) + + victim.Knockdown((weapon.weapon_weight * 2) SECONDS) + victim.Paralyze(weapon.weapon_weight SECONDS) + victim.visible_message(span_warning("[victim.name] shoots from [weapon.name], but the recoil is so strong it knocks [victim.p_they()] backwards!"), span_danger("The violent recoil sends you flying backwards!")) + victim.throw_at(knockdown_target, knockdown_range, weapon.weapon_weight) + +/datum/component/weak_body/proc/after_gun_fired(obj/item/gun/weapon) + // Возращаем оружие в норму. + weapon.spread = initial(weapon.spread) + +// ДЕБАФ НА ДВУРУЧНОЕ ОРУЖИЕ +// Сбивает нас с ног, если мы используем двуручное оружие. Можем защититься с помощью магнитных ботинок! +/datum/component/weak_body/proc/aftet_attack_act(mob/user, atom/target, obj/item/weapon, proximity_flag, click_parameters) + SIGNAL_HANDLER + if(!ismob(target)) + return + var/mob/living/carbon/human/victim = parent + var/obj/item/inactive = victim.get_inactive_held_item() + //Проверяем что мы были рядом с целью. + var/distance = get_dist_euclidian(victim, target) + if(distance > 1) + return + + if(!istype(inactive, /obj/item/offhand)) + return + + if(check_antagonists() || check_mod() || HAS_TRAIT(parent, TRAIT_NEGATES_GRAVITY)) + return + + victim.visible_message(span_danger("[victim.name] falls after attacking [target], [weapon.name] is too heavy for [victim.p_their()]"), span_danger("You attack [target], but [weapon.name] is too heavy for you.")) + victim.Knockdown(3 SECONDS) + victim.Stun(2 SECONDS) + +/datum/movespeed_modifier/teshari_pull + blacklisted_movetypes = FLYING + multiplicative_slowdown = 0.9 diff --git a/tff_modular/master_files/code/game/objectes/items/human_holder.dm b/tff_modular/master_files/code/game/objectes/items/human_holder.dm new file mode 100644 index 00000000000..67f64456312 --- /dev/null +++ b/tff_modular/master_files/code/game/objectes/items/human_holder.dm @@ -0,0 +1,43 @@ +/obj/item/clothing/head/mob_holder/human + // Сумка в которой мы сейчас находимся. + var/obj/item/storage/backpack/holding_bag + +/obj/item/clothing/head/mob_holder/human/on_exit_storage(datum/storage/master_storage) + release() + +/obj/item/clothing/head/mob_holder/human/container_resist_act() + if(!istype(holding_bag, /obj/item/storage/backpack/duffelbag)) + release() + return + var/obj/item/storage/backpack/duffelbag/bag = holding_bag + + if(!bag.zipped_up) + release() + return + + if(!do_after(held_mob, 10 SECONDS, bag)) + held_mob.balloon_alert(held_mob, "Stand still!") + return + + bag.set_zipper(FALSE) + release() + +/obj/item/clothing/head/mob_holder/human/deposit(mob/living/carbon/human/H, obj/item/storage/backpack/bag) + . = ..() + H.AddComponent(/datum/component/human_holder, holder = src, handle_human = H, handle_environment = TRUE) + SEND_SIGNAL(H, COMSIG_ATOM_ENTERED, bag) + holding_bag = bag + +/obj/item/clothing/head/mob_holder/human/relaymove(mob/living/user, direction) + held_mob.balloon_alert(held_mob, "Can't move!") + return + +/obj/item/clothing/head/mob_holder/human/release(del_on_release, display_messages) + if(held_mob) + SEND_SIGNAL(held_mob, COMSIG_ATOM_EXIT, holding_bag) + ..(TRUE, FALSE) + +/obj/item/clothing/head/mob_holder/human/on_found(mob/finder) + if(HAS_TRAIT(held_mob, TRAIT_CAN_ENTER_BAG)) + return + ..() diff --git a/tff_modular/master_files/code/game/objectes/items/ridable.dm b/tff_modular/master_files/code/game/objectes/items/ridable.dm new file mode 100644 index 00000000000..4a5b57cc116 --- /dev/null +++ b/tff_modular/master_files/code/game/objectes/items/ridable.dm @@ -0,0 +1,9 @@ +/obj/item/riding_offhand/pre_attack(atom/A, mob/living/user, params) + if(istype(A, /obj/item/storage/backpack)) + var/obj/item/storage/backpack/bag = A + var/mob/living/carbon/human/human_to_put = rider + if(human_to_put.try_put_to_bag(bag, TRUE, parent)) + Destroy() + ..(A, user, params) + + diff --git a/tff_modular/master_files/code/game/objectes/items/storage.dm b/tff_modular/master_files/code/game/objectes/items/storage.dm new file mode 100644 index 00000000000..7bab4052efc --- /dev/null +++ b/tff_modular/master_files/code/game/objectes/items/storage.dm @@ -0,0 +1,7 @@ +/obj/item/storage/MouseDrop_T(mob/user) + . = ..() + if(istype(src, /obj/item/storage/backpack)) + if(ishuman(user)) + var/mob/living/carbon/human/h = user + if(h.client == usr.client) + h.try_put_to_bag(src) diff --git a/tff_modular/master_files/code/modules/job/job_blacklist.dm b/tff_modular/master_files/code/modules/job/job_blacklist.dm new file mode 100644 index 00000000000..dc5bf7fd1ab --- /dev/null +++ b/tff_modular/master_files/code/modules/job/job_blacklist.dm @@ -0,0 +1,56 @@ +/** + * Блэклист проффесий.. Дополняйте его по мере необходимости. + */ +/datum/job/captain + species_blacklist = list(SPECIES_NABBER = TRUE) + +/datum/job/security_officer + species_blacklist = list(SPECIES_NABBER = TRUE, SPECIES_TESHARI_ALT = TRUE) + +/datum/job/warden + species_blacklist = list(SPECIES_NABBER = TRUE, SPECIES_TESHARI_ALT = TRUE) + +/datum/job/detective + species_blacklist = list(SPECIES_NABBER = TRUE) + +/datum/job/chief_medical_officer + species_blacklist = list(SPECIES_NABBER = TRUE) + +/datum/job/chief_engineer + species_blacklist = list(SPECIES_NABBER = TRUE) + +/datum/job/blueshield + species_blacklist = list(SPECIES_NABBER = TRUE) + +/datum/job/research_director + species_blacklist = list(SPECIES_NABBER = TRUE) + +/datum/job/head_of_personnel + species_blacklist = list(SPECIES_NABBER = TRUE) + +/datum/job/quartermaster + species_blacklist = list(SPECIES_NABBER = TRUE) + +/datum/job/head_of_security + species_blacklist = list(SPECIES_NABBER = TRUE, SPECIES_TESHARI_ALT = TRUE) + +/datum/job/nanotrasen_consultant + species_blacklist = list(SPECIES_NABBER = TRUE) + +/datum/job/corrections_officer + species_blacklist = list(SPECIES_NABBER = TRUE, SPECIES_TESHARI_ALT = TRUE) + +/datum/job/science_guard + species_blacklist = list(SPECIES_NABBER = TRUE, SPECIES_TESHARI_ALT = TRUE) + +/datum/job/engineering_guard + species_blacklist = list(SPECIES_NABBER = TRUE, SPECIES_TESHARI_ALT = TRUE) + +/datum/job/lawyer + species_blacklist = list(SPECIES_NABBER = TRUE) + +/datum/job/bouncer + species_blacklist = list(SPECIES_NABBER = TRUE, SPECIES_TESHARI_ALT = TRUE) + +/datum/job/orderly + species_blacklist = list(SPECIES_NABBER = TRUE, SPECIES_TESHARI_ALT = TRUE) diff --git a/tff_modular/master_files/code/modules/mob/living/carbon/human/_human.dm b/tff_modular/master_files/code/modules/mob/living/carbon/human/_human.dm new file mode 100644 index 00000000000..93e230aa626 --- /dev/null +++ b/tff_modular/master_files/code/modules/mob/living/carbon/human/_human.dm @@ -0,0 +1,158 @@ +/mob/living/carbon/human/is_shove_knockdown_blocked() + if(HAS_TRAIT(src, TRAIT_KNOCKDOWN_IMMUNE)) + return TRUE + ..() + +/mob/living/carbon/human/set_mob_height(new_height) + if(dna.species.body_size_restricted) + return FALSE + ..(new_height) + +// Предпроверка оригинальнго прока /carbon/disarm(), если src, слаб телом, прирвыаем атаку. За исключением тех случаев, если это акт эмоции. Вызывается перед оригиналом. +/mob/living/carbon/human/disarm(mob/living/carbon/target) + if((HAS_TRAIT(src, TRAIT_WEAK_BODY) && !HAS_TRAIT(target, TRAIT_WEAK_BODY)) && zone_selected != (BODY_ZONE_PRECISE_MOUTH || BODY_ZONE_PRECISE_GROIN)) + target.visible_message(span_danger("[src.name] tries shoving [target.name], but [target.p_they()] is too heavy!")) + do_attack_animation(target, ATTACK_EFFECT_DISARM) + playsound(target, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) + return + ..(target) + +// Предпроверка оригинальнго прока /living/throw_item(), если src, слаб телом, прирвыаем бросок. Вызывается перед оригиналом. +/mob/living/carbon/human/throw_item(atom/target) + //Если мы не обладатель слабого тела - то не выполняем ничего. + if(HAS_TRAIT(src, TRAIT_WEAK_BODY)) + + var/obj/item/held_item = get_active_held_item() + var/obj/item/inactive = get_inactive_held_item() + //Проверяем, что мы бросаем моба. + if(!held_item) + if(pulling && isliving(pulling) && grab_state >= GRAB_AGGRESSIVE) + var/mob/living/mob = pulling + if(!mob.buckled) + if(!HAS_TRAIT(mob, TRAIT_WEAK_BODY)) + stop_pulling() + Knockdown(3 SECONDS) + to_chat(src, span_notice("You try throwing [mob], but [mob.p_they()] is too heavy!")) + return FALSE + //Проверяем, что мы бросаем двуручный предмет. + else if(held_item.w_class >= WEIGHT_CLASS_BULKY && istype(inactive, /obj/item/offhand) && !HAS_TRAIT(src, TRAIT_NEGATES_GRAVITY)) + to_chat(src, span_notice("You try throwing [held_item], but it is too heavy!")) + Knockdown(3 SECONDS) + dropItemToGround(held_item) + return FALSE + + ..(target) + +/** + * try_put_to_bag() - Попытка зайти в сумку. + * + * forced - если истина, то src, входи с сумку не по своей воле. + * bag - сумка в которую мы пытаемся залезть. + * shoving - тот, кто засовывает нас в сумку. + * + * Если forced && shoving присудствуют. Выполняем proc/try_put_to_bag_other(), что переадресует сообщения. + */ +/mob/living/carbon/human/proc/try_put_to_bag(obj/item/storage/backpack/bag, forced = FALSE, mob/shoving) + if(forced && shoving) + return try_put_to_bag_other(bag, shoving) + + if(!can_enter_bag(bag, src)) + return FALSE + + visible_message(span_notice("[name], starts getting into [bag.name]."), span_notice("You start getting into [bag.name]")) + if(!do_after(src, 3 SECONDS, bag)) + src.balloon_alert(src, "Stand still!") + return FALSE + + visible_message(span_notice("[name], got into [bag.name]. "), span_notice("You got into [bag.name]")) + put_to_bag(bag) + return TRUE + +/mob/living/carbon/human/proc/try_put_to_bag_other(obj/item/storage/backpack/bag, mob/shoving) + if(!can_enter_bag(bag, shoving)) + return FALSE + + shoving.visible_message(span_notice("[shoving.name] starts shoving [name] into [bag.name]."), span_notice("You start shoving [name] into the [bag.name]")) + if(!do_after(shoving, 3 SECONDS, bag)) + shoving.balloon_alert(shoving, "Stand still!") + return FALSE + + shoving.visible_message(span_notice("[shoving.name] shoved [name] into [bag.name]."), span_notice("You shoved [name] into the [bag.name]")) + put_to_bag(bag) + return TRUE + +// Актуально перемещаемся в сумку. +/mob/living/carbon/human/proc/put_to_bag(obj/item/storage/backpack/bag) + if(!can_enter_bag(bag, src)) + return + + if(istype(bag.atom_storage, /datum/storage/bag_of_holding)) + for(var/obj/item/i in src.contents) + if((istype(i, /obj/item/storage/backpack/holding) && !drop_all_held_items()) || istype(back, /obj/item/storage/backpack/holding)) + visible_message(span_danger("Reality tears [name] from the inside out. "), span_userdanger("Reality is ripping you apart from the inside out!")) + gib(FALSE, TRUE, TRUE) + return + + var/obj/item/clothing/head/mob_holder/human/holder = new(get_turf(src), src, held_state, head_icon, held_lh, held_rh, worn_slot_flags) + drop_all_held_items() + holder.holding_bag = bag + holder.forceMove(bag) + +/mob/living/carbon/human/proc/can_enter_bag(obj/item/storage/backpack/bag, mob/viewer) + if(!HAS_TRAIT(src, TRAIT_CAN_ENTER_BAG)) + viewer.balloon_alert(viewer, "Too big!") + return FALSE + + //Если у нас каким-то образом есть этот трейт.. вместь с возможность влазить в сумку -,- + if(HAS_TRAIT(src, TRAIT_OVERSIZED)) + viewer.balloon_alert(viewer, "Too big!") + return FALSE + + //Если сумка и так у нас в руках. + if(bag.loc == src) + return FALSE + + //Если нас пытаются положить не в БС сумку, выполняем дополнительную проверку.. + if(!istype(bag, /obj/item/storage/backpack/holding)) + //Есть ли что-нибудь на нашей спине.(рюкзаки/оружие/прочее) + if(back) + viewer.balloon_alert(viewer, "[back.name] is on the way!") + return FALSE + + if(bag.atom_storage) + + //Рюкзаки, сатчелы и все, что меньше. + if(bag.atom_storage.max_total_storage < 20) + viewer.balloon_alert(viewer, "Too small!") + return FALSE + + if(bag.atom_storage.max_specific_storage < WEIGHT_CLASS_HUGE && !istype(bag, /obj/item/storage/backpack/duffelbag)) + viewer.balloon_alert(viewer, "Too small!") + return FALSE + + var/obj/item/blank = new() + blank.w_class = WEIGHT_CLASS_HUGE + // Пустышка для теста будет меньше, если мы хотим переместиться в дуфельбаг, если там уже кто-то не лежит. + if(istype(bag, /obj/item/storage/backpack/duffelbag)) + var/obj/item/storage/backpack/duffelbag/d = bag + blank.w_class = WEIGHT_CLASS_NORMAL + if(d.zipped_up) + blank.Destroy() + viewer.balloon_alert(viewer, "Closed") + return FALSE + + for(var/thing in d.contents) + if(!istype(thing, /obj/item/clothing/head/mob_holder/human)) + continue + blank.w_class++ + + if(!bag.atom_storage.can_insert(blank, src, FALSE)) + blank.Destroy() + viewer.balloon_alert(viewer, "No space!") + return FALSE + + blank.Destroy() + return TRUE + + viewer.balloon_alert(src, "Can't hold any!") + return FALSE diff --git a/tff_modular/master_files/code/modules/mob/living/carbon/human/human_buckle.dm b/tff_modular/master_files/code/modules/mob/living/carbon/human/human_buckle.dm new file mode 100644 index 00000000000..e22b837088a --- /dev/null +++ b/tff_modular/master_files/code/modules/mob/living/carbon/human/human_buckle.dm @@ -0,0 +1,67 @@ +// Перезапись оригинального прока human/mouse_buckle_handling, выполняет предпроверку на возможность взять цель в руку. Выполняется перед оригиналом. +/mob/living/carbon/human/mouse_buckle_handling(mob/living/M, mob/living/user) + if(pulling != M || grab_state != GRAB_AGGRESSIVE || stat != CONSCIOUS) + return FALSE + + //Если мы на ком-либо закреплены, не даем взять никакого в свои руки.. + if(buckled) + return FALSE + + if(can_buckle_to_hand(M)) + buckle_to_hand_mob(M) + return TRUE + ..(M, user) +// Перезапись оригинального прока /human/proc/fireman_carry(), проверка на трейт слабого тела. Выполняется перед оригининалом. +/mob/living/carbon/human/fireman_carry(mob/living/carbon/target) + if(!can_be_firemanned(target) || incapacitated(IGNORE_GRAB)) + to_chat(src, span_warning("You can't fireman carry [target] while [target.p_they()] [target.p_are()] standing!")) + return + + if(HAS_TRAIT(src, TRAIT_WEAK_BODY)) + visible_message(span_warning("[src] tries to carry [target], but they are too heavy!")) + return + ..(target) +// Перезапись оригинального прока /human/proc/piggybacky(), проверка на трейт слабого тела. Выполняется перед оригининалом. +/mob/living/carbon/human/piggyback(mob/living/carbon/target) + if(!can_piggyback(target)) + to_chat(target, span_warning("You can't piggyback ride [src] right now!")) + return + + if(HAS_TRAIT(src, TRAIT_WEAK_BODY) && !HAS_TRAIT(target, TRAIT_WEAK_BODY)) + target.visible_message(span_warning("[target] is too heavy for [src] to carry!")) + return + ..(target) + + +/mob/living/carbon/human/proc/buckle_to_hand_mob(mob/living/carbon/target) + if(!can_buckle_to_hand(target) || incapacitated(IGNORE_GRAB)) + to_chat(src, span_warning("You can't lift [target] to hand while [target.p_they()] [target.p_are()] standing!")) + return + + var/carrydelay = 3 SECONDS + if(HAS_TRAIT(src, TRAIT_QUICKER_CARRY) || has_quirk(/datum/quirk/oversized)) + carrydelay = 1 SECONDS + else if(HAS_TRAIT(src, TRAIT_QUICK_CARRY)) + carrydelay = 2 SECONDS + + visible_message(span_notice("[src] starts lifting [target] onto their hand..."), + span_notice("You start to lift [target] onto your hand...")) + if(!do_after(src, carrydelay, target)) + visible_message(span_warning("[src] fails to lift [target] to hand!")) + return + + if(!can_buckle_to_hand(target) || incapacitated(IGNORE_GRAB) || target.buckled) + visible_message(span_warning("[src] fails to lift [target] to hand!")) + return + + target.drop_all_held_items() + return buckle_mob(target, TRUE, TRUE, CARRIER_NEEDS_ARM) + +// Проверка на возможность взять цель в активную руку. Требуется трейта [TRAIT_CAN_BUCKLED_TO_HAND], у цели для успеха или [OVERSIZED] трейта у src. +/mob/living/carbon/human/proc/can_buckle_to_hand(mob/living/carbon/target) + if(has_quirk(/datum/quirk/oversized) && !target.has_quirk(/datum/quirk/oversized)) + return TRUE + + else if((ishuman(target) && !HAS_TRAIT(src, TRAIT_WEAK_BODY)) && HAS_TRAIT(target, TRAIT_CAN_BUCKLED_TO_HAND)) + return TRUE + return FALSE diff --git a/tff_modular/master_files/code/modules/mod/_module.dm b/tff_modular/master_files/code/modules/mod/_module.dm new file mode 100644 index 00000000000..6a8993c5745 --- /dev/null +++ b/tff_modular/master_files/code/modules/mod/_module.dm @@ -0,0 +1,23 @@ +/** + * Расширеие генирации спрайта для модулей МОДа, если специя надевшего - это тешари. + * Код выполняет сразу после оргинального прока. rpoc/handle_module_icon() + * Аргументы + * standing - текщий набор спрайтов модулей, что будет установлен на пользоветаля. + * module_icon_stane - айкон стейт для модуля + */ +/obj/item/mod/module/handle_module_icon(mutable_appearance/standing, module_icon_state) + . = ..() + if(!istesharialt(mod.wearer)) + return + // Чтожь, окей - первое, с чего мы начнем - это очистим текущий набор спрайтов. В дальейншем мы будем передать лист спрайтов. + . = list() + // Путь к хранилищу спрайтов модулей. Которыми мы хотим заменить оригинал. + var/new_icon = 'tff_modular/master_files/icons/mob/clothing/species/teshari/mod_modules.dmi' + //Создаем новую иконку модуля. + var/mutable_appearance/module_icon = mutable_appearance(new_icon, module_icon_state, layer = standing.layer + 0.1) + //Восстаналиваем цвет. + module_icon.appearance_flags |= RESET_COLOR + + . += module_icon + //Возращаем обновленные спрайты модулей. + return . diff --git a/tff_modular/master_files/code/modules/mod/mod_clothes.dm b/tff_modular/master_files/code/modules/mod/mod_clothes.dm new file mode 100644 index 00000000000..c5d9714f68d --- /dev/null +++ b/tff_modular/master_files/code/modules/mod/mod_clothes.dm @@ -0,0 +1,14 @@ +/obj/item/clothing/head/mod + worn_icon_teshari = 'tff_modular/master_files/icons/mob/clothing/species/teshari/mod.dmi' + +/obj/item/clothing/suit/mod + worn_icon_teshari = 'tff_modular/master_files/icons/mob/clothing/species/teshari/mod.dmi' + +/obj/item/clothing/gloves/mod + worn_icon_teshari = 'tff_modular/master_files/icons/mob/clothing/species/teshari/mod.dmi' + +/obj/item/clothing/shoes/mod + worn_icon_teshari = 'tff_modular/master_files/icons/mob/clothing/species/teshari/mod.dmi' + +/obj/item/mod/control + worn_icon_teshari = 'tff_modular/master_files/icons/mob/clothing/species/teshari/back.dmi' diff --git a/tff_modular/master_files/icons/mob/clothing/species/teshari/back.dmi b/tff_modular/master_files/icons/mob/clothing/species/teshari/back.dmi new file mode 100644 index 00000000000..49a7e305a55 Binary files /dev/null and b/tff_modular/master_files/icons/mob/clothing/species/teshari/back.dmi differ diff --git a/tff_modular/master_files/icons/mob/clothing/species/teshari/mod.dmi b/tff_modular/master_files/icons/mob/clothing/species/teshari/mod.dmi new file mode 100644 index 00000000000..2f20ff1660d Binary files /dev/null and b/tff_modular/master_files/icons/mob/clothing/species/teshari/mod.dmi differ diff --git a/tff_modular/master_files/icons/mob/clothing/species/teshari/mod_modules.dmi b/tff_modular/master_files/icons/mob/clothing/species/teshari/mod_modules.dmi new file mode 100644 index 00000000000..e6211c38093 Binary files /dev/null and b/tff_modular/master_files/icons/mob/clothing/species/teshari/mod_modules.dmi differ diff --git a/tff_modular/modules/cqd_holsters/code/holster.dm b/tff_modular/modules/cqd_holsters/code/holster.dm new file mode 100644 index 00000000000..ff8f29bc33b --- /dev/null +++ b/tff_modular/modules/cqd_holsters/code/holster.dm @@ -0,0 +1,70 @@ +/* +* Сама кобура. +*/ + +/obj/item/clothing/accessory/cqd_holster + name = "CQD holster" + desc = "CQD model holster made of durable materials and has tactical weapon attachment points. CQD stands for Concealed Quick Draw, this holster model developed for more comfortable weapon carry among authorized personnel." + icon = 'tff_modular/modules/cqd_holsters/icons/cqd_holster.dmi' + worn_icon = 'tff_modular/modules/cqd_holsters/icons/cqd_holster_worn.dmi' + icon_state = "cqd-holster" + above_suit = FALSE + w_class = WEIGHT_CLASS_NORMAL + attachment_slot = null + +/obj/item/clothing/accessory/cqd_holster/Initialize(mapload) + . = ..() + create_storage(storage_type = /datum/storage/cqd_holster_storage) + +// Тут на всякий случай будет проверка на наличие хранилища у формы, чтобы не сломать ничего. +/obj/item/clothing/accessory/cqd_holster/attach(obj/item/clothing/under/attach_to, mob/living/attacher) + if(attach_to.atom_storage) + return FALSE + . = ..() + +// Этот прок вызываеться при успешном надевании аксессуара, а также при надевании формы. Его я использую для перехвата разных ситуаций специфичных. Например чтобы скрыть внешний спрайтик для набберов и тешари. +/obj/item/clothing/accessory/cqd_holster/on_uniform_equipped(obj/item/clothing/under/U, user) + /* + Следующий код работает по принципу того, что он перед вызовом родительского прока проверяет носителя формы на определённые факторы. Если владелец попадает под определённые факторы - спрайт-состояние меняется на альтернативное (в нашем случае на скрытое). + + ВАЖНОЕ УТОЧНЕНИЕ! Аксессуарам ПЛЕВАТЬ на worn_icon_state, так что мне нужно менять сам icon_state, чтобы скрыть или изменить внешний спрайтик кобуры. + + Ввиду такой ситуёвины я просто создал копии обычных айтем-спрайтов кобуры просто с другим icon_state, дабы сами не пропадали при надевании на того, на ком их спрайт будет изменён/скрыт. + */ + + icon_state = initial(icon_state) + + if(isteshari(user)) + icon_state = initial(icon_state) + "_hidden" + if(isnabber(user)) + icon_state = initial(icon_state) + "_hidden" + + // Вызываем родительский прок после проверок. + . = ..() + +/obj/item/clothing/accessory/cqd_holster/detach(obj/item/clothing/under/U) + // А это костыльный обход багули, который я подглядел у кармашка для ручек. + var/drop_loc = drop_location() + for(var/atom/movable/held as anything in src) + held.forceMove(drop_loc) + return ..() + +/* +* Эстетичная кобура +*/ + +/obj/item/clothing/accessory/cqd_holster/aesthetic + name = "aesthetic CQD holster" + desc = "CQD model holster made of durable materials and has tactical weapon attachment points. CQD stands for Concealed Quick Draw, this holster model developed for more comfortable weapon carry among authorized personnel. This one partly made of leather for aesthetics." + icon = 'tff_modular/modules/cqd_holsters/icons/cqd_holster_aesthetic.dmi' + worn_icon = 'tff_modular/modules/cqd_holsters/icons/cqd_holster_worn_aesthetic.dmi' + +/* +* Синдикатовская кобура +*/ + +/obj/item/clothing/accessory/cqd_holster/syndicate + name = "blood-red CQD holster" + desc = "CQD model holster made of durable materials and has tactical weapon attachment points. CQD stands for Concealed Quick Draw, this holster model developed for more comfortable weapon carry among authorized personnel. This one made of much more sophisticated materials and has strange red coloring." + icon = 'tff_modular/modules/cqd_holsters/icons/cqd_holster_syndicate.dmi' + worn_icon = 'tff_modular/modules/cqd_holsters/icons/cqd_holster_worn_syndicate.dmi' diff --git a/tff_modular/modules/cqd_holsters/code/holster_injections.dm b/tff_modular/modules/cqd_holsters/code/holster_injections.dm new file mode 100644 index 00000000000..8f01b47cee7 --- /dev/null +++ b/tff_modular/modules/cqd_holsters/code/holster_injections.dm @@ -0,0 +1,31 @@ +/* +/ Тут будут разные перезаписи и иной код который будет выдавать кобуру кому либо. +*/ + +// Ящик БЩ +/obj/structure/closet/secure_closet/blueshield/New() + . = ..() + new /obj/item/clothing/accessory/cqd_holster(src) + +// Ящик ХоСа +/obj/structure/closet/secure_closet/hos/PopulateContents() + . = ..() + new /obj/item/clothing/accessory/cqd_holster(src) + +// Ящик Капитана +/obj/structure/closet/secure_closet/captains/PopulateContents() + . = ..() + new /obj/item/clothing/accessory/cqd_holster/aesthetic(src) + +// Ящик НТРа +/obj/structure/closet/secure_closet/nanotrasen_consultant/station/PopulateContents() + . = ..() + new /obj/item/clothing/accessory/cqd_holster/aesthetic(src) + +// Антажный вариант в аплинке +/datum/uplink_item/stealthy_tools/cqd_holster + name = "blood-red CQD holster" + desc = "CQD model holster made of durable materials and has tactical weapon attachment points. CQD stands for Concealed Quick Draw, this holster model developed for more comfortable weapon carry among authorized personnel. This one made of much more sophisticated materials and has strange red coloring." + item = /obj/item/clothing/accessory/cqd_holster/syndicate + cost = 1 + surplus = 30 diff --git a/tff_modular/modules/cqd_holsters/code/holster_storage.dm b/tff_modular/modules/cqd_holsters/code/holster_storage.dm new file mode 100644 index 00000000000..d0f092d6475 --- /dev/null +++ b/tff_modular/modules/cqd_holsters/code/holster_storage.dm @@ -0,0 +1,37 @@ +/datum/storage/cqd_holster_storage + max_slots = 1 + max_specific_storage = WEIGHT_CLASS_NORMAL + +// прок перезаписан "белого списка". +/datum/storage/cqd_holster_storage/can_insert(obj/item/to_insert, mob/user, messages = TRUE, force = FALSE) + . = ..() + if(is_type_in_typecache(to_insert, exception_hold)) + return TRUE + + +/// Хранилище для кобуры в котором прописано то, что можно будет в неё убрать +/datum/storage/cqd_holster_storage/New() + . = ..() + + // Объекты и их наследники которые по умолчанию можно будет убрать в кобуру. + // Важное уточнение! Объекты из этого списка не + // будут игнорировать размер и иные ограничения. + can_hold = typecacheof(list( + // Большая часть пистолетов и револьверов + /obj/item/gun/ballistic/revolver, + /obj/item/gun/ballistic/automatic/pistol, + + // Энергетические стволы, которые normal sized. + /obj/item/gun/energy, + )) + + // Объекты и их наследники которые по умолчанию НЕЛЬЗЯ будет убрать в кобуру. + cant_hold = typecacheof(list()) // Тут пока пусто... + + // Объекты и их наследники которые в любом случае можно будет убрать в кобуру. + // Важное уточнение! Объекты из этого списка БУДУТ игнорировать размер, "чёрный список" и иные ограничения. + exception_hold = typecacheof(list( + /obj/item/food/grown/banana, // Бананчег :D + )) + + diff --git a/tff_modular/modules/cqd_holsters/code/utility.dm b/tff_modular/modules/cqd_holsters/code/utility.dm new file mode 100644 index 00000000000..db756d931c6 --- /dev/null +++ b/tff_modular/modules/cqd_holsters/code/utility.dm @@ -0,0 +1,57 @@ +#define COMSIG_KB_HUMAN_CQD_HOLSTER_ACTION_DOWN "keybinding_human_cqd_holster_action_down" + +/* + Keybinding and verb +*/ + +/datum/keybinding/human/cqd_holster_action + hotkey_keys = list("Unbound") + name = "cqd_holster_action" + full_name = "CQD holster action" + description = "Quickly equip or hide your gun in CQD holster" + keybind_signal = COMSIG_KB_HUMAN_CQD_HOLSTER_ACTION_DOWN + +/datum/keybinding/human/cqd_holster_action/down(client/user) + . = ..() + if(.) + return + var/mob/living/carbon/human/H = user.mob + H.cqd_holster_action() + return TRUE + +/mob/living/carbon/human/verb/cqd_holster_action() + set name = "cqd-holster-action" + set hidden = TRUE + + DEFAULT_QUEUE_OR_CALL_VERB(VERB_CALLBACK(src, PROC_REF(execute_cqd_holster_action))) + +/* + Proc +*/ + +/mob/living/carbon/human/proc/execute_cqd_holster_action() + if(!can_perform_action(src, NEED_DEXTERITY|NEED_HANDS|ALLOW_RESTING)) + return + var/obj/item/clothing/under/u = get_item_by_slot(ITEM_SLOT_ICLOTHING) + if(!u) + return + var/obj/item/clothing/accessory/cqd_holster/holster + for(var/accessory in u.attached_accessories) + if(istype(accessory, /obj/item/clothing/accessory/cqd_holster)) + holster = accessory + break + if(!holster) + return + var/obj/item/item_in_hand = get_active_held_item() + if(item_in_hand) + holster.atom_storage.attempt_insert(item_in_hand, src) + else + if(length(holster.contents)) + var/obj/item/I = holster.contents[1] + if(I.attack_hand(src)) + visible_message(span_notice("[src] takes [I] out of [src]."), span_notice("You take [I] out of [holster].")) + else + to_chat(src, span_warning("You are not holding anything and the holster is empty!")) + return + + diff --git a/tff_modular/modules/cqd_holsters/icons/cqd_holster.dmi b/tff_modular/modules/cqd_holsters/icons/cqd_holster.dmi new file mode 100644 index 00000000000..1d41ff6853a Binary files /dev/null and b/tff_modular/modules/cqd_holsters/icons/cqd_holster.dmi differ diff --git a/tff_modular/modules/cqd_holsters/icons/cqd_holster_aesthetic.dmi b/tff_modular/modules/cqd_holsters/icons/cqd_holster_aesthetic.dmi new file mode 100644 index 00000000000..548b11398c9 Binary files /dev/null and b/tff_modular/modules/cqd_holsters/icons/cqd_holster_aesthetic.dmi differ diff --git a/tff_modular/modules/cqd_holsters/icons/cqd_holster_syndicate.dmi b/tff_modular/modules/cqd_holsters/icons/cqd_holster_syndicate.dmi new file mode 100644 index 00000000000..5c60a0c09d0 Binary files /dev/null and b/tff_modular/modules/cqd_holsters/icons/cqd_holster_syndicate.dmi differ diff --git a/tff_modular/modules/cqd_holsters/icons/cqd_holster_worn.dmi b/tff_modular/modules/cqd_holsters/icons/cqd_holster_worn.dmi new file mode 100644 index 00000000000..12ea7a4be29 Binary files /dev/null and b/tff_modular/modules/cqd_holsters/icons/cqd_holster_worn.dmi differ diff --git a/tff_modular/modules/cqd_holsters/icons/cqd_holster_worn_aesthetic.dmi b/tff_modular/modules/cqd_holsters/icons/cqd_holster_worn_aesthetic.dmi new file mode 100644 index 00000000000..8f60a7872b2 Binary files /dev/null and b/tff_modular/modules/cqd_holsters/icons/cqd_holster_worn_aesthetic.dmi differ diff --git a/tff_modular/modules/cqd_holsters/icons/cqd_holster_worn_syndicate.dmi b/tff_modular/modules/cqd_holsters/icons/cqd_holster_worn_syndicate.dmi new file mode 100644 index 00000000000..0dfdcbec04c Binary files /dev/null and b/tff_modular/modules/cqd_holsters/icons/cqd_holster_worn_syndicate.dmi differ diff --git a/tff_modular/modules/emotes/code/dna_screams.dm b/tff_modular/modules/emotes/code/dna_screams.dm new file mode 100644 index 00000000000..e44dc9cedec --- /dev/null +++ b/tff_modular/modules/emotes/code/dna_screams.dm @@ -0,0 +1,3 @@ +/datum/species/teshari/alt + screamsounds = list('tff_modular/modules/emotes/sounds/teshariscream.ogg') + femalescreamsounds = null diff --git a/tff_modular/modules/emotes/code/emotes.dm b/tff_modular/modules/emotes/code/emotes.dm new file mode 100644 index 00000000000..920d8424d47 --- /dev/null +++ b/tff_modular/modules/emotes/code/emotes.dm @@ -0,0 +1,9 @@ +/datum/emote/living/cough/get_sound(mob/living/user) + if(istesharialt(user)) + return 'tff_modular/modules/emotes/sounds/tesharicough.ogg' + ..(user) + +/datum/emote/living/sneeze/get_sound(mob/living/user) + if(istesharialt(user)) + return 'tff_modular/modules/emotes/sounds/tesharisneeze.ogg' + ..(user) diff --git a/tff_modular/modules/emotes/code/laugh_datums.dm b/tff_modular/modules/emotes/code/laugh_datums.dm new file mode 100644 index 00000000000..30f66a04fbb --- /dev/null +++ b/tff_modular/modules/emotes/code/laugh_datums.dm @@ -0,0 +1,5 @@ +/datum/laugh_type/teshari_alt + name = "Teshari laugh" + male_laughsounds = list('tff_modular/modules/emotes/sounds/tesharilaugh.ogg') + female_laughsounds = null + diff --git a/tff_modular/modules/emotes/code/scream_datums.dm b/tff_modular/modules/emotes/code/scream_datums.dm new file mode 100644 index 00000000000..f51ab6f26e0 --- /dev/null +++ b/tff_modular/modules/emotes/code/scream_datums.dm @@ -0,0 +1,4 @@ +/datum/scream_type/teshari_alt + name = "Teshari scream" + male_screamsounds = list('tff_modular/modules/emotes/sounds/teshariscream.ogg') + female_screamsounds = null diff --git a/tff_modular/modules/emotes/sounds/tesharicough.ogg b/tff_modular/modules/emotes/sounds/tesharicough.ogg new file mode 100644 index 00000000000..dfc594c73c1 Binary files /dev/null and b/tff_modular/modules/emotes/sounds/tesharicough.ogg differ diff --git a/tff_modular/modules/emotes/sounds/tesharilaugh.ogg b/tff_modular/modules/emotes/sounds/tesharilaugh.ogg new file mode 100644 index 00000000000..58649042fab Binary files /dev/null and b/tff_modular/modules/emotes/sounds/tesharilaugh.ogg differ diff --git a/tff_modular/modules/emotes/sounds/teshariscream.ogg b/tff_modular/modules/emotes/sounds/teshariscream.ogg new file mode 100644 index 00000000000..c48996bf82a Binary files /dev/null and b/tff_modular/modules/emotes/sounds/teshariscream.ogg differ diff --git a/tff_modular/modules/emotes/sounds/tesharisneeze.ogg b/tff_modular/modules/emotes/sounds/tesharisneeze.ogg new file mode 100644 index 00000000000..55827fcf134 Binary files /dev/null and b/tff_modular/modules/emotes/sounds/tesharisneeze.ogg differ diff --git a/tff_modular/modules/teshari_reborn/code/abilites/agility.dm b/tff_modular/modules/teshari_reborn/code/abilites/agility.dm new file mode 100644 index 00000000000..e13906680bb --- /dev/null +++ b/tff_modular/modules/teshari_reborn/code/abilites/agility.dm @@ -0,0 +1,34 @@ +#define AGILITY_DEFAULT_COOLDOWN_TIME 4 SECONDS +#define AGILITY_MODE_ABOVE "agility_mode_above" +#define AGILITY_MODE_BELOW "agility_mode_below" +#define AGILITY_MODE_IGNORE "agility_mode_ignore" + +/datum/action/cooldown/teshari/agility + name = "Toggle agility" + desc = "Toggle you agility" + cooldown_time = AGILITY_DEFAULT_COOLDOWN_TIME + current_mode = AGILITY_MODE_IGNORE + button_icon_state = AGILITY_MODE_IGNORE + +/datum/action/cooldown/teshari/agility/Activate(atom/target) + . = ..() + + if(current_mode == AGILITY_MODE_IGNORE) + update_button_state(AGILITY_MODE_ABOVE) + owner.balloon_alert(owner, "Moving above!") + passtable_on(owner, INNATE_TRAIT) + current_mode = AGILITY_MODE_ABOVE + + else if(current_mode == AGILITY_MODE_ABOVE) + update_button_state(AGILITY_MODE_IGNORE) + owner.balloon_alert(owner, "Ignoring!") + passtable_off(owner, INNATE_TRAIT) + current_mode = AGILITY_MODE_IGNORE + + return TRUE + +#undef AGILITY_DEFAULT_COOLDOWN_TIME +#undef AGILITY_MODE_ABOVE +#undef AGILITY_MODE_BELOW +#undef AGILITY_MODE_IGNORE + diff --git a/tff_modular/modules/teshari_reborn/code/abilites/echolocation.dm b/tff_modular/modules/teshari_reborn/code/abilites/echolocation.dm new file mode 100644 index 00000000000..3fcb567a3b9 --- /dev/null +++ b/tff_modular/modules/teshari_reborn/code/abilites/echolocation.dm @@ -0,0 +1,119 @@ +#define ECHOLOCATION_MAX_CREATURE 5 +#define ECHOLOCATION_BASE_COOLDWN_TIME 10 SECONDS +#define ECHOLOCATION_PING_COOLDOWN 3 SECONDS +#define ECHOLOCATION_RANGE 9 + +/datum/action/cooldown/teshari/echolocation + name = "Toggle echolocation" + desc = "Use your ears to hear the creatures around you." + + cooldown_time = ECHOLOCATION_BASE_COOLDWN_TIME + var/active = FALSE + var/cycle_cooldown = ECHOLOCATION_BASE_COOLDWN_TIME + COOLDOWN_DECLARE(echolocation_ping_cooldown) + +/datum/action/cooldown/teshari/echolocation/New(Target, original) + . = ..() + button_icon_state = "echolocation_off" + +/datum/action/cooldown/teshari/echolocation/Destroy() + . = ..() + if(active) + deisable_echolocation() + +/datum/action/cooldown/teshari/echolocation/Activate(atom/target) + if(!owner) + return FALSE + var/mob/living/carbon/human/tesh = owner + cooldown_time = ECHOLOCATION_BASE_COOLDWN_TIME + if(!tesh.can_hear()) + tesh.balloon_alert("Can't hear!") + return TRUE + + if(active) + deisable_echolocation() + return TRUE + + enable_echolocation() + return FALSE + +/datum/action/cooldown/teshari/echolocation/proc/enable_echolocation() + active = TRUE + name = "Toggle echolocation : enabled" + var/mob/living/carbon/human/tesh = owner + + tesh.visible_message(span_notice("[tesh.name], pricked up [tesh.p_their()] ears. Listening to the surroundings."), span_notice("You got your ears perked up listening to your surroundings.")) + tesh.balloon_alert(tesh, "Start echolocation!") + + update_button_state("echolocation_on") + START_PROCESSING(SSobj, src) + RegisterSignal(owner, COMSIG_CARBON_SOUNDBANG, PROC_REF(soundbang_act)) + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(deisable_echolocation)) + +/datum/action/cooldown/teshari/echolocation/proc/deisable_echolocation() + SIGNAL_HANDLER + + active = FALSE + name = "Toggle echolocation" + var/mob/living/carbon/human/tesh = owner + + tesh.visible_message(span_notice("[tesh.name], returned [tesh.p_their()] ears to normal. "), span_notice("You got your ears back to normal.")) + tesh.balloon_alert(tesh, "Stop echolocation!") + + update_button_state("echolocation_off") + STOP_PROCESSING(SSobj, src) + UnregisterSignal(owner, list(COMSIG_CARBON_SOUNDBANG, COMSIG_LIVING_DEATH)) + StartCooldown() + +/datum/action/cooldown/teshari/echolocation/process(seconds_per_tick) + . = ..() + if(!active) + return + + var/mob/living/carbon/human/tesh = owner + if(!tesh.can_hear()) + tesh.balloon_alert("Can't hear!") + deisable_echolocation() + return + + if(!COOLDOWN_FINISHED(src, echolocation_ping_cooldown)) + return + COOLDOWN_START(src, echolocation_ping_cooldown, ECHOLOCATION_PING_COOLDOWN) + + var/founding_creature = 0 + for(var/mob/living/creature in range(ECHOLOCATION_RANGE, owner)) + if(creature == owner || creature.stat == DEAD) + continue + if(founding_creature >= ECHOLOCATION_MAX_CREATURE) + break + new /obj/effect/temp_visual/sonar_ping/tesh(owner.loc, owner, creature) + founding_creature++ + +/datum/action/cooldown/teshari/echolocation/proc/soundbang_act(intensity) + SIGNAL_HANDLER + if(!owner || isdead(owner)) + return FALSE + if(!active) + return FALSE + + var/mob/living/carbon/human/tesh = owner + var/stun_time = rand(4, 8) SECONDS + + cooldown_time *= (stun_time/20) + tesh.Paralyze(stun_time/2) + tesh.Knockdown(stun_time) + var/obj/item/organ/internal/ears/E = tesh.get_organ_slot(ORGAN_SLOT_EARS) + E.apply_organ_damage(40) + + to_chat(tesh, span_userdanger("Your ears fill with pain as the horrible noise hits them!")) + deisable_echolocation() + return TRUE + +/obj/effect/temp_visual/sonar_ping/tesh + real_icon_state = "blip" + duration = 1 SECONDS + +#undef ECHOLOCATION_MAX_CREATURE +#undef ECHOLOCATION_BASE_COOLDWN_TIME +#undef ECHOLOCATION_RANGE +#undef ECHOLOCATION_PING_COOLDOWN diff --git a/tff_modular/modules/teshari_reborn/code/abilites/teshari_ability.dm b/tff_modular/modules/teshari_reborn/code/abilites/teshari_ability.dm new file mode 100644 index 00000000000..dfa8234ef88 --- /dev/null +++ b/tff_modular/modules/teshari_reborn/code/abilites/teshari_ability.dm @@ -0,0 +1,16 @@ +/datum/action/cooldown/teshari + button_icon = 'tff_modular/modules/teshari_reborn/icons/actions.dmi' + var/current_mode + +/datum/action/cooldown/teshari/IsAvailable(feedback) + . = ..() + if(!.) + return FALSE + + var/mob/living/carbon/human/tesh = owner + if(!istesharialt(tesh) || isdead(tesh)) + return FALSE + +/datum/action/cooldown/teshari/proc/update_button_state(new_state) + button_icon_state = new_state + owner.update_action_buttons() diff --git a/tff_modular/modules/teshari_reborn/code/teshari.dm b/tff_modular/modules/teshari_reborn/code/teshari.dm new file mode 100644 index 00000000000..e6317be4a4f --- /dev/null +++ b/tff_modular/modules/teshari_reborn/code/teshari.dm @@ -0,0 +1,152 @@ +#define TESHARI_ALT_HEATMOD 1.5 +#define TESHARI_ALT_COLDMOD 0.20 +#define TEHSARI_ALT_TEMP_OFFSET -50 + +/** + * ТЕШАРИ - ПЕРЕРАБОТАННЫЕ + * + * Главный файл обьявляющий новую специю. Заменяет собой прошлых тешари. + * Все файлы связанные со специей обозначаются, как teshari/alt - для удобства. + */ + + +/datum/species/teshari/alt + name = "Teshari" + id = SPECIES_TESHARI_ALT + eyes_icon = 'modular_skyrat/modules/organs/icons/teshari_eyes.dmi' + inherent_traits = list( + TRAIT_ADVANCEDTOOLUSER, + TRAIT_CAN_STRIP, + TRAIT_LITERATE, + TRAIT_MUTANT_COLORS, + TRAIT_NO_UNDERWEAR, + TRAIT_HAS_MARKINGS, + TRAIT_NO_BLOOD_OVERLAY, + TRAIT_WEAK_BODY, + TRAIT_CAN_BUCKLED_TO_HAND, + TRAIT_CAN_ENTER_BAG + ) + digitigrade_customization = DIGITIGRADE_NEVER + custom_worn_icons = list( + LOADOUT_ITEM_HEAD = TESHARI_HEAD_ICON, + LOADOUT_ITEM_MASK = TESHARI_MASK_ICON, + LOADOUT_ITEM_NECK = TESHARI_NECK_ICON, + LOADOUT_ITEM_SUIT = TESHARI_SUIT_ICON, + LOADOUT_ITEM_UNIFORM = TESHARI_UNIFORM_ICON, + LOADOUT_ITEM_HANDS = TESHARI_HANDS_ICON, + LOADOUT_ITEM_SHOES = TESHARI_FEET_ICON, + LOADOUT_ITEM_GLASSES = TESHARI_EYES_ICON, + LOADOUT_ITEM_BELT = TESHARI_BELT_ICON, + LOADOUT_ITEM_MISC = TESHARI_BACK_ICON, + LOADOUT_ITEM_ACCESSORY = TESHARI_ACCESSORIES_ICON, + LOADOUT_ITEM_EARS = TESHARI_EARS_ICON + ) + default_mutant_bodyparts = list( + "legs" = "Normal Legs" + ) + coldmod = TESHARI_ALT_COLDMOD + heatmod = TESHARI_ALT_HEATMOD + bodytemp_normal = BODYTEMP_NORMAL + (TEHSARI_ALT_TEMP_OFFSET/2) + bodytemp_heat_damage_limit = (BODYTEMP_HEAT_DAMAGE_LIMIT + (TEHSARI_ALT_TEMP_OFFSET/2)) + bodytemp_cold_damage_limit = (BODYTEMP_COLD_DAMAGE_LIMIT + TEHSARI_ALT_TEMP_OFFSET) + mutanttongue = /obj/item/organ/internal/tongue/teshari/alt + bodypart_overrides = list( + BODY_ZONE_HEAD = /obj/item/bodypart/head/mutant/teshari/alt, + BODY_ZONE_CHEST = /obj/item/bodypart/chest/mutant/teshari/alt, + BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left/mutant/teshari/alt, + BODY_ZONE_R_ARM = /obj/item/bodypart/arm/right/mutant/teshari/alt, + BODY_ZONE_L_LEG = /obj/item/bodypart/leg/left/mutant/teshari/alt, + BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/mutant/teshari/alt, + ) + + var/datum/action/cooldown/teshari/agility/teshari_agility + var/datum/action/cooldown/teshari/echolocation/teshari_echolocation + +/datum/species/teshari/alt/on_species_gain(mob/living/carbon/human/C, datum/species/old_species, pref_load) + . = ..() + teshari_agility = new(C) + teshari_agility.Grant(C) + teshari_echolocation = new(C) + teshari_echolocation.Grant(C) + + C.AddComponent(/datum/component/weak_body) + C.mob_size = MOB_SIZE_SMALL + +/datum/species/teshari/alt/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load) + . = ..() + teshari_agility.Destroy() + teshari_echolocation.Destroy() + C.mob_size = initial(C.mob_size) + +/datum/species/teshari/alt/randomize_features(mob/living/carbon/human/human_mob) + . = ..() + var/main_color = pick(COLOR_GRAY, COLOR_DARK_BROWN, COLOR_ALMOST_BLACK, COLOR_DARK_RED, COLOR_DARK_CYAN) + var/second_color = pick(COLOR_WHITE, COLOR_BLACK, COLOR_BLUE, COLOR_VIOLET) + human_mob.dna.features["mcolor"] = main_color + human_mob.dna.features["mcolor2"] = second_color + human_mob.dna.features["mcolor3"] = second_color + +/datum/species/teshari/alt/create_pref_unique_perks() + var/list/perk_descriptions = list() + + perk_descriptions += list(list( + SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK, + SPECIES_PERK_ICON = "dna", + SPECIES_PERK_NAME = "Extremely weak body", + SPECIES_PERK_DESC = "Tesharies body is extemely weak. They take A LOT OF DAMAGE from everything." + )) + + perk_descriptions += list(list( + SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK, + SPECIES_PERK_ICON = "dna", + SPECIES_PERK_NAME = "Frailty", + SPECIES_PERK_DESC = "The Teshari are weak. They cannot use heavy weapons, or carry larger loads without special equipment. Neither can they pull other bodies on top of them." + )) + + perk_descriptions += list(list( + SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK, + SPECIES_PERK_ICON = "dna", + SPECIES_PERK_NAME = "Extreme heat weakness", + SPECIES_PERK_DESC = "Teshari are extremely unstable to heat..." + )) + + perk_descriptions += list(list( + SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK, + SPECIES_PERK_ICON = "dna", + SPECIES_PERK_NAME = "Pure robust", + SPECIES_PERK_DESC = "Teshari can't push creatures bigger than them. Nor can they fight properly." + )) + + perk_descriptions += list(list( + SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, + SPECIES_PERK_ICON = "dna", + SPECIES_PERK_NAME = "Smol", + SPECIES_PERK_DESC = "Teshari is smol. Other creatures can pick them up, or put them in a bag." + )) + + perk_descriptions += list(list( + SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, + SPECIES_PERK_ICON = "user-plus", + SPECIES_PERK_NAME = "Robust cold protect", + SPECIES_PERK_DESC = "Teshari are incredibly resistant to low temperatures." + )) + + perk_descriptions += list(list( + SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, + SPECIES_PERK_ICON = "user-plus", + SPECIES_PERK_NAME = "Agility", + SPECIES_PERK_DESC = "Teshari are incredibly maneuverable, easily able to climb on, or under, tables. They are also faster than most other creatures." + )) + + perk_descriptions += list(list( + SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, + SPECIES_PERK_ICON = "user-plus", + SPECIES_PERK_NAME = "Clear hearing", + SPECIES_PERK_DESC = "Teshari - have clear hearing, allowing them to hear creatures around them, pinpointing locations." + )) + + return perk_descriptions + +/mob/living/carbon/human/species/teshari/alt + race = /datum/species/teshari/alt + diff --git a/tff_modular/modules/teshari_reborn/code/teshari_bodypart.dm b/tff_modular/modules/teshari_reborn/code/teshari_bodypart.dm new file mode 100644 index 00000000000..fcd53c95a30 --- /dev/null +++ b/tff_modular/modules/teshari_reborn/code/teshari_bodypart.dm @@ -0,0 +1,87 @@ +#define TESHARI_ALT_PUNCH_LOW 3 +#define TESHARI_ALT_PUNCH_HIGH 5 +// Значительно увеличенный урон! +#define TESHARI_ALT_BURN_MODIFIER 1.4 +#define TESHARI_ALT_BRUTE_MODIFIER 1.4 +#define TESHARI_ALT_SPEED_MODIFIER -0.3 +// Пути к файлам +#define TESHARI_ALT_HUSK_ICON 'tff_modular/modules/teshari_reborn/icons/mob/bodyparts/husk_bodyparts.dmi' + +/obj/item/organ/internal/tongue/teshari/alt + liked_foodtypes = MEAT | GORE + disliked_foodtypes = GROSS | GRAIN + +// Тешари <3, альтернативные. +/obj/item/bodypart/head/mutant/teshari/alt + husk_type = "teshari" + icon_husk = TESHARI_ALT_HUSK_ICON + max_damage = 175 // -75 от нормального. + brute_modifier = TESHARI_ALT_BRUTE_MODIFIER + burn_modifier = TESHARI_ALT_BURN_MODIFIER + head_flags = HEAD_EYESPRITES|HEAD_EYECOLOR|HEAD_EYEHOLES|HEAD_DEBRAIN + +/obj/item/bodypart/chest/mutant/teshari/alt + husk_type = "teshari" + icon_husk = TESHARI_ALT_HUSK_ICON + max_damage = 175 + brute_modifier = TESHARI_ALT_BRUTE_MODIFIER + burn_modifier = TESHARI_ALT_BURN_MODIFIER + +/obj/item/bodypart/arm/left/mutant/teshari/alt + husk_type = "teshari" + icon_husk = TESHARI_ALT_HUSK_ICON + max_damage = 40 + unarmed_damage_low = TESHARI_ALT_PUNCH_LOW + unarmed_damage_high = TESHARI_ALT_PUNCH_HIGH + brute_modifier = TESHARI_ALT_BRUTE_MODIFIER + burn_modifier = TESHARI_ALT_BURN_MODIFIER + +/obj/item/bodypart/arm/right/mutant/teshari/alt + husk_type = "teshari" + icon_husk = TESHARI_ALT_HUSK_ICON + max_damage = 40 + unarmed_damage_low = TESHARI_ALT_PUNCH_LOW + unarmed_damage_high = TESHARI_ALT_PUNCH_HIGH + brute_modifier = TESHARI_ALT_BRUTE_MODIFIER + burn_modifier = TESHARI_ALT_BURN_MODIFIER + +/obj/item/bodypart/leg/left/mutant/teshari/alt + husk_type = "teshari" + icon_husk = TESHARI_ALT_HUSK_ICON + max_damage = 40 + digitigrade_type = /obj/item/bodypart/leg/left/digitigrade/teshari/alt + brute_modifier = TESHARI_ALT_BRUTE_MODIFIER + burn_modifier = TESHARI_ALT_BURN_MODIFIER + speed_modifier = TESHARI_ALT_SPEED_MODIFIER + +/obj/item/bodypart/leg/right/mutant/teshari/alt + husk_type = "teshari" + icon_husk = TESHARI_ALT_HUSK_ICON + max_damage = 40 + digitigrade_type = /obj/item/bodypart/leg/right/digitigrade/teshari/alt + brute_modifier = TESHARI_ALT_BRUTE_MODIFIER + burn_modifier = TESHARI_ALT_BURN_MODIFIER + speed_modifier = TESHARI_ALT_SPEED_MODIFIER + +/obj/item/bodypart/leg/left/digitigrade/teshari/alt + husk_type = "teshari" + icon_husk = TESHARI_ALT_HUSK_ICON + max_damage = 40 + brute_modifier = TESHARI_ALT_BRUTE_MODIFIER + burn_modifier = TESHARI_ALT_BURN_MODIFIER + speed_modifier = TESHARI_ALT_SPEED_MODIFIER + +/obj/item/bodypart/leg/right/digitigrade/teshari/alt + husk_type = "teshari" + icon_husk = TESHARI_ALT_HUSK_ICON + max_damage = 40 + brute_modifier = TESHARI_ALT_BRUTE_MODIFIER + burn_modifier = TESHARI_ALT_BURN_MODIFIER + speed_modifier = TESHARI_ALT_SPEED_MODIFIER + +#undef TESHARI_ALT_PUNCH_LOW +#undef TESHARI_ALT_PUNCH_HIGH +#undef TESHARI_ALT_BURN_MODIFIER +#undef TESHARI_ALT_BRUTE_MODIFIER +#undef TESHARI_ALT_HUSK_ICON +#undef TESHARI_ALT_SPEED_MODIFIER diff --git a/tff_modular/modules/teshari_reborn/icons/actions.dmi b/tff_modular/modules/teshari_reborn/icons/actions.dmi new file mode 100644 index 00000000000..f5a1db5f0f3 Binary files /dev/null and b/tff_modular/modules/teshari_reborn/icons/actions.dmi differ diff --git a/tff_modular/modules/teshari_reborn/icons/mob/bodyparts/husk_bodyparts.dmi b/tff_modular/modules/teshari_reborn/icons/mob/bodyparts/husk_bodyparts.dmi new file mode 100644 index 00000000000..40d50a62716 Binary files /dev/null and b/tff_modular/modules/teshari_reborn/icons/mob/bodyparts/husk_bodyparts.dmi differ diff --git a/tff_modular/modules/teshari_reborn/icons/teshari_health.dmi b/tff_modular/modules/teshari_reborn/icons/teshari_health.dmi new file mode 100644 index 00000000000..01887b31521 Binary files /dev/null and b/tff_modular/modules/teshari_reborn/icons/teshari_health.dmi differ diff --git a/tgui/packages/tgui/interfaces/AntagInfoCyberAuth.tsx b/tgui/packages/tgui/interfaces/AntagInfoCyberAuth.tsx new file mode 100644 index 00000000000..21d872ed6a0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoCyberAuth.tsx @@ -0,0 +1,75 @@ +import { useBackend } from '../backend'; +import { Divider, Section, Stack } from '../components'; +import { Window } from '../layouts'; +import { Objective } from './common/Objectives'; + +type Info = { + antag_name: string; + objectives: Objective[]; +}; + +const textStyles = { + variable: { + color: 'white', + }, + danger: { + color: 'red', + }, +} as const; + +export const AntagInfoCyberAuth = (props, context) => { + const { data } = useBackend<Info>(context); + const { objectives = [] } = data; + + return ( + <Window width={350} height={450} theme="ntos_terminal"> + <Window.Content> + <Section scrollable fill> + <Stack fill vertical> + <Stack.Item>FN CYBER AUTHORITY UNIT (REF)</Stack.Item> + <Divider /> + <Stack.Item mb={1} bold fontSize="16px"> + <span style={textStyles.variable}> + You are a cyber authority unit. + </span> + </Stack.Item> + <Stack.Item> + Your mission: <span style={textStyles.variable}>Eliminate</span>{' '} + organic intruders to maintain the integrity of the system. + </Stack.Item> + <Stack.Item mb={1}> + <span style={textStyles.danger}>Bitrunning</span> is a crime. To + assist your task, your program has been loaded with cutting edge{' '} + <span style={textStyles.variable}>martial arts</span> skills. + </Stack.Item> + <Stack.Item grow> + Ranged weaponry is{' '} + <span style={textStyles.danger}>forbidden</span>. Ballistic + defense is frowned upon. Style is paramount. + </Stack.Item> + <Stack.Item> + <marquee scrollamount="2">{objectives[0].explanation}</marquee> + </Stack.Item> + <Divider /> + <Stack.Item> + const <span style={textStyles.variable}>TARGETS</span> ={' '} + </Stack.Item> + <Stack.Item> + <span style={textStyles.variable}>system.</span> + <span style={textStyles.danger}>INTRUDERS</span>; + </Stack.Item> + <Stack.Item> + while <span style={textStyles.variable}>TARGETS</span>.LIFE !={' '} + <span style={textStyles.variable}>stat.</span>DEAD + </Stack.Item> + <Stack.Item> + <span style={textStyles.variable}>action.</span> + <span style={textStyles.danger}>KILL()</span> + </Stack.Item> + <Stack.Item>cyber_authority_unit([0x70cf4020])</Stack.Item> + </Stack> + </Section> + </Window.Content> + </Window> + ); +}; diff --git a/tgui/packages/tgui/interfaces/AntagInfoNinja.tsx b/tgui/packages/tgui/interfaces/AntagInfoNinja.tsx new file mode 100644 index 00000000000..a537888af75 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoNinja.tsx @@ -0,0 +1,68 @@ +import { BooleanLike } from 'common/react'; +import { useBackend } from '../backend'; +import { Icon, Section, Stack } from '../components'; +import { Window } from '../layouts'; +import { ObjectivePrintout, Objective, ReplaceObjectivesButton } from './common/Objectives'; + +const ninja_emphasis = { + color: 'red', +}; + +type NinjaInfo = { + objectives: Objective[]; + can_change_objective: BooleanLike; +}; + +export const AntagInfoNinja = (props, context) => { + const { data } = useBackend<NinjaInfo>(context); + const { objectives, can_change_objective } = data; + return ( + <Window width={550} height={450} theme="hackerman"> + <Window.Content> + <Icon + size={30} + name="spider" + color="#003300" + position="absolute" + top="10%" + left="10%" + /> + <Section scrollable fill> + <Stack vertical textColor="green"> + <Stack.Item textAlign="center" fontSize="20px"> + I am an elite mercenary of the Spider Clan. + <br />A <span style={ninja_emphasis}> SPACE NINJA</span>! + </Stack.Item> + <Stack.Item textAlign="center" italic> + Surprise is my weapon. Shadows are my armor. Without them, I am + nothing. + </Stack.Item> + <Stack.Item> + <Section fill> + Your advanced ninja suit contains many powerful modules. + <br /> It can be recharged by right clicking on station APCs or + other power sources, in order to drain their battery. + <br /> + Right clicking on some kinds of machines or items wearing your + suit will hack them, to varying effect. Experiment and find out + what you can do! + </Section> + </Stack.Item> + <Stack.Item> + <ObjectivePrintout + objectives={objectives} + objectiveFollowup={ + <ReplaceObjectivesButton + can_change_objective={can_change_objective} + button_title={'Adapt Mission Parameters'} + button_colour={'green'} + /> + } + /> + </Stack.Item> + </Stack> + </Section> + </Window.Content> + </Window> + ); +}; diff --git a/tgui/packages/tgui/interfaces/AvatarHelp.tsx b/tgui/packages/tgui/interfaces/AvatarHelp.tsx new file mode 100644 index 00000000000..647d3a2e22b --- /dev/null +++ b/tgui/packages/tgui/interfaces/AvatarHelp.tsx @@ -0,0 +1,122 @@ +import { useBackend } from '../backend'; +import { Box, Icon, Section, Stack } from '../components'; +import { Window } from '../layouts'; + +type Data = { + help_text: string; +}; + +const DEFAULT_HELP = `No information available! Ask for assistance if needed.`; + +const boxHelp = [ + { + color: 'purple', + text: 'Study the area and do what needs to be done to recover the crate. Pay close attention to domain information and context clues.', + icon: 'search-location', + title: 'Search', + }, + { + color: 'green', + text: 'Bring the crate to the designated sending location in the safehouse. The area may seem out of place. Examine the safehouse to find it.', + icon: 'boxes', + 'title': 'Recover', + }, + { + color: 'blue', + text: 'The ladder represents the safest way to disconnect before the cache is recovered. Should your connection sever, the netpod offers limited resuscitation potential.', + icon: 'plug', + title: 'Disconnect', + }, + { + color: 'yellow', + text: 'While connected, you are somewhat safe from environmental hazards and intrusions, but not completely. Pay close attention to alerts.', + icon: 'id-badge', + title: 'Security', + }, + { + color: 'gold', + text: 'Generating avatars costs tremendous bandwidth. Do not waste them.', + icon: 'coins', + title: 'Limited Attempts', + }, + { + color: 'red', + text: 'Remember that you are physically linked to this presence. You are a foreign body in a hostile environment. It will attempt to forcefully eject you.', + icon: 'skull-crossbones', + title: 'Realized Danger', + }, +] as const; + +export const AvatarHelp = (props, context) => { + const { data } = useBackend<Data>(context); + const { help_text = DEFAULT_HELP } = data; + + return ( + <Window title="Domain Information" width={600} height={600}> + <Window.Content> + <Stack fill vertical> + <Stack.Item grow> + <Section + color="good" + fill + scrollable + title="Welcome to the Virtual Domain."> + {help_text} + </Section> + </Stack.Item> + <Stack.Item grow={4}> + <Stack fill vertical> + <Stack.Item grow> + <Stack fill> + {[0, 1].map((i) => ( + <BoxHelp index={i} key={i} /> + ))} + </Stack> + </Stack.Item> + <Stack.Item grow> + <Stack fill> + {[2, 3].map((i) => ( + <BoxHelp index={i} key={i} /> + ))} + </Stack> + </Stack.Item> + <Stack.Item grow> + <Stack fill> + {[4, 5].map((i) => ( + <BoxHelp index={i} key={i} /> + ))} + </Stack> + </Stack.Item> + </Stack> + </Stack.Item> + </Stack> + </Window.Content> + </Window> + ); +}; + +// I wish I had media queries +const BoxHelp = (props: { index: number }, context) => { + const { index } = props; + + return ( + <Stack.Item grow> + <Section + color="label" + fill + minHeight={10} + title={ + <Stack align="center"> + <Icon + color={boxHelp[index].color} + mr={1} + name={boxHelp[index].icon} + /> + <Box>{boxHelp[index].title}</Box> + </Stack> + }> + {boxHelp[index].text} + </Section> + </Stack.Item> + ); +}; diff --git a/tgui/packages/tgui/interfaces/CameraConsole.tsx b/tgui/packages/tgui/interfaces/CameraConsole.tsx new file mode 100644 index 00000000000..b1077f6bdcb --- /dev/null +++ b/tgui/packages/tgui/interfaces/CameraConsole.tsx @@ -0,0 +1,199 @@ +import { filter, sortBy } from 'common/collections'; +import { flow } from 'common/fp'; +import { BooleanLike, classes } from 'common/react'; +import { createSearch } from 'common/string'; +import { useBackend, useLocalState } from '../backend'; +import { Button, ByondUi, Input, NoticeBox, Section, Stack } from '../components'; +import { Window } from '../layouts'; + +type Data = { + can_spy: BooleanLike; + mapRef: string; + cameras: Camera[]; + activeCamera: Camera & { status: BooleanLike }; + network: string[]; +}; + +type Camera = { + name: string; +}; + +/** + * Returns previous and next camera names relative to the currently + * active camera. + */ +const prevNextCamera = ( + cameras: Camera[], + activeCamera: Camera & { status: BooleanLike } +) => { + if (!activeCamera) { + return []; + } + const index = cameras.findIndex( + (camera) => camera?.name === activeCamera.name + ); + return [cameras[index - 1]?.name, cameras[index + 1]?.name]; +}; + +/** + * Camera selector. + * + * Filters cameras, applies search terms and sorts the alphabetically. + */ +const selectCameras = (cameras: Camera[], searchText = ''): Camera[] => { + const testSearch = createSearch(searchText, (camera: Camera) => camera.name); + + return flow([ + // Null camera filter + filter((camera: Camera) => !!camera?.name), + // Optional search term + searchText && filter(testSearch), + // Slightly expensive, but way better than sorting in BYOND + sortBy((camera: Camera) => camera.name), + ])(cameras); +}; + +export const CameraConsole = (props, context) => { + return ( + <Window width={850} height={708}> + <Window.Content> + <CameraContent /> + </Window.Content> + </Window> + ); +}; + +export const CameraContent = (props, context) => { + return ( + <Stack fill> + <Stack.Item grow> + <CameraSelector /> + </Stack.Item> + <Stack.Item grow={3}> + <CameraControls /> + </Stack.Item> + </Stack> + ); +}; + +const CameraSelector = (props, context) => { + const { act, data } = useBackend<Data>(context); + const [searchText, setSearchText] = useLocalState(context, 'searchText', ''); + const { activeCamera } = data; + const cameras = selectCameras(data.cameras, searchText); + + return ( + <Stack fill vertical> + <Stack.Item> + <Input + autoFocus + fluid + mt={1} + placeholder="Search for a camera" + onInput={(e, value) => setSearchText(value)} + /> + </Stack.Item> + <Stack.Item grow> + <Section fill scrollable> + {cameras.map((camera) => ( + // We're not using the component here because performance + // would be absolutely abysmal (50+ ms for each re-render). + <div + key={camera.name} + title={camera.name} + className={classes([ + 'candystripe', + 'Button', + 'Button--fluid', + 'Button--color--transparent', + 'Button--ellipsis', + activeCamera && + camera.name === activeCamera.name && + 'Button--selected', + ])} + onClick={() => + act('switch_camera', { + name: camera.name, + }) + }> + {camera.name} + </div> + ))} + </Section> + </Stack.Item> + </Stack> + ); +}; + +const CameraControls = (props, context) => { + const { act, data } = useBackend<Data>(context); + const { activeCamera, can_spy, mapRef } = data; + const cameras = selectCameras(data.cameras); + + const [prevCameraName, nextCameraName] = prevNextCamera( + cameras, + activeCamera + ); + + return ( + <Section fill> + <Stack fill vertical> + <Stack.Item> + <Stack fill> + <Stack.Item grow> + {activeCamera?.name ? ( + <NoticeBox info>{activeCamera.name}</NoticeBox> + ) : ( + <NoticeBox danger>No input signal</NoticeBox> + )} + </Stack.Item> + + <Stack.Item> + {!!can_spy && ( + <Button + icon="magnifying-glass" + tooltip="Track Person" + onClick={() => act('start_tracking')} + /> + )} + </Stack.Item> + + <Stack.Item> + <Button + icon="chevron-left" + disabled={!prevCameraName} + onClick={() => + act('switch_camera', { + name: prevCameraName, + }) + } + /> + </Stack.Item> + + <Stack.Item> + <Button + icon="chevron-right" + disabled={!nextCameraName} + onClick={() => + act('switch_camera', { + name: nextCameraName, + }) + } + /> + </Stack.Item> + </Stack> + </Stack.Item> + <Stack.Item grow> + <ByondUi + height="100%" + width="100%" + params={{ + id: mapRef, + type: 'map', + }} + /> + </Stack.Item> + </Stack> + </Section> + ); +}; diff --git a/tgui/packages/tgui/interfaces/DelamProcedure.tsx b/tgui/packages/tgui/interfaces/DelamProcedure.tsx new file mode 100644 index 00000000000..f51c910d672 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DelamProcedure.tsx @@ -0,0 +1,105 @@ +import { Section, BlockQuote, Box, NoticeBox } from '../components'; +import { Window } from '../layouts'; + +export const DelamProcedure = (context) => { + return ( + <Window + title="Safety Moth - Delamination Emergency Procedure" + width={666} + height={865} + theme="dark"> + <Window.Content> + <Section title="NT-approved delam emergency procedure"> + <NoticeBox danger m={2}> + <b> + So you've found yourself in a bit of a pickle with a + delamination of a supermatter reactor. + <br /> + <br /> + Don't worry, saving the day is just a few steps away! + </b> + </NoticeBox> + <BlockQuote m={2}> + Locate the ever-elusive red emergency stop button. It's + probably hiding in plain sight, so take your time, have a laugh, and + enjoy the anticipation. Remember, it's like a treasure hunt, + only with the added bonus of preventing a nuclear disaster. + </BlockQuote> + <BlockQuote m={2}> + Once you've uncovered the button, muster all your courage and + push it like there's no tomorrow. Well, actually, you're + pushing it to ensure there is a tomorrow. But hey, who doesn't + love a little paradoxical button-pushing? + </BlockQuote> + <BlockQuote m={2}> + Prepare for the impending suppression of the supermatter engine + room, because things are about to get real quiet. Just make sure + everyone has evacuated, or else they'll be in for a surprise. + The system needs its space, and it's not known for being the + friendliest neighbour. + </BlockQuote> + <BlockQuote m={2}> + After the delamination is successfully suppressed, take a moment to + appreciate the delicate beauty of crystal-based electricity. Take a + look around and fix any damage to those fragile glass components. + Feel free to put on your finest overalls and channel your inner + engiborg while doing so. + </BlockQuote> + <BlockQuote m={2}> + Keep an eye out for fires and the infamous air mix. It's always + an adventure trying to strike the perfect balance between breathable + air and potential suffocation. Remember, oxygen plus a spark equals + fireworks - the kind you definitely don't want inside a + reactor. + </BlockQuote> + <NoticeBox info m={2}> + <b> + Did you know freon catches fire at low temperatures? + <br /> + <br /> + It even forms hot ice between 120K and 160K! + <br /> + <br /> + Remember you can always turn the engine room air alarm to + contaminated to assist in removing harmful gases! + </b> + </NoticeBox> + <BlockQuote m={2}> + To avoid singeing your eyebrows off, consider enlisting the help of + a synth or a trusty borg. After all, nothing says "safety + first" like outsourcing your firefighting to non-living, + non-breathing assistants. + </BlockQuote> + <BlockQuote m={2}> + Clear out any lightly radioactive debris and/or hot ice (The cargo + department will probably love to dispose it for you.) + </BlockQuote> + <BlockQuote m={2}> + Finally, revel in the satisfaction of knowing that you've + single-handedly prevented a delamination. But, of course, don't + forget to feel guilty because SAFETY MOTH Knows. SAFETY MOTH knows + everything. It's always watching, judging, and probably taking + notes for its next safety briefing. So bask in the glory of your + heroism, but know that the all-knowing Moff is onto you. + </BlockQuote> + <Box m={2}> + <b>Optional step, for the true daredevils out there</b> + </Box> + <BlockQuote m={2}> + When it comes time for your second attempt at starting the SM: Take + this sign, give it a good toss towards the crystal, and watch it + soar through the air. <br /> + <br /> + Nothing says "I'm dealing with a potentially catastrophic + situation" like engaging in some whimsical shenanigans. + </BlockQuote> + <NoticeBox m={2}> + <b> + Hopefully you'll never need to do this. However, good luck! + </b> + </NoticeBox> + </Section> + </Window.Content> + </Window> + ); +}; diff --git a/tgui/packages/tgui/interfaces/LingMMITalk.tsx b/tgui/packages/tgui/interfaces/LingMMITalk.tsx new file mode 100644 index 00000000000..8fd6e5d6e72 --- /dev/null +++ b/tgui/packages/tgui/interfaces/LingMMITalk.tsx @@ -0,0 +1,57 @@ +import { useBackend, useLocalState } from '../backend'; +import { Button, ByondUi, Stack, TextArea } from '../components'; +import { Window } from '../layouts'; + +type Data = { + mmi_view: string; +}; + +export const LingMMITalk = (props, context) => { + const { data, act } = useBackend<Data>(context); + const [mmiMessage, setmmiMessage] = useLocalState<string>( + context, + 'textArea', + '' + ); + + return ( + <Window title="Decoy Brain MMI View" height={360} width={360}> + <Window.Content> + <Stack vertical> + <Stack.Item align="center"> + <ByondUi + width="240px" + height="240px" + params={{ + id: data.mmi_view, + type: 'map', + }} + /> + </Stack.Item> + <Stack.Item> + <Stack width="100%"> + <Stack.Item width="85%"> + <TextArea + height="60px" + placeholder="Send a message to have our decoy brain speak." + onInput={(_, value) => setmmiMessage(value)} + value={mmiMessage} + /> + </Stack.Item> + <Stack.Item align="center"> + <Button + textAlign="center" + content="Send" + onClick={() => { + act('send_mmi_message', { message: mmiMessage }); + setmmiMessage(''); + }} + /> + </Stack.Item> + </Stack> + </Stack.Item> + </Stack> + </Window.Content> + </Window> + ); +}; diff --git a/tgui/packages/tgui/interfaces/MatMarket.tsx b/tgui/packages/tgui/interfaces/MatMarket.tsx new file mode 100644 index 00000000000..86f44462cb1 --- /dev/null +++ b/tgui/packages/tgui/interfaces/MatMarket.tsx @@ -0,0 +1,180 @@ +import { useBackend } from '../backend'; +import { Section, Stack, Button, Modal } from '../components'; +import { Window } from '../layouts'; +import { BooleanLike } from 'common/react'; +import { toTitleCase } from 'common/string'; + +type Data = { + orderingPrive: BooleanLike; // you will need to import this + canOrderCargo: BooleanLike; + creditBalance: number; + materials: Material[]; + catastrophe: BooleanLike; +}; + +type Material = { + name: string; + quantity: number; + id: string; // correct this if its a number + trend: string; + price: number; + color: string; +}; + +export const MatMarket = (props, context) => { + const { act, data } = useBackend<Data>(context); // this will tell your editor that data is the type listed above + + const { + orderingPrive, + canOrderCargo, + creditBalance, + materials = [], + catastrophe, + } = data; // better to destructure here (style nit) + return ( + <Window width={700} height={400}> + <Window.Content scrollable> + {!!catastrophe && <MarketCrashModal />} + <Section + title="Materials for sale" + buttons={ + <Button + icon="dollar" + tooltip="Place order from cargo budget." + color={!!orderingPrive && !!canOrderCargo ? '' : 'green'} + content={ + !!orderingPrive && !!canOrderCargo + ? 'Order via Cargo Budget?' + : 'Ordering via Cargo Budget' + } + onClick={() => act('toggle_budget')} + /> + }> + Buy orders for material sheets placed here will be ordered on the next + cargo shipment. + <br /> <br /> + To <b>sell materials</b>, please insert sheets or similar stacks of + materials. All minerals sold on the market directly are subject to an + 20% market fee. To prevent market manipulation, all registered traders + can buy a total of <b>10 full stacks of materials at a time</b>. + <br /> <br /> + All new purchases will <b>include the cost of the shipped crate</b>, + which may be recycled afterwards. + <Section> + Current credit balance: <b>{creditBalance || 'zero'}</b> cr. + </Section> + </Section> + {materials.map((material) => ( + <Section key={material.id}> + <Stack fill> + <Stack.Item width="75%"> + <Stack> + <Stack.Item + textColor={material.color ? material.color : 'white'} + fontSize="125%" + width="15%" + pr="3%"> + {toTitleCase(material.name)} + </Stack.Item> + + <Stack.Item width="15%" pr="2%"> + Trading at <b>{material.price}</b> cr. + </Stack.Item> + + <Stack.Item width="33%"> + <b>{material.quantity}</b> sheets of <b>{material.name}</b>{' '} + trading. + </Stack.Item> + <Stack.Item + width="40%" + color={ + material.trend === 'up' + ? 'green' + : material.trend === 'down' + ? 'red' + : 'white' + }> + <b>{toTitleCase(material.name)}</b> is trending{' '} + <b>{material.trend}</b>. + </Stack.Item> + </Stack> + </Stack.Item> + <Stack.Item> + <Button + disabled={catastrophe === 1 || material.price <= 0} + tooltip={material.price * 1} + onClick={() => + act('buy', { + quantity: 1, + material: material.name, + }) + }> + Buy 1 + </Button> + <Button + disabled={catastrophe === 1 || material.price <= 0} + tooltip={material.price * 5} + onClick={() => + act('buy', { + quantity: 5, + material: material.name, + }) + }> + 5 + </Button> + <Button + disabled={catastrophe === 1 || material.price <= 0} + tooltip={material.price * 10} + onClick={() => + act('buy', { + quantity: 10, + material: material.name, + }) + }> + 10 + </Button> + <Button + disabled={catastrophe === 1 || material.price <= 0} + tooltip={material.price * 25} + onClick={() => + act('buy', { + quantity: 25, + material: material.name, + }) + }> + 25 + </Button> + <Button + disabled={catastrophe === 1 || material.price <= 0} + tooltip={material.price * 50} + onClick={() => + act('buy', { + quantity: 50, + material: material.name, + }) + }> + 50 + </Button> + </Stack.Item> + </Stack> + </Section> + ))} + </Window.Content> + </Window> + ); +}; + +const MarketCrashModal = (props, context) => { + const { act, data } = useBackend(context); + return ( + <Modal textAlign="center" mr={1.5}> + ATTENTION! THE MARKET HAS CRASHED + <br /> <br /> + ALL MATERIALS ARE NOW WORTHLESS + <br /> <br /> + TRADING CIRCUIT BREAKER HAS BEEN ENGAGED FOR ALL TRADERS + <br /> <br /> + <b>DO NOT PANIC, WE ARE FIXING THIS</b> + </Modal> + ); +}; diff --git a/tgui/packages/tgui/interfaces/MatrixMathTester.tsx b/tgui/packages/tgui/interfaces/MatrixMathTester.tsx new file mode 100644 index 00000000000..aa0637b78d7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/MatrixMathTester.tsx @@ -0,0 +1,226 @@ +import { useBackend, useLocalState } from '../backend'; +import { Input, NumberInput, Section, Button, Table } from '../components'; +import { toFixed } from 'common/math'; +import { Window } from '../layouts'; + +const MatrixMathTesterInput = ( + props: { value: number; varName: string }, + context +) => { + const { act } = useBackend(context); + return ( + <NumberInput + value={props.value} + step={0.005} + format={(value) => toFixed(value, 3)} + width={'100%'} + onChange={(e, value) => + act('change_var', { var_name: props.varName, var_value: value }) + } + /> + ); +}; + +type MatrixData = { + matrix_a: number; + matrix_b: number; + matrix_c: number; + matrix_d: number; + matrix_e: number; + matrix_f: number; + pixelated: boolean; +}; + +export const MatrixMathTester = (props, context) => { + const { act, data } = useBackend<MatrixData>(context); + const { + matrix_a, + matrix_b, + matrix_c, + matrix_d, + matrix_e, + matrix_f, + pixelated, + } = data; + const [scaleX, setScaleX] = useLocalState(context, 'scale_x', 1); + const [scaleY, setScaleY] = useLocalState(context, 'scale_y', 1); + const [translateX, setTranslateX] = useLocalState(context, 'translate_x', 0); + const [translateY, setTranslateY] = useLocalState(context, 'translate_y', 0); + const [shearX, setShearX] = useLocalState(context, 'shear_x', 0); + const [shearY, setShearY] = useLocalState(context, 'shear_y', 0); + const [angle, setAngle] = useLocalState(context, 'angle', 0); + return ( + <Window title="Nobody Wants to Learn Matrix Math" width={290} height={270}> + <Window.Content> + <Section fill> + <Table> + <Table.Row header> + <Table.Cell width={'30%'}>X</Table.Cell> + <Table.Cell width={'30%'}>Y</Table.Cell> + <Table.Cell width={'40%'}>Z</Table.Cell> + </Table.Row> + <Table.Row> + <Table.Cell> + <MatrixMathTesterInput value={matrix_a} varName="a" /> + </Table.Cell> + <Table.Cell> + <MatrixMathTesterInput value={matrix_d} varName="d" /> + </Table.Cell> + <Table.Cell> + <Input disabled placeholder="0 (fixed value)" width={'100%'} /> + </Table.Cell> + </Table.Row> + <Table.Row> + <Table.Cell> + <MatrixMathTesterInput value={matrix_b} varName="b" /> + </Table.Cell> + <Table.Cell> + <MatrixMathTesterInput value={matrix_e} varName="e" /> + </Table.Cell> + <Table.Cell> + <Input disabled placeholder="0 (fixed value)" width={'100%'} /> + </Table.Cell> + </Table.Row> + <Table.Row> + <Table.Cell> + <MatrixMathTesterInput value={matrix_c} varName="c" /> + </Table.Cell> + <Table.Cell> + <MatrixMathTesterInput value={matrix_f} varName="f" /> + </Table.Cell> + <Table.Cell> + <Input disabled placeholder="1 (fixed value)" width={'100%'} /> + </Table.Cell> + </Table.Row> + </Table> + <Table mt={3}> + <Table.Row header> + <Table.Cell>Action</Table.Cell> + <Table.Cell>X</Table.Cell> + <Table.Cell>Y</Table.Cell> + </Table.Row> + <Table.Row> + <Table.Cell> + <Button + icon={'up-right-and-down-left-from-center'} + content={'Scale'} + width={'100%'} + onClick={() => act('scale', { x: scaleX, y: scaleY })} + /> + </Table.Cell> + <Table.Cell> + <NumberInput + value={scaleX} + step={0.05} + format={(value) => toFixed(value, 2)} + width={'100%'} + onChange={(e, value) => setScaleX(value)} + /> + </Table.Cell> + <Table.Cell> + <NumberInput + value={scaleY} + step={0.05} + format={(value) => toFixed(value, 2)} + width={'100%'} + onChange={(e, value) => setScaleY(value)} + /> + </Table.Cell> + </Table.Row> + <Table.Row> + <Table.Cell> + <Button + icon={'arrow-right'} + content={'Translate'} + width={'100%'} + onClick={() => + act('translate', { x: translateX, y: translateY }) + } + /> + </Table.Cell> + <Table.Cell> + <NumberInput + value={translateX} + step={1} + format={(value) => toFixed(value, 0)} + width={'100%'} + onChange={(e, value) => setTranslateX(value)} + /> + </Table.Cell> + <Table.Cell> + <NumberInput + value={translateY} + step={1} + format={(value) => toFixed(value, 0)} + width={'100%'} + onChange={(e, value) => setTranslateY(value)} + /> + </Table.Cell> + </Table.Row> + <Table.Row> + <Table.Cell> + <Button + icon={'maximize'} + content={'Shear'} + width={'100%'} + onClick={() => act('shear', { x: shearX, y: shearY })} + /> + </Table.Cell> + <Table.Cell> + <NumberInput + value={shearX} + step={0.005} + format={(value) => toFixed(value, 3)} + width={'100%'} + onChange={(e, value) => setShearX(value)} + /> + </Table.Cell> + <Table.Cell> + <NumberInput + value={shearY} + step={0.005} + format={(value) => toFixed(value, 3)} + width={'100%'} + onChange={(e, value) => setShearY(value)} + /> + </Table.Cell> + </Table.Row> + <Table.Row> + <Table.Cell> + <Button + icon={'rotate-right'} + content={'Rotate'} + width={'100%'} + onClick={() => act('turn', { angle: angle })} + /> + </Table.Cell> + <Table.Cell> + <NumberInput + value={angle} + step={0.5} + maxValue={360} + minValue={-360} + format={(value) => toFixed(value, 1)} + width={'100%'} + onChange={(e, value) => setAngle(value)} + /> + </Table.Cell> + <Table.Cell> + <Button + icon={'dog'} + color={'bad'} + selected={pixelated} + content={'PET'} + tooltip={'Pixel Enhanced Transforming'} + tooltipPosition={'bottom'} + width={'100%'} + onClick={() => act('toggle_pixel')} + /> + </Table.Cell> + </Table.Row> + </Table> + </Section> + </Window.Content> + </Window> + ); +}; diff --git a/tgui/packages/tgui/interfaces/Mecha/ModulesPane.tsx b/tgui/packages/tgui/interfaces/Mecha/ModulesPane.tsx new file mode 100644 index 00000000000..04a1100fa13 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Mecha/ModulesPane.tsx @@ -0,0 +1,889 @@ +import { useBackend } from '../../backend'; +import { Icon, NumberInput, ProgressBar, Box, Button, Section, Stack, LabeledList, NoticeBox, Collapsible } from '../../components'; +import { MainData, MechModule } from './data'; +import { classes } from 'common/react'; +import { toFixed } from 'common/math'; +import { formatPower } from '../../format'; +import { GasmixParser } from 'tgui/interfaces/common/GasmixParser'; + +const moduleSlotIcon = (param) => { + switch (param) { + case 'mecha_l_arm': + return 'hand'; + case 'mecha_r_arm': + return 'hand'; + case 'mecha_utility': + return 'screwdriver-wrench'; + case 'mecha_power': + return 'bolt'; + case 'mecha_armor': + return 'shield-halved'; + default: + return 'screwdriver-wrench'; + } +}; + +const moduleSlotLabel = (param) => { + switch (param) { + case 'mecha_l_arm': + return 'Left arm module'; + case 'mecha_r_arm': + return 'Right arm module'; + case 'mecha_utility': + return 'Utility module'; + case 'mecha_power': + return 'Power module'; + case 'mecha_armor': + return 'Armor module'; + default: + return 'Common module'; + } +}; + +export const ModulesPane = (props, context) => { + const { act, data } = useBackend<MainData>(context); + const { modules, selected_module_index, weapons_safety } = data; + return ( + <Section + title="Equipment" + fill + style={{ 'overflow-y': 'auto' }} + buttons={ + <Button + icon={!weapons_safety ? 'triangle-exclamation' : 'helmet-safety'} + color={!weapons_safety ? 'red' : 'default'} + onClick={() => act('toggle_safety')} + content={ + !weapons_safety + ? 'Safety Protocols Disabled' + : 'Safety Protocols Enabled' + } + /> + }> + <Stack> + <Stack.Item> + {modules.map((module, i) => + !module.ref ? ( + <Button + maxWidth={16} + p="4px" + pr="8px" + fluid + key={i} + color="transparent"> + <Stack> + <Stack.Item width="32px" height="32px" textAlign="center"> + <Icon + fontSize={1.5} + mx={0} + my="8px" + name={moduleSlotIcon(module.slot)} + /> + </Stack.Item> + <Stack.Item + lineHeight="32px" + style={{ + 'text-transform': 'capitalize', + 'overflow': 'hidden', + 'text-overflow': 'ellipsis', + }}> + {`${moduleSlotLabel(module.slot)} Slot`} + </Stack.Item> + </Stack> + </Button> + ) : ( + <Button + maxWidth={16} + p="4px" + pr="8px" + fluid + key={i} + selected={i === selected_module_index} + onClick={() => + act('select_module', { + index: i, + }) + }> + <Stack> + <Stack.Item lineHeight="0"> + <Box + className={classes(['mecha_equipment32x32', module.icon])} + /> + </Stack.Item> + <Stack.Item + lineHeight="32px" + style={{ + 'text-transform': 'capitalize', + 'overflow': 'hidden', + 'text-overflow': 'ellipsis', + }}> + {module.name} + </Stack.Item> + </Stack> + </Button> + ) + )} + </Stack.Item> + <Stack.Item grow pl={1}> + {selected_module_index !== null && modules[selected_module_index] && ( + <ModuleDetails module={modules[selected_module_index]} /> + )} + </Stack.Item> + </Stack> + </Section> + ); +}; + +export const ModuleDetails = (props, context) => { + const { act, data } = useBackend<MainData>(context); + const { slot, name, desc, icon, detachable, ref, snowflake } = props.module; + return ( + <Box> + <Section> + <Stack vertical> + <Stack.Item> + <Stack> + <Stack.Item grow> + <h2 style={{ 'text-transform': 'capitalize' }}>{name}</h2> + <Box italic opacity={0.5}> + {moduleSlotLabel(slot)} + </Box> + </Stack.Item> + {!!detachable && ( + <Stack.Item> + <Button + color="transparent" + icon="eject" + tooltip="Detach" + fontSize={1.5} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'detach', + }) + } + /> + </Stack.Item> + )} + </Stack> + </Stack.Item> + <Stack.Item>{desc}</Stack.Item> + </Stack> + </Section> + <Section> + {snowflake && snowflake.snowflake_id === MECHA_SNOWFLAKE_ID_EJECTOR ? ( + <SnowflakeCargo module={props.module} /> + ) : snowflake && + snowflake.snowflake_id === MECHA_SNOWFLAKE_ID_AIR_TANK ? ( + <SnowflakeAirTank module={props.module} /> + ) : snowflake && + snowflake.snowflake_id === MECHA_SNOWFLAKE_ID_OREBOX_MANAGER ? ( + <SnowflakeOrebox module={props.module} /> + ) : ( + <LabeledList> + <ModuleDetailsBasic module={props.module} /> + {!!snowflake && <ModuleDetailsExtra module={props.module} />} + </LabeledList> + )} + </Section> + </Box> + ); +}; + +const ModuleDetailsBasic = (props, context) => { + const { act, data } = useBackend<MainData>(context); + const { power_level, weapons_safety } = data; + const { + ref, + slot, + integrity, + can_be_toggled, + can_be_triggered, + active, + active_label, + equip_cooldown, + energy_per_use, + } = props.module; + return ( + <> + {integrity < 1 && ( + <LabeledList.Item + label="Integrity" + buttons={ + <Button + content={'Repair'} + icon={'wrench'} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'repair', + }) + } + /> + }> + <ProgressBar + ranges={{ + good: [0.75, Infinity], + average: [0.25, 0.75], + bad: [-Infinity, 0.25], + }} + value={integrity} + /> + </LabeledList.Item> + )} + {!weapons_safety && ['mecha_l_arm', 'mecha_r_arm'].includes(slot) && ( + <LabeledList.Item label="Safety" color="red"> + <NoticeBox danger>SAFETY OFF</NoticeBox> + </LabeledList.Item> + )} + {!!energy_per_use && ( + <LabeledList.Item label="Power Cost"> + {`${formatPower(energy_per_use)}, ${ + power_level ? toFixed(power_level / energy_per_use) : 0 + } uses left`} + </LabeledList.Item> + )} + {!!equip_cooldown && ( + <LabeledList.Item label="Cooldown">{equip_cooldown}</LabeledList.Item> + )} + {!!can_be_toggled && ( + <LabeledList.Item label={active_label}> + <Button + icon="power-off" + content={active ? 'Enabled' : 'Disabled'} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'toggle', + }) + } + selected={active} + /> + </LabeledList.Item> + )} + {!!can_be_triggered && ( + <LabeledList.Item label={active_label}> + <Button + icon="power-off" + content="Activate" + disabled={active} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'toggle', + }) + } + /> + </LabeledList.Item> + )} + </> + ); +}; + +const MECHA_SNOWFLAKE_ID_SLEEPER = 'sleeper_snowflake'; +const MECHA_SNOWFLAKE_ID_SYRINGE = 'syringe_snowflake'; +const MECHA_SNOWFLAKE_ID_MODE = 'mode_snowflake'; +const MECHA_SNOWFLAKE_ID_EXTINGUISHER = 'extinguisher_snowflake'; +const MECHA_SNOWFLAKE_ID_EJECTOR = 'ejector_snowflake'; +const MECHA_SNOWFLAKE_ID_OREBOX_MANAGER = 'orebox_manager_snowflake'; +const MECHA_SNOWFLAKE_ID_RADIO = 'radio_snowflake'; +const MECHA_SNOWFLAKE_ID_AIR_TANK = 'air_tank_snowflake'; +const MECHA_SNOWFLAKE_ID_WEAPON_BALLISTIC = 'ballistic_weapon_snowflake'; +const MECHA_SNOWFLAKE_ID_GENERATOR = 'generator_snowflake'; + +export const ModuleDetailsExtra = (props: { module: MechModule }, context) => { + const module = props.module; + switch (module.snowflake.snowflake_id) { + case MECHA_SNOWFLAKE_ID_WEAPON_BALLISTIC: + return <SnowflakeWeaponBallistic module={module} />; + case MECHA_SNOWFLAKE_ID_EXTINGUISHER: + return <SnowflakeExtinguisher module={module} />; + case MECHA_SNOWFLAKE_ID_SLEEPER: + return <SnowflakeSleeper module={module} />; + case MECHA_SNOWFLAKE_ID_SYRINGE: + return <SnowflakeSyringe module={module} />; + case MECHA_SNOWFLAKE_ID_MODE: + return <SnowflakeMode module={module} />; + case MECHA_SNOWFLAKE_ID_RADIO: + return <SnowflakeRadio module={module} />; + case MECHA_SNOWFLAKE_ID_GENERATOR: + return <SnowflakeGeneraor module={module} />; + default: + return null; + } +}; + +const SnowflakeWeaponBallistic = (props, context) => { + const { act, data } = useBackend<MainData>(context); + const { ref } = props.module; + const { + projectiles, + max_magazine, + projectiles_cache, + projectiles_cache_max, + disabledreload, + ammo_type, + mode, + } = props.module.snowflake; + return ( + <> + {!!ammo_type && ( + <LabeledList.Item label="Ammo">{ammo_type}</LabeledList.Item> + )} + <LabeledList.Item + label="Loaded" + buttons={ + !disabledreload && + projectiles_cache > 0 && ( + <Button + icon={'redo'} + disabled={projectiles >= max_magazine} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'reload', + }) + }> + Reload + </Button> + ) + }> + <ProgressBar value={projectiles / max_magazine}> + {`${projectiles} of ${max_magazine}`} + </ProgressBar> + </LabeledList.Item> + {!!projectiles_cache_max && ( + <LabeledList.Item label="Stored"> + <ProgressBar value={projectiles_cache / projectiles_cache_max}> + {`${projectiles_cache} of ${projectiles_cache_max}`} + </ProgressBar> + </LabeledList.Item> + )} + {!!mode && <SnowflakeMode module={props.module} />} + </> + ); +}; + +const SnowflakeSleeper = (props, context) => { + const { act, data } = useBackend<MainData>(context); + const { ref } = props.module; + const { patient } = props.module.snowflake; + return !patient ? ( + <LabeledList.Item label="Patient">None</LabeledList.Item> + ) : ( + <> + <LabeledList.Item + label="Patient" + buttons={ + <Button + icon="eject" + tooltip="Eject" + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'eject', + }) + } + /> + }> + {patient.patientname} + </LabeledList.Item> + <LabeledList.Item label={'Health'}> + {patient.is_dead ? ( + <Box color="red">Patient dead</Box> + ) : ( + <ProgressBar + ranges={{ + good: [0.75, Infinity], + average: [0.25, 0.75], + bad: [-Infinity, 0.25], + }} + value={patient.patient_health} + /> + )} + </LabeledList.Item> + <LabeledList.Item label={'Detailed Vitals'}> + <Button + content={'View'} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'view_stats', + }) + } + /> + </LabeledList.Item> + </> + ); +}; + +const SnowflakeSyringe = (props, context) => { + const { act, data } = useBackend<MainData>(context); + const { power_level, weapons_safety } = data; + const { ref, energy_per_use, equip_cooldown } = props.module; + const { mode, syringe, max_syringe, reagents, total_reagents } = + props.module.snowflake; + return ( + <> + <LabeledList.Item label={'Syringes'}> + <ProgressBar value={syringe / max_syringe}> + {`${syringe} of ${max_syringe}`} + </ProgressBar> + </LabeledList.Item> + <LabeledList.Item label={'Reagents'}> + <ProgressBar value={reagents / total_reagents}> + {`${reagents} of ${total_reagents} units`} + </ProgressBar> + </LabeledList.Item> + <LabeledList.Item label={'Mode'}> + <Button + content={mode} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'change_mode', + }) + } + /> + </LabeledList.Item> + <LabeledList.Item label={'Reagent control'}> + <Button + content={'View'} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'show_reagents', + }) + } + /> + </LabeledList.Item> + </> + ); +}; + +const SnowflakeMode = (props, context) => { + const { act, data } = useBackend<MainData>(context); + const { ref } = props.module; + const { mode, mode_label } = props.module.snowflake; + return ( + <LabeledList.Item label={mode_label}> + <Button + content={mode} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'change_mode', + }) + } + /> + </LabeledList.Item> + ); +}; + +const SnowflakeRadio = (props, context) => { + const { act, data } = useBackend<MainData>(context); + const { ref } = props.module; + const { microphone, speaker, minFrequency, maxFrequency, frequency } = + props.module.snowflake; + return ( + <> + <LabeledList.Item label="Microphone"> + <Button + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'toggle_microphone', + }) + } + selected={microphone} + icon={microphone ? 'microphone' : 'microphone-slash'}> + {(microphone ? 'En' : 'Dis') + 'abled'} + </Button> + </LabeledList.Item> + <LabeledList.Item label="Speaker"> + <Button + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'toggle_speaker', + }) + } + selected={speaker} + icon={speaker ? 'volume-up' : 'volume-mute'}> + {(speaker ? 'En' : 'Dis') + 'abled'} + </Button> + </LabeledList.Item> + <LabeledList.Item label="Frequency"> + <NumberInput + animate + unit="kHz" + step={0.2} + stepPixelSize={10} + minValue={minFrequency / 10} + maxValue={maxFrequency / 10} + value={frequency / 10} + format={(value) => toFixed(value, 1)} + onDrag={(e, value) => + act('equip_act', { + ref: ref, + gear_action: 'set_frequency', + new_frequency: value * 10, + }) + } + /> + </LabeledList.Item> + </> + ); +}; + +const SnowflakeAirTank = (props, context) => { + const { act, data } = useBackend<MainData>(context); + const { cabin_sealed, one_atmosphere } = data; + const { ref, integrity, active } = props.module; + const { + auto_pressurize_on_seal, + port_connected, + tank_release_pressure, + tank_release_pressure_min, + tank_release_pressure_max, + tank_pump_active, + tank_pump_direction, + tank_pump_pressure, + tank_pump_pressure_min, + tank_pump_pressure_max, + tank_air, + cabin_air, + } = props.module.snowflake; + return ( + <Box> + <LabeledList> + {integrity < 1 && ( + <LabeledList.Item + label="Integrity" + buttons={ + <Button + content={'Repair'} + icon={'wrench'} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'repair', + }) + } + /> + }> + <ProgressBar + ranges={{ + good: [0.75, Infinity], + average: [0.25, 0.75], + bad: [-Infinity, 0.25], + }} + value={integrity} + /> + </LabeledList.Item> + )} + </LabeledList> + <Section + title="Tank" + buttons={ + <Button + icon="power-off" + content={ + active + ? !cabin_sealed + ? 'Release Paused' + : 'Pressurizing Cabin' + : 'Release Off' + } + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'toggle', + }) + } + selected={active} + /> + }> + <LabeledList> + <LabeledList.Item label="Automation"> + <Button + content={ + auto_pressurize_on_seal ? 'Pressurize on Seal' : 'Manual' + } + selected={auto_pressurize_on_seal} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'toggle_auto_pressurize', + }) + } + /> + </LabeledList.Item> + <LabeledList.Item label="Cabin Pressure"> + <NumberInput + value={tank_release_pressure} + unit="kPa" + width="75px" + minValue={tank_release_pressure_min} + maxValue={tank_release_pressure_max} + step={10} + onChange={(e, value) => + act('equip_act', { + ref: ref, + gear_action: 'set_cabin_pressure', + new_pressure: value, + }) + } + /> + <Button + icon="sync" + disabled={tank_release_pressure === Math.round(one_atmosphere)} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'set_cabin_pressure', + new_pressure: Math.round(one_atmosphere), + }) + } + /> + </LabeledList.Item> + <LabeledList.Item + label="Pipenet Port" + buttons={ + <Button + icon="info" + color="transparent" + tooltip="Park above atmospherics connector port to connect inernal air tank with a gas network." + /> + }> + <Button + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'toggle_port', + }) + } + selected={port_connected}> + {port_connected ? 'Connected' : 'Disconnected'} + </Button> + </LabeledList.Item> + </LabeledList> + </Section> + <Section + title="External Pump" + buttons={ + <Button + icon="power-off" + content={tank_pump_active ? 'On' : 'Off'} + selected={tank_pump_active} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'toggle_tank_pump', + }) + } + /> + }> + <LabeledList.Item label="Direction"> + <Button + content={tank_pump_direction ? 'Area → Tank' : 'Tank → Area'} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'toggle_tank_pump_direction', + }) + } + /> + </LabeledList.Item> + <LabeledList.Item label="Target Pressure"> + <NumberInput + value={tank_pump_pressure} + unit="kPa" + width="75px" + minValue={tank_pump_pressure_min} + maxValue={tank_pump_pressure_max} + step={10} + format={(value) => Math.round(value)} + onChange={(e, value) => + act('equip_act', { + ref: ref, + gear_action: 'set_tank_pump_pressure', + new_pressure: value, + }) + } + /> + <Button + icon="sync" + disabled={tank_pump_pressure === Math.round(one_atmosphere)} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'set_tank_pump_pressure', + new_pressure: Math.round(one_atmosphere), + }) + } + /> + </LabeledList.Item> + </Section> + <Section title="Sensors"> + <Collapsible title="Tank Air"> + <GasmixParser gasmix={tank_air} /> + </Collapsible> + {cabin_sealed ? ( + <Collapsible title="Cabin Air"> + <GasmixParser gasmix={cabin_air} /> + </Collapsible> + ) : ( + <NoticeBox> + <Icon name="wind" mr={1} /> + Cabin Open + </NoticeBox> + )} + </Section> + </Box> + ); +}; + +const SnowflakeOrebox = (props, context) => { + const { act, data } = useBackend<MainData>(context); + const { ref } = props.module; + const { contents } = props.module.snowflake; + return ( + <Section + title="Contents" + buttons={ + <Button + icon="arrows-down-to-line" + content="Dump" + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'dump', + }) + } + disabled={!Object.keys(contents).length} + /> + }> + {Object.keys(contents).length ? ( + Object.keys(contents).map((item, i) => ( + <Stack key={i}> + <Stack.Item lineHeight="0"> + <Box + m="-4px" + className={classes([ + 'mecha_equipment32x32', + contents[item].icon, + ])} + /> + </Stack.Item> + <Stack.Item + lineHeight="24px" + style={{ + 'text-transform': 'capitalize', + 'overflow': 'hidden', + 'text-overflow': 'ellipsis', + }}> + {`${contents[item].amount}x ${contents[item].name}`} + </Stack.Item> + </Stack> + )) + ) : ( + <NoticeBox info>Ore box is empty</NoticeBox> + )} + </Section> + ); +}; + +const SnowflakeCargo = (props, context) => { + const { act, data } = useBackend<MainData>(context); + const { ref } = props.module; + const { cargo, cargo_capacity } = props.module.snowflake; + return ( + <Box> + <Section + title="Contents" + buttons={`${cargo.length} of ${cargo_capacity}`}> + {!cargo.length ? ( + <NoticeBox info>Compartment is empty</NoticeBox> + ) : ( + cargo.map((item, i) => ( + <Button + fluid + py={0.2} + key={i} + icon="eject" + onClick={() => + act('equip_act', { + ref: ref, + cargoref: item.ref, + gear_action: 'eject', + }) + } + style={{ + 'text-transform': 'capitalize', + }}> + {item.name} + </Button> + )) + )} + </Section> + </Box> + ); +}; + +const SnowflakeExtinguisher = (props, context) => { + const { act, data } = useBackend<MainData>(context); + const { ref } = props.module; + const { reagents, total_reagents, reagents_required } = + props.module.snowflake; + return ( + <> + <LabeledList.Item + label="Water" + buttons={ + <Button + content={'Refill'} + icon={'fill'} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'refill', + }) + } + /> + }> + <ProgressBar value={reagents} minValue={0} maxValue={total_reagents}> + {reagents} + </ProgressBar> + </LabeledList.Item> + <LabeledList.Item label="Extinguisher"> + <Button + content={'Activate'} + color={'red'} + disabled={reagents < reagents_required} + icon={'fire-extinguisher'} + onClick={() => + act('equip_act', { + ref: ref, + gear_action: 'activate', + }) + } + /> + </LabeledList.Item> + </> + ); +}; + +const SnowflakeGeneraor = (props, context) => { + const { act, data } = useBackend<MainData>(context); + const { sheet_material_amount } = data; + const { ref, active, name } = props.module; + const { fuel } = props.module.snowflake; + return ( + <LabeledList.Item label="Fuel Amount"> + {fuel === null + ? 'None' + : toFixed(fuel * sheet_material_amount, 0.1) + ' cm³'} + </LabeledList.Item> + ); +}; diff --git a/tgui/packages/tgui/interfaces/NetpodOutfits.tsx b/tgui/packages/tgui/interfaces/NetpodOutfits.tsx new file mode 100644 index 00000000000..afc6a2bd6ca --- /dev/null +++ b/tgui/packages/tgui/interfaces/NetpodOutfits.tsx @@ -0,0 +1,109 @@ +import { Button, Divider, Input, NoticeBox, Section, Stack, Tabs } from '../components'; +import { useBackend, useLocalState } from '../backend'; + +import { Window } from '../layouts'; +import { createSearch } from '../../common/string'; + +type Data = { + netsuit: string; + collections: Collection[]; + types: string[]; +}; + +type Collection = { + name: string; + outfits: Outfit[]; +}; + +type Outfit = { + path: string; + name: string; + type: string; +}; + +export const NetpodOutfits = (props, context) => { + const { act, data } = useBackend<Data>(context); + const { netsuit, collections = [] } = data; + const [selectedType, setSelectedType] = useLocalState<Collection>( + context, + 'selectedType', + collections[0] + ); + const [search, setSearch] = useLocalState<string>( + context, + 'outfitSearch', + '' + ); + + const searchFn = createSearch(search, (outfit: Outfit) => outfit.name); + + const filtered = selectedType?.outfits + ?.filter(searchFn) + .sort((a, b) => (a.name > b.name ? 1 : 0)); + + const selected = + selectedType.outfits?.find((outfit) => outfit.path === netsuit)?.name ?? + 'None'; + + return ( + <Window title="Net Pod" height={300} width={400}> + <Window.Content> + <Stack fill vertical> + <Stack.Item grow> + <Section + fill + title="Select an outfit" + buttons={ + <Input + autoFocus + onInput={(event, value) => setSearch(value)} + placeholder="Search" + value={search} + /> + }> + <Stack fill> + <Stack.Item grow> + <Tabs vertical> + {collections.map((collection, index) => ( + <> + <Tabs.Tab + key={collection.name} + onClick={() => setSelectedType(collection)} + selected={selectedType === collection}> + {collection.name} + </Tabs.Tab> + {index > 0 && <Divider />} + </> + ))} + </Tabs> + </Stack.Item> + <Stack.Divider /> + <Stack.Item grow={5}> + <Section fill scrollable> + {filtered.map(({ path, name }, index) => ( + <Stack.Item className="candystripe" key={index}> + <Button + selected={netsuit === path} + color="transparent" + onClick={() => + act('select_outfit', { outfit: path }) + }> + {name} + </Button> + </Stack.Item> + ))} + </Section> + </Stack.Item> + </Stack> + </Section> + </Stack.Item> + <Stack.Item> + <NoticeBox info align="right"> + {selected} + </NoticeBox> + </Stack.Item> + </Stack> + </Window.Content> + </Window> + ); +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/cyberpolice.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/cyberpolice.ts new file mode 100644 index 00000000000..03fc370c1f4 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/cyberpolice.ts @@ -0,0 +1,23 @@ +import { Antagonist, Category } from '../base'; +import { multiline } from 'common/string'; + +const CyberPolice: Antagonist = { + key: 'cyberpolice', + name: 'Cyber Police', + description: [ + multiline` + On the razor edge of the digital realm, the Cyber Authority has tasked + enforcement officers with preserving system harmony. + `, + + multiline` + Using refined martial arts skills, terminate bitrunners in the virtual + domain. Look snazzy while doing it. Cyber police are short lived combat + roles that spawn from mobs (other than elites or players) in the virtual + domain. + `, + ], + category: Category.Midround, +}; + +export default CyberPolice; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/operative_species.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/operative_species.tsx new file mode 100644 index 00000000000..82042b8efba --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/operative_species.tsx @@ -0,0 +1,6 @@ +import { CheckboxInput, FeatureToggle } from '../base'; + +export const operative_species: FeatureToggle = { + name: 'Always Human as Operative', + component: CheckboxInput, +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/prosthetic.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/prosthetic.tsx new file mode 100644 index 00000000000..adbaefe90c8 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/prosthetic.tsx @@ -0,0 +1,6 @@ +import { FeatureChoiced, FeatureDropdownInput } from '../base'; + +export const prosthetic: FeatureChoiced = { + name: 'Prosthetic', + component: FeatureDropdownInput, +}; diff --git a/tgui/packages/tgui/interfaces/QuantumConsole.tsx b/tgui/packages/tgui/interfaces/QuantumConsole.tsx new file mode 100644 index 00000000000..5e87e61bb46 --- /dev/null +++ b/tgui/packages/tgui/interfaces/QuantumConsole.tsx @@ -0,0 +1,350 @@ +import { Window } from '../layouts'; +import { useBackend } from '../backend'; +import { Button, Collapsible, Icon, NoticeBox, ProgressBar, Section, Stack, Table, Tooltip } from '../components'; +import { BooleanLike } from 'common/react'; +import { LoadingScreen } from './common/LoadingToolbox'; +import { TableCell, TableRow } from '../components/Table'; + +type Data = + | { + available_domains: Domain[]; + avatars: Avatar[]; + connected: 1; + generated_domain: string | null; + occupants: number; + points: number; + randomized: BooleanLike; + ready: BooleanLike; + retries_left: number; + scanner_tier: number; + } + | { + connected: 0; + }; + +type Avatar = { + health: number; + name: string; + pilot: string; + brute: number; + burn: number; + tox: number; + oxy: number; +}; + +type Domain = { + cost: number; + desc: string; + difficulty: number; + id: string; + name: string; + reward: number | string; +}; + +type DomainEntryProps = { + domain: Domain; +}; + +type DisplayDetailsProps = { + amount: number | string; + color: string; + icon: string; +}; + +enum Difficulty { + None, + Low, + Medium, + High, +} + +const isConnected = (data: Data): data is Data & { connected: 1 } => + data.connected === 1; + +const getColor = (difficulty: number) => { + switch (difficulty) { + case Difficulty.Low: + return 'yellow'; + case Difficulty.Medium: + return 'average'; + case Difficulty.High: + return 'bad'; + default: + return ''; + } +}; + +export const QuantumConsole = (props, context) => { + const { data } = useBackend<Data>(context); + + return ( + <Window title="Quantum Console" width={500} height={500}> + <Window.Content> + {!!data.connected && !data.ready && <LoadingScreen />} + <AccessView /> + </Window.Content> + </Window> + ); +}; + +const AccessView = (props, context) => { + const { act, data } = useBackend<Data>(context); + + if (!isConnected(data)) { + return <NoticeBox error>No server connected!</NoticeBox>; + } + + const { + available_domains = [], + generated_domain, + ready, + occupants, + points, + } = data; + + const sorted = available_domains.sort((a, b) => a.cost - b.cost); + + const selected = sorted.find(({ id }) => id === generated_domain); + + return ( + <Stack fill vertical> + <Stack.Item grow> + <Section + buttons={ + <> + <Button + disabled={ + !ready || occupants > 0 || points < 1 || !!generated_domain + } + icon="random" + onClick={() => act('random_domain')} + mr={1} + tooltip="Get a random domain for more rewards. Weighted towards your current points. Minimum: 1 point."> + Randomize + </Button> + <Tooltip content="Accrued points for purchasing domains."> + <Icon color="pink" name="star" mr={1} /> + {points} + </Tooltip> + </> + } + fill + scrollable + title="Virtual Domains"> + {sorted.map((domain) => ( + <DomainEntry key={domain.id} domain={domain} /> + ))} + </Section> + </Stack.Item> + <Stack.Item> + <AvatarDisplay /> + </Stack.Item> + <Stack.Item> + <Section> + <Stack fill> + <Stack.Item grow> + <NoticeBox info={!!generated_domain}> + {selected?.name ?? 'Nothing loaded'} + </NoticeBox> + </Stack.Item> + <Stack.Item> + <Button.Confirm + content="Stop Domain" + disabled={!ready || !generated_domain} + onClick={() => act('stop_domain')} + tooltip="Begins shutdown. Will notify anyone connected." + /> + </Stack.Item> + </Stack> + </Section> + </Stack.Item> + </Stack> + ); +}; + +const DomainEntry = (props: DomainEntryProps, context) => { + const { + domain: { cost, desc, difficulty, id, name, reward }, + } = props; + const { act, data } = useBackend<Data>(context); + if (!isConnected(data)) { + return null; + } + + const { generated_domain, ready, occupants, randomized, points } = data; + + const current = generated_domain === id; + const occupied = occupants > 0; + let buttonIcon, buttonName; + if (randomized) { + buttonIcon = ''; + buttonName = '???'; + } else if (current) { + buttonIcon = 'download'; + buttonName = 'Deployed'; + } else { + buttonIcon = 'coins'; + buttonName = 'Deploy'; + } + + return ( + <Collapsible + buttons={ + <Button + disabled={!!generated_domain || !ready || occupied || points < cost} + icon={buttonIcon} + onClick={() => act('set_domain', { id })} + tooltip={!!generated_domain && 'Stop current domain first.'}> + {buttonName} + </Button> + } + color={getColor(difficulty)} + title={ + <> + {name} + {difficulty === Difficulty.High && <Icon name="skull" ml={1} />} + </> + }> + <Stack height={5}> + <Stack.Item color="label" grow={4}> + {desc} + </Stack.Item> + <Stack.Divider /> + <Stack.Item grow> + <Table> + <TableRow> + <DisplayDetails amount={cost} color="pink" icon="star" /> + </TableRow> + <TableRow> + <DisplayDetails amount={difficulty} color="white" icon="skull" /> + </TableRow> + <TableRow> + <DisplayDetails amount={reward} color="gold" icon="coins" /> + </TableRow> + </Table> + </Stack.Item> + </Stack> + </Collapsible> + ); +}; + +const AvatarDisplay = (props, context) => { + const { act, data } = useBackend<Data>(context); + if (!isConnected(data)) { + return null; + } + + const { avatars = [], generated_domain, retries_left } = data; + + return ( + <Section + title="Connected Clients" + buttons={ + <Stack align="center"> + {!!generated_domain && ( + <Stack.Item> + <Tooltip content="Available bandwidth for new connections."> + <DisplayDetails + color="green" + icon="broadcast-tower" + amount={retries_left} + /> + </Tooltip> + </Stack.Item> + )} + <Stack.Item> + <Button + icon="sync" + onClick={() => act('refresh')} + tooltip="Refresh avatar data."> + Refresh + </Button> + </Stack.Item> + </Stack> + }> + <Table> + {avatars.map(({ health, name, pilot, brute, burn, tox, oxy }) => ( + <TableRow key={name}> + <TableCell color="label"> + {pilot} as{' '} + <span style={{ color: 'white' }}>"{name}"</span> + </TableCell> + <TableCell collapsing> + <Stack> + {brute === 0 && burn === 0 && tox === 0 && oxy === 0 && ( + <Stack.Item> + <Icon color="green" name="check" /> + </Stack.Item> + )} + <Stack.Item> + <Icon color={brute > 50 ? 'bad' : 'gray'} name="tint" /> + </Stack.Item> + <Stack.Item> + <Icon color={burn > 50 ? 'average' : 'gray'} name="fire" /> + </Stack.Item> + <Stack.Item> + <Icon + color={tox > 50 ? 'green' : 'gray'} + name="skull-crossbones" + /> + </Stack.Item> + <Stack.Item> + <Icon color={oxy > 50 ? 'blue' : 'gray'} name="lungs" /> + </Stack.Item> + </Stack> + </TableCell> + <TableCell> + <ProgressBar + minValue={-100} + maxValue={100} + ranges={{ + good: [90, Infinity], + average: [50, 89], + bad: [-Infinity, 45], + }} + value={health} + /> + </TableCell> + </TableRow> + ))} + </Table> + </Section> + ); +}; + +const DisplayDetails = (props: DisplayDetailsProps, context) => { + const { amount = 0, color, icon = 'star' } = props; + + if (amount === 0) { + return <TableCell color="label">No bandwidth</TableCell>; + } + + if (typeof amount === 'string') { + return <TableCell color="label">{String(amount)}</TableCell>; // don't ask + } + + if (amount > 4) { + return ( + <TableCell> + <Stack> + <Stack.Item>{amount}</Stack.Item> + <Stack.Item> + <Icon color={color} name={icon} /> + </Stack.Item> + </Stack> + </TableCell> + ); + } + + return ( + <TableCell> + <Stack> + {Array.from({ length: amount }, (_, index) => ( + <Stack.Item> + <Icon color={color} key={index} name={icon} /> + </Stack.Item> + ))} + </Stack> + </TableCell> + ); +}; diff --git a/tgui/packages/tgui/interfaces/common/LoadingToolbox.tsx b/tgui/packages/tgui/interfaces/common/LoadingToolbox.tsx new file mode 100644 index 00000000000..a77eb78c338 --- /dev/null +++ b/tgui/packages/tgui/interfaces/common/LoadingToolbox.tsx @@ -0,0 +1,32 @@ +import { Stack, Icon, Dimmer } from '../../components'; + +/** Spinner that represents loading states. + * + * @usage + * ```tsx + * /// rest of the component + * return ( + * ///... content to overlay + * {!!loading && <LoadingScreen />} + * /// ... content to overlay + * ); + * ``` + * OR + * ```tsx + * return ( + * {loading ? <LoadingScreen /> : <ContentToHide />} + * ) + * ``` + */ +export const LoadingScreen = (props, context) => { + return ( + <Dimmer> + <Stack align="center" fill justify="center" vertical> + <Stack.Item> + <Icon color="blue" name="toolbox" spin size={4} /> + </Stack.Item> + <Stack.Item>Please wait...</Stack.Item> + </Stack> + </Dimmer> + ); +}; diff --git a/tools/UpdatePaths/Scripts/76732_multi-tile_airlocks.txt b/tools/UpdatePaths/Scripts/76732_multi-tile_airlocks.txt new file mode 100644 index 00000000000..6541efbe8c9 --- /dev/null +++ b/tools/UpdatePaths/Scripts/76732_multi-tile_airlocks.txt @@ -0,0 +1,4 @@ +#comment Repathing for new multi-tile airlocks. + +/obj/machinery/door/airlock/multi_tile/glass : /obj/machinery/door/airlock/multi_tile/public/glass{@OLD} +/obj/machinery/door/airlock/glass_large : /obj/machinery/door/airlock/multi_tile/public/glass{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/77273_hivebots.txt b/tools/UpdatePaths/Scripts/77273_hivebots.txt new file mode 100644 index 00000000000..6d95694f940 --- /dev/null +++ b/tools/UpdatePaths/Scripts/77273_hivebots.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/hivebot/@SUBTYPES : /mob/living/basic/hivebot/@SUBTYPES{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/77330_common_ammo_caliber_to_weird_caliber.txt b/tools/UpdatePaths/Scripts/77330_common_ammo_caliber_to_weird_caliber.txt new file mode 100644 index 00000000000..19ffd2d62ac --- /dev/null +++ b/tools/UpdatePaths/Scripts/77330_common_ammo_caliber_to_weird_caliber.txt @@ -0,0 +1,13 @@ +#comment This repaths several ammo types to a new type with a different caliber. + +/obj/item/ammo_box/magazine/mm712x82 : : /obj/item/ammo_box/magazine/m7mm + +/obj/item/ammo_casing/mm712x82 : /obj/item/ammo_casing/m7mm{@OLD} + +/obj/projectile/bullet/mm712x82 : /obj/projectile/bullet/a7mm{@OLD} + +/obj/projectile/bullet/m223 : /obj/item/ammo_box/magazine/m223{@OLD} + +/obj/item/ammo_casing/a556 : /obj/item/ammo_casing/a223{@OLD} + +/obj/projectile/bullet/a556 : /obj/projectile/bullet/a223{@OLD} diff --git a/tools/UpdatePaths/Scripts/77410_eyeballs.txt b/tools/UpdatePaths/Scripts/77410_eyeballs.txt new file mode 100644 index 00000000000..767c0dd4235 --- /dev/null +++ b/tools/UpdatePaths/Scripts/77410_eyeballs.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/eyeball : /mob/living/basic/eyeball{@OLD} diff --git a/tools/UpdatePaths/Scripts/77489_ice_whelps.txt b/tools/UpdatePaths/Scripts/77489_ice_whelps.txt new file mode 100644 index 00000000000..a454c9785ca --- /dev/null +++ b/tools/UpdatePaths/Scripts/77489_ice_whelps.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/asteroid/ice_whelp : /mob/living/basic/mining/ice_whelp{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/77503_simple_to_basic_morph.txt b/tools/UpdatePaths/Scripts/77503_simple_to_basic_morph.txt new file mode 100644 index 00000000000..baa722be823 --- /dev/null +++ b/tools/UpdatePaths/Scripts/77503_simple_to_basic_morph.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/morph : /mob/living/basic/morph{@OLD} diff --git a/tools/UpdatePaths/Scripts/77523_giant_spider_repath.txt b/tools/UpdatePaths/Scripts/77523_giant_spider_repath.txt new file mode 100644 index 00000000000..1fb03473be8 --- /dev/null +++ b/tools/UpdatePaths/Scripts/77523_giant_spider_repath.txt @@ -0,0 +1,4 @@ +/mob/living/basic/spiderling/@SUBTYPES : /mob/living/basic/spider/growing/spiderling/@SUBTYPES{@OLD} +/mob/living/basic/spider_young/@SUBTYPES : /mob/living/basic/spider/growing/young/@SUBTYPES{@OLD} +/mob/living/basic/giant_spider/maintenance : /mob/living/basic/spider/maintenance{@OLD} +/mob/living/basic/giant_spider/@SUBTYPES : /mob/living/basic/spider/giant/@SUBTYPES{@OLD} diff --git a/tools/UpdatePaths/Scripts/77630_simple_to_basic_watcher.txt b/tools/UpdatePaths/Scripts/77630_simple_to_basic_watcher.txt new file mode 100644 index 00000000000..9d3679be4d4 --- /dev/null +++ b/tools/UpdatePaths/Scripts/77630_simple_to_basic_watcher.txt @@ -0,0 +1,5 @@ +/mob/living/simple_animal/hostile/asteroid/basilisk/watcher/random : /obj/effect/spawner/random/watcher +/mob/living/simple_animal/hostile/asteroid/basilisk/watcher/@SUBTYPES : /mob/living/basic/mining/watcher/@SUBTYPES{@OLD} +/mob/living/simple_animal/hostile/asteroid/basilisk : /mob/living/basic/mining/basilisk{@OLD} + +/mob/living/basic/mining/goliath/random : /obj/effect/spawner/random/goliath diff --git a/tools/UpdatePaths/Scripts/77669_iron_floor_to_iron_floor_base.txt b/tools/UpdatePaths/Scripts/77669_iron_floor_to_iron_floor_base.txt new file mode 100644 index 00000000000..8a627f97972 --- /dev/null +++ b/tools/UpdatePaths/Scripts/77669_iron_floor_to_iron_floor_base.txt @@ -0,0 +1 @@ +/obj/item/stack/tile/iron : /obj/item/stack/tile/iron/base{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/77733_goldgrub.txt b/tools/UpdatePaths/Scripts/77733_goldgrub.txt new file mode 100644 index 00000000000..00e498f6a59 --- /dev/null +++ b/tools/UpdatePaths/Scripts/77733_goldgrub.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/asteroid/goldgrub : /mob/living/basic/mining/goldgrub{@OLD;will_burrow=@SKIP} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/77740_delete_atmos_disk.txt b/tools/UpdatePaths/Scripts/77740_delete_atmos_disk.txt new file mode 100644 index 00000000000..cdf22110923 --- /dev/null +++ b/tools/UpdatePaths/Scripts/77740_delete_atmos_disk.txt @@ -0,0 +1,3 @@ +/obj/item/computer_disk/atmos : @DELETE + + diff --git a/tools/UpdatePaths/Scripts/77975_surgery_duffels_to_trays.txt b/tools/UpdatePaths/Scripts/77975_surgery_duffels_to_trays.txt new file mode 100644 index 00000000000..028de191f03 --- /dev/null +++ b/tools/UpdatePaths/Scripts/77975_surgery_duffels_to_trays.txt @@ -0,0 +1,4 @@ +#comment updates surgical duffels and coroner duffels to surgery trays and morgue surgery trays + +/obj/item/storage/backpack/duffelbag/med/surgery : /obj/item/surgery_tray{@OLD} +/obj/item/storage/backpack/duffelbag/coroner/surgery : /obj/item/surgery_tray/morgue{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/78032_miningdrone.txt b/tools/UpdatePaths/Scripts/78032_miningdrone.txt new file mode 100644 index 00000000000..2ae5cefe8fb --- /dev/null +++ b/tools/UpdatePaths/Scripts/78032_miningdrone.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/mining_drone : /mob/living/basic/mining_drone{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/78078_medical_beds.txt b/tools/UpdatePaths/Scripts/78078_medical_beds.txt new file mode 100644 index 00000000000..bc9dc6a0d2d --- /dev/null +++ b/tools/UpdatePaths/Scripts/78078_medical_beds.txt @@ -0,0 +1,4 @@ +#Repathing for new medical beds + +/obj/structure/bed/roller : /obj/structure/bed/medical/emergency{@OLD} +/obj/item/roller : /obj/item/emergency_bed{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/78273_cryo_removed_atmosmachinery_path.txt b/tools/UpdatePaths/Scripts/78273_cryo_removed_atmosmachinery_path.txt new file mode 100644 index 00000000000..6d8a2c54f38 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78273_cryo_removed_atmosmachinery_path.txt @@ -0,0 +1,2 @@ +#Updates cryo cells pathing by removing the atmosmachinery path +/obj/machinery/atmospherics/components/unary/cryo_cell : /obj/machinery/cryo_cell{@OLD} diff --git a/tools/UpdatePaths/Scripts/78323_recyclerdirection.txt b/tools/UpdatePaths/Scripts/78323_recyclerdirection.txt new file mode 100644 index 00000000000..97d5b0a6895 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78323_recyclerdirection.txt @@ -0,0 +1 @@ +/obj/machinery/recycler : /obj/machinery/recycler {@OLD;dir=8} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/78364_full_surgery_tray_pathing.txt b/tools/UpdatePaths/Scripts/78364_full_surgery_tray_pathing.txt new file mode 100644 index 00000000000..bc8eadd4991 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78364_full_surgery_tray_pathing.txt @@ -0,0 +1,5 @@ +#updates mapped-in surgery trays to the full subtype, so empty trays can be crafted +/obj/item/surgery_tray : /obj/item/surgery_tray/full{@OLD} +/obj/item/surgery_tray/deployed : /obj/item/surgery_tray/full/deployed{@OLD} +/obj/item/surgery_tray/morgue : /obj/item/surgery_tray/full/morgue{@OLD} +/obj/item/surgery_tray/advanced : /obj/item/surgery_tray/full/advanced{@OLD} diff --git a/tools/UpdatePaths/Scripts/78365_explodable_walls.txt b/tools/UpdatePaths/Scripts/78365_explodable_walls.txt new file mode 100644 index 00000000000..ee7655699f1 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78365_explodable_walls.txt @@ -0,0 +1 @@ +/turf/closed/indestructible/explodable/@SUBTYPES : /turf/closed/indestructible/@SUBTYPES, /obj/effect/mapping_helpers/bombable_wall diff --git a/tools/UpdatePaths/Scripts/78382_remove_lapvend.txt b/tools/UpdatePaths/Scripts/78382_remove_lapvend.txt new file mode 100644 index 00000000000..63ba4e3c5c5 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78382_remove_lapvend.txt @@ -0,0 +1 @@ +/obj/machinery/lapvend : @DELETE diff --git a/tools/UpdatePaths/Scripts/78424_simple_to_basic_brimdemon.txt b/tools/UpdatePaths/Scripts/78424_simple_to_basic_brimdemon.txt new file mode 100644 index 00000000000..9895ad7f5f4 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78424_simple_to_basic_brimdemon.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/asteroid/brimdemon : /mob/living/basic/mining/brimdemon{@OLD} diff --git a/tools/UpdatePaths/Scripts/78448_basicclowns.txt b/tools/UpdatePaths/Scripts/78448_basicclowns.txt new file mode 100644 index 00000000000..91d134d157e --- /dev/null +++ b/tools/UpdatePaths/Scripts/78448_basicclowns.txt @@ -0,0 +1,3 @@ +#comment Repaths simpleanimal clowns to basicmob clowns + +/mob/living/simple_animal/hostile/retaliate/clown/@SUBTYPES : /mob/living/basic/clown/@SUBTYPES{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/78612_simple_to_basic_snakes.txt b/tools/UpdatePaths/Scripts/78612_simple_to_basic_snakes.txt new file mode 100644 index 00000000000..083fc74e24f --- /dev/null +++ b/tools/UpdatePaths/Scripts/78612_simple_to_basic_snakes.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/retaliate/snake : /mob/living/basic/snake{@OLD} diff --git a/tools/UpdatePaths/Scripts/78624_simple_to_basic_legion.txt b/tools/UpdatePaths/Scripts/78624_simple_to_basic_legion.txt new file mode 100644 index 00000000000..bf397a83bb2 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78624_simple_to_basic_legion.txt @@ -0,0 +1,3 @@ +/mob/living/simple_animal/hostile/asteroid/hivelord : /mob/living/basic/mining/hivelord{@OLD} +/mob/living/simple_animal/hostile/big_legion : /mob/living/basic/mining/legion/big{@OLD} +/mob/living/simple_animal/hostile/asteroid/hivelord/legion/@SUBTYPES : /mob/living/basic/mining/legion/@SUBTYPES{@OLD} diff --git a/tools/UpdatePaths/Scripts_Skyrat/22951_medpen_bag_to_pouch.txt b/tools/UpdatePaths/Scripts_Skyrat/22951_medpen_bag_to_pouch.txt new file mode 100644 index 00000000000..2d6cb609cf7 --- /dev/null +++ b/tools/UpdatePaths/Scripts_Skyrat/22951_medpen_bag_to_pouch.txt @@ -0,0 +1,5 @@ +# Replaces modular bag types with pouches. They were only subtypes to fit in pockets, anyway. + +/obj/item/storage/bag/medpens : /obj/item/storage/pouch/medpens +/obj/item/storage/bag/ammo : /obj/item/storage/pouch/ammo +/obj/item/storage/bag/material : /obj/item/storage/pouch/material diff --git a/tools/UpdatePaths/Scripts_Skyrat/23804_repath_cc_job_areas.txt b/tools/UpdatePaths/Scripts_Skyrat/23804_repath_cc_job_areas.txt new file mode 100644 index 00000000000..e5e36d333a2 --- /dev/null +++ b/tools/UpdatePaths/Scripts_Skyrat/23804_repath_cc_job_areas.txt @@ -0,0 +1,5 @@ +# Updates NTrep and Blueshield paths + +/area/command/heads_quarters/captain/private/nt_rep : /area/station/command/heads_quarters/nt_rep{@OLD} + +/area/blueshield : /area/station/command/heads_quarters/blueshield{@OLD} diff --git a/tools/ci/annotate_dm.sh b/tools/ci/annotate_dm.sh new file mode 100644 index 00000000000..e43f930ba1a --- /dev/null +++ b/tools/ci/annotate_dm.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +set -euo pipefail +tools/bootstrap/python -m dm_annotator "$@" diff --git a/tools/dm_annotator/__main__.py b/tools/dm_annotator/__main__.py new file mode 100644 index 00000000000..4948fd08656 --- /dev/null +++ b/tools/dm_annotator/__main__.py @@ -0,0 +1,51 @@ +import sys +import re +import os.path as path + +# Usage: tools/bootstrap/python -m dm_annotator [filename] +# If filename is not provided, stdin is checked instead + +def red(text): + return "\033[31m" + str(text) + "\033[0m" + +def green(text): + return "\033[32m" + str(text) + "\033[0m" + +def yellow(text): + return "\033[33m" + str(text) + "\033[0m" + +def annotate(raw_output): + # Remove ANSI escape codes + raw_output = re.sub(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]', '', raw_output) + + print("::group::DreamChecker Output") + print(raw_output) + print("::endgroup::") + + annotation_regex = r'(?P<filename>.*?), line (?P<line>\d+), column (?P<column>\d+):\s{1,2}(?P<type>error|warning): (?P<message>.*)' + has_issues = False + + print("DM Code Annotations:") + for annotation in re.finditer(annotation_regex, raw_output): + print(f"::{annotation['type']} file={annotation['filename']},line={annotation['line']},col={annotation['column']}::{annotation['message']}") + has_issues = True + + if not has_issues: + print(green("No DM issues found")) + +def main(): + if len(sys.argv) > 1: + if not path.exists(sys.argv[1]): + print(red(f"Error: Annotations file '{sys.argv[1]}' does not exist")) + sys.exit(1) + with open(sys.argv[1], 'r') as f: + annotate(f.read()) + elif not sys.stdin.isatty(): + annotate(sys.stdin.read()) + else: + print(red("Error: No input provided")) + print("Usage: tools/bootstrap/python -m dm_annotator [filename]") + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/tools/maplint/lints/stray_item.yml b/tools/maplint/lints/stray_item.yml new file mode 100644 index 00000000000..0f5e6f4181a --- /dev/null +++ b/tools/maplint/lints/stray_item.yml @@ -0,0 +1,3 @@ +/turf/closed: + banned_neighbors: + - =/obj/item diff --git a/tools/ticked_file_enforcement/schemas/modular_skyrat.json b/tools/ticked_file_enforcement/schemas/modular_skyrat.json new file mode 100644 index 00000000000..10fdbaa08c4 --- /dev/null +++ b/tools/ticked_file_enforcement/schemas/modular_skyrat.json @@ -0,0 +1,7 @@ +{ + "file": "tgstation.dme", + "scannable_directory": "modular_skyrat/", + "subdirectories": true, + "excluded_files": [], + "forbidden_includes": [] +} diff --git a/tools/tts/tts-api/off1.wav b/tools/tts/tts-api/off1.wav new file mode 100644 index 00000000000..9c509139f5d Binary files /dev/null and b/tools/tts/tts-api/off1.wav differ diff --git a/tools/tts/tts-api/off2.wav b/tools/tts/tts-api/off2.wav new file mode 100644 index 00000000000..e84221070a5 Binary files /dev/null and b/tools/tts/tts-api/off2.wav differ diff --git a/tools/tts/tts-api/off3.wav b/tools/tts/tts-api/off3.wav new file mode 100644 index 00000000000..ca1f9b377c3 Binary files /dev/null and b/tools/tts/tts-api/off3.wav differ diff --git a/tools/tts/tts-api/off4.wav b/tools/tts/tts-api/off4.wav new file mode 100644 index 00000000000..6831bc1bcde Binary files /dev/null and b/tools/tts/tts-api/off4.wav differ diff --git a/tools/tts/tts-api/on1.wav b/tools/tts/tts-api/on1.wav new file mode 100644 index 00000000000..5137fa195a7 Binary files /dev/null and b/tools/tts/tts-api/on1.wav differ diff --git a/tools/tts/tts-api/on2.wav b/tools/tts/tts-api/on2.wav new file mode 100644 index 00000000000..d0178cae432 Binary files /dev/null and b/tools/tts/tts-api/on2.wav differ